Spring Boot 4.1 gRPC: Why I Swapped One REST Call and What It Revealed About Microservice Boundaries
The author migrates a high-frequency internal inventory query from REST to gRPC in Spring Boot 4.1, discovering that gRPC's contract-first approach, binary encoding, and explicit timeout handling improve interface governance for service-to-service RPC, while REST remains better for web-facing and low-frequency APIs.
Introduction
While reorganizing service calls in a project, the author revisited a long-standing internal link: the Order Service querying SKU available stock from the Inventory Service. This call had evolved from RestTemplate to Feign to Spring Boot's @HttpExchange, but remained a simple HTTP/JSON endpoint.
Defining the Protobuf Contract
The first step in adopting gRPC is defining a .proto file, which serves as the single source of truth for both service methods and data structures — unlike REST where the HTTP path and JSON schema are separate concerns.
syntax = "proto3";
package inventory;
option java_multiple_files = true;
option java_package = "com.example.inventory.grpc";
service InventoryService {
rpc GetInventory(
GetInventoryRequest
) returns (
GetInventoryResponse
);
}
message GetInventoryRequest {
int64 sku_id = 1;
}
message GetInventoryResponse {
int64 sku_id = 1;
int32 available = 2;
int32 locked = 3;
string warehouse = 4;
}This contract replaces the previous Java DTO approach where server and client often duplicated DTOs or relied on shared JARs ( inventory-common, inventory-dto) that eventually accumulated unrelated utilities and created tight coupling.
Server Implementation with Spring Boot 4.1
Spring Boot 4.1 provides spring-boot-starter-grpc-server and integrates the protobuf-maven-plugin (managed by spring-boot-starter-parent). After compilation, the plugin generates request/response classes and a base service implementation.
@GrpcService
@RequiredArgsConstructor
public class InventoryGrpcService
extends InventoryServiceGrpc.InventoryServiceImplBase {
private final InventoryService inventoryService;
@Override
public void getInventory(
GetInventoryRequest request,
StreamObserver<GetInventoryResponse> responseObserver) {
long skuId = request.getSkuId();
Inventory inventory = inventoryService.query(skuId);
GetInventoryResponse response = GetInventoryResponse.newBuilder()
.setSkuId(inventory.getSkuId())
.setAvailable(inventory.getAvailable())
.setLocked(inventory.getLocked())
.setWarehouse(inventory.getWarehouse())
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}The author notes the immediate structural difference: the REST chain Controller → DTO → Service → JSON serialization becomes gRPC Service → Service → Protobuf, eliminating the Controller layer and fixing the interface contract at compile time.
Client Configuration
The client adds spring-boot-starter-grpc-client and registers the generated blocking stub via @ImportGrpcClients using a logical name ( inventory) that maps to a physical address in configuration:
spring:
grpc:
client:
channel:
inventory:
target: static://inventory-service:9090In Kubernetes, inventory-service resolves to the internal Service DNS. The business gateway then uses the stub with an explicit deadline:
@Service
@RequiredArgsConstructor
public class InventoryGateway {
private final InventoryServiceGrpc.InventoryServiceBlockingStub inventoryStub;
public InventoryResult query(Long skuId) {
GetInventoryRequest request = GetInventoryRequest.newBuilder()
.setSkuId(skuId)
.build();
GetInventoryResponse response = inventoryStub
.withDeadlineAfter(800, TimeUnit.MILLISECONDS)
.getInventory(request);
return new InventoryResult(
response.getSkuId(),
response.getAvailable(),
response.getLocked(),
response.getWarehouse()
);
}
}Contract Discipline and Schema Evolution
Protobuf identifies fields by number, not name. This forces disciplined evolution: deprecated fields are marked reserved and their numbers are never reused. Example:
message StockResponse {
int64 sku_id = 1;
reserved 2;
reserved "stock";
int32 available = 3;
int32 locked = 4;
}This prevents the casual field renaming/deletion common in REST DTOs (e.g., changing stock to available / locked without coordination) and treats the API as a long-term contract.
Binary Encoding Benefits
For high-frequency internal RPC, protobuf's binary encoding avoids transmitting field names (e.g., "skuId": 10001, "available": 238) and reduces parsing overhead. The author emphasizes this only matters for high-frequency service-to-service calls, not low-frequency admin APIs.
Timeout Handling
Migrating from HTTP client timeouts ( connect-timeout: 1s, read-timeout: 2s) to gRPC required explicit per-call deadlines via withDeadlineAfter(800, MILLISECONDS). The author argues that even with virtual threads, downstream latency must be bounded — especially for aggregate endpoints (order detail also calls member, coupon, logistics). A 5-second inventory stall would block the entire page.
Error Mapping
gRPC uses its own status codes (e.g., NOT_FOUND, DEADLINE_EXCEEDED) instead of HTTP codes. The server maps business conditions:
if (inventory == null) {
responseObserver.onError(
Status.NOT_FOUND
.withDescription("SKU not found: " + request.getSkuId())
.asRuntimeException()
);
return;
}The client catches StatusRuntimeException and translates to domain exceptions, keeping gRPC details inside the gateway:
try {
return inventoryStub
.withDeadlineAfter(800, TimeUnit.MILLISECONDS)
.getInventory(request);
} catch (StatusRuntimeException e) {
if (e.getStatus().getCode() == Status.Code.NOT_FOUND) {
throw new SkuNotFoundException(skuId);
}
if (e.getStatus().getCode() == Status.Code.DEADLINE_EXCEEDED) {
throw new InventoryTimeoutException(skuId);
}
throw new InventoryServiceException("库存服务调用失败", e);
}This gateway pattern ( OrderService → InventoryGateway → gRPC Stub) isolates protocol changes, mirroring the earlier @HttpExchange approach.
Modeling Money and Dates
Protobuf lacks a native BigDecimal. The author rejects double for monetary values and uses integer cents:
message Price {
int64 amount_cent = 1;
string currency = 2;
}For timestamps, protobuf's built-in Timestamp type avoids format ambiguity (ISO string vs. Unix seconds vs. milliseconds).
Proto Repository Management
As services adopt gRPC, sharing .proto files via copy-paste fails. The author recommends a dedicated protocol repository:
company-protos
├── inventory
│ └── v1
│ └── inventory.proto
├── member
│ └── v1
│ └── member.proto
└── payment
└── v1
└── payment.protoChanges require review; rules include: never reuse field numbers, never change types of deployed fields, reserve deleted fields, and maintain backward compatibility when adding fields.
Performance Reality
The author did not benchmark "10x faster" claims. If DB query takes 300ms and JSON serialization 1ms, protobuf cannot reduce total latency to 30ms. gRPC's value appears in high-frequency, fixed-structure, internally controlled RPCs — not in low-frequency or web-facing APIs.
When to Use gRPC vs. REST
Continue REST for: Web frontend APIs, third-party developer APIs (debugging/compatibility), low-frequency admin interfaces, stable Feign calls with no pain points.
Consider gRPC for: Internal service-to-service calls, high frequency, fixed request/response structure, both sides owned by the team, no browser access needed.
Conclusion
After migrating only the inventory link ( REST + JSON → gRPC + Protobuf), the author's view shifted from "HTTP/2, binary, high performance, streaming" to valuing the explicit contract:
service InventoryService {
rpc GetInventory(
GetInventoryRequest
) returns (
GetInventoryResponse
);
}Client/server language, Controller implementation, DTO packaging become secondary. The interface itself has a clear, versioned contract. Performance gains should be validated on the specific hot paths, not by blindly replacing all REST controllers.
For new Java microservices, the guiding question becomes: Is this API for humans/external systems (REST) or for high-frequency internal service-to-service RPC (gRPC)?
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
LuTiao Programming
LuTiao Programming is a friendly community offering free programming lessons. We inspire learners to explore new ideas and technologies and quickly acquire job-ready skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
