Mastering gRPC with Spring Boot: An Official Guide
Learn how to build high-performance microservices with gRPC in Spring Boot. This official guide covers setting up servers, clients, and essential configurations.
gRPC offers a modern, high-performance framework for RPC (Remote Procedure Call) communication, ideal for microservices. Leveraging HTTP/2 and Protocol Buffers, it provides efficiency and strong typing. Spring Boot integrates seamlessly with gRPC through the official Spring gRPC project (org.springframework.grpc), allowing developers to build gRPC servers and clients using familiar Spring paradigms.
This guide follows the official Spring gRPC documentation structure to get you started quickly.
1. Speedrun: Your First gRPC Service π
Let's create a working service quickly.
Initialize Project
- Go to the Spring Initializr, select Maven, Spring Boot 3.5.7, Java 21, and add the
gRPCdependency. - Generate and download the project.
Open in IDE
Unzip the project and open it in your preferred IDE.
Define Proto
Create a Protobuf service definition file at src/main/proto/hello.proto. Remember to replace <your-package-name-goes-here> with your actual base package name.
// src/main/proto/hello.proto
syntax = "proto3";
option java_multiple_files = true;
// IMPORTANT: Change this package to match your project's base package
option java_package = "<your-package-name-goes-here>.proto";
option java_outer_classname = "HelloWorldProto";
// The greeting service definition.
service Simple {
// Sends a greeting (Unary RPC)
rpc SayHello(HelloRequest) returns (HelloReply) {}
// Sends multiple greetings (Server Streaming RPC)
rpc StreamHello(HelloRequest) returns (stream HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
Generate Java Stubs
Compile the project to generate Java code from the .proto file.
./mvnw clean package
This generates code in target/generated-sources/protobuf/java and target/generated-sources/protobuf/grpc-java. You might need to mark these directories as Generated Source Root in your IDE (e.g., IntelliJ IDEA).
Implement the Service
Create a Spring service bean that extends the generated SimpleImplBase class.
package <your-package-name-goes-here>; // Use your actual package
import <your-package-name-goes-here>.proto.HelloReply; // Import generated classes
import <your-package-name-goes-here>.proto.HelloRequest;
import <your-package-name-goes-here>.proto.SimpleGrpc;
import io.grpc.stub.StreamObserver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Service; // Standard Spring @Service
@Service // Mark as a Spring bean; Spring gRPC automatically detects BindableService beans
public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
private static Log log = LogFactory.getLog(GrpcServerService.class);
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
log.info("Received SayHello request for: " + req.getName());
HelloReply reply = HelloReply.newBuilder().setMessage("Hello ==> " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void streamHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
log.info("Received StreamHello request for: " + req.getName());
int count = 0;
while (count < 10) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello(" + count + ") ==> " + req.getName()).build();
responseObserver.onNext(reply);
count++;
try {
Thread.sleep(1000L); // Simulate streaming delay
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Streaming interrupted", e);
responseObserver.onError(io.grpc.Status.CANCELLED
.withDescription("Stream interrupted")
.withCause(e)
.asRuntimeException());
return;
}
}
responseObserver.onCompleted();
}
}
Note: The official Spring gRPC starter automatically discovers beans implementingio.grpc.BindableService(like our@ServiceextendingSimpleImplBase) and registers them with the gRPC server.
Run the Application
Start your main Spring Boot application class.
package <your-package-name-goes-here>; // Use your actual package
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GrpcDemoApplication { // Rename if needed
public static void main(String[] args) {
SpringApplication.run(GrpcDemoApplication.class, args);
System.out.println("gRPC Server started on port 9090 (default)");
}
}
Run using:
./mvnw spring-boot:run
The gRPC server will start, typically on port 9090.
Test with grpcurl
# Ensure grpcurl is installed: https://github.com/fullstorydev/grpcurl
grpcurl -d '{"name":"World"}' -plaintext localhost:9090 Simple.SayHello
Response:
{
"message": "Hello ==> World"
}
2. Maven Configuration (pom.xml)
This pom.xml includes the necessary dependencies and the build plugin, using the versions you specified earlier.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
<relativePath />
</parent>
<groupId>com.example</groupId>
<artifactId>grpc-spring-official-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpc-spring-official-demo</name>
<description>Demo project using official Spring gRPC</description>
<properties>
<java.version>21</java.version>
<grpc.version>1.76.0</grpc.version>
<protobuf.version>4.33.0</protobuf.version>
<spring-grpc.version>3.1.0</spring-grpc.version>
<protobuf-plugin.version>0.6.1</protobuf-plugin.version>
<os-maven-plugin.version>1.7.1</os-maven-plugin.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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${os-maven-plugin.version}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${protobuf-plugin.version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/protobuf/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3. gRPC Server Details π₯οΈ
- Service Implementation: As shown in the Speedrun, implement
BindableService(usually by extending the generatedServiceImplBase) and annotate it with@Service. Spring gRPC discovers these beans automatically. - Interceptors: For cross-cutting concerns (like logging or metrics), define
ServerInterceptorbeans. Annotate them with@GlobalServerInterceptorand@Orderto apply them globally.
Configuration: Use spring.grpc.server.* properties in application.yml/properties to configure the server (e.g., port, max-inbound-message-size). The default port is 9090.
spring:
grpc:
server:
port: 9090 # Default is 9090
# max-inbound-message-size: 4MB # Default
4. gRPC Client Details π
- Dependencies: Add
spring-grpc-client-spring-boot-starterand the necessarygrpc-protobuf,grpc-stub,protobuf-java,jakarta.annotation-apidependencies. Ensure the generated stub classes are on the client's classpath. - Channel Factory: Spring Boot autoconfigures a
GrpcChannelFactorybean.
Configuration (application.yml)
Define named channels under spring.grpc.client.channels.<channel-name>.*. For unsecured connections, use negotiation-type: plaintext.
spring:
grpc:
client:
channels:
# Named channel 'hello-service' connecting unsecured to localhost:9090
hello-service:
address: static://localhost:9090 # Use 'static://' for direct address
negotiation-type: plaintext # IMPORTANT for unsecured connection
Creating Stubs via @Bean
Inject GrpcChannelFactory and create stub beans manually. This is explicit and clear.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.grpc.client.GrpcChannelFactory;
import <your-package-name-goes-here>.proto.SimpleGrpc; // Import generated stub
@Configuration
public class GrpcClientConfig {
@Bean
SimpleGrpc.SimpleBlockingStub simpleBlockingStub(GrpcChannelFactory channelFactory) {
// Creates a channel using the configuration for 'hello-service'
// The factory uses the 'plaintext' setting from application.yml
return SimpleGrpc.newBlockingStub(channelFactory.createChannel("hello-service"));
}
// Example: Async stub
// @Bean
// SimpleGrpc.SimpleStub simpleAsyncStub(GrpcChannelFactory channelFactory) {
// return SimpleGrpc.newStub(channelFactory.createChannel("hello-service"));
// }
}
Using the Stub
Inject the created stub bean into your client service and call its methods.
package <your-package-name-goes-here>; // Use your actual package
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import <your-package-name-goes-here>.proto.HelloRequest;
import <your-package-name-goes-here>.proto.HelloReply;
import <your-package-name-goes-here>.proto.SimpleGrpc;
@Service
public class MyGrpcClientService {
@Autowired
private SimpleGrpc.SimpleBlockingStub simpleStub;
public String sendMessage(String name) {
try {
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response = simpleStub.sayHello(request); // Make the RPC call
return response.getMessage();
} catch (io.grpc.StatusRuntimeException e) {
System.err.println("RPC failed: " + e.getStatus());
return "Error: " + e.getStatus().getDescription();
}
}
}
5. Security Considerations π‘οΈ (Brief Mention)
While this guide focuses on the basic setup, production environments MUST secure gRPC communication. The standard mechanisms involve:
- TLS (Transport Layer Security): Encrypts traffic. Configure this using Spring Boot SSL Bundles and set
negotiation-type: tlson the client. - mTLS (Mutual TLS): Both client and server authenticate each other using certificates. Also configured via SSL Bundles.
- Token Authentication (e.g., JWT): Use gRPC Interceptors (both client and server) to pass and validate tokens via
Metadata(headers). Spring Security integration is available for advanced scenarios.
Do not run unsecured gRPC services in production. Refer to the official Spring gRPC documentation for detailed security configuration.
Conclusion
The official Spring gRPC project provides excellent integration for using gRPC within Spring Boot. By defining your service with Protobuf, configuring the Maven plugin, implementing your service as a standard Spring @Service, and configuring the client channel, you can easily leverage gRPC's performance benefits. Remember that security is paramount for production systems. For further details, always consult the Spring gRPC Reference Documentation.