Definition
grpc-spring-boot-starter– Auto-configures a Netty-based gRPC server and client management in a Spring Boot application.@GrpcService– Annotation that registers a gRPC server implementation class, which extends the generatedImplBase, with the embedded server.@GrpcClient("name")– Annotation that injects a generated client stub into a Spring bean, resolving the service name fromapplication.yml.- Service discovery – Resolves the logical service name through Eureka, Consul, or Kubernetes headless-service DNS so the client finds live instances.
- gRPC Health Checking Protocol – A built-in
grpc.health.v1.Healthservice withCheckandWatchmethods that Kubernetes can probe natively from version 1.24.
Think of National Bank of India wiring up a new department. The grpc-spring-boot-starter is the office manager who automatically sets up the phone line and the reception desk the moment you hire a department, so you just annotate the department as open for business with @GrpcService. When another department needs to call it, it simply asks for it by name with @GrpcClient, and the building directory (Eureka, Consul, or Kubernetes DNS) tells it which rooms currently have staff. The health check is the daily roll call: a supervisor periodically asks each department "are you ready to serve?" and routes customers only to the ones that say yes.
Understanding the Concepts
What is the grpc-spring-boot-starter?
The grpc-spring-boot-starter (commonly the net.devh project) auto-configures an embedded gRPC server on a configured port, separate from the HTTP port, and scans for beans annotated with @GrpcService. It also manages gRPC client channels and interceptors.
<!-- pom.xml dependencies --syn-kw">>
syn-kw"><dependencysyn-kw">>
syn-kw"><groupIdsyn-kw">>net.devhsyn-kw"></groupIdsyn-kw">>
syn-kw"><artifactIdsyn-kw">>grpc-spring-boot-startersyn-kw"></artifactIdsyn-kw">>
syn-kw"><versionsyn-kw">>3.1.0.RELEASEsyn-kw"></versionsyn-kw">>
syn-kw"></dependencysyn-kw">>
syn-kw"><dependencysyn-kw">>
syn-kw"><groupIdsyn-kw">>io.grpcsyn-kw"></groupIdsyn-kw">>
syn-kw"><artifactIdsyn-kw">>grpc-protobufsyn-kw"></artifactIdsyn-kw">>
syn-kw"><versionsyn-kw">>1.63.0syn-kw"></versionsyn-kw">>
syn-kw"></dependencysyn-kw">>
How do you define a .proto file for banking services?
You define messages and RPC methods in a .proto file. The protobuf Maven or Gradle plugin generates Java stubs and server base classes from it.
syntax = "proto3";
package com.bank.grpc;
option java_package = "com.bank.grpc";
option java_outer_classname = "TransferProto";
service TransferService {
rpc Transfer (TransferRequest) returns (TransferResponse);
}
message TransferRequest {
string from_account = 1;
string to_account = 2;
string amount = 3; // String for BigDecimal safety
}
message TransferResponse {
bool success = 1;
string message = 2;
string new_balance = 3;
}
How do you implement the server with @GrpcService?
Your server class extends the generated ImplBase and carries @GrpcService. The starter registers it automatically with no bootstrapping code.
package com.bank.grpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
import java.math.BigDecimal;
@GrpcService
public class TransferGrpcService
extends TransferServiceGrpc.TransferServiceImplBase {
@Override
public void transfer(
TransferRequest request,
StreamObserver<TransferResponse> responseObserver) {
BigDecimal amount = new BigDecimal(request.getAmount());
String from = request.getFromAccount();
String to = request.getToAccount();
// Business logic: debit from, credit to
boolean success = processTransfer(from, to, amount);
TransferResponse response = TransferResponse.newBuilder()
.setSuccess(success)
.setMessage(success ? "Transfer complete" : "Insufficient funds")
.setNewBalance("250000.00")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
private boolean processTransfer(
String from, String to, BigDecimal amount) {
// Rajesh transfers to Priya
System.out.println("Transfer " + amount + " from " + from
+ " to " + to);
return true;
}
}
How do you inject and use a client stub with @GrpcClient?
You inject a stub into any Spring bean with @GrpcClient("service-name"). A matching entry in application.yml provides the address and negotiation type.
package com.bank.service;
import com.bank.grpc.TransferProto.TransferRequest;
import com.bank.grpc.TransferProto.TransferResponse;
import com.bank.grpc.TransferServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
@GrpcClient("transfer-service")
private TransferServiceGrpc.TransferServiceBlockingStub stub;
public String initiateTransfer(
String from, String to, String amount) {
TransferRequest request = TransferRequest.newBuilder()
.setFromAccount(from)
.setToAccount(to)
.setAmount(amount)
.build();
TransferResponse response = stub.transfer(request);
return response.getMessage();
}
}
# application.yml
grpc:
client:
transfer-service:
address: 'dns:///transfer-service:9090'
negotiationType: plaintext
How does service discovery integrate?
Instead of a static address, you point the client at a discovery-backed address. With Eureka or Consul the starter resolves discovery:///transfer-service to live registered instances. With Kubernetes, a headless service DNS returns all pod IPs so the client can client-side load balance across them.
# Kubernetes headless service for gRPC discovery
apiVersion: v1
kind: Service
metadata:
name: transfer-service
spec:
clusterIP: None # headless: returns all pod IPs
selector:
app: transfer-service
ports:
- port: 9090
targetPort: 9090
protocol: TCP
How does gRPC health checking work?
The gRPC Health Checking Protocol defines a Health service with a Check method returning SERVING or NOT_SERVING and a Watch for streaming updates. The starter can expose this automatically.
package com.bank.grpc;
import io.grpc.health.v1.HealthCheckResponse;
import io.grpc.health.v1.HealthGrpc;
import io.grpc.health.v1.HealthCheckRequest;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class BankHealthService
extends HealthGrpc.HealthImplBase {
private volatile boolean databaseReady = false;
@Override
public void check(
HealthCheckRequest request,
StreamObserver<HealthCheckResponse> observer) {
HealthCheckResponse.ServingStatus status = databaseReady
? HealthCheckResponse.ServingStatus.SERVING
: HealthCheckResponse.ServingStatus.NOT_SERVING;
observer.onNext(HealthCheckResponse.newBuilder()
.setStatus(status).build());
observer.onCompleted();
}
public void setDatabaseReady(boolean ready) {
this.databaseReady = ready;
}
}
How do Kubernetes gRPC probes work?
From Kubernetes 1.24 onward, a native gRPC probe calls the Health service directly instead of needing a custom exec script.
# Pod spec with gRPC probes
containers:
- name: transfer-service
ports:
- containerPort: 9090
readinessProbe:
grpc:
port: 9090
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
grpc:
port: 9090
initialDelaySeconds: 10
periodSeconds: 30
Why prefer client-side load balancing for gRPC?
gRPC uses long-lived HTTP/2 connections. A single connection through an L4 load balancer pins all calls to one pod. Client-side load balancing with a Kubernetes headless service returns all pod IPs, and the client opens a subchannel to each, rotating requests across them.
package com.bank.config;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class GrpcChannelConfig {
public ManagedChannel createBalancedChannel() {
return ManagedChannelBuilder
.forTarget("dns:///transfer-service:9090")
.defaultLoadBalancingPolicy("round_robin")
.usePlaintext()
.build();
}
}
What should a senior engineer know?
Tie the health status to real readiness: report NOT_SERVING while the database connection pool is still warming up, so traffic is not routed to a pod that will fail. Prefer client-side load balancing with the Kubernetes headless service for gRPC, because a single long-lived HTTP/2 connection through an L4 load balancer would otherwise pin all calls to one pod. Use global and per-client interceptors for cross-cutting concerns like authentication and logging.
Visual Overview
┌────────────────────────────────────────────────────┐
│ gRPC in Spring Boot Architecture │
├────────────────────────────────────────────────────┤
│ │
│ .proto file │
│ │ │
│ ▼ │
│ protoc + gRPC plugin ──→ Generated Stubs │
│ │ + ImplBase classes │
│ ▼ │
│ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ Server Pod │ │ Client Pod │ │
│ │ │ │ │ │
│ │ @GrpcService │ │ @GrpcClient("name") │ │
│ │ extends ImplBase │◄──│ BlockingStub injected │ │
│ │ │ │ │ │
│ │ Health Service │ │ round_robin policy │ │
│ │ (SERVING/NOT) │ │ │ │
│ └──────────────────┘ └────────────────────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ K8s gRPC │ │ Headless DNS │ │
│ │ Probe │ │ returns all │ │
│ │ :9090 │ │ pod IPs │ │
│ └──────────┘ └──────────────┘ │
└────────────────────────────────────────────────────┘
Key Takeaway
To implement a gRPC service in Spring Boot you add the grpc-spring-boot-starter and the protobuf build plugin with the gRPC Java plugin, define the proto file, and let the build generate the stubs and server base classes. The starter autoconfigures an embedded gRPC server on its own port and scans for beans annotated with @GrpcService. The server class simply extends the generated ImplBase and carries @GrpcService, and it is registered automatically. To consume it from another service, inject a stub into any bean with @GrpcClient naming the service, and a matching entry in application.yml gives the address and negotiation type.
Service discovery is where the client stops using a hard-coded address and instead resolves the logical service name to live instances. With Eureka or Consul the client resolves something like discovery:///transfer-service to the registered instances. In Kubernetes, a headless service DNS returns all pod IPs so the client sees every instance and can client-side load balance across them with a round-robin policy. That client-side load balancing matters specifically for gRPC, because a single long-lived HTTP/2 connection through a plain layer-four load balancer would pin every call to one pod.
Health checking uses the standard gRPC Health Checking Protocol, which is a built-in Health service with a Check method that returns SERVING or NOT_SERVING and a Watch for streaming updates. The starter can expose this automatically, and from Kubernetes 1.24 onward there is a native gRPC probe, so readiness and liveness probes just say grpc on the port and Kubernetes calls the Health service directly instead of needing a custom script.
In banking work at National Bank of India the transfer service ran as several pods, and this wiring meant other services called it simply by name, Kubernetes routed traffic only to ready pods, and load spread evenly across them. The senior detail to always add is to tie the reported health to real readiness: keep the status NOT_SERVING until the database connection pool is warmed up and only then flip it to SERVING, so Kubernetes never routes a transfer to a pod that would immediately fail.
Remember one sentence: @GrpcService registers the server and @GrpcClient injects the stub, while a headless DNS and the gRPC health protocol let Kubernetes discover and probe pods natively.
---