Techdozo
  • Microservices
    • All
    • GraphQL
    • gRPC
    • Spring Boot
    gRPC Bidirectional Streaming with Code Example

    gRPC Bidirectional Streaming with Code Example

    gRPC Client Streaming

    gRPC Client Streaming

    Distributed transaction in microservices using Saga

    Distributed Transactions in Microservices: implementing Saga with Temporal

    Temporal Workflow Orchestration

    Workflow Orchestration with Temporal and Spring Boot

    GraphQL Directive

    GraphQL Directive

    Spring for GraphQL mutation

    Spring for GraphQL: Mutation

  • Spring Boot
    Spring Boot GraphQL service

    Getting started with Spring Boot GraphQL service

    Deploying a RESTful Spring Boot Microservice on Kubernetes

    Deploying a RESTful Spring Boot Microservice on Kubernetes

    RESTful Microservices with Spring Boot and Kubernetes

    RESTful Microservices with Spring Boot and Kubernetes

    RESTful API Gateway with gRPC

    RESTful API Gateway with gRPC

  • gRPC
    gRPC Bidirectional Streaming with Code Example

    gRPC Bidirectional Streaming with Code Example

    gRPC Client Streaming

    gRPC Client Streaming

    gRPC Interceptor: unary interceptor with code example

    gRPC Interceptor: unary interceptor with code example

    gRPC: synchronous and asynchronous Server streaming RPC

    gRPC: synchronous and asynchronous Server streaming RPC

    Photo by Ramón Salinero on Unsplash

    gRPC: synchronous and asynchronous unary RPC in Java

    Microservices inter-process communication using gRPC

    gRPC for microservices communication

  • GraphQL
    GraphQL Directive

    GraphQL Directive

    Spring for GraphQL mutation

    Spring for GraphQL: Mutation

    Spring for GraphQL: How to solve the N+1 Problem?

    Spring for GraphQL: How to solve the N+1 Problem?

    Spring GraphQL with @Controller, @SchemaMapping and @QueryMapping

    Spring for GraphQL : @SchemaMapping and @QueryMapping

    Spring Boot GraphQL service

    Getting started with Spring Boot GraphQL service

  • Kubernetes
    Deploying a RESTful Spring Boot Microservice on Kubernetes

    Deploying a RESTful Spring Boot Microservice on Kubernetes

    Components of Kubernetes Architecture

    Components of Kubernetes Architecture

    Helm Chart: quick start your app deployment on Kubernetes

    Helm Chart: quick start your app deployment on Kubernetes

    gRPC load balancing on Kubernetes (using Headless Service)

    gRPC load balancing on Kubernetes (using Headless Service)

    Getting started with Kind: quick start a multi-node local Kubernetes cluster

    Getting started with Kind: quick start a multi-node local Kubernetes cluster

    Getting started with Minikube: deploying application on local Kubernetes cluster

    Getting started with Minikube: deploying application on local Kubernetes cluster

  • Java
    Java Streams: Stream Operation with Examples

    Java Streams: Stream Operation with Examples

    Java Streams: stream creation with examples

    Java Streams: stream creation with examples

    Garbage Collection

    Super Fast Garbage Collectors in Java

    Calculus

    Functional Programming in Java

No Result
View All Result
  • Login
  • Microservices
    • All
    • GraphQL
    • gRPC
    • Spring Boot
    gRPC Bidirectional Streaming with Code Example

    gRPC Bidirectional Streaming with Code Example

    gRPC Client Streaming

    gRPC Client Streaming

    Distributed transaction in microservices using Saga

    Distributed Transactions in Microservices: implementing Saga with Temporal

    Temporal Workflow Orchestration

    Workflow Orchestration with Temporal and Spring Boot

    GraphQL Directive

    GraphQL Directive

    Spring for GraphQL mutation

    Spring for GraphQL: Mutation

  • Spring Boot
    Spring Boot GraphQL service

    Getting started with Spring Boot GraphQL service

    Deploying a RESTful Spring Boot Microservice on Kubernetes

    Deploying a RESTful Spring Boot Microservice on Kubernetes

    RESTful Microservices with Spring Boot and Kubernetes

    RESTful Microservices with Spring Boot and Kubernetes

    RESTful API Gateway with gRPC

    RESTful API Gateway with gRPC

  • gRPC
    gRPC Bidirectional Streaming with Code Example

    gRPC Bidirectional Streaming with Code Example

    gRPC Client Streaming

    gRPC Client Streaming

    gRPC Interceptor: unary interceptor with code example

    gRPC Interceptor: unary interceptor with code example

    gRPC: synchronous and asynchronous Server streaming RPC

    gRPC: synchronous and asynchronous Server streaming RPC

    Photo by Ramón Salinero on Unsplash

    gRPC: synchronous and asynchronous unary RPC in Java

    Microservices inter-process communication using gRPC

    gRPC for microservices communication

  • GraphQL
    GraphQL Directive

    GraphQL Directive

    Spring for GraphQL mutation

    Spring for GraphQL: Mutation

    Spring for GraphQL: How to solve the N+1 Problem?

    Spring for GraphQL: How to solve the N+1 Problem?

    Spring GraphQL with @Controller, @SchemaMapping and @QueryMapping

    Spring for GraphQL : @SchemaMapping and @QueryMapping

    Spring Boot GraphQL service

    Getting started with Spring Boot GraphQL service

  • Kubernetes
    Deploying a RESTful Spring Boot Microservice on Kubernetes

    Deploying a RESTful Spring Boot Microservice on Kubernetes

    Components of Kubernetes Architecture

    Components of Kubernetes Architecture

    Helm Chart: quick start your app deployment on Kubernetes

    Helm Chart: quick start your app deployment on Kubernetes

    gRPC load balancing on Kubernetes (using Headless Service)

    gRPC load balancing on Kubernetes (using Headless Service)

    Getting started with Kind: quick start a multi-node local Kubernetes cluster

    Getting started with Kind: quick start a multi-node local Kubernetes cluster

    Getting started with Minikube: deploying application on local Kubernetes cluster

    Getting started with Minikube: deploying application on local Kubernetes cluster

  • Java
    Java Streams: Stream Operation with Examples

    Java Streams: Stream Operation with Examples

    Java Streams: stream creation with examples

    Java Streams: stream creation with examples

    Garbage Collection

    Super Fast Garbage Collectors in Java

    Calculus

    Functional Programming in Java

No Result
View All Result
Techdozo
No Result
View All Result
Home Microservices gRPC

Error handling in gRPC with public RESTFul API

Pankaj by Pankaj
June 12, 2021
in gRPC, Microservices
Reading Time: 14 mins read
0
A A
0
Error handling in gRPC with public RESTFul API

Photo by Erik Mclean on Unsplash

0
SHARES
7.5k
VIEWS
Share on FacebookShare on TwitterShare on Linkedin

Getting error handling right is hard in the gRPC applications. It’s harder if your application consists of many microservices, exposing REST and gRPC services. The consumer of your API needs a consistent experience of error handling. You must hide internal implementation details from the API consumers.

In this article, I will explain how to develop an error handling framework, that works with RESTFul and gRPC APIs. Although the example is based on Spring Boot and Java gRPC the concept is common for all gRPC languages.

Code Example

The working code example of this article is listed on GitHub . To run the example, clone repository, and import grpc-spring-boot as a project in your favorite IDE.

As a reminder, the code example consists of two microservices –

  • Product Gateway – acts as an API Gateway (client of Product Service) and exposes REST APIs (Gradle module product-api-gateway)
  • Product Service – exposes gRPC APIs (Gradle module product-service)

There is a 3rd Gradle module, called commons, which contains common Exception classes consumed by both Product Gateway Service and Product Service.

You can start these services from IDE by calling the main method of ProductGatewayApplication and ProductApplication respectively.

You can test the application by calling Product Gateway Service API as :


curl --location --request GET 'http://localhost:8080/products/32c29935-da42-4801-825a-ac410584c281' 
--data-raw ''

Problem Description

The Product service throws a domain exception ResourceNotFoundException when productId is not found as:


public Product get(String productId) {
   var product = Optional.ofNullable(productStorage.get(productId));
   return product.orElseThrow(
       () ->
           new ResourceNotFoundException(
               "Product ID not found",
               Map.of("resource_id", productId, "message", "Product ID not found")));
 }

This exception needs to be handled by the client application Product Gateway and show a meaningful error response to the caller. The error response must not expose internal implementation detail.

Error handling implementation

So, you need to develop an error handling framework to handle errors thrown by the gRPC service. You also need to propagate those errors in a standard error response to the API consumer. You must take care to not expose the internal details of the system.

The sequence diagram of error handling looks like this:



The application flow for a GET /products/{productId} API is :

  1. API consumer makes API call to public RESTful API.
  2. The API call is delegated to gRPC client (ProductService), which is responsible for calling gRPC API.
  3. The gRPC client calls gRPC API using the gRPC channel.
  4. The gRPC API calls the repository (application layer) to fetch the product from the database.
  5. If the product is not found in the database, the application layer throws a domain exception (ResourceNotFoundException).
  6. The domain exception is handled by an gRPC interceptor, which creates a gRPC specific error detail object and throws gRPC specific StatusRuntimeException.
  7. On receiving StatusRuntimeException, the client service creates and throws a domain-specific ServiceException.
  8. The ServiceException is caught by the GlobalExceptionHandler, which returns a standard error response to the API client.

gRPC Error Model

As you know from the previous article Getting Error Handling right in gRPC, you can propagate error information from the gRPC service using google.rpc.Status. For that, you need to define a custom error model as:


message ErrorDetail {
  // Error code
  string errorCode = 1;
  //Error message
  string message = 2;
  // Additional metadata associated with the Error
  map<string, string> metadata = 3;
}

Here, the value for the “code” name/value pair is a language-independent string. Its value is a service-defined error code that should be human-readable.

Your application can define a handful of Error Codes as:


public enum ErrorCode {

  RESOURCE_NOT_FOUND("ResourceNotFound", "Resource not found", HttpStatus.NOT_FOUND),
  BAD_ARGUMENT("BadArgument", "Bad argument", HttpStatus.BAD_REQUEST),
  // Other error codes
}

gRPC error interceptor

The gRPC service defines an error interceptor ExceptionHandler, which transforms domain-specific Exception to the StatusRuntimeException as:


@GrpcExceptionHandler(ResourceNotFoundException.class)
public StatusRuntimeException handleResourceNotFoundException(ResourceNotFoundException cause) {

  ErrorDetail errorDetail =
      ErrorDetail.newBuilder()
          .setErrorCode(cause.getErrorCode().getShortCode())
          .setMessage(cause.getMessage())
          .putAllMetadata(cause.getErrorMetaData())
          .build();

  var status =
      com.google.rpc.Status.newBuilder()
          .setCode(Code.NOT_FOUND.getNumber())
          .setMessage("Resource not found")
          .addDetails(Any.pack(errorDetail))
          .build();

  return StatusProto.toStatusRuntimeException(status);
}

Exception mapping at the gRPC Client

As StatusRuntimeException thrown by the gRPC server is an internal implementation detail, you need to map it to the domain exception. On the gRPC client, you can catch StatusRuntimeException and map to domain-specific ServiceException by calling ServiceExceptionMapper.map(error) as :


public Product getProduct(String productId) {
  Product product;
  try {
    //gRPC Server call
    var request = GetProductRequest.newBuilder().setProductId(productId).build();
    var productApiServiceBlockingStub = ProductServiceGrpc.newBlockingStub(managedChannel);
    var response = productApiServiceBlockingStub.getProduct(request);
  } catch (StatusRuntimeException error) {
    throw ServiceExceptionMapper.map(error);
  }
  return product;
}

Where ServiceExceptionMapper maps StatusRuntimeException to a domain-specific ServiceException as :


public static ServiceException map(StatusRuntimeException error) {

  var status = io.grpc.protobuf.StatusProto.fromThrowable(error);
  ErrorDetail errorDetail = null;

  for (Any any : status.getDetailsList()) {
    if (!any.is(ErrorDetail.class)) {
      continue;
    }
    try {
      errorDetail = any.unpack(ErrorDetail.class);
    } catch (InvalidProtocolBufferException cause) {
      errorDetail =
          ErrorDetail.newBuilder()
              .setCode(ErrorCode.INVALID_OPERATION.getMessage())
              .setMessage(cause.getMessage())
              .putAllMetadata(Map.of())
              .build();
    }
  }

  return new ServiceException(
      ErrorCode.errorCode(errorDetail.getCode()),
      errorDetail.getMessage(),
      errorDetail.getMetadataMap());
}

Exception handling in a RESTful API

Before we delve into exception handling in a RESTful API, it’s important to understand the importance of standard error messages.

API error message

As an API producer, you may want to provide a consistent experience for your API consumer. You may want to return a consistent response format for all error conditions. One example of such error format could be:


{
    "error": {
        "code": "ResourceNotFound",
        "message": "Resource not found",
        "details": {
            "message": "Product ID not found",
            "resource_id": "32c29935-da42-4801-825a-ac410584c281"
        }
    }
}

Here, the value for the “code” name/value pair is a language-independent string. Its value is a service-defined error code that should be human-readable. This code serves as a more specific indicator of the error than the HTTP error code specified in the response. Services should have a relatively small number (about 20) of possible values for “code”, and all clients MUST be capable of handling all of them.

You can check Microsoft REST API Guidelines or Google Error Model for reference.

The next step is to create a GlobalExceptionHandler in the Spring Boot application Product Gateway. The responsibility of the GlobalExceptionHandler to handle errors and return a standard error response to the RESTful API client. Before we implement GlobalExceptionHandler, let’s see how can we handle errors in a Spring Boot application.

Default error message in spring boot

Spring Boot allows a Spring project to be set up with minimal configuration. Spring Boot creates sensible defaults automatically when it detects certain key classes and packages on the classpath. It also has a default error-handling mechanism built in. Let’s try to understand how default exception handling works in a Spring Boot application.

Let’s assume we have a ProductController, which exposes an API GET /products/{productId}, whose getProduct(..) method throw exception in case of error conditions as:


@GetMapping("/products/{productId}")
public ResponseEntity<Product> getProduct(@PathVariable @NotBlank String productId) {
  //Throws Service Exception
  var product = productService.getProduct(productId);
  return new ResponseEntity<>(product, HttpStatus.CREATED);
}

In case of error, the GET /products/{productId} API returns the response as :

{
    "timestamp": "2021-06-06T04:23:32.050+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "",
    "path": "/products/32c29935-da42-4801-825a-ac410584c281"
}

As you can see, the default message provided by Spring is helpful but not much. First of all, it says all errors as HTTP status as 500, which is stands for internal server error. As per documentation –

The HyperText Transfer Protocol (HTTP) 500 Internal Server Error server error response code indicates that the server encountered an unexpected condition that prevented it from fulfilling the request.

Also, one of the most important parameters’message‘ is empty.

So, how does this default error mechanism works?

In the event of any unhandled error, Spring Boot forwards internally to /error. Spring Boot sets up a BasicErrorController to handle any request to /error. The controller adds error information to the internal model and returns error as the logical view.

Spring boot also provides us some basic ability to customize this error using Spring Boot Server properties.

Customizing Error handling using @ResponseStatus

The simplest way to customize error handling is by using @ResponseStatus, which can be applied to the Exception class itself as:


@ResponseStatus (value = HttpStatus.I_AM_A_TEAPOT)
public class ServiceException extends BaseException {
  //
}

Now, the error message response looks like this:


{
    "timestamp": "2021-06-06T11:39:32.453+00:00",
    "status": 418,
    "error": "I'm a teapot",
    "message": "",
    "path": "/products/32c29935-da42-4801-825a-ac410584c281"
}

And yes, there is a status code called 418 I’m a teapot

If you believe in the separation of concern, this way of error handling may seem wrong to you as the error class itself specifies how to handle the error. The error, such as ServiceException belongs to the application logic and it should not make any assumption about how the error should be handled ( HTTP belongs to the transport/presentation layer).

Once you have defined a standard error response, you can implement global error handling in a common shared library so that it can be imported by all microservices. Obviously, this works only when you have the same language stack in all of your microservices.

Let’s see how we can implement a global mechanism for handling error conditions in Spring Boot microservices.

Global Exception handling using @ControllerAdvice

In a spring boot microservice application, you can define a class with annotation @ControllerAdvice and define methods annotated with @ExceptionHandler as:


@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleException(ResourceNotFoundException cause) {
        var errorResponse = new ErrorResponse();
        errorResponse.setCode(Code.RESOURCE_NOT_FOUND);
        errorResponse.setMessage(cause.getMessage());
        errorResponse.setDetails(cause.getErrorMetaData());
        return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
    }
}

Where the ErrorResponse class is defined as:


@JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.NAME, visible = true)
@JsonTypeName("error")
public class ErrorResponse {
  private Code code;
  private String message;
  private Map<String, String> details;
}

First two lines @JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.NAME, visible = true) and @JsonTypeName("error") helps us to output error JSON as "error": {..}.

It’s not required to have a class annotated with @ControllerAdvice, we can even define @ExceptionHandler(ResourceNotFoundException.class) methods in controller classes but that will not help in achieving consistent global exception handling.

You can define GlobalExceptionHandler in the common library which can be imported by all microservices by adding a dependency on this library. Now, all you need to do is to tell Spring Boot about GlobalExceptionHandler, which can be done as:


@SpringBootApplication(scanBasePackages = {"dev.techdozo.api.product", "dev.techdozo.commons"})
public class ProductGatewayApplication {

  public static void main(String[] args) {
    SpringApplication.run(ProductGatewayApplication.class, args);
  }
}

Please note the package of error handling class is ‘dev.techdozo.commons‘

Putting it all together

All set. Now, if you call GET products/{productId} API with nonexistent productId, you’ll see an error message like:

Summary

Getting error handling right can be very tricky in a microservices application consisting of a mixture of gRPC and RESTful API. We must provide consistent error responses to the API consumer without exposing internal implementation details. Using google.rpc.Status we can propagate rich error models to gRPC clients. Spring Boot provides very flexible and powerful error handling capabilities using @ControllerAdvice.

Tags: grpcjavamicroservicesspring-boot
Previous Post

Getting Error Handling right in gRPC

Next Post

Getting started with Minikube: deploying application on local Kubernetes cluster

Pankaj

Pankaj

Software Architect @ Schlumberger ``` Cloud | Microservices | Programming | Kubernetes | Architecture | Machine Learning | Java | Python ```

Related Posts

gRPC Bidirectional Streaming with Code Example
gRPC

gRPC Bidirectional Streaming with Code Example

February 17, 2023
gRPC Client Streaming
gRPC

gRPC Client Streaming

January 20, 2023
Distributed transaction in microservices using Saga
Microservices

Distributed Transactions in Microservices: implementing Saga with Temporal

November 8, 2022
Temporal Workflow Orchestration
Microservices

Workflow Orchestration with Temporal and Spring Boot

October 29, 2022

Discussion about this post

Recent Articles

gRPC Bidirectional Streaming with Code Example

gRPC Bidirectional Streaming with Code Example

February 17, 2023
gRPC Client Streaming

gRPC Client Streaming

January 20, 2023
Distributed transaction in microservices using Saga

Distributed Transactions in Microservices: implementing Saga with Temporal

November 8, 2022
Temporal Workflow Orchestration

Workflow Orchestration with Temporal and Spring Boot

October 29, 2022
  • Trending
  • Comments
  • Latest
Deploying a RESTful Spring Boot Microservice on Kubernetes

Deploying a RESTful Spring Boot Microservice on Kubernetes

August 18, 2021
gRPC Interceptor: unary interceptor with code example

gRPC Interceptor: unary interceptor with code example

April 30, 2022
Temporal Workflow Orchestration

Workflow Orchestration with Temporal and Spring Boot

October 29, 2022
Microservices inter-process communication using gRPC

gRPC for microservices communication

August 29, 2021
Calculus

Functional Programming in Java

0
Java Streams: stream creation with examples

Java Streams: stream creation with examples

0
Garbage Collection

Super Fast Garbage Collectors in Java

0
Java Streams: Stream Operation with Examples

Java Streams: Stream Operation with Examples

0
gRPC Bidirectional Streaming with Code Example

gRPC Bidirectional Streaming with Code Example

February 17, 2023
gRPC Client Streaming

gRPC Client Streaming

January 20, 2023
Distributed transaction in microservices using Saga

Distributed Transactions in Microservices: implementing Saga with Temporal

November 8, 2022
Temporal Workflow Orchestration

Workflow Orchestration with Temporal and Spring Boot

October 29, 2022
Facebook Twitter Pinterest

TECHDOZO

Simplifying modern tech stack!

Browse by Category

  • Bitesize
  • GraphQL
  • gRPC
  • Java
  • Kubernetes
  • Microservices
  • Spring Boot

Recent Articles

gRPC Bidirectional Streaming with Code Example

gRPC Bidirectional Streaming with Code Example

February 17, 2023
gRPC Client Streaming

gRPC Client Streaming

January 20, 2023

© 2023 Techdozo.

No Result
View All Result
  • Home
  • gRPC
  • Kubernetes
  • Microservices
  • GraphQL

© 2023 Techdozo.

Welcome Back!

Sign In with Google
OR

Login to your account below

Forgotten Password?

Retrieve your password

Please enter your username or email address to reset your password.

Log In