Testing, Tracing, Tooling: gRPC produktionsreif einsetzen mit Spring Boot 4

Julian | Feb 13, 2026 min read

Wer in den letzten Jahren gRPC mit Spring Boot nutzen wollte, musste oft auf Community-Starter zurückgreifen. Mit Spring Boot 4 ändert sich das Spiel: gRPC ist nun ein First-Class Citizen. In diesem Guide schauen wir uns an, wie du ein Greenfield-Projekt mit Java aufsetzt und warum Virtual Threads die Art und Weise, wie wir gRPC-Services schreiben, fundamental verändern.

Warum gRPC in 2026?

REST ist nach wie vor der Standard für öffentliche APIs. Aber im Maschinenraum deiner Microservices – der Inter-Service-Kommunikation – zählt Effizienz. gRPC bietet:

  • Protobuf: Binäre Serialisierung statt aufgeblähtem JSON.
  • HTTP/2: Multiplexing und Header-Kompression standardmäßig.
  • Typsicherheit: Der Vertrag (.proto) generiert den Code. Keine manuellen DTOs mehr, die aus dem Ruder laufen.

Das Szenario: Inventory-Service

Wir bauen einen klassischen Anwendungsfall: Ein Order-Service fragt beim Inventory-Service die Verfügbarkeit eines Produkts ab.

Der Vertrag: inventory.proto

Alles beginnt mit dem Interface-Design. In Spring Boot 4 legen wir diese Files standardmäßig unter src/main/proto ab.

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

Dank des neuen offiziellen Starters brauchen wir kaum noch Konfiguration. Die Code-Generierung ist nun tiefer in die Spring-Tooling-Kette integriert.

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 ist das spring-boot-maven-plugin intelligent genug, um mit dem gRPC-Protokollpuffer umzugehen, sofern wir die passende Erweiterung hinzufügen.

<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>

Hinweis: Achte darauf, dass deine .proto Files unter src/main/proto liegen. Nach einem mvn clean compile generiert Maven die Java-Klassen automatisch in den target/generated-sources Ordner, den deine IDE (IntelliJ/VS Code) dann indizieren kann.

Implementierung des Servers

Hier glänzt Spring Boot 4. Wir müssen keine komplexen Server-Builder mehr manuell konfigurieren. Ein einfacher @GrpcService reicht aus.

@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 sind Virtual Threads standardmäßig aktiviert (spring.threads.virtual.enabled=true). Das bedeutet, jeder gRPC-Call läuft in seinem eigenen Virtual Thread. Du musst dich nicht mehr mit reaktiven Flux/Mono-Ketten quälen, um Skalierbarkeit zu erreichen. Schreib einfach imperativen Code, er skaliert trotzdem.

Die Client-Seite: Inter-Service Communication

Jetzt, wo unser Server läuft, muss der Order-Service ihn aufrufen. In Spring Boot 4 geschieht dies über eine deklarative Injection. Wir müssen uns nicht mehr um das manuelle Erstellen von ManagedChannels kümmern.

Der gRPC Client im Order-Service

In der application.yaml konfigurieren wir das Ziel:

spring:
  threads:
    virtual:
      enabled: true
  grpc:
    client:
      inventory-service:
        address: 'static://localhost:9090'
        negotiation-type: plaintext # Für lokale Entwicklung ohne TLS

Und so sieht der Aufruf im Java-Code aus:

@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: Keine Blindflüge mehr

Wenn der Order-Service den Inventory-Service via gRPC aufruft, wollen wir genau sehen, wo die Millisekunden bleiben. Spring Boot 4 nutzt hierfür nativ das OpenTelemetry (OTLP) Protokoll.

Die neue „One-Stop“ Dependency

Vergiss die Zeiten, in denen du Tracing, Metrics und Logs einzeln konfigurieren musstest. In Spring Boot 4 reicht ein einziger Starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>

Dank der nativen Integration von Micrometer Observation werden gRPC-Calls automatisch instrumentiert. Ein gRPC-Server-Interzeptor registriert Spans für jeden eingehenden Call, und der Client tut dasselbe für ausgehende Requests.

Zero-Config Tracing

In der application.yaml musst du nur noch sagen, wohin die Daten fließen sollen (z.B. an einen Jaeger- oder Grafana-Tempo-Collector):

management:
  otlp:
    tracing:
      endpoint: http://otel-collector:4317
  tracing:
    sampling:
      probability: 1.0 # In Produktion eher 0.1

Deep Dive: Das Geniale an Spring Boot 4 ist der Bridge-Effekt. Die Micrometer Observation API fungiert als gemeinsames Interface. Wenn du eine Methode mit @Observed markierst, erstellt Spring daraus gleichzeitig einen Trace-Span für Tempo und ein Metric-Sample für Prometheus.

Das Zusammenspiel mit Virtual Threads

Hier schließt sich der Kreis zu unserem Tech-Stack: Da wir Virtual Threads nutzen, ist das Context Propagation Thema (also das Mitführen der Trace-ID über Thread-Grenzen hinweg) in Spring Boot 4 endlich stabil gelöst. Du musst keine speziellen ThreadLocal-Hacks mehr schreiben; der Trace-Context “wandert” einfach mit dem Virtual Thread mit.

Absolut richtig. Wer schon mal ratlos vor einem INTERNAL: Unknown error saß, weiß: Ohne sauberes Error Handling ist gRPC im produktiven Einsatz ein Albtraum. Da gRPC nicht auf HTTP-Statuscodes wie 404 oder 500 basiert, sondern auf eigenen gRPC Status Codes, müssen wir hier präzise sein.

In Spring Boot 4 nutzen wir dafür den Global Interceptor Ansatz oder – noch eleganter – den neuen @GrpcExceptionHandler.

Error Handling: Schluss mit “Unknown Error”

In der REST-Welt nutzen wir @ControllerAdvice. In Spring Boot 4 mit gRPC gibt es ein analoges Konzept, um Exceptions in gRPC-Statuscodes zu übersetzen.

Der deklarative Ansatz

Statt in jedem Service try-catch Blöcke zu schreiben, definieren wir eine zentrale Komponente:

@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)

Manchmal reicht ein einfacher Statuscode nicht aus. Wenn wir z.B. Validierungsfehler mitsenden wollen, nutzen wir das Rich Error Model. Spring Boot 4 unterstützt dies nativ über Metadaten (Trailers).

Tip: Nutze niemals Status.INTERNAL für Business-Logik-Fehler. Das signalisiert dem Client meistens, dass der Server abgestürzt ist. Nutze stattdessen spezifische Codes wie FAILED_PRECONDITION oder OUT_OF_RANGE, um dem Aufrufer mitzuteilen, was inhaltlich schiefgelaufen ist.

Fazit: Warum Spring Boot 4 ein Gamechanger für gRPC ist

Wir haben gesehen, dass gRPC im Jahr 2026 keine “Nischen-Technologie” mehr für High-Frequency-Trading ist. Durch die native Integration in Spring Boot 4 wird es zur echten Alternative für jeden internen Microservice-Call.

Die Key Takeaways:

  • Offizieller Support: Kein Basteln mit Drittanbieter-Startern mehr.
  • Virtual Threads: Maximale Skalierbarkeit bei gewohntem, synchronem Programmiermodell.
  • Observability: Tracing und Metriken sind dank OpenTelemetry out-of-the-box dabei.

Testing: Verlässliche gRPC-Suiten ohne Overhead

Niemand möchte einen kompletten Server auf Port 9090 starten, nur um eine Business-Logik zu validieren. In Spring Boot 4 nutzen wir den In-Process-Server, der die Kommunikation im Speicher abwickelt.

Unit Testing mit @GrpcTest

Der neue @GrpcTest-Slicing-Annotation erlaubt es uns, nur den gRPC-Layer zu laden – ohne den gesamten 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

Für echte End-to-End-Tests in einer Microservice-Landschaft nutzen wir Testcontainers. Seit 2026 gibt es verbesserte Module, die gRPC-Health-Checks nativ unterstützen, um sicherzustellen, dass der Container bereit ist, bevor der Test startet.

Tip: gRPC-Services lassen sich hervorragend mit BloomRPC oder Postman (das seit v10 gRPC nativ unterstützt) manuell testen. Für automatisierte Contract-Tests empfehle ich jedoch Pact, um sicherzustellen, dass Änderungen an der .proto-Datei den Order-Service nicht lautlos zerbrechen.

Zusammenfassung: Der Gold-Standard 2026

Wir haben den Kreis geschlossen:

  1. Definition via Protobuf für maximale Typsicherheit.
  2. Effizienz durch HTTP/2 und Binär-Payloads.
  3. Skalierbarkeit dank Java Virtual Threads in Spring Boot 4.
  4. Sicherheit durch globales Error Handling und Observability.
  5. Stabilität durch In-Process-Testing.