- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Handle Errors in a Quotes API with Spring Boot 4
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.
Lab Info
Table of Contents
-
Challenge
Introduction
Welcome to Part 3 of the lab series: Handle Errors in a Quotes API with Spring Boot 4.
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/9endpoint. The application currently contains only three hardcoded quotes, so a request for quote9should result in a404 Not Foundexception. -
Challenge
Starting the REST API Server
Start the application and test the endpoint
To observe the changes made to the REST API, you'll start the server.
In the first Terminal window, run:
./gradlew bootRunAs you make changes to the code, return to this Terminal window and stop the application by pressing
Ctrl+C. Then restart the server.You can press the up arrow to quickly rerun the previous command.
In the second Terminal send a request to the API to see the current state of the application, run:
curl -i http://localhost:8888/api/quotes/9Note: the
-iflag will include the response headers. You may need to scroll up to see these.Currently, instead of the expected
404 - Not Foundresponse, the endpoint is returning a500 - Internal Server Error.
Why the endpoint returns a 500 error
If you examine the
getQuoteByIndexmethod inQuotesController.java, it directly returns the outcome of thequotes.getQuoteByIndex(index)call.In
QuotesService.java, thegetQuoteByIndexthrows aQuoteIndexOutOfBoundsExceptionwhen the index falls outside the range of the quotes list.The reason for the
500 Internal Server Erroris that theQuoteIndexOutOfBoundsExceptionis 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.
-
Challenge
Throwing a `ResponseStatusException`
One way to handle exceptions with a specific HTTP status code in Spring is through the
ResponseStatusExceptionclass. This approach is beneficial when you need to send a particular status code along with an exception message.To implement this in the
getQuoteByIndex()method inQuotesController.java, wrap the method call in atry-catchblock.Catch the
QuoteIndexOutOfBoundsExceptionexception and throw theResponseStatusExceptionwith the appropriate HTTP status 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.
Press
Ctrl+Cto stop the application, then press the up arrow and run./gradlew bootRun.After restarting the server, you can proceed to rerun the curl request in the second Terminal window.
curl -i http://localhost:8888/api/quotes/9Now the endpoint is correctly responding with the
404 - Not Foundmessage. ---Point of Interest:
ThrowingResponseStatusExceptiondirectly from theQuoteService'sgetQuoteByIndexmethod might appear as a simpler solution to handle exceptions in a Spring Boot application. However, this class is part of theorg.springframework.web.serverpackage, 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.--- Using
ResponseStatusExceptionprovides a straightforward approach for sending custom error messages to the client. However, 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.
-
Challenge
Declaring an `ExceptionHandler` in the `@RestController` Class
Handle exceptions with
@ExceptionHandlerAnother way to handle exceptions within a
RestControllerclass is by using the@ExceptionHandlerannotation. This allows you to define methods in the controller that handle specific exceptions.To implement this approach, add the following method to the
QuotesController.javafile:@ExceptionHandler(QuoteIndexOutOfBoundsException.class) @ResponseStatus(HttpStatus.NOT_FOUND) public String handleExceptions(QuoteIndexOutOfBoundsException ex) { return "ExceptionHandler"; }The
@ExceptionHandlerannotation specifies which exception this method handles. The@ResponseStatusannotation sets the HTTP response status to404 - Not Found. This annotation allows the method to return different types of responses. In this case, the simple stringExceptionHandleris returned to verify the correct handler is being used.This method is now configured to catch any
QuoteIndexOutOfBoundsExceptionthrown within this class and respond to the client with a more friendly message.Test the exception handler
Restart the server and resend the curl request.
curl -i http://localhost:8888/api/quotes/9You will still see the
ResponseStatusException. This is because thegetQuoteByIndexmethod was changed to capture theQuoteIndexOutOfBoundsExceptionand throw aResponseStatusException.To test the @ExceptionHandler, you have two options.
Option 1: Use the test endpoint
Test the last change by making a request for the
/api/quote/exceptionendpoint:curl -i http://localhost:8888/api/quotes/exceptionOption 2: Update the controller method
You could also update the
getQuoteByIndexmethod back to just returning the response from the service:@GetMapping("/{index}") public String getQuoteByIndex(@PathVariable int index) { return quotes.getQuoteByIndex(index); }After restarting the server, a request to the
/api/quotes/9endpoint will show the new error message. This shows that the@ExceptionHandleris handling all instances of this particular exception:curl -i http://localhost:8888/api/quotes/9In the next step, you'll see how the
ExceptionHandlercan be declared across the full application. -
Challenge
Declaring a `@RestControllerAdvice` Class
In the previous approach, the
ExceptionHandlermethod 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.A more advanced approach is to use
RestControllerAdviceclass, which allows you to handle exceptions globally across multiple controllers.With this approach, the exception handling logic is separated from the main controller logic, improving code organization and maintainability. This separation of concerns allows the controller to focus on handling HTTP requests and responses, while the
@RestControllerAdvicemanages the exception handling logic.To get started, open the
GlobalExceptionHandler.javafile and decorate the class with the@RestControllerAdviceannotation.Then declare the
handleExceptionmethod as in the previous step, but return the messageRestControllerAdvice.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/exceptionendpoint.curl -i http://localhost:8888/api/quotes/exceptionThe
ExceptionHandlermessage will still be displayed. This is because theExceptionHandlerdeclared within theRestControllerclass takes precedence over the one declared in theRestControllerAdviceclass.For now, comment out the
@ExceptionHandler(QuoteIndexOutOfBoundsException.class)annotation in theQuotesController.javafile.After restarting the server, send a new request to
api/quotes/exception.curl -i http://localhost:8888/api/quotes/exceptionAs a result, the
RestControllerAdvicemessage will be returned, indicating that theGlobalExceptionHandler.javafile'sExceptionHandleris now handling the exception instead of the one in theQuotesController.javafile.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.
-
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
GlobalExceptionHandlerclass, you can create aLoggerobject using theLoggerFactoryclass from the SLF4J library. Inside the catch block of thehandleExceptions()method, you can log the exception using theerror()method of theLoggerobject. This will log the stack trace of the exception along with any other useful information.First, 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
LoggerandLoggerFactoryclasses. Verify that you are importing from theorg.slf4jpackage.Then, inside the
GlobalExceptionHandlerclass, create aLoggerinstance from theLoggerFactory.getLogger()method:private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);Note:
- The
loggervariable is declared asprivate 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 aClassobject or aString. In this case, theGlobalExceptionHandler.classis referenced.
After creating the
loggerinstance, 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, anderror. 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 ofinfoseverity or higher.To demonstrate how to use the
loggerwith different severity levels, you can add the following code snippet just before thereturn "RestControllerAdvice";line in thehandleExceptions()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/exceptionendpoint:curl -i http://localhost:8888/api/quotes/exceptionYou should now see a
logsfolder containing anapp.logfile. 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 MessageNotice that only the
ERROR,WARN, andINFOmessages have been logged. - The
-
Challenge
Next Steps
Congratulations on completing part 3 of this lab series on Spring!
Continue working with this lab to see what improvements you can make.
Consider the following enhancements:
- Refactor the response type of the
ExceptionHandlerclass 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.propertiesfile. Uncomment the differentloggingproperties, 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.
- Refactor the response type of the
About the author
Real skill practice before real-world application
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.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.