How to Build gRPC Services in Spring Boot 4.1 (Auto-Config)
Writing
SPRING BOOT
Published September 25, 202611 min read

How to Build gRPC Services in Spring Boot 4.1 (Auto-Config)

Spring Boot 4.1 gRPC auto-configuration replaces manual wiring. Build a gRPC server and client step by step with @GrpcAdvice, transports, and mTLS.

Rabinarayan Patra - Software Development Engineer

By Rabinarayan Patra

SDE II at Amazon

spring-boot-grpc-auto-configurationspring-boot-4-1grpcspring-grpcjavamicroservices

For years, adding gRPC to a Spring Boot app meant pulling in a community starter and hoping it kept pace with each Boot release. I did exactly that on a payments service back in 2024, and every Spring Boot upgrade turned into a small archaeology project: which starter version matches which Boot version, and what broke this time.

Spring Boot 4.1 ends that. It ships gRPC auto-configuration in the core, so a gRPC server now boots with the same zero-wiring feel as a REST controller. Spring Boot 4.1.0 went GA on June 10, 2026, built on Spring Framework 7.0 with a Java 17 baseline, and the gRPC support runs on gRPC Java 1.80.0. This guide walks through the whole thing end to end, and every snippet maps to the real 4.1 API.

What is gRPC auto-configuration in Spring Boot 4.1?

gRPC auto-configuration in Spring Boot 4.1 is built-in support that starts a gRPC server and wires client stubs straight from your classpath and properties, with no manual server bean. It arrives through Spring gRPC 1.1.0, whose main change in this version was moving its auto-configuration into Spring Boot 4.1.0 itself.

Here's what that means in practice. You add one starter, write a .proto file, implement the generated service base class as a normal Spring bean, and the server comes up on port 9090. No Server bean, no manual ServerBuilder, no lifecycle code. The same auto-configuration handles the client side too, so a blocking stub becomes an injectable bean.

The release notes call out three things I care about most: standalone Netty and Servlet HTTP/2 transports, @GrpcAdvice for centralized exception handling, and an auto-configured ObservationGrpcServerInterceptor that feeds metrics and tracing with custom observation conventions. I'll cover each of those below.

If you've used the REST API versioning support in Spring Framework 7, the mental model is the same: convention and properties first, hand-wiring only when you need it.

How do you add gRPC to a Spring Boot 4.1 project?

You add the Spring gRPC starter and a protobuf build plugin, then put your .proto files under src/main/proto. The fastest route is start.spring.io with the gRPC dependency selected, which scaffolds the protobuf plugin for you. If you're adding it to an existing project, here is the Maven setup.

The starter itself is one dependency. Spring Boot 4.1 manages the version, so you don't pin it:

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

The proto compilation is standard gRPC Java work, not a Spring concern, so you wire the usual protobuf plugin. It reads src/main/proto and writes stubs to target/generated-sources/protobuf:

<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:${protobuf-java.version}:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Notice the ${protobuf-java.version} and ${grpc.version} placeholders. Those come from Spring Boot's dependency management, so the generated stubs match the gRPC Java 1.80.0 runtime the starter brings in. No version drift between your protoc plugin and the runtime, which was a frequent source of pain on the old setup.

On Gradle, the equivalent is the com.google.protobuf plugin plus the same starter dependency. It writes stubs to build/generated/source/proto/main. Run ./mvnw clean package (or ./gradlew build) once and the generated classes show up on the compile path.

How do you define a gRPC service with a proto schema?

You write a .proto schema, let the plugin generate the base class, then extend that base class in a Spring bean. Start with the contract in src/main/proto/greeter.proto:

syntax = "proto3";
 
option java_multiple_files = true;
option java_package = "com.example.grpc.proto";
 
package greeter;
 
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}
 
message HelloRequest {
  string name = 1;
}
 
message HelloReply {
  string message = 1;
}

After a build, the plugin generates GreeterGrpc plus the message types. To implement the service, extend the generated GreeterImplBase and register it as a Spring bean. The key detail: Spring gRPC picks up any bean of type BindableService, so a plain @Service is enough. You do not need a special annotation just to expose the service.

import com.example.grpc.proto.GreeterGrpc;
import com.example.grpc.proto.HelloReply;
import com.example.grpc.proto.HelloRequest;
import io.grpc.stub.StreamObserver;
import org.springframework.stereotype.Service;
 
@Service
public class GreeterService extends GreeterGrpc.GreeterImplBase {
 
  @Override
  public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
    if (request.getName().isBlank()) {
      throw new IllegalArgumentException("name must not be blank");
    }
    HelloReply reply = HelloReply.newBuilder()
        .setMessage("Hello " + request.getName())
        .build();
    responseObserver.onNext(reply);
    responseObserver.onCompleted();
  }
}

That's the entire server. Run the app, and the gRPC server listens on 9090. You can tune it through properties, no Java config required:

spring:
  grpc:
    server:
      port: 9090
      max-inbound-message-size: 4MB

If you want per-service interceptors, that's where the @GrpcService annotation comes in. Annotate the bean with @GrpcService(interceptors = AuditInterceptor.class) to attach interceptors to one service, or register a global one with @GlobalServerInterceptor on a ServerInterceptor bean. For a single shared concern like request logging, I reach for the global interceptor and order it with @Order.

How do you handle gRPC errors with @GrpcAdvice?

You put your error translation in one class annotated with @GrpcAdvice and add @GrpcExceptionHandler methods that map a Java exception to a gRPC Status. This is the gRPC analogue of @ControllerAdvice in Spring MVC, and it's one of the additions Spring Boot 4.1 highlights.

Without it, every service method has to catch its own exceptions and translate them into status codes, which gets repetitive fast. With @GrpcAdvice, the GreeterService above can just throw a normal IllegalArgumentException and let the advice turn it into a proper INVALID_ARGUMENT response:

import io.grpc.Status;
import io.grpc.StatusRuntimeException;
 
@GrpcAdvice
public class GrpcErrorAdvice {
 
  @GrpcExceptionHandler(IllegalArgumentException.class)
  public StatusRuntimeException handleBadInput(IllegalArgumentException ex) {
    return Status.INVALID_ARGUMENT
        .withDescription(ex.getMessage())
        .asRuntimeException();
  }
}

A handler method takes the exception type as its argument and returns a Status, StatusException, or StatusRuntimeException. Return the Status directly when you don't need trailing metadata, or build a StatusRuntimeException with a Metadata object when you want to attach extra detail like an error code or a retry hint. Both annotations live in Spring gRPC's server exception package.

For dynamic cases where one method must decide across many exception types, Spring gRPC also lets you register a functional GrpcExceptionHandler bean that returns a Status for the exceptions it recognizes and null for the rest, so other handlers get a turn. I stick with the annotation style for clarity and only drop to the functional bean when the mapping is computed at runtime. The same instinct I described in the Debezium outbox pattern guide applies here: keep cross-cutting translation in one obvious place.

How do you choose between the Netty and Servlet transports?

You pick Netty for a standalone gRPC server on its own port, and the Servlet transport when you want gRPC to share your existing web server. Both speak HTTP/2. The choice is a single dependency swap, and it changes which properties apply.

The default starter, spring-grpc-spring-boot-starter, runs an embedded Netty server. The Servlet variant, spring-grpc-server-web-spring-boot-starter, runs gRPC inside the same Servlet container that serves your REST controllers, so they share a port. One thing that trips people up: under the Servlet transport, the spring.grpc.server.* properties are ignored except max-inbound-message-size. You configure the address through the normal server.* properties instead, because there is only one web server now.

My rule of thumb: a dedicated internal microservice that only speaks gRPC gets Netty, because a separate port keeps concerns clean and lets me scale and secure it independently. A service that already exposes REST and wants to add a few gRPC endpoints behind one ingress gets the Servlet transport, since fronting two ports through a gateway is more trouble than it's worth. If you're already deep into ahead-of-time builds, both transports work under the AOT engine I covered in the Spring Boot 4 AOT data repositories post.

How do you add observability and mTLS to your gRPC server?

You get metrics and tracing by adding Actuator, and you get mutual TLS by pointing the server at an SSL bundle. Both are property-driven, which is the part that saves the most boilerplate.

For observability, add the Actuator starter and a Micrometer bridge for your platform. Spring gRPC auto-configures an ObservationGrpcServerInterceptor that produces per-call metrics and trace spans, and it respects custom observation conventions if you register one:

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

Once that's on the classpath, every RPC shows up as timed metrics, and if you've wired tracing, each call carries a span you can follow across services. I didn't have to touch a single interceptor registration to get this.

For transport security, Spring gRPC reuses Spring Boot's SSL bundles, so the same bundle format you use for HTTPS covers gRPC. Mutual TLS means the server presents a certificate and also verifies the client's certificate against a trust store:

spring:
  grpc:
    server:
      ssl:
        bundle: grpc-server
  ssl:
    bundle:
      jks:
        grpc-server:
          keystore:
            location: classpath:server.p12
            password: secret
            type: PKCS12
          truststore:
            location: classpath:server-truststore.p12
            password: secret

The trust store is what makes it mutual. The server checks each incoming client certificate against it, so an unknown client gets rejected at the TLS layer before any RPC runs. On the client side you set negotiation-type: TLS and point at the matching bundle, which I'll show next.

How do you call the gRPC service from a Spring client?

You import the generated client stubs with @ImportGrpcClients and inject a blocking stub like any other bean. Spring gRPC builds the channel from your client properties, so you never touch ManagedChannelBuilder directly.

Turn on stub scanning at the application class, pointing it at the package that holds the generated GreeterGrpc:

import com.example.grpc.proto.GreeterGrpc;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.grpc.client.ImportGrpcClients;
 
@SpringBootApplication
@ImportGrpcClients(basePackageClasses = GreeterGrpc.class)
public class ClientApplication {
  public static void main(String[] args) {
    org.springframework.boot.SpringApplication.run(ClientApplication.class, args);
  }
}

Now the blocking stub is injectable. Constructor injection works the same as it does for a repository or a RestClient:

import com.example.grpc.proto.GreeterGrpc;
import com.example.grpc.proto.HelloReply;
import com.example.grpc.proto.HelloRequest;
import org.springframework.stereotype.Component;
 
@Component
public class GreeterClient {
 
  private final GreeterGrpc.GreeterBlockingStub stub;
 
  public GreeterClient(GreeterGrpc.GreeterBlockingStub stub) {
    this.stub = stub;
  }
 
  public String greet(String name) {
    HelloReply reply = stub.sayHello(
        HelloRequest.newBuilder().setName(name).build());
    return reply.getMessage();
  }
}

The stub needs an address. Configure the default channel, or define named channels when the app talks to several services:

spring:
  grpc:
    client:
      default-channel:
        address: 'localhost:9090'
      channels:
        greeter:
          address: 'localhost:9090'
          negotiation-type: TLS
          ssl:
            bundle: grpc-client

If you'd rather build the stub yourself, inject a GrpcChannelFactory and call channels.createChannel("greeter"), then wrap it with GreeterGrpc.newBlockingStub(...). In tests, the @LocalGrpcPort annotation hands you the random port of an in-process server, so an integration test can call the real service without binding 9090. That made my first round of contract tests far less fiddly than the manual channel juggling I used to do.

Should you adopt Spring Boot 4.1 gRPC auto-configuration?

If you run gRPC and you're on Spring Boot, yes, and the reason is maintenance, not novelty. The old community starters were good work, but they sat outside the Spring release train, so every Boot bump carried a question mark. Folding gRPC into Spring Boot 4.1 puts it on the same upgrade cadence as everything else you depend on, which is the kind of boring stability that pays off two years from now.

What I didn't expect was how much the SSL bundle reuse would matter. Sharing one certificate configuration format across HTTPS and gRPC means there's one place to rotate certs, not two, and that alone removes a class of mismatched-config incidents. The annotation-driven error handling is the other quiet win, because it pulls status mapping out of your business logic where it never belonged. Start with the Netty transport on a single internal service, get the observability wired, and you'll have a template you can copy across the fleet.

For the official details, see the Spring Boot 4.1 release announcement, the Spring gRPC reference documentation, and InfoQ's Spring Boot 4.1 release coverage.

Keep Reading

Frequently Asked Questions

What is gRPC auto-configuration in Spring Boot 4.1?

Spring Boot 4.1 gRPC auto-configuration is built-in support that wires a gRPC server and client from your classpath and properties, with no manual bean setup. It ships through Spring gRPC 1.1.0, which moved its auto-configuration into Spring Boot itself, and runs on gRPC Java 1.80.0. You add a starter, drop in a proto file, and the server starts on its own.

Do I still need a third-party gRPC starter with Spring Boot 4.1?

No. The official Spring gRPC starter is now the supported path, so you no longer depend on community projects like grpc-spring-boot-starter to bridge gRPC and Spring. Before 4.1 you had to wire gRPC manually or rely on a third-party starter that often lagged each Spring Boot release.

Should I use the Netty or Servlet transport for gRPC in Spring Boot?

Use the default Netty transport when the service is gRPC-only, since it runs a dedicated HTTP/2 server on its own port (9090 by default). Switch to the Servlet transport when you want gRPC to share the same web server and port as your REST endpoints, which simplifies deployment behind a single ingress.

How do you handle gRPC exceptions in Spring Boot 4.1?

Annotate a class with @GrpcAdvice and add methods marked @GrpcExceptionHandler that map a Java exception to a gRPC Status. The handler returns a Status, StatusException, or StatusRuntimeException, which keeps error translation in one place instead of scattered across every service method.

Rabinarayan Patra - Software Development Engineer

Rabinarayan Patra

SDE II at Amazon. Previously at ThoughtClan Technologies building systems that processed 700M+ daily transactions. I write about Java, Spring Boot, microservices, and the things I figure out along the way. More about me →

X (Twitter)LinkedIn

Stay in the loop

Get the latest articles on system design, frontend and backend development, and emerging tech trends, straight to your inbox. No spam.