Search Tutorials


Spring Boot 3 + gRPC Client Streaming Example | JavaInUse

Spring Boot 3 + gRPC Client Streaming Example

In previous tutorial we looked at Spring Boot 3 + gRPC Server Streaming Example. In this tutorial we will be implementing spring boot + gRPC Client Streaming call. Client streaming gRPC is a communication pattern in which the client sends multiple messages to the server using a single gRPC connection. It allows the client to initiate a stream of data and send chunks of information to the server, which processes the data and responds accordingly.
One common use case for Client streaming gRPC is when the client needs to send a large amount of data in small chunks to the server. It can be useful in scenarios where the client is continuously generating or collecting data that needs to be processed or analyzed on the server side.
We will be implementing the following scenario.

Use Case

Consider a banking service where the the client needs to upload address proof pdf to back server. This is large amount of information to be sent by the client to the server. So this address pdf is uploaded by the client to the server.
Bank Client Streaming gRPC

Video

This tutorial is explained in the below Youtube Video.

gRPC - Table of Contents

Spring Boot+ gRPC Hello World Example Spring Boot gRPC Server + C# gRPC Client Example Spring Boot 3 + gRPC - Types of gRPC Spring Boot 3 + gRPC Unary Example Spring Boot 3 + gRPC Server Streaming Example Spring Boot 3 + gRPC Client Streaming Example Spring Boot 3 + gRPC Bidirectional Streaming Example Spring Boot + gRPC Deadline Example Spring Boot + gRPC Error Handling Example Spring Boot + gRPC Error Handling - Using Trailer Metadata Spring Boot + gRPC Error Handling - Global Exception Handler Using GrpcAdvice

Implementation

Spring Boot Client Streaming gRPC Server

We will be creating a maven project as follows -
Bank Client Streaming gRPC maven
The pom.xml will be as follows. We have the following dependencies
  • spring-boot-starter-parent- We have used version - 3.2.0. It provides default configurations, dependencies, and plugins that are commonly used in Spring Boot applications.
  • grpc-server-spring-boot-starter- We have used version - 2.15.0. Which is the latest dependency provided by maven repository. It provides the necessary components to integrate and run a gRPC server within a Spring Boot application.
  • grpc-stub-- We have used version - 1.59.0. This dependency provides classes and utilities for creating and using gRPC stubs, which are used to make remote procedure calls.
  • grpc-protobuf-- We have used version - 1.59.0. This library provides support for the Protocol Buffers serialization format used in gRPC, allowing you to define and exchange structured data between your gRPC client and server.
  • annotations-api- We have used version - 6.0.53. This artifact provides the necessary annotations for Java code generated by the protobuf-maven-plugin.
<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>
	<groupId>com.javainuse</groupId>
	<artifactId>boot-client-streaming-grpc-server</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.0</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<protobuf.version>3.17.3</protobuf.version>
		<protobuf-plugin.version>0.6.1</protobuf-plugin.version>
		<grpc.version>1.59.0</grpc.version>
	</properties>
	<dependencies>

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

		<dependency>
			<groupId>io.grpc</groupId>
			<artifactId>grpc-stub</artifactId>
			<version>${grpc.version}</version>
		</dependency>
		<dependency>
			<groupId>io.grpc</groupId>
			<artifactId>grpc-protobuf</artifactId>
			<version>${grpc.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>annotations-api</artifactId>
			<version>6.0.53</version>
			<scope>provided</scope>
		</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.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<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.59.0:exe:${os.detected.classifier}
					</pluginArtifact>
					<protoSourceRoot>
						${basedir}/src/main/proto/
					</protoSourceRoot>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>compile</goal>
							<goal>compile-custom</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>




Next create the proto file named bank-service.proto in src/main/proto folder. A proto file in gRPC is a special type of file that helps define the structure and communication between different software components. It acts like a blueprint for creating and interacting with these components. This protocol defines the format of messages for a banking system. It includes a message for requesting address proof with account holder name, account number, and PDF file. It also has a message for the server's response. The service "BankService" allows clients to upload address proof documents using client streaming for efficiency.
syntax = "proto3";

package banking;

option java_multiple_files = true;
option java_package = "com.javainuse.banking";

message AddressProofRequest {
  string account_holder_name = 1;  // Provide the name of the account holder
  string account_number = 2;       // Specify the account number
  
  // Stream the PDF file as bytes
  // The client will send chunks of the file using streaming
  // By doing so, the client can send large files in multiple parts
  // to enhance efficiency.
  // The server will concatenate all the chunks at the end to form the complete document.
  // If you wish to receive multiple files, you can define a repeated field of bytes.
  // Make sure to add appropriate validation and error handling logic.
  bytes pdf_file = 3;
}

message AddressProofResponse {
  bool success = 1;  // Whether the upload was successful
  string message = 2;  // A response message from the server
}

service BankService {
  // Upload the address proof document as a PDF file using client streaming.
  rpc UploadAddressProof(stream AddressProofRequest) returns (AddressProofResponse) {}
}
Next create the spring bootstrap class as follows-
package com.javainuse.bank;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootGrpcServerExampleApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootGrpcServerExampleApplication.class, args);
	}
}
Run the maven install command which will generate the classes using the proto file.
Create the class named UploadAddressProofObserver. This class UploadAddressProofObserver implements a stream observer for address proof requests. It receives chunks of data for account holder name, account number, and PDF file. It processes the PDF file by writing it to a file and sends a response message confirming successful upload. It handles errors during streaming.
gRPC StreamObserver
package com.javainuse.bank.service;

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import com.javainuse.banking.AddressProofRequest;
import com.javainuse.banking.AddressProofResponse;

import io.grpc.stub.StreamObserver;

public class UploadAddressProofObserver implements StreamObserver<AddressProofRequest> {

	private String accountHolderName;
	private String accountNumber;
	private ByteArrayOutputStream pdfBytes = new ByteArrayOutputStream();
	private final StreamObserver<AddressProofResponse> responseObserver;

	public UploadAddressProofObserver(StreamObserver<AddressProofResponse> responseObserver) {
		this.responseObserver = responseObserver;
	}

	@Override
	public void onNext(AddressProofRequest request) {
		// Retrieve data from each request chunk
		if (request.hasField(request.getDescriptor().findFieldByName("account_holder_name"))) {
			accountHolderName = request.getAccountHolderName();
		}
		if (request.hasField(request.getDescriptor().findFieldByName("account_number"))) {
			accountNumber = request.getAccountNumber();
		}
		if (request.hasField(request.getDescriptor().findFieldByName("pdf_file"))) {
			try {
				// Append the received bytes from the PDF file
				pdfBytes.write(request.getPdfFile().toByteArray());
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void onError(Throwable t) {
		// Handle any errors that occur during streaming
	}

	@Override
	public void onCompleted() {
		// Process the complete PDF file
		// Implement the required logic to handle the address proof document
		// You can write the byte array stored in pdfBytes to a file or store it in a
		// database, etc.
		try {
			FileOutputStream fileOutputStream = new FileOutputStream("D://data/address_proof.pdf");
			pdfBytes.writeTo(fileOutputStream);
			fileOutputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Create and send a response message
		AddressProofResponse response = AddressProofResponse.newBuilder().setSuccess(true)
				.setMessage("Address proof document uploaded successfully for " + accountHolderName
						+ " having account number " + accountNumber)
				.build();
		responseObserver.onNext(response);
		responseObserver.onCompleted();
	}
}
ProcessAddressService is a class that extends BankServiceGrpc.BankServiceImplBase and is annotated as @GrpcService. We use the @GrpcService to expose this class as a grpc service and expose it over the gRPC protocol. BankServiceGrpc.BankServiceImplBase is the abstract base class generated by the gRPC framework based on the protocol buffer definition file bank-service.proto for the service. By extending the generated base class, you get a template with the necessary methods and can focus on implementing the business logic without worrying about handling low-level details of the gRPC communication.
It overrides a method to upload an address proof request and returns a stream observer for handling the response. The method returns a new instance of UploadAddressProofObserver to handle the upload process.
package com.javainuse.bank.service;

import com.javainuse.banking.AddressProofRequest;
import com.javainuse.banking.AddressProofResponse;
import com.javainuse.banking.BankServiceGrpc;

import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;

@GrpcService
public class ProcessAddressService extends BankServiceGrpc.BankServiceImplBase {

	@Override
	public StreamObserver<AddressProofRequest> uploadAddressProof(
			StreamObserver<AddressProofResponse> responseObserver) {
		
		 return new UploadAddressProofObserver(responseObserver);
	}

}
Next create a file named application.properties where we specify the grpc port configuration-
grpc.server.port=8090

Test using BloomRPC

Start the spring boot project created. Start BloomRPC and load the proto file. This will create the gRPC client.
Bank Client Streaming gRPC BloomRPC

Spring Boot Client Streaming gRPC Client

We will be creating a maven project as follows -
Bank Client Streaming gRPC client
The pom.xml will be as follows-
<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>
	<groupId>com.javainuse</groupId>
	<artifactId>boot-client-streaming-grpc-client</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.0</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<protobuf.version>3.17.3</protobuf.version>
		<protobuf-plugin.version>0.6.1</protobuf-plugin.version>
		<grpc.version>1.59.0</grpc.version>
	</properties>
	<dependencies>

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

		<dependency>
			<groupId>io.grpc</groupId>
			<artifactId>grpc-stub</artifactId>
			<version>${grpc.version}</version>
		</dependency>
		<dependency>
			<groupId>io.grpc</groupId>
			<artifactId>grpc-protobuf</artifactId>
			<version>${grpc.version}</version>
		</dependency>

		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>annotations-api</artifactId>
			<version>6.0.53</version>
			<scope>provided</scope>
		</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.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<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.59.0:exe:${os.detected.classifier}
					</pluginArtifact>
					<protoSourceRoot>
						${basedir}/src/main/proto/
					</protoSourceRoot>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>compile</goal>
							<goal>compile-custom</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>
Next create the proto file named bank-service.proto in src/main/proto folder
syntax = "proto3";

package banking;

option java_multiple_files = true;
option java_package = "com.javainuse.banking";

message AddressProofRequest {
  string account_holder_name = 1;  // Provide the name of the account holder
  string account_number = 2;       // Specify the account number
  
  // Stream the PDF file as bytes
  // The client will send chunks of the file using streaming
  // By doing so, the client can send large files in multiple parts
  // to enhance efficiency.
  // The server will concatenate all the chunks at the end to form the complete document.
  // If you wish to receive multiple files, you can define a repeated field of bytes.
  // Make sure to add appropriate validation and error handling logic.
  bytes pdf_file = 3;
}

message AddressProofResponse {
  bool success = 1;  // Whether the upload was successful
  string message = 2;  // A response message from the server
}

service BankService {
  // Upload the address proof document as a PDF file using client streaming.
  rpc UploadAddressProof(stream AddressProofRequest) returns (AddressProofResponse) {}
}
Next create the spring bootstrap class as follows-
package com.javainuse.bank;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringBootGrpcClientExampleApplication {

	public static void main(String[] args) throws InterruptedException {
		SpringApplication.run(SpringBootGrpcClientExampleApplication.class, args);
	}
}
Run the maven install command which will generate the classes using the proto file.
Create a ResponseObserver as follows. ResponseObserver is a class that implements StreamObserver for AddressProofResponse. It has methods to handle server responses, errors, and completion of file uploads. It uses a lock and boolean flag to ensure synchronization and provides a method to await completion of the response.
package com.javainuse.bank.service;

import com.javainuse.banking.AddressProofResponse;

import io.grpc.stub.StreamObserver;

public class ResponseObserver implements StreamObserver<AddressProofResponse> {
	private final Object lock = new Object();
	private boolean completed = false;

	@Override
	public void onNext(AddressProofResponse response) {
		System.out.println("Server response: " + response.getMessage());
	}

	@Override
	public void onError(Throwable throwable) {
		System.err.println("Error occurred: " + throwable.getMessage());
		synchronized (lock) {
			completed = true;
			lock.notifyAll();
		}
	}

	@Override
	public void onCompleted() {
		System.out.println("File uploaded successfully!");
		synchronized (lock) {
			completed = true;
			lock.notifyAll();
		}
	}

	public void awaitCompletion() {
		synchronized (lock) {
			while (!completed) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
The BankService class is used to upload address proof documents to a gRPC server. It establishes a connection to the server, sends the file in chunks, and waits for a response. Once the upload is complete, it closes the connection. The upload process is handled using gRPC stubs and observers.
package com.javainuse.bank.service;

import java.io.FileInputStream;
import java.io.IOException;

import org.springframework.stereotype.Service;

import com.javainuse.banking.AddressProofRequest;
import com.javainuse.banking.BankServiceGrpc;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

@Service
public class BankService {

	public void uploadAddressProof() {

		String filePath = "C:\\test\\test.pdf";
		String accountHolderName = "javainuse";
		String accountNumber = "account5";
		// Create a channel to connect to the gRPC server
		ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8090).usePlaintext().build();

		// Create a gRPC stub
		BankServiceGrpc.BankServiceStub stub = BankServiceGrpc.newStub(channel);

		// Create an instance of the ResponseObserver class to handle the response from
		// the server
		ResponseObserver responseObserver = new ResponseObserver();

		// Create a StreamObserver to send the request to the server
		StreamObserver<AddressProofRequest> requestObserver = stub.uploadAddressProof(responseObserver);

		// Read the PDF file from disk and send it to the server in chunks
		try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
			byte[] buffer = new byte[1024];
			int bytesRead;
			while ((bytesRead = fileInputStream.read(buffer)) != -1) {
				// Create an AddressProofRequest with the account holder name, account number,
				// and file chunk
				AddressProofRequest request = AddressProofRequest.newBuilder().setAccountHolderName(accountHolderName)
						.setAccountNumber(accountNumber)
						.setPdfFile(com.google.protobuf.ByteString.copyFrom(buffer, 0, bytesRead)).build();

				// Send the request to the server
				requestObserver.onNext(request);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

		// Mark the client-side stream as complete
		requestObserver.onCompleted();

		// Wait for the response from the server
		responseObserver.awaitCompletion();

		// Close the channel after the upload is completed
		channel.shutdown();
	}
}
Finally modify the spring bootstrap class to call the uploadAddressProof method of the BankService-
package com.javainuse.bank;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import com.javainuse.bank.service.BankService;

@SpringBootApplication
public class SpringBootGrpcClientExampleApplication {

	public static void main(String[] args) throws InterruptedException {
		ApplicationContext context = SpringApplication.run(SpringBootGrpcClientExampleApplication.class, args);
		BankService bankService = context.getBean(BankService.class);
		bankService.uploadAddressProof();
	}
}
Run the client to get the response.
Bank Client Streaming gRPC output


	

Download Source Code

Download it - Spring Boot + gRPC Client Streaming Client Example
Download it - Spring Boot + gRPC Client Streaming Server Example