Production-ready reactive gRPC implementation with Spring WebFlux, Protocol Buffers, and JWT authentication for high-performance real-time request processing.
A fully reactive microservices communication system built with gRPC and Spring Boot. Features non-blocking request/response patterns, type-safe Protocol Buffers serialization, OAuth2 JWT authentication, and reactive streaming for efficient inter-service communication.
Key Features:
- Fully reactive gRPC with Project Reactor
- Protocol Buffers for efficient serialization
- JWT Bearer token authentication
- Non-blocking request/response patterns
- Type-safe service contracts
- Spring Boot gRPC integration
- Client-side load balancing ready
- OpenAPI documentation (client)
| Category | Technologies |
|---|---|
| Core | Java 21, Spring Boot 3.2.2 |
| Reactive | Spring WebFlux, Project Reactor |
| RPC | gRPC 1.60.0, Reactor gRPC 1.2.4 |
| Serialization | Protocol Buffers 3.25.0 |
| Security | OAuth2 Resource Server, JWT |
| Framework | Spring Boot gRPC Starter 2.15.0 |
Java 21+
Maven 3.8+
Protocol Buffers Compiler (protoc)cd reactive-gRPC-common
mvn clean installThis generates:
- gRPC service stubs
- Reactive gRPC stubs
- Protocol Buffers Java classes
cd reactive-gRPC-server
mvn clean installcd reactive-gRPC-client
mvn clean installapplication.yaml:
grpc:
server:
port: 8000
server:
port: 8080
spring:
application:
name: reactive-gRPC-Server
security:
oauth2:
resourceserver:
jwt:
key-value: classpath:rsa-public.keyapplication.properties:
server.port=8085
grpc.server.port=9090cd reactive-gRPC-server
mvn spring-boot:runServer starts on:
- gRPC:
localhost:8000 - HTTP:
localhost:8080
cd reactive-gRPC-client
mvn spring-boot:runClient starts on http://localhost:8085
Swagger UI: http://localhost:8085/webjars/swagger-ui/index.html
Request:
curl -X GET http://localhost:8085What Happens:
- REST API calls gRPC client
- Client creates Protobuf message
- Sends via gRPC to server
- Server authenticates JWT token
- Processes request reactively
- Returns Protobuf response
- Client converts to JSON
Response:
{
"inventoryId": "550e8400-e29b-41d4-a716-446655440000",
"name": "NEAL"
}InventoryManager.proto:
syntax = "proto3";
option java_package = "com.reactive.grpc";
option java_outer_classname = "InventoryManagerProto";
import "Inventory.proto";
import "InventoryRequest.proto";
service InventoryManagerService {
rpc saveInventory(Inventory) returns (Inventory);
rpc findInventoryById(InventoryRequest) returns (Inventory);
}Inventory.proto:
syntax = "proto3";
option java_package = "com.reactive.grpc";
option java_outer_classname = "InventoryProto";
message Inventory {
string inventoryId = 1;
string name = 2;
}@GrpcService
@RequiredArgsConstructor
public class InventoryManagerService
extends ReactorInventoryManagerServiceGrpc.InventoryManagerServiceImplBase {
private final InventoryProtoMapper inventoryProtoMapper;
@Override
public Mono saveInventory(
Mono request) {
return request
.map(inventoryProtoMapper::fromProto)
.doOnNext(inventory -> log.info("Processing: {}", inventory))
.map(inventoryProtoMapper::toProto)
.doOnError(error -> log.error("Error: {}", error.getMessage()))
.onErrorResume(Mono::error);
}
}@Component
@RequiredArgsConstructor
public class InventoryManagerGrpcClient implements InventoryManagerClient {
private final ReactorInventoryManagerServiceGrpc.ReactorInventoryManagerServiceStub stub;
private final InventoryProtoMapper inventoryProtoMapper;
@Override
public Mono saveInventory() {
Inventory inventory = Inventory.builder()
.inventoryId(UUID.randomUUID().toString())
.name("NEAL")
.build();
Mono protoMono =
stub.saveInventory(Mono.just(inventoryProtoMapper.toProto(inventory)));
return protoMono.map(inventoryProtoMapper::fromProto);
}
}@Configuration
public class GrpcConfiguration {
@Bean
public ReactorInventoryManagerServiceGrpc.ReactorInventoryManagerServiceStub
inventoryManagerServiceStub() {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 8000)
.usePlaintext()
.build();
return ReactorInventoryManagerServiceGrpc.newReactorStub(channel);
}
}@Configuration
public class SecurityConfiguration {
@Value("${spring.security.oauth2.resourceserver.jwt.key-value}")
RSAPublicKey publicKey;
@Bean
public AuthenticationManager authenticationManager() {
List providers = new ArrayList<>();
providers.add(new JwtAuthenticationProvider(jwtDecoder()));
return new ProviderManager(providers);
}
@Bean
public GrpcAuthenticationReader authenticationReader() {
return new BearerAuthenticationReader(BearerTokenAuthenticationToken::new);
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(publicKey).build();
}
}@Component
public class InventoryProtoMapper
implements ProtobufMapper {
@Override
public InventoryProto.Inventory toProto(Inventory inventory) {
return InventoryProto.Inventory.newBuilder()
.setInventoryId(inventory.getInventoryId())
.setName(inventory.getName())
.build();
}
@Override
public Inventory fromProto(InventoryProto.Inventory proto) {
return Inventory.builder()
.inventoryId(proto.getInventoryId())
.name(proto.getName())
.build();
}
}The server expects Bearer token in Authorization header:
Authorization: Bearer <JWT_TOKEN>
Sample Token (from Bearer-token.txt):
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzdWJqZWN0IiwiaWF0IjoxNTE2MjM5MDIyLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCJ9.bsRCpUEaiWnzX4OqNxTBqwUD4vxxtPp-CHKTw7XcrglrvZ2lvYXaiZZbCp-hcPhuzMEzEAFuH6s4GZZOWVIX-wT47GdTz9cfA-Z4QPjS2RxePKphFXgBI3jHEpQo94Qya2fJdV4LvgBmA1uM_RTnYY1UbmeYuHKnXrZoGyV8QQQ
Server uses RSA public key for JWT validation:
rsa-public.key:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd
UWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs
HUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D
o2kQ+X5xK9cipRgEKwIDAQAB
-----END PUBLIC KEY-----
For a comprehensive guide on building reactive gRPC applications with Spring Boot:
Reactive Real-Time Request Processing with Reactive gRPC and Spring WebFlux