- Lab
- Core Tech

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.

Path Info
Table of Contents
-
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 quote9
should result in a404 Not Found
exception.To get started, click the Next step > button below.
-
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 a500 - Internal Server Error
.If you examine the
getQuoteByIndex
method inQuotesController.java
, you will see that it simply returns the outcome of thequotes.getQuoteByIndex(index)
call. InQuotesService.java
, thegetQuoteByIndex
method is programmed to throw theQuoteIndexOutOfBoundsException
if the index provided falls outside the range of the quotes list.The reason for the
500 Internal Server Error
is that theQuoteIndexOutOfBoundsException
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.
-
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 theQuotesController.java
file, you can wrap the method call with a try-catch block and catch theQuoteIndexOutOfBoundsException
exception. In the catch block, throw theResponseStatusException
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 throwingResponseStatusException
from theQuoteService
'sgetQuoteByIndex
method might appear as a simpler solution to handle exceptions in a Spring Boot application. However, this class is part of theorg.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.
-
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 to404 - Not Found
. This annotation allows the method to respond with various return types. In this case, the simple stringExceptionHandler
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 thegetQuoteByIndex
method was changed to capture theQuoteIndexOutOfBoundsException
and throw aResponseStatusException
.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.
-
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 aRestControllerAdvice
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 ofRestControllerAdvice
.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, theExceptionHandler
message will still be displayed. This is because theExceptionHandler
declared within theRestController
class takes precedence over the one declared in theRestControllerAdvice
class.curl -i http://localhost:8888/api/quotes/exception
For now, comment out the
@ExceptionHandler(QuoteIndexOutOfBoundsException.class)
annotation in theQuotesController.java
file. After restarting the server, send a new request toapi/quotes/exception
. As a result, theRestControllerAdvice
message will be returned, indicating that theGlobalExceptionHandler.java
file'sExceptionHandler
is now handling the exception instead of the one in theQuotesController.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.
-
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 aLogger
object using theLoggerFactory
class from the SLF4J library. Inside the catch block of thehandleExceptions()
method, you can log the exception using theerror()
method of theLogger
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
andLoggerFactory
classes. Verify that you are importing from theorg.slf4j
package.Then, inside the
GlobalExceptionHandler
class, create aLogger
instance from theLoggerFactory.getLogger()
method:private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
Note:
- The
logger
variable 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 aClass
object or aString
. 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, aretrace
,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 ofinfo
severity or higher.To demonstrate how to use the
logger
with 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/exception
endpoint:curl -i http://localhost:8888/api/quotes/exception
You should now see a
logs
folder containing anapp.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
, andINFO
messages have been logged.When ready, click the Next step > button below.
- The
-
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 differentlogging
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.
- Refactor the response type of the
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.