Spring Boot + gRPC Error Handling - Using Trailer Metadata
In the example we implemented previously the gRPC server passed the gRPC error status code and description to the client.
Now suppose we also need the server send some more application specific error information to the client. This is where trailer metadata comes into picture.
Trailers are a special kind of header that is sent after the message data. They are used internally to communicate the outcome of an RPC. At the application level, custom trailers can be used to communicate things not directly part of the data, such as server utilization and query cost. Trailers are sent only by the server.
Using Trailers we can provide additional information or context at the end of an RPC (Remote Procedure Call) response or response stream. Trailers are sent as part of the RPC's trailing metadata-metadata that is sent after the main response data. This additional metadata often includes details related to the RPC's status, error conditions, or other context information.
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
We will be modifying the code we had implemented for Spring Boot gRPC Error Handling Example.Suppose the gRPC server needs to send the error cause along with the description and gRPC server code.
Create a class named AccountNotFoundException which should have more custom information that needs to be sent to the client.
package com.javainuse.bank.exception; public class AccountNotFoundException extends RuntimeException { private static final long serialVersionUID = 1L; public AccountNotFoundException(String message) { super(); } }
package com.javainuse.bank.service; import com.javainuse.banking.AccountBalanceResponse; import com.javainuse.banking.AccountBalanceServiceGrpc; import com.javainuse.banking.AccountRequest; import io.grpc.Status; import io.grpc.stub.StreamObserver; import net.devh.boot.grpc.server.service.GrpcService; @GrpcService public class BankAccountBalanceService extends AccountBalanceServiceGrpc.AccountBalanceServiceImplBase { @Override public void getAccountBalance(AccountRequest request, StreamObserver<com.javainuse.banking.AccountBalanceResponse> responseObserver) { if ((request.getAccountNumber().equals("account5"))) { responseObserver.onError((Status.NOT_FOUND.withDescription("The requested Account Number cannot be found.") .withCause(new AccountNotFoundException("Database Error- Connection Refused."))).asRuntimeException()); return; } AccountBalanceResponse empResp = AccountBalanceResponse.newBuilder() .setAccountNumber(request.getAccountNumber()).setBalance(100).build(); // set the response object responseObserver.onNext(empResp); // mark process is completed responseObserver.onCompleted(); } }At the gRPC client end let us now try to print the cause when the exception is received.
package com.javainuse.bank.service; import org.springframework.stereotype.Service; import com.javainuse.banking.AccountBalanceResponse; import com.javainuse.banking.AccountBalanceServiceGrpc; import com.javainuse.banking.AccountRequest; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Status; import io.grpc.StatusRuntimeException; @Service public class BankService { public void getAccountBalance() { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8090).usePlaintext().build(); AccountBalanceServiceGrpc.AccountBalanceServiceBlockingStub stub = AccountBalanceServiceGrpc .newBlockingStub(channel); try { AccountBalanceResponse bookResponse = stub .getAccountBalance(AccountRequest.newBuilder().setAccountNumber("account5").build()); System.out.println(bookResponse); } catch (StatusRuntimeException ex) { Status status = ex.getStatus(); System.out.println("error code -" + status.getCode()); System.out.println("error description -" + status.getDescription()); System.out.println("error cause -" + status.getCause()); } channel.shutdown(); } }