In recent years, anyone who wanted to use gRPC with Spring Boot often had to resort to community starters. With Spring Boot 4 the game changes: gRPC is now a first-class citizen. In this guide, we’ll look at how to set up a greenfield project with Java and why Virtual Threads fundamentally change the way we write gRPC services.
Why gRPC in 2026?
REST is still the standard for public APIs. But in the engine room of your microservices – inter-service communication – efficiency counts. gRPC offers:
- Protobuf: Binary serialization instead of bloated JSON.
- HTTP/2: Multiplexing and header compression by default.
- Type safety: The contract (
.proto) generates the code. No more manual DTOs getting out of hand.
The scenario: inventory service
We build a classic use case: An “order service” queries the “inventory service” about the availability of a product.
The contract: inventory.proto
Everything starts with interface design. In Spring Boot 4 we put these files under src/main/proto by default.
syntax = "proto3";
option java_package = "dev.julianpaul.inventory.grpc";
option java_multiple_files = true;
service InventoryService {
// Unary: Einfache Anfrage, einfache Antwort
rpc GetStock (StockRequest) returns (StockResponse);
}
message StockRequest {
string product_id = 1;
}
message StockResponse {
int32 quantity = 1;
bool available = 2;
}
Dependencies
Thanks to the new official starter, we hardly need any configuration. Code generation is now more deeply integrated into the Spring tooling chain.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-grpc'
// Virtual Threads Support ist in Boot 4 per Default aktiv,
// wenn Java 21+ genutzt wird.
}
In Spring Boot 4, the spring-boot-maven-plugin is smart enough to handle the gRPC protocol buffer as long as we add the appropriate extension.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.25.1: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>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Note: Make sure your
.protofiles are undersrc/main/proto. After anmvn clean compile, Maven automatically generates the Java classes into thetarget/generated-sourcesfolder, which your IDE (IntelliJ/VS Code) can then index.
Implementation of the server
This is where Spring Boot 4 shines. We no longer have to manually configure complex server builders. A simple @GrpcService is enough.
@GrpcService
public class InventoryServiceImpl extends InventoryServiceGrpc.InventoryServiceImplBase {
@Override
public void getStock(StockRequest request, StreamObserver<StockResponse> responseObserver) {
// Dank Virtual Threads in SB4 können wir hier blockierende
// DB-Calls (z.B. JPA) ohne Performance-Einbußen machen!
int stockCount = 42; // Simulierter DB-Call
StockResponse response = StockResponse.newBuilder()
.setQuantity(stockCount)
.setAvailable(stockCount > 0)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
Tip: In Spring Boot 4, Virtual Threads are enabled by default (
spring.threads.virtual.enabled=true). This means that each gRPC call runs in its own virtual thread. You no longer have to struggle with reactive flux/mono chains to achieve scalability. Just write imperative code, it still scales.
The client side: Inter-Service Communication
Now that our server is running, the Order-Service needs to call it. In Spring Boot 4 this is done via declarative injection. We no longer have to worry about creating ManagedChannels manually.
The gRPC client in the order service
In the application.yaml we configure the target:
spring:
threads:
virtual:
enabled: true
grpc:
client:
inventory-service:
address: 'static://localhost:9090'
negotiation-type: plaintext # Für lokale Entwicklung ohne TLS
And this is what the call looks like in the Java code:
@Service
public class OrderService {
// Spring Boot 4 injiziert den Stub automatisch basierend auf der Config
@GrpcClient("inventory-service")
private InventoryServiceGrpc.InventoryServiceBlockingStub inventoryStub;
public void processOrder(String productId) {
StockRequest request = StockRequest.newBuilder()
.setProductId(productId)
.build();
// Der Call blockiert den Virtual Thread, aber nicht den Plattform-Thread!
StockResponse response = inventoryStub.getStock(request);
if (response.getAvailable()) {
System.out.println("Artikel verfügbar: " + response.getQuantity());
}
}
}
Observability: No more blind flights
When the order service calls the inventory service via gRPC, we want to see exactly where the milliseconds are. Spring Boot 4 natively uses the OpenTelemetry (OTLP) protocol for this.
The new “One-Stop” Dependency
Forget the days when you had to configure tracing, metrics and logs individually. In Spring Boot 4, a single launcher is enough:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
Thanks to the native integration of Micrometer Observation, gRPC calls are automatically instrumented. A gRPC server interceptor registers spans for each incoming call, and the client does the same for outgoing requests.
Zero-Config Tracing
In the application.yaml you just have to say where the data should flow (e.g. to a Jaeger or Grafana Tempo collector):
management:
otlp:
tracing:
endpoint: http://otel-collector:4317
tracing:
sampling:
probability: 1.0 # In Produktion eher 0.1
Deep Dive: The genius of Spring Boot 4 is the Bridge Effect. The ‘Micrometer Observation API’ acts as a common interface. If you mark a method with
@Observed, Spring will simultaneously create a trace span for Tempo and a metric sample for Prometheus.
The interaction with virtual threads
This is where our tech stack comes full circle: Since we use virtual threads, the context propagation issue (i.e. carrying the trace ID across thread boundaries) is finally solved in a stable manner in Spring Boot 4. You no longer need to write special ThreadLocal hacks; the trace context simply “moves” with the virtual thread.
Absolutely correct. Anyone who has ever sat at a loss in front of an ‘INTERNAL: Unknown error’ knows: Without proper error handling, gRPC is a nightmare in productive use. Since gRPC is not based on HTTP status codes like 404 or 500, but on its own gRPC Status Codes, we need to be precise here.
In Spring Boot 4 we use the Global Interceptor approach or - even more elegantly - the new @GrpcExceptionHandler.
Error Handling: No more “Unknown Errors”
In the REST world we use @ControllerAdvice. In Spring Boot 4 with gRPC, there is an analogous concept to translate exceptions into gRPC status codes.
The declarative approach
Instead of writing try-catch blocks in every service, we define a central component:
@GrpcAdvice
public class GlobalGrpcExceptionHandler {
@GrpcExceptionHandler(EntityNotFoundException.class)
public Status handleNotFound(EntityNotFoundException ex) {
return Status.NOT_FOUND
.withDescription(ex.getMessage())
.withCause(ex);
}
@GrpcExceptionHandler(IllegalArgumentException.class)
public Status handleInvalidArgument(IllegalArgumentException ex) {
return Status.INVALID_ARGUMENT
.withDescription("Check your input: " + ex.getMessage());
}
}
Rich Error Model (Google RPC Status)
Sometimes a simple status code isn’t enough. For example, if we want to send validation errors, we use the Rich Error Model. Spring Boot 4 supports this natively via metadata (trailers).
Tip: Never use
Status.INTERNALfor business logic errors. This usually signals to the client that the server has crashed. Instead, use specific codes likeFAILED_PRECONDITIONorOUT_OF_RANGEto tell the caller what content went wrong.
Conclusion: Why Spring Boot 4 is a game changer for gRPC
We have seen that in 2026 gRPC will no longer be a “niche technology” for high-frequency trading. The native integration into Spring Boot 4 makes it a real alternative for every internal microservice call.
Die Key Takeaways:
- Official Support: No more tinkering with third-party launchers.
- Virtual Threads: Maximum scalability with the usual, synchronous programming model.
- Observability: Tracing and metrics are included out-of-the-box thanks to OpenTelemetry.
Testing: Reliable gRPC suites with no overhead
Nobody wants to start an entire server on port 9090 just to validate some business logic. In Spring Boot 4 we use the In-Process Server which handles the communication in memory.
Unit Testing mit @GrpcTest
The new @GrpcTest slicing annotation allows us to load only the gRPC layer - without the entire application context.
@GrpcTest
@ContextConfiguration(classes = InventoryServiceImpl.class)
class InventoryServiceTest {
@GrpcClient("test")
private InventoryServiceGrpc.InventoryServiceBlockingStub inventoryStub;
@Test
void shouldReturnStockWhenProductExists() {
StockRequest request = StockRequest.newBuilder()
.setProductId("PROD-123")
.build();
StockResponse response = inventoryStub.getRawStock(request);
assertThat(response.getAvailable()).isTrue();
assertThat(response.getQuantity()).isEqualTo(42);
}
}
Integration Testing mit Testcontainers
For true end-to-end testing in a microservices landscape, we use test containers. Since 2026, there are improved modules that natively support gRPC health checks to ensure the container is ready before the test starts.
Tip: gRPC services can be easily tested manually using BloomRPC or Postman (which has natively supported gRPC since v10). However, for automated contract testing, I recommend Pact to ensure that changes to the
.protofile do not silently break theOrder-Service.
Summary: The Gold Standard 2026
We have come full circle:
- Definition via Protobuf for maximum type safety.
- Efficiency through HTTP/2 and binary payloads.
- Scalability thanks to Java Virtual Threads in Spring Boot 4.
- Security through global error handling and observability.
- Stability through in-process testing.
![[EN] Testing, Tracing, Tooling: gRPC produktionsreif einsetzen mit Spring Boot 4](/images/Spring-Boot-4-gRPC-BlogHeader.jpeg)