Exception Handling in Spring Boot REST API
2 CommentsLast Updated on May 2, 2021 by jt
When you develop a Spring Bool RESTful service, you as a programmer are responsible for handling exceptions in the service. For instance, by properly handling exceptions, you can stop the disruption of the normal flow of the application. In addition, proper exception handling ensures that the code doesn’t break when an exception occurs.
Another important thing is to ensure as a programmer is not to send any exceptions or error stacks to clients. Exception and error messages sent to clients should be short and meaningful.
In this post, I will explain how to gracefully handle exceptions in Spring Boot RESTful services.
Dependency
For this post, we will create a Sprinfg Boot RESTful service that performs CRUD operations on Blog entities. We will use embedded H2 as the database. The following code shows the dependencies of the application in the pom.xml
file.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.200</version> </dependency>
Exception Handling in Spring Example
In the context of our Blog
RESTful service, the application may encounter several types of exceptions. For example, the database may be down. Another scenario can be a user trying to save an already existing blog. Or a user trying to access a blog yet to be published.
You should handle such scenarios gracefully in the application.
As an example, for database failure, the application throws SQLException.
Instead of returning the exception stack trace to client, you should return a meaningful exception message.
The Entity Class
The code for the Blog
Entity class is this.
Blog.java
@Entity public class Blog { @Id private int blogId; private String blogTitle; private String blogCreator; private int yearOfPost; // No-Args and Parametrized Constructor //Getters and Setters }
It is a JPA Entity class annotated with the @Entity
annotation and corresponding getters and setters for the fields.
The Repository
This is the Blog Repository
Interface.
BlogRepository.java
package org.springframework.guru.repository; import org.springframework.guru.model.Blog; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @Repository public interface BlogRepository extends CrudRepository<Blog,Integer> { }
Here, the BlogRepository
extends the CrudRepository
of Spring Data JPA.
Custom Exception Classes
In our application, we will create custom exception classes. Such classes enable us to customize an exception according to the callers’ needs.
We will create two custom exception classes:
BlogAlreadyExistsException
: Is thrown when a user tries to add an already existing blog.BlogNotFoundException
: Is thrown when a user tries to access a blog that is not present.
The code of the BlogAlreadyExistsException
class is this.
BlogAlreadyExistsException.java
package org.springframework.guru.exception; public class BlogAlreadyExistsException extends RuntimeException { private String message; public BlogAlreadyExistsException(String message) { super(message); this.message = message; } public BlogAlreadyExistsException() { } }
The code for the BlogNotFoundException
class is this.
BlogNotFoundException.java
package org.springframework.guru.exception; public class BlogNotFoundException extends RuntimeException { private String message; public BlogNotFoundException(String message) { super(message); this.message = message; } public BlogNotFoundException() { } }
The Service
This is the BlogService
interface which has various methods to perform operations on Blog
entities.
BlogService.java
package org.springframework.guru.service; import org.springframework.guru.exception.BlogAlreadyExistsException; import org.springframework.guru.exception.BlogNotFoundException; import org.springframework.guru.model.Blog; import java.util.List; public interface BlogService { Blog saveBlog(Blog blog) throws BlogAlreadyExistsException; List getAllBlogs() throws BlogNotFoundException; Blog getBlogById(int id) throws BlogNotFoundException; }
In the preceding BlogService interface, the saveBlog()
method declares that it throws BlogAlreadyExistsException
. The two other methods, getAllBlogs()
and getBlogById()
declares that they throw BlogNotFoundException
.
The service implementation class for BlogService
is this.
BlogServiceImpl.java
@Service public class BlogServiceImpl implements BlogService { private BlogRepository blogRepository; @Autowired public BlogServiceImpl(BlogRepository blogRepository) { this.blogRepository = blogRepository; } @Override public Blog saveBlog(Blog blog) { if (blogRepository.existsById(blog.getBlogId())) { throw new BlogAlreadyExistsException(); } Blog savedBlog = blogRepository.save(blog); return savedBlog; } @Override public List getAllBlogs() { return (List) blogRepository.findAll(); } @Override public Blog getBlogById(int id) throws BlogNotFoundException { Blog blog; if (blogRepository.findById(id).isEmpty()) { throw new BlogNotFoundException(); } else { blog = blogRepository.findById(id).get(); } return blog; } }
The preceding BlogServiceImpl
class implements the methods declared in the BlogService
interface.
There are two paths in exception handling. One is the code handles the exception using a try-catch block. The other is to propagate back a custom exception to the caller. The preceding service class uses the latter approach.
Line 12 – Line 3 checks if the blog already exists in the database. If true the method throws a BlogAlreadyExistsException
. Else, the method saves the Blog
object.
Line 27 – Line 28 throws a BlogNotFoundException
if the Blog
with the specified Id is not present in the database.
The Controller
The code for the BlogController
is this.
BlogController.java
@RestController @RequestMapping("api/v1") public class BlogController { private BlogService blogService; @Autowired public BlogController(BlogService blogService) { this.blogService = blogService; } @PostMapping("/blog") public ResponseEntity saveBlog(@RequestBody Blog blog) throws BlogAlreadyExistsException { Blog savedBlog = blogService.saveBlog(blog); return new ResponseEntity<>(savedBlog, HttpStatus.CREATED); } @GetMapping("/blogs") public ResponseEntity<List> getAllBlogs() throws BlogNotFoundException { return new ResponseEntity<List>((List) blogService.getAllBlogs(), HttpStatus.OK); } @GetMapping("blog/{id}") public ResponseEntity getBlogById(@PathVariable("id") int id) throws BlogNotFoundException { return new ResponseEntity(blogService.getBlogById(id), HttpStatus.OK); }
The preceding controller class is not handling the custom exceptions. Instead, it throws the exceptions back to the caller – which in our scenario is a REST client. This is not what we want – directly sending back exceptions to clients.
Instead, we should handle the exception and send back a short and meaningful exception message to the client. We can use different approaches to achieve this.
Approach 1: Traditional try-catch Block
The first approach is to use Java try-catch block to handle the exception in the controller methods. The code to handle BlogNotFoundException
in the getBlogById()
method is this.
@GetMapping("blog/{id}") public ResponseEntity getBlogById(@PathVariable("id") int id) { try{ return new ResponseEntity(blogService.getBlogById(id), HttpStatus.OK); } catch(BlogNotFoundException blogNotFoundException ){ return new ResponseEntity(blogNotFoundException.getMessage(), HttpStatus.CONFLICT); } }
In the preceding code, the call to the BlogService.getBlogById()
method is wrapped in a try
block. If a method call to getBlogById()
throws BlogNotFoundException
, the catch
block handles the exception. In the catch
block, the ResponseEntity
object is used to send a custom error message with a status code as a response.
Approach 2: Spring @ExceptionHandler Annotation
Spring provides the @ExceptionHandler
annotation to handle exceptions in specific handler classes or handler methods.
Spring configuration will detect this annotation and register the method as an exception handler. The method will handle the exception and its subclasses passed to the annotation.
@ExceptionHandler(value = BlogAlreadyExistsException.class) public ResponseEntity handleBlogAlreadyExistsException(BlogAlreadyExistsException blogAlreadyExistsException) { return new ResponseEntity("Blog already exists", HttpStatus.CONFLICT); }
When any method in the controller throws the BlogAlreadyExistsException
exception, Spring invokes the handleBlogAlreadyExistsException()
method. This method returns a ResponseEntity
that wraps a custom error message and a status code.
When you run the application and send a POST request to add an existing blog, you will get this output.
Approach 3: Global Exception Handling with @ControllerAdvice
The @ExceptionHandler
annotation is only active for that particular class where it is declared. If you want a global exception handler you can use Spring AOP. A global exception handler provides a standard way of handling exceptions throughout the application. In addition, it considerably reduces the amount of code written for exception handling.
The Spring @ExceptionHandler
along with @ControllerAdvice
of Spring AOP enables a mechanism to handle exceptions globally.
The code for the GlobalExceptionHandler
class is this.
GlobalExceptionHandler.java
@ControllerAdvice public class GlobalExceptionHandler { @Value(value = "${data.exception.message1}") private String message1; @Value(value = "${data.exception.message2}") private String message2; @Value(value = "${data.exception.message3}") private String message3; @ExceptionHandler(value = BlogNotFoundException.class) public ResponseEntity blogNotFoundException(BlogNotFoundException blogNotFoundException) { return new ResponseEntity(message2, HttpStatus.NOT_FOUND); } @ExceptionHandler(value = Exception.class) public ResponseEntity<> databaseConnectionFailsException(Exception exception) { return new ResponseEntity<>(message3, HttpStatus.INTERNAL_SERVER_ERROR); } }
The @ControllerAdvice
annotation in Line 1 consolidates multiple @ExceptionHandlers
into a single, global exception handling component.
The @Value
annotation injects exception messages specified in the application.properties
file into the fields.
The application.properties
file is this.
data.exception.message1=BlogAlreadyExists data.exception.message2=BlogNotFound data.exception.message3=DataConnectivityisLost
Let’s send a GET Request tolocalhost:8080/api/v1/blog/2
to retrieve an unpublished blog. The response is shown in this Figure.
You can find the source code of this post on Github
For in-depth knowledge on the Spring Framework and Spring Boot, you can check my Udemy Best Seller Course Spring Framework 5: Beginner to Guru
Benaya
I tried method number 3, and the globalExceptionHandler class, it complains about two things –
1. the 3 messages in the application.properties file: ‘data.exception.message1’ is an unknown property.(PROP_UNKNOWN_PROPERTY)
2. in the return value for the first function – ResponseEntity is a raw type. References to generic type ResponseEntity should be parameterizedJava(16777788)
any idea why this happen? do you know a possible solution?
thanks for a great tutorial!
roli
its fien though but no logging there