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:

  1. Download the protoc compiler.
  2. Use the grpc-java plugin to generate gRPC-specific stubs (Base implementations and Client stubs).
  3. 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.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.