Back to Blog
grpcspring-bootmicroservicesperformancejavaprotobuf

gRPC with Spring Boot: 10x Faster Than REST for Internal Services (2026)

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.

J

JOptimize Team

May 25, 2026· 9 min read

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.


When to Use gRPC Instead of REST

gRPC wins for:

  • High-frequency internal service calls (hundreds per second)
  • Services where payload size matters (IoT, mobile backends)
  • Bidirectional streaming (chat, live feeds)
  • Polyglot microservices (gRPC has clients for every language)

REST wins for:

  • Public-facing APIs consumed by browsers
  • APIs consumed by partners who need curl/Postman access
  • Simple CRUD with low call frequency

Step 1: Define the Contract (Proto File)

// 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.


Step 2: Build Setup (Maven)

<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>

Step 3: Implement the Server

@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

Step 4: Client-Side

@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

Performance Comparison: gRPC vs REST

MetricREST/JSONgRPC/Protobuf
Payload size (1000 records)~120 KB~18 KB
Serialization time~8ms~0.8ms
Throughput (same hardware)~15K req/s~110K req/s
Connection setupNew TCP per requestHTTP/2 multiplexed
Schema validationRuntime (Jackson)Compile time (proto)

The throughput advantage comes primarily from binary serialization (no JSON parsing) and HTTP/2 multiplexing (multiple requests over one connection).


Common Mistakes to Avoid

  • Using blocking stubs in WebFlux or reactive pipelines — blocking stubs block threads; use AsyncStub or reactor-grpc for non-blocking behavior
  • No deadline on client calls — without withDeadlineAfter(), a slow server blocks the client indefinitely
  • Not adding TLS in production — gRPC defaults to plaintext; in production, configure mutual TLS between services
  • Breaking proto changes — removing fields, changing field numbers, or changing field types in .proto breaks backward compatibility silently; always add new fields, never change existing ones

Summary

gRPC 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.


Detect API Design Issues in Your Services

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.

Want to go deeper?

Master Spring Boot, security, and Java performance with hands-on courses.

Detect issues in your project

JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.