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.

Mastering gRPC with Spring Boot: An Official Guide

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

  1. Go to the Spring Initializr, select Maven, Spring Boot 3.5.7, Java 21, and add the gRPC dependency.
  2. 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 implementing io.grpc.BindableService (like our @Service extending SimpleImplBase) 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 generated ServiceImplBase) and annotate it with @Service. Spring gRPC discovers these beans automatically.
  • Interceptors: For cross-cutting concerns (like logging or metrics), define ServerInterceptor beans. Annotate them with @GlobalServerInterceptor and @Order to 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-starter and the necessary grpc-protobuf, grpc-stub, protobuf-java, jakarta.annotation-api dependencies. Ensure the generated stub classes are on the client's classpath.
  • Channel Factory: Spring Boot autoconfigures a GrpcChannelFactory bean.

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: tls on 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.

Subscribe to Root Logic

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe