Implementing gRPC Services

1. Adding gRPC Dependencies (grpc-spring-boot-starter)

Example: gRPC server dependency

<dependency>
  <groupId>net.devh</groupId>
  <artifactId>grpc-server-spring-boot-starter</artifactId>
  <version>3.1.0.RELEASE</version>
</dependency>

2. Defining Protocol Buffers (.proto files)

Example: Proto definition for GreeterService

syntax = "proto3";
package com.example.grpc;
option java_multiple_files = true;
service GreeterService {
  rpc SayHello (HelloRequest) returns (HelloReply);
  rpc SayHellos (HelloRequest) returns (stream HelloReply);
}
message HelloRequest { string name = 1; }
message HelloReply   { string message = 1; }

3. Generating Java Classes from Proto Files

Example: Protobuf Maven plugin

<plugin>
  <groupId>org.xolstice.maven.plugins</groupId>
  <artifactId>protobuf-maven-plugin</artifactId>
  <configuration>
    <protocArtifact>com.google.protobuf:protoc:3.25.0:exe:${os.detected.classifier}</protocArtifact>
    <pluginId>grpc-java</pluginId>
    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.62.2:exe:${os.detected.classifier}</pluginArtifact>
  </configuration>
</plugin>

4. Creating gRPC Server Services (@GrpcService)

Example: Unary gRPC service implementation

@GrpcService
public class GreeterServiceImpl extends GreeterServiceGrpc.GreeterServiceImplBase {
  @Override public void sayHello(HelloRequest req, StreamObserver<HelloReply> out) {
    out.onNext(HelloReply.newBuilder().setMessage("Hello " + req.getName()).build());
    out.onCompleted();
  }
}

5. Implementing Unary RPC Methods

Pattern Client → Server
Unary 1 req → 1 resp
Server streaming 1 req → many resp
Client streaming many req → 1 resp
Bidirectional many req ↔ many resp

6. Implementing Server Streaming RPC

Example: Server-streaming RPC

@Override public void sayHellos(HelloRequest req, StreamObserver<HelloReply> out) {
  for (int i = 0; i < 5; i++)
    out.onNext(HelloReply.newBuilder().setMessage("#" + i + " " + req.getName()).build());
  out.onCompleted();
}

7. Implementing Client Streaming RPC

Example: Client-streaming RPC

@Override public StreamObserver<HelloRequest> collect(StreamObserver<HelloReply> out) {
  return new StreamObserver<>() {
    final List<String> names = new ArrayList<>();
    public void onNext(HelloRequest r) { names.add(r.getName()); }
    public void onError(Throwable t) {}
    public void onCompleted() {
      out.onNext(HelloReply.newBuilder().setMessage("Hello " + names).build());
      out.onCompleted();
    }
  };
}

8. Implementing Bidirectional Streaming RPC

Example: Bidirectional streaming RPC

@Override public StreamObserver<HelloRequest> chat(StreamObserver<HelloReply> out) {
  return new StreamObserver<>() {
    public void onNext(HelloRequest r) {
      out.onNext(HelloReply.newBuilder().setMessage("Echo " + r.getName()).build());
    }
    public void onError(Throwable t) { out.onError(t); }
    public void onCompleted() { out.onCompleted(); }
  };
}

9. Creating gRPC Clients (gRPC stubs)

Example: gRPC client with blocking stub

@Service
public class GreeterClient {
  @GrpcClient("greeter") private GreeterServiceGrpc.GreeterServiceBlockingStub stub;
  public String hello(String name) {
    return stub.sayHello(HelloRequest.newBuilder().setName(name).build()).getMessage();
  }
}
// application.yml: grpc.client.greeter.address=static://localhost:9090

10. Handling gRPC Exceptions and Status Codes

Status Meaning
INVALID_ARGUMENT Bad input
NOT_FOUND Resource missing
UNAUTHENTICATED No/invalid creds
PERMISSION_DENIED Forbidden
DEADLINE_EXCEEDED Timeout
UNAVAILABLE Server down/retryable
INTERNAL Server error

Example: Return gRPC NOT_FOUND status

out.onError(Status.NOT_FOUND.withDescription("user not found").asRuntimeException());

11. Implementing gRPC Interceptors

Example: Global gRPC server interceptor

@GrpcGlobalServerInterceptor
public class LoggingInterceptor implements ServerInterceptor {
  @Override public <ReqT,RespT> ServerCall.Listener<ReqT> interceptCall(
      ServerCall<ReqT,RespT> call, Metadata md, ServerCallHandler<ReqT,RespT> next) {
    log.info("gRPC {} from {}", call.getMethodDescriptor().getFullMethodName(),
             call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
    return next.startCall(call, md);
  }
}