Custom Error Handling in API Gateway

In Spring Boot microservices architecture, the API Gateway can act as the entry point for all the client requests, directing them to the appropriate microservices. This gateway must handle errors gracefully. It can ensure that any issues in the downstream services are communicated back to the client in a clear and useful manner. Custom error handling within the API Gateway can help improve the user experience by providing meaningful error messages instead of generic or cryptic ones.

Error handling in an API Gateway can include intercepting exceptions thrown by the services, processing these exceptions, and returning the appropriate responses to the client. This process typically includes:

  • Exception Interception: Capturing the exceptions that occur within the gateway or during the communication with the downstream services.
  • Exception Mapping: Mapping these exceptions to the user-friendly error messages or status codes.
  • Response Customization: Crafting the responses that provide the relevant information to the client while avoiding the technical details that could be confusing or insecure.

Improving the User Experience

Effective error handling can improves the user experience by:

  • Clarity: It can provides the clear and concise error message helps users understand what went wrong and what actions they can take to the resolve the issue.
  • Consistency: It can ensuring the consistent error responses across the different endpoints helps maintain the predictable interface.
  • Security: It can avoiding the exposure of the internal system details in the error messages prevents the potential security risks.
  • Support: It can offering the helpful hints or links to the documentation can guide the users towards the resolving the issues on their own.

Key Terminologies:

  • Spring Cloud Gateway: This is the part of the larger Spring Cloud family, It provides the simple, monitoring/metrics and resiliency and it is built on Spring WebFlux using asynchronous communication which is essential for handling large number of the simultaneous connections.
  • Error Handling: In context of API Gateways, error handling refers to the process of the catching exceptions that occur during the routing of the requests to backend services and transforming these exceptions into the meaningful responses that can be understood by the client.
  • Custom Error Handling: This involves defining the specific behaviors or responses when errors occur and rather than the relying on default error messages. It can handling aims to the provide clearer, more informative and potentially localized error messages to the client.
  • Route Locator: In Spring Cloud Gateway, the Route Locator is where you can define the paths and the destinations that the gateway will route the requests to. It is crucial for the setting up the routes that the API Gateway will manage.
  • Error Attributes: These are data structures that can provides the details about the error such as the timestamp, message, status code and any other relevant information that can help in the diagnosing the receives the helpful error message.

Implementation of Custom Error Handling in API Gateway

Below are the implementation steps to handle custom error in API gateway

Step 1: Create a spring boot project using spring initializer and add the below required dependency.

Dependencies:

  • Spring Web
  • Spring Dev Tools
  • Lombok
  • Spring Cloud Routing

After creating the Spring project, the file structure will be like the below image.


Step 2: Open the application.properties file and rename it to application.yml. Then, add the following YAML code for configuring the server port and API routing:

spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user-service
uri: http://localhost:9001
predicates:
- Path=/users/**


Step 3: Create the Configuration class.

Go to src > org.example.apigateway > GatewayConfig and put the below code.

Java
package org.example.apigateway;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/users/**")
                        .uri("http://localhost:9001"))
                .build();
    }
}


Step 4: Create the Custom Error Handling class.

Go to src > org.example.apigateway > CustomGlobalExceptionHandler and put the below code.

Java
package org.example.apigateway;


import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.reactive.result.method.annotation.ResponseEntityExceptionHandler;
;

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Exception.class)
    protected ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
        ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "Error occurred");
        return buildResponseEntity(apiError);
    }

    private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
        return new ResponseEntity<>(apiError, apiError.getStatus());
    }

    // Define other methods to handle specific exceptions as needed
}

class ApiError {
    private HttpStatus status;
    private String message;
    private String debugMessage;

    public ApiError(HttpStatus status, String message, String debugMessage) {
        this.status = status;
        this.message = message;
        this.debugMessage = debugMessage;
    }

    // Getters and setters omitted for brevity

    public HttpStatus getStatus() {
        return status;
    }

    public void setStatus(HttpStatus status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getDebugMessage() {
        return debugMessage;
    }

    public void setDebugMessage(String debugMessage) {
        this.debugMessage = debugMessage;
    }
}


Step 5: Open the main class (No changes are required) and put the below code.

Java
package org.example.apigateway;

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

@SpringBootApplication
public class ApiGatewayApplication {

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

}


Step 6: Run the application

Once the Spring project is completed successfully, it will start at port 8080.


user-service

Step 1: Create the spring project using spring initializer on creating the project add the below dependencies into the project.

Dependencies:

  • Spring Web
  • Spring Dev Tools
  • Lombok

After creating the Spring project, the folder structure will be like below.


Step 2: Open the application.properties file and put the below code for the server port and eureka client configuration to the project.

spring.application.name=user-service
server.port=9001


Step 3: Create a new Java class named “User”.

Go to src > org.example.userservice > User and put the below code.

Java
package org.example.userservice;

public class User {
    private Long id;
    private String name;

    public User(Long id, String exampleUser) {
    }
}


Step 4: Create the new Java class named UserService.

Go to src > org.example.userservice > UserService and put the below code.

Java
package org.example.userservice;

import org.springframework.stereotype.Service;

@Service
public class UserService {

    public User getUserById(Long id) {
       
        return new User(id, "Example User");
    }

    public User createUser(User user) {
        
        return user;
    }
}


Step 5: Create a new Java class named “UserController”.

Go to src > org.example.userservice > UserController and put the below code.

Java
package org.example.userservice;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }

    @PostMapping("/")
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userService.createUser(user);
        return ResponseEntity.ok(savedUser);
    }
}


Step 6: Open the main class (No changes are required) and put the below code.

Java
package org.example.userservice;

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


@SpringBootApplication
public class UserServiceApplication {

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

}


Step 7: Run the application

After the project completed, we will run the project and below is the console image.


Custom Error Handling Testing:

GET http://localhost:8080/user/123

Output:

““


By following these steps, we can implement the effective custom error handling in the Spring Boot Gateway and it improves the robustness and user friendliness of the microservice architecture.



Contact Us