DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Challenges of Using Nginx in a Microservices Architecture
  • Integrating Spring Boot Microservices With MySQL Container Using Docker Desktop
  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  • Failure Handling Mechanisms in Microservices and Their Importance

Trending

  • Building Resilient Networks: Limiting the Risk and Scope of Cyber Attacks
  • Building Resilient Identity Systems: Lessons from Securing Billions of Authentication Requests
  • Unlocking Data with Language: Real-World Applications of Text-to-SQL Interfaces
  • Mastering Fluent Bit: Installing and Configuring Fluent Bit on Kubernetes (Part 3)
  1. DZone
  2. Software Design and Architecture
  3. Microservices
  4. Effective Exception Handling in Microservices Integration

Effective Exception Handling in Microservices Integration

Effectively handle errors in a Spring Boot microservices application with custom exceptions, ensuring meaningful error responses and seamless integration.

By 
RENJITH RAMACHANDRAN user avatar
RENJITH RAMACHANDRAN
·
Dec. 31, 24 · Analysis
Likes (3)
Comment
Save
Tweet
Share
5.4K Views

Join the DZone community and get the full member experience.

Join For Free

Microservices architecture offers benefits such as scalability, agility, and maintainability, making it ideal for building robust applications. Spring Boot, as the preferred framework for developing microservices, provides various mechanisms to simplify integration with different systems. The modules offered by the Spring framework abstract much of the complexity, allowing developers to integrate seamlessly with external systems.

Integration types may vary depending on the system, including API integration, messaging system integration, or database connectivity. Each system requires specific error-handling mechanisms. Regardless of the integration type, the API layer should not directly expose errors returned by the integrated systems to ensure a consistent and user-friendly response.

Error Handling in a Sample Spring Boot Application

Below is an example of a Spring Boot application with a /register API call for user registration. This API demonstrates integration with a database to save user details, an internal messaging system to post messages, and an external API. 

Code Snippet 1:

Java
 
@PostMapping("/register")
    public ResponseEntity<String> registerUser(@RequestBody User user) {
        userRegistrationService.registerUser(user);
        return new ResponseEntity<>("User registered successfully", HttpStatus.CREATED);
    }


Code Snippet 2:

Java
 
public void registerUser(User user) {
        saveUserEntity(user);
        registerEvents(user);
        invokeLoginApi(user);
    }

    public ResponseEntity<String> invokeLoginApi(User user) {
        LoginDTO loginDTO = new LoginDTO();
        loginDTO.setUsername(user.getUsername());
        loginDTO.setPassword(user.getPassword());
        String url = "http://localhost:8080/api/auth/login";
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "application/json");
        HttpEntity<LoginDTO> request = new HttpEntity<>(loginDTO, headers);
        return restTemplate.exchange(url, HttpMethod.POST, request, String.class);
    }

    private UserEntity saveUserEntity(User user) {
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername(user.getUsername());
        userEntity.setFirstName(user.getFirstName());
        userEntity.setLastName(user.getLastName());
        userEntity.setEmail(user.getEmail());
        userRegistrationRepository.save(userEntity);
        LoginDTO loginDTO = saveLoginDTO(user);
        return userEntity;
    }

    private User registerEvents(User user) {
        UserRegisteredEvent userRegisteredEvent = new UserRegisteredEvent();
        userRegisteredEvent.setEmail(user.getEmail());
        userRegisteredEvent.setFirstName(user.getFirstName());
        userRegisteredEvent.setLastName(user.getLastName());
        userRegisteredEvent.setEmail(user.getEmail());
        applicationEventPublisher.publishEvent(userRegisteredEvent);
        return user;
    }

Code Snippet 1 illustrates the controller code, which uses dependency injection to autowire and call the service layer. The register function in the service layer performs three key operations: saving user information in the database, producing an event, and invoking the login API to authenticate the user. 

If an error occurs during data saving, event publishing, or API invocation, the system will return a generic 500 error, as shown in Figure 1. This error is not informative, making it difficult for the invoking client to understand the root cause. Developers must rely on logs to identify and debug the issue.

Figure 1

Figure 1

Controller Advice

A Controller advice can handle these exceptions and return a meaningful error, which invoking clients can use, as shown in Code Snippet 3 and Figure 2. 

Code Snippet 3:

Java
 
@ControllerAdvice
public class GlobalExceptionHandler {


    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception ex) {
        return new ResponseEntity<>("An unexpected error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Figure 2

Figure 2

Each integration layer may encounter different types of errors, and it is crucial to return meaningful information to the invoking client so that appropriate messages can be displayed. Returning a generic error for all scenarios is not a good design practice. 

The Approach

To handle errors effectively, custom exceptions should be defined for each integration layer. Exceptions specific to an integration layer should be caught and encapsulated within these custom exception classes. These exceptions can be grouped under a single custom exception or differentiated by introducing specific attributes, enabling the Controller Advice to return more detailed and meaningful error responses for each scenario.

Figure 3 below illustrates the implementation of different custom exception classes, designed to encapsulate exceptions from various integration layers. Each custom exception class extends a base class, DemoException, which itself extends the RuntimeException class. This hierarchical structure ensures a consistent approach to exception handling across all integration layers.

Figure 3

Figure 3

DemoException


Code Snippet 4:
Java
 
package com.example.demo.exceptionhandling.exception;

public class DemoException extends RuntimeException {
    private String errorCode;

    public DemoException(String errorCode) {
        super(errorCode);
    }
    public DemoException(){
        super();
    }
}


Code Snippet 5:
Java
 
public void registerUser(User user) throws DemoException {
        saveUserEntity(user);
        registerEvents(user);
        invokeLoginApi(user);
    }

    public ResponseEntity<String> invokeLoginApi(User user) throws DemoAPIException {
        try {
        LoginDTO loginDTO = new LoginDTO();
        loginDTO.setUsername(user.getUsername());
        loginDTO.setPassword(user.getPassword());
        String url = "http://localhost:8080/api/auth/login";
        HttpHeaders headers = new HttpHeaders();
        headers.set("Content-Type", "application/json");
        HttpEntity<LoginDTO> request = new HttpEntity<>(loginDTO, headers);
        return restTemplate.exchange(url, HttpMethod.POST, request, String.class);
        }catch (Exception e){
            throw new DemoAPIException("API-001:Error while invoking API");
        }
    }

    private UserEntity saveUserEntity(User user) throws DemoDataException {
        try {
            UserEntity userEntity = new UserEntity();
            userEntity.setUsername(user.getUsername());
            userEntity.setFirstName(user.getFirstName());
            userEntity.setLastName(user.getLastName());
            userEntity.setEmail(user.getEmail());
            userRegistrationRepository.save(userEntity);
            LoginDTO loginDTO = saveLoginDTO(user);
            return userEntity;
        }catch (Exception e){
            throw new DemoDataException("DATA-001:Error while saving user data");
        }

    }

    private User registerEvents(User user) throws DemoDataException{
        try {
        UserRegisteredEvent userRegisteredEvent = new UserRegisteredEvent();
        userRegisteredEvent.setEmail(user.getEmail());
        userRegisteredEvent.setFirstName(user.getFirstName());
        userRegisteredEvent.setLastName(user.getLastName());
        userRegisteredEvent.setEmail(user.getEmail());
        applicationEventPublisher.publishEvent(userRegisteredEvent);
        return user;
        }catch (Exception e){
            throw new DemoEventException("EVENT-001:Error while sending the user data");
        }
    }


As illustrated in Code Snippet 5, each function throws exceptions specific to its functionality, allowing the errors to be handled in the ControllerAdvice class. This enables the application to return detailed and specific error responses. Code Snippet 6 demonstrates the ControllerAdvice code, which handles each exception individually. Figure 4 shows the formatted error response. Unlike the generic error shown in Figure 3, the new error response is more descriptive, enabling the client code to handle it more effectively.

Code Snippet 6:

Java
 
 @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception ex) {
        return new ResponseEntity<>("An unexpected error occurred: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(DemoException.class)
    public ResponseEntity<String> handleDemoException(DemoException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(DemoDataException.class)
    public ResponseEntity<String> handleDemoDataException(DemoDataException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(DemoAPIException.class)
    public ResponseEntity<String> handleDemoAPIException(DemoAPIException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.SERVICE_UNAVAILABLE);
    }


Figure 4

Figure 4


Conclusion

Proper handling of errors from different integration layers is essential when developing microservices. It provides interfacing applications with better visibility into the errors, allowing them to handle issues appropriately while preventing the exposure of implementation details and code-related information, which could pose security risks.

The code for the example above can be found in this link.

Architecture Spring Boot Integration microservices

Opinions expressed by DZone contributors are their own.

Related

  • Challenges of Using Nginx in a Microservices Architecture
  • Integrating Spring Boot Microservices With MySQL Container Using Docker Desktop
  • Next Evolution in Integration: Architecting With Intent Using Model Context Protocol
  • Failure Handling Mechanisms in Microservices and Their Importance

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!