Spring Boot gRPC: How to return error code when business error happens?
Asked Answered
F

5

10

I am implementing a gRPC API using LogNet grpc-spring-boot-starter.

I want to return, for instance, an INVALID_ARGUMENT error code when an incorrect argument has been passed.

If I throw a custom exception it ends up with io.grpc.StatusRuntimeException: UNKNOWN.

Q: Is it possible to define some exception handling mechanism so that exceptions of a particular type will always lead to correct gRPC statuses?

There is unfortunately not so much documentation in the project.

Foredoom answered 5/7, 2019 at 13:58 Comment(2)
I'm not familiar with gRPC project, but I think this might be helpful toptal.com/java/spring-boot-rest-api-error-handlingRodge
@Rodge Unfortunately this is not a rest situation, this is Protobuf/gRPC which is different.Rafter
E
6

gRPC discourages you from throwing an exception in order to communicate that error to the user. This is because it is trivial to accidentally leak information that you may not have considered being sent to a client.

Instead, you are encouraged to pass a StatusException or StatusRuntimeException to streamObserver.onError(Throwable). If you are using exceptions to communicate this information within your own code, you can put a try-catch within your code and pass the exception to onError(). For example, this might be fair for StatusException, since it is a checked exception.

There is the TransmitStatusRuntimeExceptionInterceptor which will catch exceptions during callbacks and if it is a StatusRuntimeException, close the call with the exception's status. This matches closely to what you're asking for, but it is not enabled by default on purpose.

Excipient answered 9/7, 2019 at 0:2 Comment(0)
K
1

I just published an article on this topic Exception Handling and Error Propagation in gRPC Java.

You can handle exceptions using an interceptor, e.g.:

public class ExceptionHandler implements ServerInterceptor {

    @Override
    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata,
                                                                 ServerCallHandler<ReqT, RespT> serverCallHandler) {
        ServerCall.Listener<ReqT> listener = serverCallHandler.startCall(serverCall, metadata);
        return new ExceptionHandlingServerCallListener<>(listener, serverCall, metadata);
    }

    private class ExceptionHandlingServerCallListener<ReqT, RespT>
            extends ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {
        private ServerCall<ReqT, RespT> serverCall;
        private Metadata metadata;

        ExceptionHandlingServerCallListener(ServerCall.Listener<ReqT> listener, ServerCall<ReqT, RespT> serverCall,
                                            Metadata metadata) {
            super(listener);
            this.serverCall = serverCall;
            this.metadata = metadata;
        }

        @Override
        public void onHalfClose() {
            try {
                super.onHalfClose();
            } catch (RuntimeException ex) {
                handleException(ex, serverCall, metadata);
                throw ex;
            }
        }

        @Override
        public void onReady() {
            try {
                super.onReady();
            } catch (RuntimeException ex) {
                handleException(ex, serverCall, metadata);
                throw ex;
            }
        }

        private void handleException(RuntimeException exception, ServerCall<ReqT, RespT> serverCall, Metadata metadata) {
            if (exception instanceof IllegalArgumentException) {
                serverCall.close(Status.INVALID_ARGUMENT.withDescription(exception.getMessage()), metadata);
            } else {
                serverCall.close(Status.UNKNOWN, metadata);
            }
        }
    }
}
Kakapo answered 10/7, 2019 at 7:0 Comment(0)
G
0

gRPC does not propagate error. From official documentation -

Create a derived instance of Status with the given cause. However, the cause is not transmitted from server to client.

If you want to pass custom information from server to client then you have a couple of option -

  1. Use metadata to propagate error information from server to client
  2. Use google.rpc.Status to pass error detail in repeated google.protobuf.Any details

You need to catch the exception in both cases, prepare an error message, and send it back to the client.

I have written a detailed blog post about error handling in gRPC.

Germinative answered 19/6, 2021 at 18:15 Comment(0)
D
0

Ref- Spring Boot + gRPC Error Handling Example
At the gRPC server end we return the Status Code back to the client.

 if ((request.getAccountNumber().equals("account5"))) {
            responseObserver.onError((Status.INVALID_ARGUMENT.withDescription("The requested Account Number cannot be found."))
                        .asRuntimeException());
                return;
            }

At the Client end - We catch the StatusRuntimeException. From the exception we get the Status and print the code which will be displayed as INVALID_ARGUMENT

catch (StatusRuntimeException ex) {
            Status status = ex.getStatus();
            System.out.println("error code -" + status.getCode());
            System.out.println("error description -" + status.getDescription());
}
Daphie answered 10/3 at 19:22 Comment(0)
M
-1

The latest version of the starter integrates spring validation support. It returns INVALID_ARGUMENT if validation fails.

Disclosure : I'm the creator of this starter.

Mustee answered 22/1, 2021 at 13:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.