In this post, we will explore how to integrate gRPC into a Spring Boot application with the Spring gRPC project.
gRPC is a high-performance, open-source universal RPC framework that can run in any environment. It enables client and server applications to communicate transparently and makes it easier to build connected systems. In this example, both the server and client are implemented in Java, but gRPC supports many languages, so you could have a client in Python, Go, or any other supported language communicating with a Spring Boot gRPC server.
gRPC uses Protocol Buffers (Protobuf) as its interface definition language and as its underlying message interchange format. Protobuf allows you to define your service methods and message types in a .proto file. The protobuf compiler then generates code in your chosen language, which contains the serialization/deserialization logic and the gRPC stubs for client and server.
Protobuf is a binary format, which makes it more compact and faster to serialize/deserialize than text-based formats like JSON or XML. This can lead to improved performance, especially in high-throughput scenarios.
Protobuf definition ¶
For this example, I wrote a simple Protobuf file that models an IoT anomaly detection service.
The gRPC part is defined by the service block, which declares two RPC methods: EvaluateReading (a unary call) and SubscribeAlerts (a server-streaming call).
service IotAnomalyService {
rpc EvaluateReading (SensorReadingRequest) returns (ReadingAssessment);
rpc SubscribeAlerts (AlertSubscriptionRequest) returns (stream AnomalyAlert);
}
The messages define the structure of the requests and responses for these methods.
message SensorReadingRequest {
string sensor_id = 1;
string site_id = 2;
string metric_type = 3;
double value = 4;
double baseline = 5;
int64 captured_at_epoch_ms = 6;
}
message ReadingAssessment {
string sensor_id = 1;
bool anomaly = 2;
double z_score = 3;
string severity = 4;
string summary = 5;
int64 recommended_check_after_seconds = 6;
}
message AlertSubscriptionRequest {
string site_id = 1;
string device_group = 2;
int32 max_events = 3;
}
message AnomalyAlert {
string alert_id = 1;
string sensor_id = 2;
string site_id = 3;
string metric_type = 4;
double observed_value = 5;
double threshold = 6;
string severity = 7;
string message = 8;
int64 detected_at_epoch_ms = 9;
}
This Protobuf file serves as the single source of truth for both the server and client implementations. This is also one of the benefits of using gRPC and Protobuf: you can define your service and message contracts in a language-agnostic way, and then generate code for any supported language from that definition. This helps ensure consistency between the server and client and reduces the likelihood of serialization/deserialization errors due to mismatched data structures.
Dependencies ¶
The recommended approach is to use the Spring gRPC BOM (Bill of Materials) to manage the versions of related dependencies. This ensures that you have compatible versions of Spring gRPC, gRPC, and Protobuf libraries. In a Maven project, you can do this by adding the following to your pom.xml:
<properties>
<java.version>25</java.version>
<grpc.version>1.77.1</grpc.version>
<protobuf-java.version>4.33.4</protobuf-java.version>
<spring-grpc.version>1.0.2</spring-grpc.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-dependencies</artifactId>
<version>${spring-grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
As a dependency, you only need to add spring-grpc-spring-boot-starter. This brings in the core Spring gRPC integration and transitively includes the necessary gRPC and Protobuf dependencies.
Security is a common concern for gRPC services. The Spring gRPC project provides integration with Spring Security, allowing you to secure your gRPC endpoints using familiar Spring Security mechanisms.
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Code generation ¶
gRPC and Protobuf work with generated code. You write your .proto file, then run the Protobuf compiler (protoc) to generate Java classes for your messages and gRPC stubs for your service.
For this purpose, I added the following plugin to the pom.xml. Make sure to configure the plugin to point to your .proto files and specify the correct versions of the Protobuf compiler and gRPC plugin.
<plugin>
<groupId>io.github.ascopes</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>5.0.1</version>
<configuration>
<protoc>${protobuf-java.version}</protoc>
<sourceDirectories>
<sourceDirectory>${project.basedir}/../proto</sourceDirectory>
</sourceDirectories>
<plugins>
<plugin kind="binary-maven">
<groupId>io.grpc</groupId>
<artifactId>protoc-gen-grpc-java</artifactId>
<version>${grpc.version}</version>
</plugin>
</plugins>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
You can find more information about the plugin configuration in the official documentation.
Server implementation ¶
To implement the gRPC server, you need to create a class that extends the generated base class for your service. In this case, IotAnomalyServiceImplBase is the generated base class for the IotAnomalyService defined in the .proto file.
Apart from extending the base class, this is a normal Spring service class, so you can inject other Spring beans into it, use Spring's lifecycle annotations, and so on.
@Service
public class IotAnomalyGrpcService extends IotAnomalyServiceGrpc.IotAnomalyServiceImplBase {
The evaluateReading method implements the EvaluateReading RPC defined in the .proto file. It takes a SensorReadingRequest as input and returns a ReadingAssessment. This method is a unary RPC, which means it receives a single request and sends back a single response. The responseObserver is used to send the response to the client with the onNext method, and then onCompleted is called to indicate that the response has been fully sent.
@Override
public void evaluateReading(
SensorReadingRequest request,
StreamObserver<ReadingAssessment> responseObserver) {
AnomalyScoringService.ReadingScore score = this.anomalyScoringService.evaluate(
request.getSensorId(),
request.getMetricType(),
request.getValue(),
request.getBaseline(),
request.getCapturedAtEpochMs());
ReadingAssessment assessment = ReadingAssessment.newBuilder()
.setSensorId(request.getSensorId())
.setAnomaly(score.anomaly())
.setZScore(score.zScore())
.setSeverity(score.severity())
.setSummary(score.summary())
.setRecommendedCheckAfterSeconds(score.recommendedCheckAfterSeconds())
.build();
log.info("Evaluated reading for sensor={} severity={} anomaly={}",
request.getSensorId(), assessment.getSeverity(), assessment.getAnomaly());
responseObserver.onNext(assessment);
responseObserver.onCompleted();
}
subscribeAlerts implements the SubscribeAlerts RPC, which is a server-streaming RPC. This means that the client sends a single request, but the server can send back multiple responses over time. The responseObserver is used to send AnomalyAlert messages to the client as they are generated. onNext sends a response, onCompleted is called when the server has finished sending responses, and onError signals an error condition to the client.
@Override
public void subscribeAlerts(
AlertSubscriptionRequest request,
StreamObserver<AnomalyAlert> responseObserver) {
int maxEvents = request.getMaxEvents() <= 0 ? 8 : request.getMaxEvents();
log.info("Starting alert stream: site={} group={} maxEvents={}",
request.getSiteId(), request.getDeviceGroup(), maxEvents);
try {
for (int i = 1; i <= maxEvents; i++) {
double threshold = 75.0;
double observed = threshold + ThreadLocalRandom.current().nextDouble(-10.0, 22.0);
String severity = observed >= 95.0 ? "CRITICAL" : (observed >= 85.0 ? "HIGH" : "MEDIUM");
AnomalyAlert alert = AnomalyAlert.newBuilder()
.setAlertId(UUID.randomUUID().toString())
.setSensorId("sensor-" + String.format(Locale.ROOT, "%03d", i))
.setSiteId(request.getSiteId())
.setMetricType("temperature_celsius")
.setObservedValue(observed)
.setThreshold(threshold)
.setSeverity(severity)
.setMessage(String.format(Locale.ROOT,
"%s anomaly: %.2f exceeds threshold %.2f at %s",
severity,
observed,
threshold,
Instant.now()))
.setDetectedAtEpochMs(System.currentTimeMillis())
.build();
responseObserver.onNext(alert);
Thread.sleep(700L);
}
responseObserver.onCompleted();
log.info("Completed alert stream for site={}", request.getSiteId());
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
responseObserver.onError(ex);
}
}
Security ¶
Spring gRPC provides integration with Spring Security, allowing you to secure your gRPC endpoints using familiar Spring Security mechanisms. To keep things simple, this example uses an in-memory user store with a single user and then secures the gRPC endpoints with HTTP Basic authentication.
@Configuration
public class GrpcSecurityConfiguration {
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withUsername("iot-client")
.password("{noop}iot-secret")
.roles("USER")
.build());
}
@Bean
@GlobalServerInterceptor
AuthenticationProcessInterceptor grpcAuthenticationInterceptor(GrpcSecurity grpc) throws Exception {
return grpc
.authorizeRequests(requests -> requests
.methods("grpc.*/*").permitAll()
.allRequests().authenticated())
.httpBasic(withDefaults())
.build();
}
}
GrpcSecurityConfiguration.java
Do not use Basic authentication in production, as it is not secure. For production applications, consider using more robust authentication and authorization mechanisms, such as JWT tokens, OAuth2, or mTLS (mutual TLS) for securing gRPC endpoints.
Configuration ¶
The gRPC server listens on a specific port for incoming requests. In this example, the server is configured to listen on port 9090. You can specify this in the application.yml file.
grpc:
server:
port: 9090
You can find a list of all available configuration properties in the official documentation.
That is all you need to set up a basic gRPC server with Spring Boot. Next, let us look at how to implement a gRPC client that can communicate with this server.
Client ¶
On the client side, we do not need to implement any interfaces. We can simply use the generated gRPC stubs to call the server. The ClientGrpcConfig class creates beans for both the blocking and async stubs, which are configured to connect to the gRPC server and include a basic authentication interceptor with credentials.
Channels in gRPC are the abstraction that represents a connection to a gRPC server. They manage the underlying network connections and provide a way for clients to call remote methods on the server. When you create a channel, you specify the target address of the server and any options or interceptors that should be applied to the calls made through that channel.
@Configuration
public class ClientGrpcConfig {
@Value("${app.grpc.username}")
private String username;
@Value("${app.grpc.password}")
private String password;
@Bean
IotAnomalyServiceGrpc.IotAnomalyServiceBlockingStub anomalyBlockingStub(GrpcChannelFactory channels) {
return IotAnomalyServiceGrpc.newBlockingStub(channels.createChannel("anomaly-server", channelOptions()));
}
@Bean
IotAnomalyServiceGrpc.IotAnomalyServiceStub anomalyAsyncStub(GrpcChannelFactory channels) {
return IotAnomalyServiceGrpc.newStub(channels.createChannel("anomaly-server", channelOptions()));
}
private ChannelBuilderOptions channelOptions() {
return ChannelBuilderOptions.defaults()
.withInterceptors(List.of(new BasicAuthenticationInterceptor(this.username, this.password)));
}
}
The server address is configured in the application.yml file. Make sure that the code references the same channel name as the one defined in the configuration (anomaly-server in this case).
grpc:
client:
channels:
anomaly-server:
address: localhost:9090
Calling the server ¶
The application can then use these stubs to call the server. For example, the runUnaryCheck method builds a SensorReadingRequest, applies a deadline of 3 seconds, and calls evaluateReading on the blocking stub.
private void runUnaryCheck() {
SensorReadingRequest request = SensorReadingRequest.newBuilder()
.setSensorId("sensor-441")
.setSiteId("warehouse-eu-1")
.setMetricType("temperature_celsius")
.setValue(91.4)
.setBaseline(73.0)
.setCapturedAtEpochMs(System.currentTimeMillis())
.build();
ReadingAssessment assessment = this.blockingStub
.withDeadlineAfter(Duration.ofSeconds(3))
.evaluateReading(request);
Streaming ¶
The runAlertStream method demonstrates how to call a server-streaming RPC. It builds an AlertSubscriptionRequest and calls subscribeAlerts on the async stub. The method takes the request and a StreamObserver<AnomalyAlert> as parameters. The StreamObserver handles the incoming stream of AnomalyAlert messages from the server. The methods onNext, onError, and onCompleted are called by the gRPC framework as responses are received, when an error occurs, or when the stream completes.
private void runAlertStream() throws InterruptedException {
CountDownLatch done = new CountDownLatch(1);
AlertSubscriptionRequest streamRequest = AlertSubscriptionRequest.newBuilder()
.setSiteId("warehouse-eu-1")
.setDeviceGroup("freezers")
.setMaxEvents(6)
.build();
this.asyncStub.subscribeAlerts(streamRequest, new StreamObserver<>() {
@Override
public void onNext(AnomalyAlert alert) {
log.info("Stream alert -> id={} sensor={} severity={} observed={} threshold={} msg={}",
alert.getAlertId(),
alert.getSensorId(),
alert.getSeverity(),
String.format("%.2f", alert.getObservedValue()),
String.format("%.2f", alert.getThreshold()),
alert.getMessage());
}
@Override
public void onError(Throwable throwable) {
log.error("Alert stream failed", throwable);
done.countDown();
}
@Override
public void onCompleted() {
log.info("Alert stream completed");
done.countDown();
}
});
if (!done.await(20, TimeUnit.SECONDS)) {
log.warn("Alert stream timeout reached");
}
}
Wrapping up ¶
The Spring gRPC project provides a convenient way to build and integrate gRPC services and clients within the Spring ecosystem. It also integrates with Spring Boot and Spring Security so you can secure your gRPC endpoints in a familiar way.
For more information and examples, refer to the official Spring gRPC documentation.