Companion code for the ankurm.com guide. Verified on Spring Boot 4.1.0, spring-grpc 1.1.0,
grpc-java 1.80.0, protobuf-java 4.34.2, JDK 25.0.3. results-full.txt is unedited mvn test
output: 11 tests, 0 failures. No installs needed - protoc and the gRPC codegen plugin resolve
as Maven artifacts, and every test uses the in-process transport.
_01_basics all four call types and how their failure modes differ: iterator semantics on
server streaming, the half-close that client streaming hangs without, and the
independence of the two streams in bidi.
_02_troubleshooting
four failures reproduced then fixed - the 4 MB message limit and which side
enforces it, the absent default deadline, cancellation that never interrupts a
thread, and errors that arrive as UNKNOWN with no description.
_03_exceptions @GrpcAdvice / @GrpcExceptionHandler: domain exception to NOT_FOUND with
trailers, validation to INVALID_ARGUMENT, and a catch-all that returns a
deliberate INTERNAL without leaking the original message across the boundary.
docs/01 symptom-first troubleshooting index, each entry marked [tested] or [documented]
docs/02 complete spring.grpc.* property reference, and the split between the Boot 4.1
integration (org.springframework.boot, version 4.1.0, owns properties and
auto-configuration) and the Spring gRPC project (org.springframework.grpc,
version 1.1.0, owns the programming model). The older standalone
spring-grpc-spring-boot-starter stops at 1.0.3 and is what most search results
describe.
Findings worth the commit message
- The in-process transport CANNOT enforce message size limits: it passes messages by
reference and never serialises them. A 4 MB + 1 KB message goes through cleanly in tests
and fails in production with RESOURCE_EXHAUSTED. The test asserts this rather than
pretending otherwise. Same blind spot covers compression, TLS, keepalive and LB.
- Two property names cost real time while writing this:
spring.grpc.server.inprocess.name (not in-process) and
spring.grpc.client.channel.<n>.target (singular channel, and target not address).
The second failure surfaces as UnknownHostException on the channel NAME.
- gRPC still has no default deadline, and cancellation only sets a Context flag.
7.1 KiB
Properties reference, and which project owns what
Two things that cause disproportionate confusion: the exact property names, and the fact that "Spring gRPC" now means two different things with two different version numbers.
1. Boot 4.1 integration vs. the Spring gRPC project
These are separate artifacts with separate versions, and both are on your classpath.
| Spring Boot gRPC integration | Spring gRPC | |
|---|---|---|
| Group | org.springframework.boot |
org.springframework.grpc |
| Artifacts | spring-boot-starter-grpc-server, spring-boot-starter-grpc-client, spring-boot-grpc-server, spring-boot-grpc-client, spring-boot-grpc-test |
spring-grpc-core |
| Version here | 4.1.0 (the Boot version) | 1.1.0 |
| Owns | auto-configuration, spring.grpc.* properties, starters, actuator/health, observation wiring |
the programming model: @GrpcService, @GrpcAdvice, @GrpcExceptionHandler, GrpcChannelFactory, @ImportGrpcClients, interceptor infrastructure |
| Managed by | the Boot BOM directly | the Boot BOM, via the spring-grpc.version property |
Practical consequences:
- Property names (
spring.grpc.*) come from Boot 4.1, defined inGrpcServerProperties/GrpcClientPropertiesunderorg.springframework.boot.grpc.*.autoconfigure. Version them against your Boot version. - Annotations and interfaces come from Spring gRPC 1.1.0, in
org.springframework.grpc.*. Version them againstspring-grpc.version. - Boot 4.1 also pins
grpc-java(1.80.0 here) andprotobuf-java(4.34.2). Both are older than the newest releases on Maven Central — that is deliberate, and overriding them independently is how you getNoSuchMethodErrorbetween grpc-java and its Netty shading.
The older path, and why you will see it in search results
Before Boot 4, the community project shipped its own starter:
<!-- Boot 3 era -->
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-spring-boot-starter</artifactId>
</dependency>
That artifact stops at 1.0.3 and is not what Boot 4 uses. On Boot 4 use the Boot starters and let the BOM manage everything:
<!-- Boot 4 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-grpc-client</artifactId>
</dependency>
Most blog posts and Stack Overflow answers you find will be describing the older artifact. The programming model is largely the same; the dependency coordinates and the property names are not.
2. Server properties — exact names
Read from spring-boot-grpc-server-4.1.0.jar's configuration metadata. This is the complete list.
| Property | Notes |
|---|---|
spring.grpc.server.enabled |
|
spring.grpc.server.port |
-1 disables the network server (in-process only); 0 picks an ephemeral port |
spring.grpc.server.address |
|
spring.grpc.server.inprocess.name |
inprocess, one word — not in-process |
spring.grpc.server.inbound.message.max-size |
the 4 MB default, for incoming requests |
spring.grpc.server.inbound.metadata.max-size |
header size limit |
spring.grpc.server.shutdown.grace-period |
|
spring.grpc.server.keepalive.time |
|
spring.grpc.server.keepalive.timeout |
|
spring.grpc.server.keepalive.permit.time |
minimum client ping interval the server tolerates |
spring.grpc.server.keepalive.permit.without-calls |
|
spring.grpc.server.keepalive.connection.max-age |
|
spring.grpc.server.keepalive.connection.max-idle-time |
|
spring.grpc.server.keepalive.connection.grace-period |
|
spring.grpc.server.ssl.enabled / .bundle / .client-auth / .secure |
uses Boot SSL bundles |
spring.grpc.server.health.enabled |
standard gRPC health service |
spring.grpc.server.health.service |
|
spring.grpc.server.health.include-overall-health |
|
spring.grpc.server.health.status.mapping / .order |
actuator status → gRPC serving status |
spring.grpc.server.health.schedule.enabled / .delay / .period |
|
spring.grpc.server.health.services.validate-membership |
|
spring.grpc.server.reflection.enabled |
needed for grpcurl without local .proto files |
spring.grpc.server.observation.enabled |
Micrometer observations |
spring.grpc.server.netty.transport |
|
spring.grpc.server.netty.domain-socket-path |
|
spring.grpc.server.servlet.enabled / .validate-http2 |
gRPC over the servlet container |
spring.grpc.server.security.csrf.enabled |
|
spring.grpc.server.factory.enabled |
3. Client properties — exact names
The client side is a map keyed by channel name:
spring.grpc.client.channel.<name>.<property>
channel, singular. channels is wrong and produces no error — see below.
| Property | Notes |
|---|---|
spring.grpc.client.channel.<name>.target |
target, not address |
spring.grpc.client.channel.<name>.user-agent |
|
spring.grpc.client.channel.<name>.bypass-certificate-validation |
test/dev only |
spring.grpc.client.channel.<name>.default.deadline |
the default deadline gRPC otherwise lacks |
spring.grpc.client.channel.<name>.default.load-balancing-policy |
e.g. round_robin (default is pick_first) |
spring.grpc.client.channel.<name>.inbound.message.max-size |
4 MB default, for incoming responses |
spring.grpc.client.channel.<name>.inbound.metadata.max-size |
|
spring.grpc.client.channel.<name>.keepalive.time / .timeout / .without-calls |
|
spring.grpc.client.channel.<name>.idle.timeout |
|
spring.grpc.client.channel.<name>.ssl.enabled / .bundle |
|
spring.grpc.client.channel.<name>.health.enabled / .service-name |
client-side health checking |
spring.grpc.client.channel.<name>.service-config.* |
retry, load balancing, throttling |
spring.grpc.client.enabled |
|
spring.grpc.client.inprocess.enabled |
register the in-process channel factory |
spring.grpc.client.observation.enabled |
|
spring.grpc.client.channelfactory.enabled |
service-config maps to the gRPC service config record with loadbalancing, method,
retrythrottling and healthcheck sections.
4. The two mistakes, and their symptoms
| Wrong | Right | Symptom |
|---|---|---|
spring.grpc.server.in-process.name |
spring.grpc.server.inprocess.name |
property silently ignored; server never binds in-process |
spring.grpc.client.channels.x.address |
spring.grpc.client.channel.x.target |
UNAVAILABLE: Unable to resolve host x / UnknownHostException: x |
The second is the expensive one. An unmatched channel name is handed to the DNS resolver as a literal target, so the error names your logical channel as if it were a hostname — and sends you to investigate DNS, service discovery and networking instead of a typo. Both cost real time while building this repository.
Guard against it: add spring-boot-configuration-processor and your IDE will flag unknown
spring.grpc.* keys. It will not catch a wrong value inside the channel.<name> map (the keys
there are arbitrary), but it catches everything else.