0% found this document useful (0 votes)
60 views61 pages

Exception Handling, Best Practice and Example Mini Project

The document discusses the importance of exception handling in software development, particularly within Spring MVC and Spring Boot applications. It introduces the @ExceptionHandler annotation for managing exceptions efficiently, along with best practices such as using custom exceptions, HTTP status codes, and centralized error handling with @ControllerAdvice. The document emphasizes the benefits of effective exception handling for improving user experience and application resilience.

Uploaded by

mankarsarang934
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
60 views61 pages

Exception Handling, Best Practice and Example Mini Project

The document discusses the importance of exception handling in software development, particularly within Spring MVC and Spring Boot applications. It introduces the @ExceptionHandler annotation for managing exceptions efficiently, along with best practices such as using custom exceptions, HTTP status codes, and centralized error handling with @ControllerAdvice. The document emphasizes the benefits of effective exception handling for improving user experience and application resilience.

Uploaded by

mankarsarang934
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Exception Handling

Introduction
Errors and exceptions are an inevitable part of software development.
Exception handling is one of the critical aspects of ensuring that your
application gracefully recovers from unexpected conditions and provides a
user-friendly experience. In the world of Spring MVC, the
@ExceptionHandler annotation provides a seamless way to manage such
unwanted scenarios. This article goes into how you can leverage the power
of @ExceptionHandler to handle exceptions efficiently in your Spring MVC
application.

Understanding the Need for Exception Handling


Imagine this: Your web application has a form, and users can input data and
submit it. However, if the user inputs invalid data, the application crashes,
presenting the user with an unsightly error page. This not only disrupts the
user experience but may also cause a loss of trust in your application.

That’s where exception handling comes into play. Instead of letting the
application crash, you can catch the error and redirect the user to a
custom error page, maybe even provide them with suggestions on how to
correct their mistake.

Enter @ExceptionHandler
Spring MVC provides a convenient annotation called @ExceptionHandler
that you can use to define methods to handle specific exceptions. These
methods act as centralized handlers for exceptions thrown within your
controller.

How to Use @ExceptionHandler


Basic Usage:

Here’s a basic example. Let’s assume we want to handle a


NullPointerException in our controller.
@Controller
public class MyController {

@RequestMapping("/someEndpoint")
public String someEndpoint() {
// Some logic that might throw a NullPointerException
return "viewName";
}

@ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(Exception ex) {
ModelAndView model = new ModelAndView("error");
model.addObject("exception", ex);
return model;
}
}

In the above code, if a NullPointerException is thrown within the controller,


the handleNullPointerException method will be invoked. We can then
redirect the user to an error page and display the relevant error message.

Handling Multiple Exceptions

Sometimes, you might want to handle multiple exceptions in a similar


fashion. You can do this by providing multiple exception types to the
@ExceptionHandler annotation:
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public ModelAndView handleMultipleExceptions(Exception ex) {
// Handle the exception and return the model and view
}

Global Exception Handling

In larger applications, it’s common to have multiple controllers. Instead of


defining the same exception handlers in each controller, you can use
@ControllerAdvice to handle exceptions globally.
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(NullPointerException.class)
public ModelAndView handleNullPointerException(Exception ex) {
// Handle the exception globally for all controllers
}
}

With @ControllerAdvice, any exception thrown in any controller that


matches the exception types specified in the @ExceptionHandler will be
caught and handled.

Customizing the Response


With the combination of @ExceptionHandler and @ResponseStatus, you
can also customize the HTTP status code of the response when an
exception occurs.
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ModelAndView handleResourceNotFound(Exception ex) {
// Return custom 404 Not Found page
}

In the above code, if a ResourceNotFoundException is thrown, the


response will have a 404 Not Found HTTP status.

Benefits of Using @ExceptionHandler


Centralized Exception Handling: With @ExceptionHandler, you have a
centralized place to handle exceptions, which makes your code cleaner
and more maintainable.
Custom Error Responses: You can provide custom error pages or
messages, enhancing the user experience.
Enhanced Application Strength: By handling exceptions gracefully, you
ensure that minor errors don’t crash your entire application.

Conclusion
Exception handling is a crucial aspect of building strong and user-friendly
applications. Spring MVC’s @ExceptionHandler provides developers with a
powerful and flexible tool to handle exceptions seamlessly. Whether you're
catching exceptions at the controller level or globally with
@ControllerAdvice, @ExceptionHandler ensures that your application can
recover gracefully from unexpected situations. Embrace the art of
exception handling, and elevate your Spring MVC application's resilience
and user experience.

Effective Exception Handling


What are Exceptions?
In Java, an exception is an event that occurs during the execution of a
program that disrupts the normal flow of instructions. Exceptions are used
to handle errors, unexpected conditions, or other exceptional events that
may occur during runtime. Java provides a mechanism for handling
exceptions through the use of try-catch blocks.

Exception Handling in Spring Boot


In Spring Boot, exception handling is a crucial part of building robust and
reliable applications. Spring Boot provides a comprehensive framework for
handling exceptions, including both built-in and custom exceptions.

Spring Boot’s exception handling mechanism is based on the concept of


centralized error handling. This means that all exceptions thrown by an
application are handled in a single place, rather than scattered throughout
the application’s codebase.

Best Practices for Exception Handling in Spring Boot


Here are some best practices for handling exceptions in Spring Boot
applications:

Use descriptive error messages

When an exception occurs, it is important to provide a clear and


descriptive error message that explains the cause of the exception. This
will help developers quickly identify and resolve the issue.
@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}

In this example, we define a custom exception called


ResourceNotFoundException. This exception extends the
RuntimeException class and takes a message as an argument. The message
provides a clear and descriptive error message that explains the cause of
the exception.

Use HTTP status codes

Spring Boot provides built-in support for mapping exceptions to HTTP


status codes. Use these status codes to provide a clear indication of the
nature of the exception. For example, a 404 status code can be used to
indicate that a resource was not found.
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found with id " + id));
return ResponseEntity.ok(user);
}

In this example, we use the @ResponseStatusException to throw an


exception and map it to a 404 NOT FOUND HTTP status code. If the user is
not found, this exception will be thrown and the appropriate HTTP status
code will be returned in the response.

Use @ExceptionHandler

Spring Boot provides the @ExceptionHandler annotation to handle


exceptions thrown by a specific controller method. This annotation can be
used to provide customized error responses for specific exceptions.
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
Map<String, Object> body = new HashMap<>();
body.put("message", ex.getMessage());

return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);


}

In this example, we define an @ExceptionHandler method that handles the


ResourceNotFoundException thrown by the getUser() method in the
previous example. The method takes the ResourceNotFoundException as
an argument and returns a ResponseEntity that includes a message body
with the error message and a 404 HTTP status code.

Use @ControllerAdvice

Spring Boot provides the @ControllerAdvice annotation to handle


exceptions globally across all controllers. This annotation can be used to
provide a centralized error handling mechanism for an entire application.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleException(Exception ex) {
Map<String, Object> body = new HashMap<>();
body.put("message", "An error occurred");

return new ResponseEntity<>(body, HttpStatus.INTERNAL_SERVER_ERROR);


}
}

In this example, we define a global exception handler using the


@ControllerAdvice annotation. The handler method handles all exceptions
thrown by the application and returns a generic error message with a 500
HTTP status code.

Use loggers

Logging is an important part of exception handling. Use a logger to record


details of the exception, including the stack trace, timestamp, and any
relevant context information.
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
log.error("Resource not found", ex);

Map<String, Object> body = new HashMap<>();


body.put("message", ex.getMessage());

return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);


}

Use custom exceptions


Spring Boot allows you to define your own custom exceptions. Use these
exceptions to provide more specific error messages and to handle unique
exceptions that may not be covered by built-in Spring Boot exceptions.
public class InvalidRequestException extends RuntimeException {
public InvalidRequestException(String message) {
super(message);
}
}

@GetMapping("/users")
public ResponseEntity<List<User>> getUsers(@RequestParam("limit") int limit) {
if (limit < 1) {
throw new InvalidRequestException("Limit must be greater than zero");
}

List<User> users = userRepository.findAll();


return ResponseEntity.ok(users);
}

In this example, we define a custom exception called


InvalidRequestException. This exception is thrown when an invalid request
is made, such as when the limit parameter is less than 1. We use this
exception to provide a specific error message for this unique situation.

Use right level of abstraction

When handling exceptions, it is important to use the right level of


abstraction. In general, you should catch exceptions at the highest level
possible, and handle them at the lowest level possible. This will help to
minimize the impact of exceptions on the application’s performance and
stability.
public class UserService {
private final UserRepository userRepository;

public UserService(UserRepository userRepository) {


this.userRepository = userRepository;
}

public User getUserById(Long id) {


try {
return userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found with id " + id));
} catch (DataAccessException e) {
throw new DatabaseAccessException("Error accessing the database", e);
}
}
}

In this example, we catch the DataAccessException at the UserService


level, which is the highest level possible. We then throw a custom
exception called DatabaseAccessException, which is handled at a lower
level.

Use error codes

In addition to HTTP status codes, it can be useful to define your own error
codes to provide additional information about the cause of an exception.
These error codes can be included in the response body or in the logs,
making it easier to diagnose and fix issues.
public class ResourceNotFoundException extends RuntimeException {
private final int errorCode;

public ResourceNotFoundException(int errorCode, String message) {


super(message);
this.errorCode = errorCode;
}

public int getErrorCode() {


return errorCode;
}
}

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException(1001, "User not found with id " + id));
return ResponseEntity.ok(user);
}

In this example, we define a custom exception called


ResourceNotFoundException that includes an error code. We use this error
code to provide additional information about the cause of the exception.

Provide fallback mechanisms


In some cases, it may be appropriate to provide fallback mechanisms to
handle exceptions. For example, if a database connection cannot be
established, you could fall back to a cache or use a default value. This can
help to improve the overall resilience of the application.
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userRepository.findById(id)
.orElseGet(() -> {
log.warn("User not found in the database, falling back to cache");
return cacheService.getUser(id);
});

return ResponseEntity.ok(user);
}

In this example, if the user is not found in the database, we fall back to the
cache to retrieve the user. This helps to ensure that the application remains
functional even if the database is unavailable.

Use exception hierarchies

When defining custom exceptions, it can be useful to create an exception


hierarchy to provide a more organized and consistent approach to handling
exceptions. This can also help to reduce code duplication and improve
maintainability.
public class ApplicationException extends RuntimeException {
public ApplicationException(String message) {
super(message);
}

public ApplicationException(String message, Throwable cause) {


super(message, cause);
}
}

public class ResourceNotFoundException extends ApplicationException {


public ResourceNotFoundException(String message) {
super(message);
}
}

@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
try {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found with id " + id));
return ResponseEntity.ok(user);
} catch (ApplicationException e) {
log.error("An error occurred while retrieving the user", e);
throw e;
}
}

Use validation

Validating input data is an important part of preventing exceptions. Use


validation annotations to ensure that input data is valid before processing
it. This can help to prevent exceptions from occurring in the first place.
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody @Valid User user) {
User savedUser = userRepository.save(user);
return ResponseEntity.ok(savedUser);
}

In this example, we use the @Valid annotation to validate the user object
before saving it to the database. If the user object is not valid, an exception
is thrown before the user is saved.

Use response wrappers

Use response wrappers to provide a standardized response format for


exceptions. This can help to ensure that all exceptions are handled
consistently and that the response format is standardized across the
application.
public class ErrorResponse {
private final int errorCode;
private final String errorMessage;

public ErrorResponse(int errorCode, String errorMessage) {


this.errorCode = errorCode;
this.errorMessage = errorMessage;
}

public int getErrorCode() {


return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
}

@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException
ex) {
ErrorResponse errorResponse = new ErrorResponse(1001, ex.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
}

In this example, we define an ErrorResponse class that includes an error


code and error message. We then use this class to create a standardized
response format for exceptions.

Provide clear error messages

Clear error messages are essential for debugging and troubleshooting


issues. Ensure that error messages are clear and informative, providing
enough detail to help developers diagnose and fix issues.
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(ResourceNotFoundException ex) {
String message = "Resource not found: " + ex.getMessage();
log.error(message, ex);

Map<String, Object> body = new HashMap<>();


body.put("message", message);

return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);


}

In this example, we include a clear and informative error message that


explains the cause of the exception.

Provide context information

Context information can be useful for debugging issues. Include context


information such as user IDs, request IDs, and timestamps in error
messages and logs to provide additional information about the cause of
the exception.
@GetMapping("/users/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
try {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found with id " + id));
return ResponseEntity.ok(user);
} catch (ResourceNotFoundException e) {
String message = "Resource not found: " + e.getMessage() + " (id=" + id + ")";
log.error(message, e);

Map<String, Object> body = new HashMap<>();


body.put("message", message);

return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);


}
}

In this example, we include the ID of the user that was not found in the
error message, providing additional context information that can be useful
for debugging.

Consider using AOP

Aspect-oriented programming (AOP) can be a useful technique for handling


exceptions in Spring Boot applications. Use AOP to centralize exception
handling and reduce code duplication.
@Aspect
@Component
public class ExceptionHandlerAspect {
@Autowired
private HttpServletResponse response;

@ExceptionHandler(Exception.class)
public void handleException(Exception e) throws IOException {
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An error occurred");
}
}

In this example, we use AOP to intercept all exceptions thrown by the


application. We then use the HttpServletResponse object to send a
standardized error response to the client.
Conclusion
Exception handling is an important part of building reliable and robust
Spring Boot applications. By following these best practices and using the
provided code examples, you can ensure that your application handles
exceptions in a clear and consistent manner, making it easier to debug and
maintain.

Handle Exceptions in Spring Boot:


A Guide to Clean Code Principles
Exception handling is an essential part of writing robust, maintainable
software. In the Spring Boot ecosystem, it’s vital to handle exceptions
gracefully while maintaining clarity, readability, and simplicity. For
developers who embrace clean code principles, proper exception
handling is non-negotiable. Clean code is not just about writing code
that works, but writing code that is understandable, reusable, and easy
to maintain.

In this blog post, we’ll explore how to handle exceptions in Spring Boot,
focusing on clean code principles. You will also find best practices for
working with checked and unchecked exceptions, as well as dos and
don’ts with code snippets to ensure you’re writing the highest quality
exception handling code.

Why Is Exception Handling Important in Clean Code?


When writing clean code, we strive for clarity and simplicity. If
exceptions are handled poorly, they can clutter the code and make it
harder to understand. Poor exception handling often leads to:

Obscured logic: Catching broad exceptions or handling them


incorrectly can make it hard to discern the flow of the application.
Inconsistent responses: Having inconsistent ways of handling
exceptions can make it difficult for other developers to understand how
the app responds to errors.
Hard-to-maintain code: Without clear separation of concerns,
exception handling code becomes tangled with business logic, making it
more difficult to change and extend.

Clean exception handling allows you to make your app more


predictable, maintainable, and robust.

Best Practices for Exception Handling in Spring Boot

1. Use Specific Exceptions Over Generic Ones


A clean coder avoids using generic exceptions like Exception or
RuntimeException because they provide no meaningful information
about the error. Always use more specific exceptions. In Spring Boot,
you can create custom exceptions to represent specific error
conditions.

Do: Define Custom Exception Class


public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}

Don’t: Use Generic Exceptions


public class MyController {
@GetMapping("/resource")
public String getResource() {
try {
// Do something that could fail
} catch (Exception e) {
throw new RuntimeException("Something went wrong");
}
}
}

2. Handle Exceptions Using @ControllerAdvice


Spring Boot offers @ControllerAdvice as a centralized exception
handler, making your code more organized and maintainable. It allows
you to catch exceptions globally and return custom error responses
without cluttering business logic with error handling.

Do: Use @ControllerAdvice to Handle Exceptions Globally


@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return new ResponseEntity<>("Internal server error", HttpStatus.INTERNAL_SERVER_ERROR);
}
}

Don’t: Handle Exceptions Locally in Controllers


@RestController
public class MyController {

@GetMapping("/resource")
public String getResource() {
try {
// Code that could throw an exception
} catch (Exception e) {
return "Internal Server Error";
}
}
}

3. Use Checked Exceptions for Recoverable Errors and Unchecked for


Unrecoverable

Checked exceptions should be used for situations where the caller can
recover or take action.
Unchecked exceptions (typically extending RuntimeException) are
meant for situations where the error is beyond recovery, such as
programming mistakes or unexpected conditions.

Best Practices:
Checked exceptions should be used for errors like validation failures,
missing data, etc.
Unchecked exceptions should be used for situations where the
application cannot recover, such as null pointer exceptions or database
connection failures.

Best Practice for Checked Exceptions


public class InvalidDataException extends Exception {
public InvalidDataException(String message) {
super(message);
}
}

public class UserService {


public User findUserById(String id) throws InvalidDataException {
if (id == null || id.isEmpty()) {
throw new InvalidDataException("Invalid user ID");
}
// logic to find the user
return new User();
}
}

Best Practice for Unchecked Exceptions


public class DatabaseConnectionException extends RuntimeException {
public DatabaseConnectionException(String message) {
super(message);
}
}

public class DatabaseService {


public void connect() {
try {
// Attempt to connect to the database
} catch (Exception e) {
throw new DatabaseConnectionException("Could not connect to the database");
}
}
}

4. Provide Meaningful Error Messages


Error messages should be clear, informative, and user-friendly. Always
avoid exposing sensitive information (like stack traces) in production
environments.

Do: Use Custom Error Messages with HTTP Status Codes


@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorDetails> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorDetails errorDetails = new ErrorDetails("RESOURCE_NOT_FOUND", ex.getMessage());
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}

Don’t: Expose Stack Traces in Production


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

5. Use @ResponseStatus for HTTP Error Codes


Spring provides the @ResponseStatus annotation, which allows you to
automatically associate HTTP status codes with exceptions.

Do: Use @ResponseStatus to Simplify Exception Handling


@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Resource not found")
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}

Don’t: Use Multiple Custom ResponseEntity Wrappers


@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<String> handleResourceNotFound(ResourceNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}

Key Things to Focus on During Code Review


When conducting code reviews for exception handling, here are a few
key things to focus on:

1. Consistency in Exception Handling


Ensure that the exception handling strategy is consistent across the
codebase. Use the same structure for throwing, catching, and
responding to exceptions. Avoid mixing checked and unchecked
exceptions unnecessarily.

2. Meaningful Error Messages

Review error messages for clarity and ensure they provide useful
information. Avoid vague messages like “An error occurred.”

3. Proper HTTP Status Codes

Ensure that appropriate HTTP status codes are returned with the
response. For example:

404 for ResourceNotFoundException


400 for client errors like invalid input
500 for internal server errors

4. Global vs Local Exception Handling

Check if exceptions are handled globally using @ControllerAdvice or


locally in controllers. Global exception handling makes code more
maintainable.

5. Avoiding Catch-All Exceptions

Ensure that exceptions aren’t being caught and ignored without


providing meaningful recovery or response. Avoid catching Exception or
RuntimeException unless absolutely necessary.

Conclusion
Clean code is all about writing code that is simple, readable, and
maintainable. Exception handling, while often overlooked, plays a
significant role in achieving this goal. By following these best practices
and tips, you’ll be able to handle exceptions in Spring Boot in a way
that’s consistent with clean code principles.

Remember, the key to handling exceptions effectively is clarity. Make


your intentions clear, separate concerns, and ensure that your error-
handling code is predictable and easy to follow.
Exception Handling in Spring Boot
Exception handling in Spring Boot helps deal with errors and exceptions present
in APIs, delivering a robust enterprise application. This article covers various
ways in which exceptions can be handled and how to return meaningful error
responses to the client in a Spring Boot Project.

Key Approaches to Exception Handling in Spring Boot

Here are some key approaches to exception handling in Spring Boot:

Default exception handling by Spring Boot


Using @ExceptionHandler annotation
Using @ControllerAdvice for global exception handling

Spring Boot Exception Handling Simple Example Project


Let's do the initial setup to explore each approach in more depth.

Initial Setup

To create a simple Spring Boot project using Spring Initializer, please refer to this
article. Now let's develop a Spring Boot RESTful web service that performs
CRUD operations on a Customer Entity. We will be using a MYSQL database for
storing all necessary data.

Step 1: Creating a JPA Entity Class Customer

We will create a Customer class, which represents the entity for our database.
The class is annotated with @Entity.

// Creating a JPA Entity class Customer with

// three fields: id, name, and address

package com.customer.model;

import jakarta.persistence.Entity;

import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;

import jakarta.persistence.Id;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

@Entity

@Data

@AllArgsConstructor

@NoArgsConstructor

public class Customer {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String name;

private String address;

private String email;

The Customer class is annotated with @Entity annotation and defines getters,
setters, and constructors for the fields.

Step 2: Creating a CustomerRepository Interface

Next, we create the repository interface for CRUD operations on the Customer
entity. This interface extends JpaRepository, which provides built-in methods for
data access.

// Creating a repository interface extending JpaRepository

package com.customer.repository;
import com.customer.model.Customer;

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.stereotype.Repository;

@Repository

public interface CustomerRepository extends JpaRepository<Customer, Long> {

Optional<Customer> findByEmail(String email);

Note: The CustomerRepository interface is annotated with @Repository


annotation and extends the JpaRepository of Spring Data JPA .

Step 3: Creating Custom Exceptions

Now, we will create two custom exceptions:

CustomerAlreadyExistsException: This exception can be thrown when the user


tries to add a customer that already exists in the database.

// Creating a custom exception that can be thrown when a user tries to add a customer that already
exists

package com.customer.exception;

public class CustomerAlreadyExistsException extends RuntimeException {

private String message;

public CustomerAlreadyExistsException() {}

public CustomerAlreadyExistsException(String msg) {

super(msg);

this.message = msg;

NoSuchCustomerExistsException: This exception can be thrown when the user


tries to delete or update a customer record that doesn't exist in the database.
// Creating a custom exception that can be thrown when a user tries to update/delete a customer that
doesn't exist

package com.customer.exception;

public class NoSuchCustomerExistsException extends RuntimeException {

private String message;

public NoSuchCustomerExistsException() {}

public NoSuchCustomerExistsException(String msg) {

super(msg);

this.message = msg;

Note: Both Custom Exception classes extend RuntimeException.

Step 4: Creating the Service Layer

The CustomerService interface defines three different methods:

Customer getCustomer(Long id): To get a customer record by its id. This


method throws a NoSuchElementException exception when it doesn't find a
customer record with the given id.
String addCustomer(Customer customer): To add details of a new Customer
to the database. This method throws a CustomerAlreadyExistsException
exception when the user tries to add a customer that already exists.
String updateCustomer(Customer customer): To update details of Already
existing Customers. This method throws a NoSuchCustomerExistsException
exception when the user tries to update details of a customer that doesn't
exist in the database.

The Interface and service implementation class is as follows:

CustomerService Interface:

// Creating service interface


package com.customer.service;

import com.customer.model.Customer;

public interface CustomerService {

// Method to get customer by its Id

Customer getCustomer(Long id);

// Method to add a new Customer

// into the database

String addCustomer(Customer customer);

// Method to update details of a Customer

String updateCustomer(Customer customer);

}
CustomerServiceImpl Implementation:

// Implementing the service class

package com.customer.service;

import com.customer.exception.CustomerAlreadyExistsException;

import com.customer.exception.NoSuchCustomerExistsException;

import com.customer.model.Customer;

import com.customer.repository.CustomerRepository;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service

public class CustomerServiceImpl implements CustomerService {

@Autowired

private CustomerRepository customerRespository;


// Method to get customer by Id. Throws

// NoSuchElementException for invalid Id

public Customer getCustomer(Long id) {

return customerRespository.findById(id).orElseThrow(

() -> new NoSuchElementException("NO CUSTOMER PRESENT WITH ID = " + id));

// Simplifying the addCustomer and updateCustomer

// methods with Optional for better readability.

public String addCustomer(Customer customer) {

Optional<Customer> existingCustomer = customerRespository.findById(customer.getId());

if (!existingCustomer.isPresent()) {

customerRespository.save(customer);

return "Customer added successfully";

} else {

throw new CustomerAlreadyExistsException("Customer already exists!!");

public String updateCustomer(Customer customer) {

Optional<Customer> existingCustomer = customerRespository.findById(customer.getId());

if (!existingCustomer.isPresent()) {

throw new NoSuchCustomerExistsException("No Such Customer exists!!");

} else {

existingCustomer.get().setName(customer.getName());

existingCustomer.get().setAddress(customer.getAddress());

customerRespository.save(existingCustomer.get());
return "Record updated Successfully";

Step 5: Creating the CustomerController

The controller exposes RESTful endpoints for customer-related operations. The


methods in this class will throw exceptions, which we will handle using various
exception handling techniques.

// Creating Rest Controller CustomerController which

// defines various API's.

package com.customer.controller;

import com.customer.exception.CustomerAlreadyExistsException;

import com.customer.exception.ErrorResponse;

import com.customer.exception.NoSuchCustomerExistsException;

import com.customer.model.Customer;

import com.customer.service.CustomerService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.PutMapping;

import org.springframework.web.bind.annotation.RequestBody;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController

public class CustomerController {

@Autowired private CustomerService customerService;

// Get Customer by Id

@GetMapping("/getCustomer/{id}")

public Customer getCustomer(@PathVariable("id") Long id) {

return customerService.getCustomer(id);

// Add new Customer

@PostMapping("/addCustomer")

public String addcustomer(@RequestBody Customer customer) {

return customerService.addCustomer(customer);

// Update Customer details

@PutMapping("/updateCustomer")

public String updateCustomer(@RequestBody Customer customer) {

return customerService.updateCustomer(customer);

// Adding exception handlers for NoSuchCustomerExistsException

// and NoSuchElementException.

@ExceptionHandler(value = NoSuchCustomerExistsException.class)

@ResponseStatus(HttpStatus.NOT_FOUND)

public ErrorResponse handleNoSuchCustomerExistsException(NoSuchCustomerExistsException ex) {

return new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());


}

@ExceptionHandler(value = NoSuchElementException.class)

@ResponseStatus(HttpStatus.NOT_FOUND)

public ErrorResponse handleNoSuchElementException(NoSuchElementException ex) {

return new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());

Handling Exceptions in Spring Boot


Now let's go through the various ways in which we can handle the Exceptions
thrown in this project.

1. Default Exception Handling by Spring Boot

The getCustomer() method defined by CustomerController is used to get a


customer with a given Id. It throws a NoSuchElementException when it doesn't
find a Customer record with the given id. On Running the Spring Boot
Application and hitting the /getCustomer API with an Invalid Customer Id, we get
a NoSuchElementException completely handled by Spring Boot as follows:
Spring Boot provides a systematic error response to the user with information
such as timestamp, HTTP status code, error, message, and the path.

2. Using @ExceptionHandler Annotation

@ExceptionHandler annotation provided by Spring Boot can be used to


handle exceptions in particular Handler classes or Handler methods.
Any method annotated with this is automatically recognized by Spring
Configuration as an Exception Handler Method.
An Exception Handler method handles all exceptions and their subclasses
passed in the argument.
It can also be configured to return a specific error response to the user.

So let's create a custom ErrorResponse class so that the exception is conveyed


to the user in a clear and concise way as follows:

// Custom Error Response Class

package com.customer.exception;

import lombok.AllArgsConstructor;
import lombok.Data;

import lombok.NoArgsConstructor;

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ErrorResponse {

private int statusCode;

private String message;

public ErrorResponse(String message)

super();

this.message = message;

The addCustomer() method defined by CustomerController throws a


CustomerAlreadyExistsException when the user tries to add a Customer that
already exists in the database else it saves the customer details.

To handle this exception let's define a handler method


handleCustomerAlreadyExistsException() in the CustomerController. So, now
when addCustomer() throws a CustomerAlreadyExistsException, the handler
method gets invoked which returns a proper ErrorResponse to the user.

// Exception Handler method added in CustomerController to handle CustomerAlreadyExistsException

@ExceptionHandler(value = CustomerAlreadyExistsException.class)

@ResponseStatus(HttpStatus.CONFLICT)

public ErrorResponse handleCustomerAlreadyExistsException(CustomerAlreadyExistsException ex) {

return new ErrorResponse(HttpStatus.CONFLICT.value(), ex.getMessage());

}
Note : Spring Boot allows to annotate a method with @ResponseStatus to
return the required Http Status Code.

On Running the Spring Boot Application and hitting the /addCustomer API with
an existing Customer, CustomerAlreadyExistsException gets completely
handled by handler method as follows:

3. Using @ControllerAdvice for Global Exception Handling

In the previous approach, the @ExceptionHandler annotated method can only


handle exceptions thrown by that particular class. If we want to handle any
exception thrown throughout the application, we can define a global exception
handler class and annotate it with @ControllerAdvice. This annotation helps to
integrate multiple exception handlers into a single global unit.

The updateCustomer() method defined in CustomerController throws a


NoSuchCustomerExistsException if the user tries to update details of a
customer that doesn't already exist in the database. To handle this exception,
define a GlobalExceptionHandler class annotated with @ControllerAdvice.

// Class to handle exceptions globally

package com.customer.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice

public class GlobalExceptionHandler {

@ExceptionHandler(value = NoSuchCustomerExistsException.class)

@ResponseStatus(HttpStatus.NOT_FOUND)

public @ResponseBody ErrorResponse handleException(NoSuchCustomerExistsException ex) {

return new ErrorResponse(HttpStatus.NOT_FOUND.value(), ex.getMessage());

On running the Spring Boot application and hitting the /updateCustomer API
with invalid customer details, NoSuchCustomerExistsException gets thrown,
which is completely handled by the handler method defined in the
GlobalExceptionHandler class as follows:
Global Exception Handler
Hello, in this article, we will create an Exception Handling mechanism for
use in a Spring Boot project. Managing errors in a project is one of the most
important aspects since it is crucial for the server to work continuously
and correctly. Handling errors and exceptions that may occur in the
program flow in an appropriate manner is necessary. Otherwise, it can
affect the user’s experience.

As the project grows, the number of possible exceptions that can occur will
increase. Therefore, to reduce this problem and simplify error management
as much as possible, we need to build certain structures. In this article, we
will create a GlobalExceptionHandler mechanism to centralize the
handling of exceptions that may occur during a request and return an
appropriate response.

When an exception occurs, the Spring Boot application will redirect it to


the GlobalExceptionHandler. Depending on the specific type of exception,
the GlobalExceptionHandler can generate an appropriate error message
and return it to the client. This helps provide more meaningful error
messages to the clients. It can also be used to log detailed information
about exceptions.

We will continue explaining the topic using a project we have previously


written. We will integrate this mechanism into the mentioned project.
Below you can see the StudentController class:
package com.aedemirsen.springboot_mongodb.controller;

import com.aedemirsen.springboot_mongodb.dto.StudentDto;
import com.aedemirsen.springboot_mongodb.mapper.IStudentMapper;
import com.aedemirsen.springboot_mongodb.service.interfaces.IStudentService;
import lombok.AllArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@AllArgsConstructor
@RequestMapping("/api")
public class StudentController {
private final IStudentService studentService;
private final IStudentMapper studentMapper;
@GetMapping("/student/all")
public List<StudentDto> findAllStudents() {
return studentService.findAllStudents().stream()
.map(studentMapper::fromStudent).collect(Collectors.toList());
}
@GetMapping("/student/id/{id}")
public ResponseEntity<StudentDto> findStudentById(@PathVariable String id) {
var student = studentService.findStudentById(id);
if(student == null) {
return ResponseEntity.notFound().build();
}
var foundStudent = studentMapper.fromStudent(student);
return ResponseEntity.ok(foundStudent);
}
@PostMapping("/student")
public ResponseEntity<StudentDto> insertStudent(@RequestBody StudentDto studentDto) {
var studentToInsert = studentMapper.toStudent(studentDto);
var insertedStudent = studentService.insertStudent(studentToInsert);
var insertedStudentDto = studentMapper.fromStudent(insertedStudent);
return ResponseEntity.ok(insertedStudentDto);
}
}

Lets see StudentService class:


package com.aedemirsen.springboot_mongodb.service.impl;

import com.aedemirsen.springboot_mongodb.document.Student;
import com.aedemirsen.springboot_mongodb.repository.IStudentRepository;
import com.aedemirsen.springboot_mongodb.service.interfaces.IStudentService;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class StudentService implements IStudentService {
private final IStudentRepository studentRepository;
public StudentService(IStudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
@Override
public List<Student> findAllStudents() {
return studentRepository.findAll();
}
@Override
public Student findStudentById(String id) {
return studentRepository.findById(id).orElse(null);
}
@Override
public Student insertStudent(Student student) {
return studentRepository.insert(student);
}
}

As we can see from the above classes, there are some mechanisms missing
to catch and handle possible errors. Let’s create our own Exception classes
below and use them in the above classes to manage potential errors:

StudentNotFoundException
StudentAlreadyExistsException

Keeping it as simple as possible, let’s derive the previous classes from the
RunTimeException class:
package com.aedemirsen.springboot_mongodb.exception;

public class StudentNotFoundException extends RuntimeException{


public StudentNotFoundException(String message){
super(message);
}
}

package com.aedemirsen.springboot_mongodb.exception;

public class StudentAlreadyExistsException extends RuntimeException{


public StudentAlreadyExistsException(String message){
super(message);
}
}

Let’s update the findStudentById method in the Service class as follows:


@Override
public Student findStudentById(String id) {
return studentRepository.findById(id)
.orElseThrow(() -> new StudentNotFoundException("Student not found with the given ID."));
}

With this update, we have made it possible to throw a relevant exception


instead of returning null if no student is found in the searched id.

Now let’s update the insertStudent method as follows:


@Override
public Student insertStudent(Student student) {
var existingStudent = studentRepository.findById(student.getId()).orElse(null);
if(existingStudent != null){
throw new StudentAlreadyExistsException("Student already exists with the given ID.");
}
return studentRepository.insert(student);
}

With this update, we first check whether a record has been made in the
database with the ID of the student to be registered. If there is already a
student with this id, we throw StudentAlreadyExistsException. If not, the
database is registered.

After these changes, we need to make some changes in the


StudentController class.
@GetMapping("/student/id/{id}")
public ResponseEntity<StudentDto> findStudentById(@PathVariable String id) {
var student = studentService.findStudentById(id);
var foundStudent = studentMapper.fromStudent(student);
return ResponseEntity.ok(foundStudent);
}

Above, we no longer need to check if the variable student is null. Because if


it is null, an exception will be thrown in the service layer anyway. In this
way, we have also reduced the complexity and code clutter in the
controller class.

Yep, we’re done here. So where do these thrown exceptions go now? If we


run our service in this way, the thrown errors will disrupt the flow of the
program and return undesirable results to the client.

How do we solve this problem?

GlobalExceptionHandler

To solve this problem, we will create a class and with @ControllerAdvice


annotation we will make it listen for exceptions thrown from all controller
classes in our spring project.
package com.aedemirsen.springboot_mongodb.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler({StudentNotFoundException.class})
public ResponseEntity<Object> handleStudentNotFoundException(StudentNotFoundException exception) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(exception.getMessage());
}
@ExceptionHandler({StudentAlreadyExistsException.class})
public ResponseEntity<Object> handleStudentAlreadyExistsException(StudentAlreadyExistsException
exception) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(exception.getMessage());
}
@ExceptionHandler({RuntimeException.class})
public ResponseEntity<Object> handleRuntimeException(RuntimeException exception) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(exception.getMessage());
}
}

The above structure will provide this process with basic principles. In a
nutshell, this is where exceptions of the specified types are thrown, where
they will be caught, and error messages specified by the developer will be
delivered to the client.

Now let’s perform a test and query a student who is not in the database
with his/her ID:
Response:

In this article, I tried to explain the GlobalExceptionHandler mechanism


within the framework of basic principles. Integrating this structure into
your projects at the initial stage will facilitate your work in the future and
simplify exception-error management. It will also prevent unnecessary
codes and ambiguity in Controller and Service layers.

Complete Guide to Exception


Handling in Spring Boot
December 31, 2020

Spring Boot

Table Of Contents

Handling exceptions is an important part of building a robust application.


Spring Boot offers more than one way of doing it.

This article will explore these ways and will also provide some pointers on
when a given way might be preferable over another.

Example Code
This article is accompanied by a working code example on GitHub.

Introduction
Spring Boot provides us tools to handle exceptions beyond simple ‘try-
catch’ blocks. To use these tools, we apply a couple of annotations that
allow us to treat exception handling as a cross-cutting concern:

@ResponseStatus

@ExceptionHandler

@ControllerAdvice

Before jumping into these annotations we will first look at how Spring
handles exceptions thrown by our web controllers - our last line of defense
for catching an exception.

We will also look at some configurations provided by Spring Boot to


modify the default behavior.

We’ll identify the challenges we face while doing that, and then we will try
to overcome those using these annotations.

Spring Boot’s Default


Exception Handling
Mechanism
Let’s say we have a controller named ProductController whose
getProduct(...) method is throwing a NoSuchElementFoundException
runtime exception when a Product with a given id is not found:

@RestController

@RequestMapping("/product")
public class ProductController {

private final ProductService productService;

//constructor omitted for brevity...

@GetMapping("/{id}")

public Response getProduct(@PathVariable String id){

// this method throws a "NoSuchElementFoundException" exception

return productService.getProduct(id);

If we call the /product API with an invalid id the service will throw a
NoSuchElementFoundException runtime exception and we’ll get the
following response:

"timestamp": "2020-11-28T13:24:02.239+00:00",

"status": 500,

"error": "Internal Server Error",

"message": "",

"path": "/product/1"

We can see that besides a well-formed error response, the payload is not
giving us any useful information. Even the message field is empty, which
we might want to contain something like “Item with id 1 not found”.

Let’s start by fixing the error message issue.


Spring Boot provides some properties with which we can add the
exception message, exception class, or even a stack trace as part of the
response payload:

server:

error:

include-message: always

include-binding-errors: always

include-stacktrace: on_trace_param

include-exception: false

Using these Spring Boot server properties in our application.yml we can


alter the error response to some extent.

Now if we call the /product API again with an invalid id we’ll get the
following response:

"timestamp": "2020-11-29T09:42:12.287+00:00",

"status": 500,

"error": "Internal Server Error",

"message": "Item with id 1 not found",

"path": "/product/1"

Note that we’ve set the property include-stacktrace to on_trace_param


which means that only if we include the trace param in the URL (?
trace=true), we’ll get a stack trace in the response payload:

"timestamp": "2020-11-29T09:42:12.287+00:00",

"status": 500,
"error": "Internal Server Error",

"message": "Item with id 1 not found",

"trace":
"io.reflectoring.exception.exception.NoSuchElementFoundException: Item
with id 1 not found...",

"path": "/product/1"

We might want to keep the value of include-stacktrace flag to never, at


least in production, as it might reveal the internal workings of our
application.

Moving on! The status and error message - 500 - indicates that something
is wrong with our server code but actually it’s a client error because the
client provided an invalid id.

Our current status code doesn’t correctly reflect that. Unfortunately, this
is as far as we can go with the server.error configuration properties, so
we’ll have to look at the annotations that Spring Boot offers.

@ResponseStatus
As the name suggests, @ResponseStatus allows us to modify the HTTP
status of our response. It can be applied in the following places:

On the exception class itself

Along with the @ExceptionHandler annotation on methods

Along with the @ControllerAdvice annotation on classes

In this section, we’ll be looking at the first case only.

Let’s come back to the problem at hand which is that our error responses
are always giving us the HTTP status 500 instead of a more descriptive
status code.
To address this we can we annotate our Exception class with
@ResponseStatus and pass in the desired HTTP response status in its
value property:

@ResponseStatus(value = HttpStatus.NOT_FOUND)

public class NoSuchElementFoundException extends RuntimeException {

...

This change will result in a much better response if we call our controller
with an invalid ID:

"timestamp": "2020-11-29T09:42:12.287+00:00",

"status": 404,

"error": "Not Found",

"message": "Item with id 1 not found",

"path": "/product/1"

Another way to achieve the same is by extending the


ResponseStatusException class:

public class NoSuchElementFoundException extends


ResponseStatusException {

public NoSuchElementFoundException(String message){

super(HttpStatus.NOT_FOUND, message);

}
@Override

public HttpHeaders getResponseHeaders() {

// return response headers

This approach comes in handy when we want to manipulate the response


headers, too, because we can override the getResponseHeaders()
method.

@ResponseStatus, in combination with the server.error configuration


properties, allows us to manipulate almost all the fields in our Spring-
defined error response payload.

But what if want to manipulate the structure of the response payload as


well?

Let’s see how we can achieve that in the next section.

@ExceptionHandler
The @ExceptionHandler annotation gives us a lot of flexibility in terms of
handling exceptions. For starters, to use it, we simply need to create a
method either in the controller itself or in a @ControllerAdvice class and
annotate it with @ExceptionHandler:

@RestController

@RequestMapping("/product")

public class ProductController {

private final ProductService productService;


//constructor omitted for brevity...

@GetMapping("/{id}")

public Response getProduct(@PathVariable String id) {

return productService.getProduct(id);

@ExceptionHandler(NoSuchElementFoundException.class)

@ResponseStatus(HttpStatus.NOT_FOUND)

public ResponseEntity<String> handleNoSuchElementFoundException(

NoSuchElementFoundException exception

){

return ResponseEntity

.status(HttpStatus.NOT_FOUND)

.body(exception.getMessage());

The exception handler method takes in an exception or a list of exceptions


as an argument that we want to handle in the defined method. We
annotate the method with @ExceptionHandler and @ResponseStatus to
define the exception we want to handle and the status code we want to
return.

If we don’t wish to use these annotations, then simply defining the


exception as a parameter of the method will also do:
@ExceptionHandler

public ResponseEntity<String> handleNoSuchElementFoundException(

NoSuchElementFoundException exception)

Although it’s a good idea to mention the exception class in the annotation
even though we have mentioned it in the method signature already. It gives
better readability.

Also, the annotation @ResponseStatus(HttpStatus.NOT_FOUND) on the


handler method is not required as the HTTP status passed into the
ResponseEnity will take precedence, but we have kept it anyway for the
same readability reasons.

Apart from the exception parameter, we can also have


HttpServletRequest, WebRequest, or HttpSession types as parameters.

Similarly, the handler methods support a variety of return types such as


ResponseEntity, String, or even void.

Find more input and return types in @ExceptionHandler java


documentation.

With many different options available to us in form of both input


parameters and return types in our exception handling function, we are in
complete control of the error response.

Now, let’s finalize an error response payload for our APIs. In case of any
error, clients usually expect two things:

An error code that tells the client what kind of error it is. Error codes can
be used by clients in their code to drive some business logic based on it.
Usually, error codes are standard HTTP status codes, but I have also seen
APIs returning custom errors code likes E001.

An additional human-readable message which gives more information on


the error and even some hints on how to fix them or a link to API docs.

We will also add an optional stackTrace field which will help us with
debugging in the development environment.
Lastly, we also want to handle validation errors in the response. You can
find out more about bean validations in this article on Handling Validations
with Spring Boot.

Keeping these points in mind we will go with the following payload for the
error response:

@Getter

@Setter

@RequiredArgsConstructor

@JsonInclude(JsonInclude.Include.NON_NULL)

public class ErrorResponse {

private final int status;

private final String message;

private String stackTrace;

private List<ValidationError> errors;

@Getter

@Setter

@RequiredArgsConstructor

private static class ValidationError {

private final String field;

private final String message;

public void addValidationError(String field, String message){

if(Objects.isNull(errors)){
errors = new ArrayList<>();

errors.add(new ValidationError(field, message));

Now, let’s apply all these to our NoSuchElementFoundException handler


method.

@RestController

@RequestMapping("/product")

@AllArgsConstructor

public class ProductController {

public static final String TRACE = "trace";

@Value("${reflectoring.trace:false}")

private boolean printStackTrace;

private final ProductService productService;

@GetMapping("/{id}")

public Product getProduct(@PathVariable String id){

return productService.getProduct(id);

@PostMapping
public Product addProduct(@RequestBody @Valid ProductInput input){

return productService.addProduct(input);

@ExceptionHandler(NoSuchElementFoundException.class)

@ResponseStatus(HttpStatus.NOT_FOUND)

public ResponseEntity<ErrorResponse> handleItemNotFoundException(

NoSuchElementFoundException exception,

WebRequest request

){

log.error("Failed to find the requested element", exception);

return buildErrorResponse(exception, HttpStatus.NOT_FOUND, request);

@ExceptionHandler(MethodArgumentNotValidException.class)

@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)

public ResponseEntity<ErrorResponse>
handleMethodArgumentNotValid(

MethodArgumentNotValidException ex,

WebRequest request

){

ErrorResponse errorResponse = new ErrorResponse(

HttpStatus.UNPROCESSABLE_ENTITY.value(),

"Validation error. Check 'errors' field for details."


);

for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {

errorResponse.addValidationError(fieldError.getField(),

fieldError.getDefaultMessage());

return ResponseEntity.unprocessableEntity().body(errorResponse);

@ExceptionHandler(Exception.class)

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

public ResponseEntity<ErrorResponse> handleAllUncaughtException(

Exception exception,

WebRequest request){

log.error("Unknown error occurred", exception);

return buildErrorResponse(

exception,

"Unknown error occurred",

HttpStatus.INTERNAL_SERVER_ERROR,

request

);

}
private ResponseEntity<ErrorResponse> buildErrorResponse(

Exception exception,

HttpStatus httpStatus,

WebRequest request

){

return buildErrorResponse(

exception,

exception.getMessage(),

httpStatus,

request);

private ResponseEntity<ErrorResponse> buildErrorResponse(

Exception exception,

String message,

HttpStatus httpStatus,

WebRequest request

){

ErrorResponse errorResponse = new ErrorResponse(

httpStatus.value(),

exception.getMessage()

);
if(printStackTrace && isTraceOn(request)){

errorResponse.setStackTrace(ExceptionUtils.getStackTrace(exception));

return ResponseEntity.status(httpStatus).body(errorResponse);

private boolean isTraceOn(WebRequest request) {

String [] value = request.getParameterValues(TRACE);

return Objects.nonNull(value)

&& value.length > 0

&& value[0].contentEquals("true");

Couple of things to note here:

Providing a Stack Trace


Providing stack trace in the error response can save our developers and
QA engineers the trouble of crawling through the log files.

As we saw in Spring Boot’s Default Exception Handling Mechanism, Spring


already provides us with this functionality. But now, as we are handling
error responses ourselves, this also needs to be handled by us.

To achieve this, we have first introduced a server-side configuration


property named reflectoring.trace which, if set to true, To achieve this, we
have first introduced a server-side configuration property named
reflectoring.trace which, if set to true, will enable the stackTrace field in
the response. To actually get a stackTrace in an API response, our clients
must additionally pass the trace parameter with the value true:
curl --location --request GET 'http://localhost:8080/product/1?trace=true'

Now, as the behavior of stackTrace is controlled by our feature flag in our


properties file, we can remove it or set it to false when we deploy in
production environments.

Catch-All Exception Handler


Gotta catch em all:

try{

performSomeOperation();

} catch(OperationSpecificException ex){

//...

} catch(Exception catchAllExcetion){

//...

As a cautionary measure, we often surround our top-level method’s body


with a catch-all try-catch exception handler block, to avoid any unwanted
side effects or behavior. The handleAllUncaughtException() method in our
controller behaves similarly. It will catch all the exceptions for which we
don’t have a specific handler.

One thing I would like to note here is that even if we don’t have this catch-
all exception handler, Spring will handle it anyway. But we want the
response to be in our format rather than Spring’s, so we have to handle the
exception ourselves.

A catch-all handler method is also be a good place to log exceptions as


they might give insight into a possible bug. We can skip logging on field
validation exceptions such as MethodArgumentNotValidException as they
are raised because of syntactically invalid input, but we should always log
unknown exceptions in the catch-all handler.
Order of Exception Handlers
The order in which you mention the handler methods doesn’t matter.
Spring will first look for the most specific exception handler method.

If it fails to find it then it will look for a handler of the parent exception,
which in our case is RuntimeException, and if none is found, the
handleAllUncaughtException() method will finally handle the exception.

This should help us handle the exceptions in this particular controller, but
what if these same exceptions are being thrown by other controllers too?
How do we handle those? Do we create the same handlers in all controllers
or create a base class with common handlers and extend it in all
controllers?

Luckily, we don’t have to do any of that. Spring provides a very elegant


solution to this problem in form of “controller advice”.

Let’s study them.

@ControllerAdvice
Why is it called "Controller Advice"?
The term 'Advice' comes from Aspect-Oriented Programming (AOP) which
allows us to inject cross-cutting code (called "advice") around existing
methods. A controller advice allows us to intercept and modify the return
values of controller methods, in our case to handle exceptions.

Controller advice classes allow us to apply exception handlers to more


than one or all controllers in our application:

@ControllerAdvice

public class GlobalExceptionHandler extends


ResponseEntityExceptionHandler {
public static final String TRACE = "trace";

@Value("${reflectoring.trace:false}")

private boolean printStackTrace;

@Override

@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)

protected ResponseEntity<Object> handleMethodArgumentNotValid(

MethodArgumentNotValidException ex,

HttpHeaders headers,

HttpStatus status,

WebRequest request

){

//Body omitted as it's similar to the method of same name

// in ProductController example...

//.....

@ExceptionHandler(ItemNotFoundException.class)

@ResponseStatus(HttpStatus.NOT_FOUND)

public ResponseEntity<Object> handleItemNotFoundException(

ItemNotFoundException itemNotFoundException,

WebRequest request
){

//Body omitted as it's similar to the method of same name

// in ProductController example...

//.....

@ExceptionHandler(RuntimeException.class)

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

public ResponseEntity<Object> handleAllUncaughtException(

RuntimeException exception,

WebRequest request

){

//Body omitted as it's similar to the method of same name

// in ProductController example...

//.....

//....

@Override

public ResponseEntity<Object> handleExceptionInternal(

Exception ex,

Object body,
HttpHeaders headers,

HttpStatus status,

WebRequest request) {

return buildErrorResponse(ex,status,request);

The bodies of the handler functions and the other support code are
omitted as they’re almost identical to the code we saw in the
@ExceptionHandler section. Please find the full code in the Github Repo’s
GlobalExceptionHandler class.

A couple of things are new which we will talk about in a while. One major
difference here is that these handlers will handle exceptions thrown by all
the controllers in the application and not just ProductController.

If we want to selectively apply or limit the scope of the controller advice to


a particular controller, or a package, we can use the properties provided by
the annotation:

@ControllerAdvice("com.reflectoring.controller"): we can pass a package


name or list of package names in the annotation’s value or basePackages
parameter. With this, the controller advice will only handle exceptions of
this package’s controllers.

@ControllerAdvice(annotations = Advised.class): only controllers marked


with the @Advised annotation will be handled by the controller advice.

Find other parameters in the @ControllerAdvice annotation docs.

ResponseEntityExceptionHandler
ResponseEntityExceptionHandler is a convenient base class for controller
advice classes. It provides exception handlers for internal Spring
exceptions. If we don’t extend it, then all the exceptions will be redirected
to DefaultHandlerExceptionResolver which returns a ModelAndView
object. Since we are on the mission to shape our own error response, we
don’t want that.

As you can see we have overridden two of the


ResponseEntityExceptionHandler methods:

handleMethodArgumentNotValid(): in the @ExceptionHandler section we


have implemented a handler for it ourselves. In here we have only
overridden its behavior.

handleExceptionInternal(): all the handlers in the


ResponseEntityExceptionHandler use this function to build the
ResponseEntity similar to our buildErrorResponse(). If we don’t override
this then the clients will receive only the HTTP status in the response
header but since we want to include the HTTP status in our response
bodies as well, we have overridden the method.

Handling NoHandlerFoundException
Requires a Few Extra Steps
This exception occurs when you try to call an API that doesn't exist in the
system. Despite us implementing its handler via
ResponseEntityExceptionHandler class the exception is redirected to
DefaultHandlerExceptionResolver.

To redirect the exception to our advice we need to set a couple of


properties in the the properties file: spring.mvc.throw-exception-if-no-
handler-found=true and spring.web.resources.add-mappings=false

Some Points to Keep in Mind when


Using @ControllerAdvice
To keep things simple always have only one controller advice class in the
project. It’s good to have a single repository of all the exceptions in the
application. In case you create multiple controller advice, try to utilize the
basePackages or annotations properties to make it clear what controllers
it’s going to advise.

Spring can process controller advice classes in any order unless we have
annotated it with the @Order annotation. So, be mindful when you write a
catch-all handler if you have more than one controller advice. Especially
when you have not specified basePackages or annotations in the
annotation.

How Does Spring Process The


Exceptions?
Now that we have introduced the mechanisms available to us for handling
exceptions in Spring, let’s understand in brief how Spring handles it and
when one mechanism gets prioritized over the other.

Have a look through the following flow chart that traces the process of the
exception handling by Spring if we have not built our own exception
handler:
Conclusion
When an exception crosses the boundary of the controller, it’s destined to
reach the client, either in form of a JSON response or an HTML web page.

In this article, we saw how Spring Boot translates those exceptions into a
user-friendly output for our clients and also configurations and
annotations that allow us to further mold them into the shape we desire.

Basics of Exception handling


Exception handling is a critical part of building robust and user-friendly
applications. In Spring Boot, we can handle exceptions in various ways to
ensure our application remains stable and provides meaningful feedback
to users. This guide will cover different strategies for exception handling,
including custom exceptions, global exception handling, validation errors,
and best practices for production.

1. Basics of Exception Handling

Exceptions are events that disrupt the normal flow of a program. They can
be divided into:

Checked Exceptions: Exceptions that are checked at compile-time.


Unchecked Exceptions (Runtime Exceptions): Exceptions that occur during runtime.
Errors: Serious issues that applications should not handle, like OutOfMemoryError.

Best Practices for Production

1. Consistent Error Responses: Ensure your application returns


consistent and structured error responses. Use a standard error
response class.
2. Logging: Log exceptions for debugging and monitoring purposes.
Ensure sensitive information is not exposed in logs.
3. User-Friendly Messages: Provide user-friendly error messages. Avoid
exposing internal details or stack traces to users.
4. Security: Be cautious about the information included in error
responses to avoid exposing sensitive data.
5. Documentation: Document your exception handling strategy for your
team and future maintainers.

Summary

Exception handling in Spring Boot involves using annotations like


@ExceptionHandler, @ControllerAdvice, and @ResponseStatus to manage
errors effectively. By creating custom exceptions, handling validation
errors, and following best practices, you can build robust applications that
handle errors gracefully and provide meaningful feedback to users. Using
Java 17 features ensures your application leverages the latest
improvements in the Java ecosystem.

You might also like