gRPC Implementation With Spring Boot

Nil Seri
turkcell
Published in
6 min readOct 1, 2021

--

gRPC Implementation with Java and Spring Boot

Photo by Agnieszka Ziomek on Unsplash

It would be good to understand differences between HTTP/1.1 and HTTP/2 since gRPC uses HTTP/2 as default.

HTTP/1.1 vs HTTP/2

HTTP/1.1 has

  • Textual format
  • Plain Text Headers
  • TCP connection requires 3 way hand shake process (single request and response with 1 single TCP connection)

HTTP/2 brings

  • Binary format
  • Header Compression
  • Flow Control
  • Multiplexing (Same TCP connection can be reused for multiplexing. Server streaming — Client streaming — Bi-directional streaming is possible)

You can see how downloading each part behaves for HTTP1 and HTTP2 (multiplexing) with this link visually:

This section is to understand the reasons and requirements for deciding to switch from using REST with JSON to gRPC using Protocol Buffers.

JSON vs gRPC (uses Protocol Buffers)

JSON

  • Has no schema definition
  • Textual (so serialization/deserialization is slow and resource consuming)
  • Used in REST

gPRC

  • Strict schema definition & Type safety (IDL : Interface Definition Language for API)
  • Binary (which makes serialization/deserialization faster)
  • Auto generation of code (useful for Polygot environments), optimized for interservices communication
  • HTTP/2 is default so multiplexing is supported.
gPRC Service-Client Communication
Project Setup (consisting of interface, server and client projects)

For a performance comparison, you can read here or watch what is written in the link in action below:

Data Types

int32 (for int) — default value: 0
int64 (for long) — default value: 0
float — default value: 0
double — default value: 0
bool — default value: false
string — default value: empty string
bytes (for byte[])
repeated (for List/Collection)
map (for Map) — default value: empty map
enum — default value: first value in the list of values

There are also wrapper values (like Integer, etc. in Java) that you can use by importing in your proto file before use.

import “google/protobuf/wrappers.proto”; 

Usage:

google.protobuf.UInt64Value id_number = 1;

If you want to add a timestamp field, you can use it by adding the import below as described here:

import "google/protobuf/timestamp.proto";

Usage:

google.protobuf.Timestamp timestamp = 2;

Setting Up the Project

It is recommended to create a separate module for proto model and service definitions for common use (as a dependency).

File naming convention for proto files, it is recommended to use as “lower_snake_case.proto”. You can see here for style guide.

Your variable names should be lower snake case as well.

I created a proto file named “city_score.proto”:

When I did maven compile, I got an error:

So added these properties:

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>

Then, I got a compilation error:

For this, I updated my maven dependencies to the latest version, just like given here:

<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.41.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.41.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.41.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>

Everything went fine until, in my service project, I got an error for mismatching versions of proto dependencies (that came in after I added my common proto module as a dependency) and “grpc-server-spring-boot-starter” so I had to downgrade my proto dependency versions to 1.37 (which is the one the latest version of grpc-server-spring-boot-starter — 2.12.0.RELEASE — is using):

<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.37.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.37.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
</dependencies>

After a successful “maven compile”, these are the generated sources for my proto file:

My Project Structure

- City Score Service
- Score Segment Service
- Score Calculator Service (aggregator service that calls both City Score and Score Segment)

I will be using unary calls in my implementation as you will see from my code samples.

Service Project Development

You need “server” version of grpc spring-boot-starter in your pom.xml file:

<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.12.0.RELEASE</version>
</dependency>

<dependency>
<groupId>com.nils.gprc</groupId>
<artifactId>proto-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

This is my City Score Service implementation:

Request Validation

You can find examples and different methods for gPRC error handling here.

If you want to return an error response instead of throwing an exception, you can use “oneof” and send success response for successful requests and error response for exceptions:

oneof response {
SuccessResponse success_response = 1;
ErrorResponse error_response = 2;
}
}

I will be throwing an exception so I implemented “GrpcAdvice” and “GrpcExceptionHandler” to throw a detailed exception with a proper gPRC status code. You can get general information from Spring official docs site.

There are two options as described here if you want to provide detailed information in your exception.
- Metadata
- Any.pack(yourCustomExceptionResponseObject)

CityScoreException is the custom RuntimeException I created for request validation errors. You can go back to my proto file to check my custom “CityScoreExceptionResponse” message. This is my final “Grpc Exception Handler” class:

Service Project Ports

Default port for gRPC Server is 9090. You can set it to a different value with “grpc.server.port” property:

grpc.server.port=8000

Client Project Development

You need “client” version of grpc spring-boot-starter in your pom.xml file:

<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.12.0.RELEASE</version>
</dependency>

<dependency>
<groupId>com.nils.gprc</groupId>
<artifactId>proto-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

This will be an aggregator service which will be collection responses from my service project calls and combine them and return to endpoint user by a RestController (so it will also be using “spring-boot-starter-web”).

Here is the implementation:

Finally, don’t forget to add your grpc service urls to your application.properties file. Your property names should be same as @GrpcClient annotated ones.

grpc.client.city-score.address=static://localhost:8000
grpc.client.city-score.negotiation-type=plaintext

grpc.client.score-segment.address=static://localhost:8100
grpc.client.score-segment.negotiation-type=plaintext

Time to Test!

To call your services, you can use BloomRPC (as you use Postman for REST API calls).

To install on Mac using HomeBrew:

brew install --cask bloomrpc

After the installation, you can find it located under Applications.

Another way is to install gRPCurl for cURL operations with gPRC. You can again install with HomeBrew:

brew install grpcurl

I will be using BloomRPC to test my endpoints. We add our proto files using the plus button:

Click on the method, here it is “calculateCityScore”:

It automatically creates a sample request. We update port information (grpc.server.port) for the service and hit the play button:

To test exception case, I put a negative value for city_code and hit play:

Lastly, we can call our aggregator service — Score Calculator, using Postman:

If I send “-35” for cityCode and inspect the handled exception part, we can now see the printed values of exception response on console:

Happy Coding!

--

--

Nil Seri
turkcell

I would love to change the world, but they won’t give me the source code | coding 👩🏻‍💻 | coffee ☕️ | jazz 🎷 | anime 🐲 | books 📚 | drawing 🎨