gRPC uses HTTP/2 and Protocol Buffers to deliver 10x better throughput than REST/JSON for internal microservice communication. Learn how to implement gRPC in Spring Boot with real benchmarks.
JOptimize Team
REST/JSON is excellent for public APIs consumed by browsers and third parties. For internal microservice communication — where you control both the client and server — gRPC offers a compelling alternative: binary Protocol Buffers serialization (5-10x smaller payloads), HTTP/2 multiplexing (no connection per request), strict schema enforcement, and built-in streaming.
The trade-off: more setup, less human-readable, and harder to debug with curl.
gRPC wins for:
REST wins for:
// src/main/proto/order.proto syntax = "proto3"; option java_multiple_files = true; option java_package = "com.myapp.grpc"; package order; service OrderService { rpc GetOrder (GetOrderRequest) returns (Order); rpc CreateOrder (CreateOrderRequest) returns (Order); rpc StreamOrders (StreamOrdersRequest) returns (stream Order); // Server streaming rpc WatchOrders (stream OrderEvent) returns (stream OrderStatus); // Bidirectional } message GetOrderRequest { int64 id = 1; } message CreateOrderRequest { int64 customer_id = 1; repeated OrderItem items = 2; string currency = 3; } message Order { int64 id = 1; int64 customer_id = 2; repeated OrderItem items = 3; string status = 4; int64 created_at = 5; } message OrderItem { int64 product_id = 1; int32 quantity = 2; double price = 3; }
The .proto file IS the API contract — both sides compile it to generate type-safe code.
<dependency> <groupId>net.devh</groupId> <artifactId>grpc-server-spring-boot-starter</artifactId> <version>3.1.0.RELEASE</version> </dependency> <dependency> <groupId>net.devh</groupId> <artifactId>grpc-client-spring-boot-starter</artifactId> <version>3.1.0.RELEASE</version> </dependency> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.25.1:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.62.2:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions><execution><goals><goal>compile</goal><goal>compile-custom</goal></goals></execution></executions> </plugin>
@GrpcService @RequiredArgsConstructor public class OrderGrpcService extends OrderServiceGrpc.OrderServiceImplBase { private final OrderRepository orderRepository; @Override public void getOrder( GetOrderRequest request, StreamObserver<Order> responseObserver) { orderRepository.findById(request.getId()) .ifPresentOrElse( order -> { responseObserver.onNext(toProto(order)); // Send response responseObserver.onCompleted(); // Signal completion }, () -> responseObserver.onError( Status.NOT_FOUND .withDescription("Order " + request.getId() + " not found") .asRuntimeException() ) ); } // Server streaming — return multiple responses for one request @Override public void streamOrders( StreamOrdersRequest request, StreamObserver<Order> responseObserver) { orderRepository.findByCustomerId(request.getCustomerId()) .forEach(order -> responseObserver.onNext(toProto(order))); responseObserver.onCompleted(); } private Order toProto(com.myapp.model.Order order) { return Order.newBuilder() .setId(order.getId()) .setCustomerId(order.getCustomerId()) .setStatus(order.getStatus().name()) .setCreatedAt(order.getCreatedAt().toEpochSecond(ZoneOffset.UTC)) .build(); } }
# application.properties grpc.server.port=9090 grpc.server.security.enabled=false # Add TLS in production
@Service public class OrderClientService { @GrpcClient("order-service") private OrderServiceGrpc.OrderServiceBlockingStub orderStub; public OrderDto getOrder(Long id) { GetOrderRequest request = GetOrderRequest.newBuilder().setId(id).build(); try { Order order = orderStub .withDeadlineAfter(500, TimeUnit.MILLISECONDS) // Timeout .getOrder(request); return OrderMapper.fromProto(order); } catch (StatusRuntimeException e) { if (e.getStatus().getCode() == Status.Code.NOT_FOUND) { throw new OrderNotFoundException(id); } throw new ServiceException("Order service unavailable", e); } } }
# application.properties — client targets grpc.client.order-service.address=static://order-service:9090 grpc.client.order-service.negotiation-type=plaintext
| Metric | REST/JSON | gRPC/Protobuf |
|---|---|---|
| Payload size (1000 records) | ~120 KB | ~18 KB |
| Serialization time | ~8ms | ~0.8ms |
| Throughput (same hardware) | ~15K req/s | ~110K req/s |
| Connection setup | New TCP per request | HTTP/2 multiplexed |
| Schema validation | Runtime (Jackson) | Compile time (proto) |
The throughput advantage comes primarily from binary serialization (no JSON parsing) and HTTP/2 multiplexing (multiple requests over one connection).
AsyncStub or reactor-grpc for non-blocking behaviorwithDeadlineAfter(), a slow server blocks the client indefinitely.proto breaks backward compatibility silently; always add new fields, never change existing onesgRPC delivers 5-10x better throughput than REST/JSON for internal service communication through binary Protocol Buffers serialization and HTTP/2 multiplexing. Use it for high-frequency internal calls, streaming use cases, and polyglot service meshes. The learning curve is the proto schema and generated code, but once in place it enforces stricter API contracts than REST ever could.
JOptimize analyzes your internal service communication patterns for missing timeouts, inefficient serialization, and N+1 service call patterns across your microservices.
Optimize service-to-service communication — free architecture scan.
Master Spring Boot, security, and Java performance with hands-on courses.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.