Partner – Jmix-Haulmont – NPI (cat= Spring Boot)
announcement - icon

Whether you're just starting out or have years of experience, Spring Boot is obviously a great choice for building a web application.

Jmix builds on this highly powerful and mature Boot stack, allowing devs to build and deliver full-stack web applications without having to code the frontend. Quite flexibly as well, from simple web GUI CRUD applications to complex enterprise solutions.

Concretely, The Jmix Platform includes a framework built on top of Spring Boot, JPA, and Vaadin, and comes with Jmix Studio, an IntelliJ IDEA plugin equipped with a suite of developer productivity tools.

The platform comes with interconnected out-of-the-box add-ons for report generation, BPM, maps, instant web app generation from a DB, and quite a bit more:

>> Become an efficient full-stack developer with Jmix

Course – RWSB – NPI (cat=REST/Spring Boot)
announcement - icon

Now that the new version of REST With Spring - “REST With Spring Boot” is finally out, the current price will be available until the 22nd of June, after which it will permanently increase by 50$

>> GET ACCESS NOW

Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

gRPC is a high-performance, open-source RPC framework initially developed by Google. It helps to eliminate boilerplate code and connect polyglot services in and across data centers. The API is based on Protocol Buffers, which provides a protoc compiler to generate code for different supported languages.

We can view gRPC as an alternative to REST, SOAP, or GraphQL, built on top of HTTP/2 to use features like multiplexing or streaming connections.

In this tutorial, we’ll learn how to implement gRPC service providers and consumers with Spring Boot.

2. Challenges

First, we can note that there is no direct support for gRPC in Spring Boot. Only Protocol Buffers are supported, which allows us to implement protobuf-based REST services. So, we need to include gRPC by using a third-party library, or by managing a few challenges by ourselves:

  • Platform-dependent compiler: The protoc compiler is platform-dependent. So, if the stubs should be generated during build-time, the build gets more complex and error-prone.
  • Dependencies: We need compatible dependencies within our Spring Boot application. Unfortunately, protoc for Java adds a javax.annotation.Generated annotation, which forces us to add a dependency to the old Java EE Annotations for Java library for compilation.
  • Server Runtime: gRPC service providers need to run within a server. The gRPC for Java project provides a shaded Netty, which we need to either include in our Spring Boot application or replace by a server already provided by Spring Boot.
  • Message Transport: Spring Boot provides different clients, like the RestClient (blocking) or the WebClient (non-blocking), that unfortunately cannot be configured and used for gRPC, because gRPC uses custom transport technologies for both blocking and non-blocking calls.
  • Configuration: Because gRPC brings its own technologies, we need configuration properties to configure them the Spring Boot way.

3. Sample Projects

Fortunately, there are third-party Spring Boot Starters that we can use to master the challenges for us, such as the one from LogNet or the grpc ecosystem project. Both starters are easy to integrate, but the latter one has both provider and consumer support as well as many other integration features, so that’s the one we chose for our examples.

In this sample, we design just a simple HelloWorld API with a single Proto file:

syntax = "proto3";

option java_package = "com.baeldung.helloworld.stubs";
option java_multiple_files = true;

message HelloWorldRequest {
    // a name to greet, default is "World"
    optional string name = 1;
}

message HelloWorldResponse {
    string greeting = 1;
}

service HelloWorldService {
    rpc SayHello(stream HelloWorldRequest) returns (stream HelloWorldResponse);
}

As we can see, we use the Bidirectional Streaming feature.

3.1. gRPC Stubs

Because the stubs are the same for both provider and consumer, we generate them within a separate, Spring-indepentent project. This has the advantage that the project’s lifecycle, including the protoc compiler configuration and the Java EE Annotations for Java dependency, can be isolated from the Spring Boot project’s lifecycle.

3.2. Service Provider

Implementing the service provider is pretty easy. First, we need to add the dependencies for the starter and our stubs project:

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-server-spring-boot-starter</artifactId>
    <version>2.15.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.baeldung.spring-boot-modules</groupId>
    <artifactId>helloworld-grpc-java</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

There’s no need to include Spring MVC or WebFlux because the starter dependency brings the shaded Netty server. We can configure it within the application.yml, for example, by configuring the server port:

grpc:
  server:
    port: 9090

Then, we need to implement the service and annotate it with @GrpcService:

@GrpcService
public class HelloWorldController extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {

    @Override
    public StreamObserver<HelloWorldRequest> sayHello(
        StreamObserver<HelloWorldResponse> responseObserver
    ) {
        // ...
    }
}

3.3. Service Consumer

For the service consumer, we need to add the dependencies to the starter and the stubs:

<dependency>
    <groupId>net.devh</groupId>
    <artifactId>grpc-client-spring-boot-starter</artifactId>
    <version>2.15.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.baeldung.spring-boot-modules</groupId>
    <artifactId>helloworld-grpc-java</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

Then, we configure the connection to the service in the application.yml:

grpc:
  client:
    hello:
      address: localhost:9090
      negotiation-type: plaintext

The name “hello” is a custom one. This way, we can configure multiple connections and refer to this name when injecting the gRPC client into our Spring component:

@GrpcClient("hello")
HelloWorldServiceGrpc.HelloWorldServiceStub stub;

4. Pitfalls

Implementing and consuming a gRPC service with Spring Boot is pretty easy. But there are some pitfalls that we should be aware of.

4.1. SSL-Handshake

Transferring data over HTTP means sending information unencrypted unless we use SSL. The integrated Netty server does not use SSL by default, so we need to explicitly configure it.

Otherwise, for local tests, we can leave the connection unprotected. In this case, we need to configure the consumer, as already shown:

grpc:
  client:
    hello:
      negotiation-type: plaintext

The default for the consumer is to use TLS, while the default for the provider is to skip SSL encryption. So, the defaults for consumer and provider don’t match each other.

4.2. Consumer Injection Without @Autowired

We implement the consumer by injecting a client object into our Spring component:

@GrpcClient("hello")
HelloWorldServiceGrpc.HelloWorldServiceStub stub;

This is implemented by a BeanPostProcessor and works as an addition to Spring’s built-in dependency injection mechanism. That means we can’t use the @GrpcClient annotation in conjunction with @Autowired or constructor injection. Instead, we’re restricted to using field injection.

We could only separate the injection by using a configuration class:

@Configuration
public class HelloWorldGrpcClientConfiguration {

    @GrpcClient("hello")
    HelloWorldServiceGrpc.HelloWorldServiceStub helloWorldClient;

    @Bean
    MyHelloWorldClient helloWorldClient() {
      return new MyHelloWorldClient(helloWorldClient);
    }
}

4.3. Mapping Transfer Objects

The data types generated by protoc can fail when invoking setters with null values:

public HelloWorldResponse map(HelloWorldMessage message) {
    return HelloWorldResponse
      .newBuilder()
      .setGreeting( message.getGreeting() ) // might be null
      .build();
}

So, we need null checks before invoking the setters. When we use mapping frameworks, we need to configure the mapper generation to do such null checks. A MapStruct mapper, for example, would need some special configuration:

@Mapper(
  componentModel = "spring",
  nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
  nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS
)
public interface HelloWorldMapper {
    HelloWorldResponse map(HelloWorldMessage message);
}

4.4. Testing

The starter doesn’t include any special support for implementing tests. Even the gRPC for Java project has only minimal support for JUnit 4, and no support for JUnit 5.

4.5. Native Images

When we want to build native images, there’s currently no support for gRPC. Because the client injection is done via reflection, this won’t work without extra configuration.

5. Conclusion

In this article, we’ve learned that we can easily implement gRPC providers and consumers within our Spring Boot application. We should note, however, that this comes with some restrictions, like missing support for testing and native images.

As usual, all the code implementations are available over on GitHub.

Course – RWSB – NPI (cat=REST/Spring Boot)
announcement - icon

Now that the new version of REST With Spring - “REST With Spring Boot” is finally out, the current price will be available until the 22nd of June, after which it will permanently increase by 50$

>> GET ACCESS NOW

Course – LS – All
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

res – REST with Spring (eBook) (everywhere)
1 Comment
Oldest
Newest
Inline Feedbacks
View all comments