Exception Handling in Spring Boot REST API

Exception Handling in Spring Boot REST API

1 Comment

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 @ExceptionHandlerannotation 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.

@Exception Handler output for Blog already exists exception

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.

@Controller Advice Output

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

Spring Framework 5

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like

    One comment

    1. July 1, 2021 at 1:35 pm

      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!

      Reply

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.