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.
101 lines
4.5 KiB
Markdown
101 lines
4.5 KiB
Markdown
# Spring gRPC with Spring Boot 4
|
|
|
|
Companion repository for **[Spring gRPC with Spring Boot 4](https://ankurm.com/spring-grpc-spring-boot-4/)**
|
|
on [ankurm.com](https://ankurm.com).
|
|
|
|
The post covers the concepts. This repository adds the part that is hard to find written down: a
|
|
symptom-first troubleshooting guide, with the failures reproduced as tests wherever a test can
|
|
honestly reproduce them.
|
|
|
|
Verified against **Spring Boot 4.1.0**, **spring-grpc 1.1.0**, **grpc-java 1.80.0**,
|
|
**protobuf-java 4.34.2**, **JDK 25.0.3**. Full output in [`results-full.txt`](results-full.txt).
|
|
|
|
---
|
|
|
|
## Quick start
|
|
|
|
Nothing to install — `protoc` and the gRPC codegen plugin are resolved as Maven artifacts, and every
|
|
test runs over the in-process transport, so no port is bound.
|
|
|
|
```console
|
|
git clone https://ankurm.com/git.app/asmhatre/spring-grpc-boot4.git
|
|
cd spring-grpc-boot4
|
|
mvn test
|
|
```
|
|
|
|
8 tests, a few seconds.
|
|
|
|
---
|
|
|
|
## What is here
|
|
|
|
| Test | Covers |
|
|
|---|---|
|
|
| [`FourCallTypesTest`](src/test/java/com/ankurm/grpc/_01_basics/FourCallTypesTest.java) | Unary, server streaming, client streaming, bidirectional — and how their failure modes differ |
|
|
| [`HardToDiagnoseTest`](src/test/java/com/ankurm/grpc/_02_troubleshooting/HardToDiagnoseTest.java) | Four failures reproduced and fixed: message size limits, missing deadlines, unobserved cancellation, useless error statuses |
|
|
| [`ExceptionMappingTest`](src/test/java/com/ankurm/grpc/_03_exceptions/ExceptionMappingTest.java) | `@GrpcAdvice` / `@GrpcExceptionHandler` — domain exception → `NOT_FOUND`, validation → `INVALID_ARGUMENT`, catch-all → `INTERNAL` without leaking detail |
|
|
|
|
| Document | Covers |
|
|
|---|---|
|
|
| [Troubleshooting](docs/01-troubleshooting.md) | Symptom-first index: every entry starts from the error message you actually see |
|
|
| [Properties and versions](docs/02-properties-and-versions.md) | Complete `spring.grpc.*` reference, and which of the two projects owns what |
|
|
|
|
### Exception mapping
|
|
|
|
[`OrderExceptionAdvice`](src/main/java/com/ankurm/grpc/orders/OrderExceptionAdvice.java) is the gRPC
|
|
analogue of `@RestControllerAdvice`. The service throws plain domain exceptions and never mentions
|
|
`Status`:
|
|
|
|
```java
|
|
@GrpcAdvice
|
|
public class OrderExceptionAdvice {
|
|
|
|
@GrpcExceptionHandler(OrderNotFoundException.class)
|
|
public StatusException handleNotFound(OrderNotFoundException ex) {
|
|
Metadata trailers = new Metadata();
|
|
trailers.put(REASON_KEY, "ORDER_NOT_FOUND");
|
|
return Status.NOT_FOUND.withDescription(ex.getMessage()).asException(trailers);
|
|
}
|
|
}
|
|
```
|
|
|
|
Without it, every domain failure arrives as `UNKNOWN` with a **null description** — no code to
|
|
branch on and no message to read.
|
|
|
|
The service itself ([`OrderServiceImpl`](src/main/java/com/ankurm/grpc/orders/OrderServiceImpl.java))
|
|
is heavily commented and is where the correct patterns live — cancellation checks, half-close
|
|
handling, `StreamObserver` thread-safety, structured errors.
|
|
|
|
---
|
|
|
|
## Five findings
|
|
|
|
1. **gRPC is a first-class Boot starter now, and "Spring gRPC" means two things.**
|
|
`org.springframework.boot:spring-boot-starter-grpc-server` (version **4.1.0**) owns
|
|
auto-configuration and every `spring.grpc.*` property; `org.springframework.grpc:spring-grpc-core`
|
|
(version **1.1.0**) owns the programming model — `@GrpcService`, `@GrpcAdvice`,
|
|
`GrpcChannelFactory`. The older standalone `spring-grpc-spring-boot-starter` stops at 1.0.3 and
|
|
is what most search results describe. See [docs/02](docs/02-properties-and-versions.md).
|
|
2. **The in-process transport cannot enforce message size limits.** It passes messages by reference
|
|
and never serialises them, so a 4 MB + 1 KB message sails through — and fails in production with
|
|
`RESOURCE_EXHAUSTED`. Asserted in the test suite. The same blind spot covers compression, TLS,
|
|
keepalive and load balancing.
|
|
3. **Two property names that cost real time.** `spring.grpc.server.inprocess.name` (not
|
|
`in-process`) and `spring.grpc.client.channel.<n>.target` (singular `channel`, and `target` not
|
|
`address`). Getting the second wrong yields `UnknownHostException` on the channel *name*, which
|
|
sends you to look at DNS.
|
|
4. **gRPC has no default deadline.** A call without one waits forever. This is the single most
|
|
common cause of a gRPC service that silently stops responding.
|
|
5. **Cancellation does not interrupt your thread.** It sets a `Context` flag. A server that never
|
|
checks it keeps working for a client that left ten minutes ago.
|
|
|
|
---
|
|
|
|
## Reference machine
|
|
|
|
AMD Ryzen 5 5600U, Windows 11, `java 25.0.3+9-LTS-195`, Maven 3.9.9.
|
|
|
|
## Licence
|
|
|
|
MIT. See [LICENSE](LICENSE).
|