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: 4MBIf 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: secretThe 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-clientIf 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
- How to Version REST APIs in Spring Framework 7. The same convention-first design shows up in Spring's REST versioning, a useful companion when a service speaks both protocols.
- Spring Boot 4 AOT Data Repositories. If you run native images, this explains how the AOT engine treats your beans, which applies to gRPC services too.
- How to Implement the Debezium Outbox Pattern in Spring Boot. A practical look at keeping cross-service messaging reliable once your gRPC services start talking to each other.
- Spring AI 2.0 MCP Annotations: From Tool to Production. Another case where Spring Boot 4 moved a fast-moving integration into first-class, annotation-driven support.
