For years, the Spring community relied on excellent third-party starters (like net.devh) to bridge the gap between Spring Boot and gRPC. With the evolution of Spring Boot 4 and the official Spring gRPC project, we now have native support that aligns perfectly with Spring’s dependency injection, observability, and configuration models.
This guide covers how to set up a production-grade Spring gRPC application using Gradle, specifically focusing on the critical step of generating Java stubs from your .proto definitions.
1. The Blueprint: Contract-First Design
In gRPC, the API contract is defined before a single line of Java is written. We use Protocol Buffers (protobuf) to define our services and messages.
Create a file named src/main/proto/calculator.proto.
syntax = "proto3";
// This option places generated classes in this specific package
option java_package = "com.example.grpc.demo.proto";
// This option generates a separate .java file for each class (recommended)
option java_multiple_files = true;
service CalculatorService {
rpc Add (OperationRequest) returns (OperationResponse);
}
message OperationRequest {
int32 number1 = 1;
int32 number2 = 2;
}
message OperationResponse {
int32 result = 1;
}
2. The Build: Gradle Configuration
The most complex part of gRPC integration is often the build system. We need to configure Gradle to:
- Download the
protoccompiler. - Use the
grpc-javaplugin to generate gRPC-specific stubs (Base implementations and Client stubs). - Attach these generated sources to the project’s source set so your IDE can see them.
Here is the build.gradle configuration:
plugins {
id 'java'
id 'org.springframework.boot' version '4.0.0' // Updated to Spring Boot 4
id 'io.spring.dependency-management' version '1.1.7'
// The official protobuf plugin for Gradle
id 'com.google.protobuf' version '0.9.4'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
// Spring Boot 4 likely requires Java 21 as the baseline
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
}
// Manage versions using the official Spring gRPC BOM
dependencyManagement {
imports {
// Assuming 1.0.0 aligns with the Spring Boot 4 release
mavenBom "org.springframework.grpc:spring-grpc-dependencies:1.0.0"
}
}
dependencies {
// The new official starter
implementation 'org.springframework.grpc:spring-grpc-spring-boot-starter'
// Grpc Netty Shaded is the recommended transport
implementation 'io.grpc:grpc-netty-shaded'
// Protobuf dependencies
implementation 'io.grpc:grpc-protobuf'
implementation 'io.grpc:grpc-stub'
// Helper to prevent annotation issues (Tomcat version bumped for SB 4)
compileOnly 'org.apache.tomcat:annotations-api:11.0.0'
}
// Configuration for code generation
protobuf {
protoc {
// download the compiler artifact from maven central
artifact = "com.google.protobuf:protoc:3.25.1"
}
plugins {
grpc {
// download the grpc-java plugin
artifact = "io.grpc:protoc-gen-grpc-java:1.69.0"
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
Generating the Code
To generate the Java files, simply run the build. The plugin hooks into the build lifecycle.
./gradlew build
Tip: If your IDE doesn’t see the generated classes immediately, look for the build/generated/source/proto directory and mark it as a “Generated Source Root”.
3. The Server: Implementing the Service
With the official Spring gRPC starter, we no longer need special @GrpcService annotations from third-party libraries. We can use the standard Spring @Service annotation.
The starter automatically scans for beans that extend BindableService (which your generated ImplBase class does).
package com.example.grpc.demo.service;
import com.example.grpc.demo.proto.CalculatorServiceGrpc;
import com.example.grpc.demo.proto.OperationRequest;
import com.example.grpc.demo.proto.OperationResponse;
import io.grpc.stub.StreamObserver;
import org.springframework.stereotype.Service;
@Service
public class CalculatorServiceImpl extends CalculatorServiceGrpc.CalculatorServiceImplBase {
@Override
public void add(OperationRequest request, StreamObserver<OperationResponse> responseObserver) {
int result = request.getNumber1() + request.getNumber2();
OperationResponse response = OperationResponse.newBuilder()
.setResult(result)
.build();
// Send the response
responseObserver.onNext(response);
// Complete the RPC call
responseObserver.onCompleted();
}
}
4. The Client: Consumption
To consume the service, we can use the GrpcChannelFactory provided by the starter to create channels. As an architect, you might prefer defining these clients as explicit Beans for better testability and injection control.
package com.example.grpc.demo.config;
import com.example.grpc.demo.proto.CalculatorServiceGrpc;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.grpc.client.GrpcChannelFactory;
@Configuration
public class GrpcClientConfig {
@Bean
public CalculatorServiceGrpc.CalculatorServiceBlockingStub calculatorClient(GrpcChannelFactory channelFactory) {
// "0.0.0.0:9090" is the default server address; in prod, this would be a service name
return CalculatorServiceGrpc.newBlockingStub(channelFactory.createChannel("0.0.0.0:9090"));
}
}
You can now inject CalculatorServiceBlockingStub into any controller or service just like a standard Spring bean.
Conclusion
The official spring-grpc support simplifies the ecosystem significantly. By combining the power of Gradle’s protobuf plugin with Spring Boot 4’s auto-configuration, we get a type-safe, high-performance RPC layer that feels entirely native to the Spring developer experience.
Discover more from GhostProgrammer - Jeff Miller
Subscribe to get the latest posts sent to your email.
