gRPC is a modern, high-performance RPC (Remote Procedure Call) framework that can run in any environment. It uses HTTP/2 for transport, Protocol Buffers as the interface description language, and provides features such as authentication, bidirectional streaming and flow control.
Overview
In this article, we'll create a simple gRPC service in Java that demonstrates:
- Defining a service using Protocol Buffers
- Implementing a gRPC server
- Creating a gRPC client to call the service
- Basic unary RPC calls
Prerequisites
- Java 8 or higher
- Maven or Gradle build tool
- Protocol Buffers compiler (protoc)
Step 1: Define the Service with Protocol Buffers
First, create a .proto file that defines your service and messages:
syntax = "proto3";
option java_package = "com.example.grpc";
option java_multiple_files = true;
service GreetingService {
rpc Greet (GreetingRequest) returns (GreetingResponse);
}
message GreetingRequest {
string name = 1;
}
message GreetingResponse {
string greeting = 1;
}
Step 2: Project Setup and Dependencies
Add the following dependencies to your pom.xml if using Maven:
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.58.0</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Step 3: Implement the gRPC Server
Create the server implementation:
package com.example.grpc.server;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import com.example.grpc.GreetingRequest;
import com.example.grpc.GreetingResponse;
import com.example.grpc.GreetingServiceGrpc;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class GreetingServer {
private Server server;
private final int port;
public GreetingServer(int port) {
this.port = port;
}
public void start() throws IOException {
server = ServerBuilder.forPort(port)
.addService(new GreetingServiceImpl())
.build()
.start();
System.out.println("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
try {
GreetingServer.this.stop();
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
System.err.println("*** server shut down");
}));
}
public void stop() throws InterruptedException {
if (server != null) {
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
}
}
public void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
static class GreetingServiceImpl extends GreetingServiceGrpc.GreetingServiceImplBase {
@Override
public void greet(GreetingRequest request, StreamObserver<GreetingResponse> responseObserver) {
String greeting = "Hello, " + request.getName() + "! Welcome to gRPC.";
GreetingResponse response = GreetingResponse.newBuilder()
.setGreeting(greeting)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}
public static void main(String[] args) throws IOException, InterruptedException {
GreetingServer server = new GreetingServer(50051);
server.start();
server.blockUntilShutdown();
}
}
Step 4: Implement the gRPC Client
Create the client implementation:
package com.example.grpc.client;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import com.example.grpc.GreetingRequest;
import com.example.grpc.GreetingResponse;
import com.example.grpc.GreetingServiceGrpc;
import java.util.concurrent.TimeUnit;
public class GreetingClient {
private final ManagedChannel channel;
private final GreetingServiceGrpc.GreetingServiceBlockingStub blockingStub;
public GreetingClient(String host, int port) {
this.channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build();
this.blockingStub = GreetingServiceGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String name) {
System.out.println("Sending greeting request for: " + name);
GreetingRequest request = GreetingRequest.newBuilder()
.setName(name)
.build();
GreetingResponse response = blockingStub.greet(request);
System.out.println("Received: " + response.getGreeting());
}
public static void main(String[] args) throws InterruptedException {
GreetingClient client = new GreetingClient("localhost", 50051);
try {
// Test with different names
client.greet("Alice");
client.greet("Bob");
client.greet("Charlie");
} finally {
client.shutdown();
}
}
}
Step 5: Build and Run
- Generate Java classes from .proto file:
mvn compile
- Start the server:
mvn exec:java -Dexec.mainClass="com.example.grpc.server.GreetingServer"
- Run the client (in a separate terminal):
mvn exec:java -Dexec.mainClass="com.example.grpc.client.GreetingClient"
Expected Output
Server output:
Server started, listening on 50051
Client output:
Sending greeting request for: Alice Received: Hello, Alice! Welcome to gRPC. Sending greeting request for: Bob Received: Hello, Bob! Welcome to gRPC. Sending greeting request for: Charlie Received: Hello, Charlie! Welcome to gRPC.
Key gRPC Concepts Demonstrated
- Service Definition: Protocol Buffers define the service contract
- Server Implementation: Extend the generated base class and implement service methods
- Client Stubs: Use generated client stubs to make RPC calls
- Communication: HTTP/2 transport with Protocol Buffers serialization
- Lifecycle Management: Proper startup and shutdown of gRPC components
Advanced Features
gRPC supports several advanced features you can explore:
- Streaming RPCs: Client streaming, server streaming, and bidirectional streaming
- Error Handling: Status codes and error details
- Deadlines/Timeouts: Setting timeouts for RPC calls
- Interceptors: For cross-cutting concerns like logging, authentication
- Load Balancing: Client-side load balancing
This example provides a solid foundation for building more complex gRPC services in Java. The type-safe API, performance benefits, and cross-language support make gRPC an excellent choice for microservices and distributed systems.