Featured resource
pluralsight tech forecast
2025 Tech Forecast

Which technologies will dominate in 2025? And what skills do you need to keep up?

Check it out
Hamburger Icon
  • Labs icon Lab
  • Core Tech
Labs

Guided: Creating a Quotes REST API with Spring Boot 3 - Part 3 - Exception Handling

In this lab, you will gain hands-on experience with handling exceptions in a Spring Boot REST API. You will learn how to effectively manage the response returned to the user and incorporate logging for developer support. Throughout the lab, you will work with annotations such as ResponseStatusException, ExceptionHandler, and RestControllerAdvice. By the end of this hands-on exercise, you will have a comprehensive understanding of exception handling and logging techniques that can be applied to your own applications.

Labs

Path Info

Level
Clock icon Intermediate
Duration
Clock icon 40m
Published
Clock icon Jun 13, 2023

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    Welcome to Part 3 of the lab series:

    Guided: Creating a Quotes REST API with Spring Boot 3.

    This series of labs will guide you through some of the basic concepts for creating a RESTful API Service using Spring Boot with the Spring Web dependency.

    In this lab, you will implement several different ways of handling exceptions that occur within a Spring Boot REST API. This will include handling the response returned to the user and also logging for developer support.

    Effective exception handling is essential to maintain a stable and reliable software application. Handling exceptions requires providing informative feedback to the user and logging the exception for support and troubleshooting purposes. This approach ensures a smooth user experience and helps developers investigate and diagnose the issue, leading to a stable and reliable application.

    The objective of this lab is to modify the way exceptions are handled for the /api/quotes/9 endpoint. As of now, the example code only has 3 quotes hardcoded, so a request for quote 9 should result in a 404 Not Found exception.

    To get started, click the Next step > button below.

  2. Challenge

    Starting the REST API Server

    To observe the changes made to the REST API, it is necessary to start the server.

    In the first Terminal window, execute the command:

    ./gradlew bootRun
    

    As you make changes to the code, you will need to change focus back to this Terminal window and stop the server by using the Ctrl+C command. Then you can restart the server with the ./gradlew bootRun command. This can easily be done by pressing the up arrow to restore that command.

    You can use the second Terminal window to make requests against the quotes api.

    To see the current state of the application, in the second Terminal window execute the command:

    curl -i http://localhost:8888/api/quotes/9
    

    Note: the -i flag will include the response headers. You may need to scroll up to see these.

    Currently, instead of the expected 404 - Not Found response, the endpoint is returning a 500 - Internal Server Error.

    If you examine the getQuoteByIndex method in QuotesController.java, you will see that it simply returns the outcome of the quotes.getQuoteByIndex(index) call. In QuotesService.java, the getQuoteByIndex method is programmed to throw the QuoteIndexOutOfBoundsException if the index provided falls outside the range of the quotes list.

    The reason for the 500 Internal Server Error is that the QuoteIndexOutOfBoundsException is not being handled. Therefore, the endpoint fails, and the client receives an incorrect response.

    In the following steps, you will learn different ways to handle this exception more effectively.

    When ready, click the Next step > button below.

  3. Challenge

    Throwing a `ResponseStatusException`

    One way to handle exceptions with a specific HTTP status code in Spring is through the ResponseStatusException class. This approach is beneficial when you need to send a particular status code along with an exception message.

    To implement this technique in the getQuoteByIndex() method of the QuotesController.java file, you can wrap the method call with a try-catch block and catch the QuoteIndexOutOfBoundsException exception. In the catch block, throw the ResponseStatusException with the desired HTTP status code and message to be returned to the client, such as:

    @GetMapping("/{index}") 
    public String getQuoteByIndex(@PathVariable int index) {
        try {
            return quotes.getQuoteByIndex(index);
        } catch (QuoteIndexOutOfBoundsException qex) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "ResponseStatusException");
        }
    }
    

    After making this change, restart the server in the first Terminal window.

    Click in the first Terminal window and send the Ctrl+C command. Then press the up arrow to restore the ./gradlew bootRun command.

    After restarting the server, you can proceed to rerun the curl request in the second Terminal window.

    curl -i http://localhost:8888/api/quotes/9
    

    Now the endpoint is correctly responding with the 404 - Not Found message.

    Point of Interest:
    Directly throwing ResponseStatusException from the QuoteService's getQuoteByIndex method might appear as a simpler solution to handle exceptions in a Spring Boot application. However, this class is part of the org.springframework.web.server package, and adding it to the service class would violate the separation of concerns principle. It would push web implementation details into the application layer, tying the service layer to the web layer. This would make it challenging to modify or replace either layer without affecting the other and would also make the code less reusable and harder to test.

    The use of ResponseStatusException provides a straightforward approach for sending custom error messages to the client, but this requires the exception to be handled in each method. In the following step, you will learn about another technique to handle exceptions across all methods within a class.

    When ready, click the Next step > button below.

  4. Challenge

    Declaring an `ExceptionHandler` in the `@RestController` Class

    Another approach to handle exceptions within a particular RestController class is by utilizing the @ExceptionHandler annotation. This permits the declaration of methods within the controller to handle particular exceptions.

    To implement this approach, add the following method to the QuotesController.java file:

    @ExceptionHandler(QuoteIndexOutOfBoundsException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handleExceptions(QuoteIndexOutOfBoundsException ex) {
        return "ExceptionHandler";
    }
    

    The @ExceptionHandler annotation is used to specify the exception class that this method will handle. Additionally, the @ResponseStatus annotation sets the HTTP response status to 404 - Not Found. This annotation allows the method to respond with various return types. In this case, the simple string ExceptionHandler is returned to verify the correct handler is being used.

    This method is now configured to catch anytime the QuoteIndexOutOfBoundsException is thrown within this class and respond to the client with a more friendly message.

    After you restart the server and resend the curl request, you will still see the ResponseStatusException. This is because the getQuoteByIndex method was changed to capture the QuoteIndexOutOfBoundsException and throw a ResponseStatusException.

    For right now you can test the last change by making a request for the /api/quote/exception endpoint:

    curl -i http://localhost:8888/api/quotes/exception
    

    You could also modify the getQuoteByIndex method back to just returning the response from the service:

    @GetMapping("/{index}") 
    public String getQuoteByIndex(@PathVariable int index) {
        return quotes.getQuoteByIndex(index);
    }
    

    And then after restarting the server, a request to the /api/quotes/9 endpoint will show the new error message. Showing that the @ExceptionHandler is handling all instances of this particular exception:

    curl -i http://localhost:8888/api/quotes/9
    

    In the next Step you'll see how this ExceptionHandler can be declared across the full application.

    When ready, click the Next step > button below.

  5. Challenge

    Declaring a `@RestControllerAdvice` Class

    In the previous approach, the ExceptionHandler method was used to separate the exception handling logic from the endpoint logic. This reduced code duplication and handled the exception for all methods within the class. However, a more advanced approach is to declare a RestControllerAdvice class, which enables you to handle exceptions globally across a group of controllers.

    With this approach, the exception handling logic is separated from the main controller logic, which enhances code organization and maintainability. This separation of concerns allows the controller to focus on handling HTTP requests and responses, while the @RestControllerAdvice manages the exception handling logic.

    To get started, open the GlobalExceptionHandler.java file and decorate the class with the @RestControllerAdvice annotation.

    Then declare the handleException method just like was done in the previous step, but this time return a message of RestControllerAdvice.

    The class should now look like:

    @RestControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler(QuoteIndexOutOfBoundsException.class)
        @ResponseStatus(HttpStatus.NOT_FOUND)
        public String handleExceptions(QuoteIndexOutOfBoundsException ex) {
            return "RestControllerAdvice";
        }
    }
    

    Restart the server and then make the curl request to the api/quotes/exception endpoint, the ExceptionHandler message will still be displayed. This is because the ExceptionHandler declared within the RestController class takes precedence over the one declared in the RestControllerAdvice class.

    curl -i http://localhost:8888/api/quotes/exception
    

    For now, comment out the @ExceptionHandler(QuoteIndexOutOfBoundsException.class) annotation in the QuotesController.java file. After restarting the server, send a new request to api/quotes/exception. As a result, the RestControllerAdvice message will be returned, indicating that the GlobalExceptionHandler.java file's ExceptionHandler is now handling the exception instead of the one in the QuotesController.java file.

    You have now seen several ways of handling the response sent back to the client when an exception occurs, but that is only part of the job of handling exceptions. In the next step you will see how to add logging to your exceptions.

    When ready, click the Next step > button below.

  6. Challenge

    Add Logging for Troubleshooting Support

    When handling exceptions, it's not enough to just provide a helpful response to the client. While that is an important part of the process, it's equally important to log the exception for the support team to troubleshoot any underlying issues that may have caused the exception. Logging the exception details allows the support team to quickly diagnose and fix the problem. The log can also be used for future analysis and improvement of the application.

    SLF4J is a widely used logging abstraction layer that offers a unified interface for various logging frameworks. This allows developers to utilize SLF4J in their code and change the underlying logging framework, such as Logback or Log4j2, without modifying the code itself. The default logging framework for Spring Boot is Logback.

    To implement SLF4J logging in the GlobalExceptionHandler class, you can create a Logger object using the LoggerFactory class from the SLF4J library. Inside the catch block of the handleExceptions() method, you can log the exception using the error() method of the Logger object. This will log the stack trace of the exception along with any other useful information.

    First, you will need to add the following import statements to the top of the class:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    

    Note: Many of the logging framework libraries provide Logger and LoggerFactory classes. Verify that you are importing from the org.slf4j package.

    Then, inside the GlobalExceptionHandler class, create a Logger instance from the LoggerFactory.getLogger() method:

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    

    Note:

    • The logger variable is declared as private static final. This ensures that only one instance of the logger is created per class, which helps to avoid memory leaks and improves performance.
    • The getLogger() method can take either a Class object or a String. In this case, theGlobalExceptionHandler.class is referenced.

    After creating the logger instance, it can be called within any method of the class to log messages with different severities. SLF4J offers different logging levels that can be utilized to categorize log statements based on their severity. The available levels, ordered by increasing severity, are trace, debug, info, warn, and error. Whether a specific level is printed or not, depends on the configuration in the application. By default, Spring Boot is configured to print log statements of info severity or higher.

    To demonstrate how to use the logger with different severity levels, you can add the following code snippet just before the return "RestControllerAdvice"; line in the handleExceptions() method:

    logger.error("Error Message");
    logger.warn("Warning Message");
    logger.info("Info Message");
    logger.debug("Debug Message");
    logger.trace("Trace Message");
    

    Restart the server and then make another request to the api/quotes/exception endpoint:

    curl -i http://localhost:8888/api/quotes/exception
    

    You should now see a logs folder containing an app.log file. Opening that file, you should see several error messages similar to the following:

    2023-05-03T15:53:02.780Z ERROR 5394 --- [http-nio-0.0.0.0-8888-exec-1] c.p.quotes.GlobalExceptionHandler        : Error Message
    2023-05-03T15:53:02.780Z  WARN 5394 --- [http-nio-0.0.0.0-8888-exec-1] c.p.quotes.GlobalExceptionHandler        : Warning Message
    2023-05-03T15:53:02.780Z  INFO 5394 --- [http-nio-0.0.0.0-8888-exec-1] c.p.quotes.GlobalExceptionHandler        : Info Message
    

    Notice that only the ERROR, WARN, and INFO messages have been logged.

    When ready, click the Next step > button below.

  7. Challenge

    Next Steps

    Congratulations on completing Part 3 of this guided lab series on Spring!

    You are welcome to continue working with this lab to see what improvements you can make.

    Some ideas are to:

    • Refactor the response type of the ExceptionHandler class to better communicate the nature of the exception to the client.
    • Modify the logging messages to provide more detail to help with troubleshooting.
    • View and modify some logging options available in the application.properties file. Uncomment the different logging properties, restart the server, and then make a request to see how these options change the reported logs.

    The best to you as you continue your learning journey here at Pluralsight.

Jeff Hopper is a polyglot solution developer with over 20 years of experience across several business domains. He has enjoyed many of those years focusing on the .Net stack.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.