Handle Exceptions in Spring Boot RESTful Service

With this Spring Boot tutorial, I will share how to handle exceptions in your RESTful Web Service application build with Spring Boot.

Read the “Handling Exceptions in Project Reactor” tutorial if you use a project reactor. And to learn how to test your code for Exceptions, read the “Test for Exceptions with the JUnit” tutorial.

What If Exception Takes Place

The good news is that if an Exception takes place and your code does not handle it, Spring Boot will handle the Exception for you and return a well-formatted JSON or XML message to a calling client application.

{
    "timestamp": "2018-04-18T23:38:30.376+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "could not execute statement; SQL [n/a]; constraint [UK_6dotkott2kjsp8vw4d0m25fb7]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
    "path": "/users"
}

To produce this JSON document, I did not have to programmatically create the JSON document or a Java class and then convert it to JSON. I did not have to set the values of error, path or status code. All of these are handled for us by the framework.

What If You Throw a Custom Exception

Another good news is that if you throw your custom Exception, the calling client application will get the same JSON or XML payload structure. Of course, the error message and the timestamp values will be different, but the document’s structure will be the same.  So you do not need to create it and set its values programmatically.

Let’s create our own Runtime Exception and throw it.

package com.appsdeveloperblog.app.ws.exceptions;

public class UserServiceException extends RuntimeException{
    
    private static final long serialVersionUID = 5776681206288518465L;
    
    public UserServiceException(String message)
    {
    	  super(message);
    }
}

Now let’s throw this Exception

@Override
public UserDetails loadUserByUsername(String username) throws UserServiceException {

    UserEntity userEntity = userRepository.findUserByEmail(username);

    if (userEntity == null) {
        throw new UserServiceException("User " + username + " not found");
    }

    return new User(userEntity.getEmail(), userEntity.getEncryptedPassword(), new ArrayList<>());
}

the resulting JSON payload returned back with a Response will be:

{
    "timestamp": "2018-04-18T23:51:54.405+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "User sergey not found",
    "path": "/users/sergey"
}

How to Handle Any Exception

You can handle any exception that takes place anywhere in your RESTful Web Services app built with Spring Boot. To do that, we need to define a new Java class with a single method that will be responsible for catching all those Exceptions.

  1. Create a new class annotated with @ControllerAdvice
  2. Add a method annotated with @ExceptionHandler specifying which Exception class this method needs to handle.
package com.appsdeveloperblog.app.ws.exceptions;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class EntityExceptionHandler {

@ExceptionHandler(value = { Exception.class })
public ResponseEntity<Object> handleAnyException(Exception ex, WebRequest request) {
        return new ResponseEntity<>(
          ex.getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Return Custom Error Message Object

To make the returned error message be of a specific JSON or XML structure, you can create a Java bean class and use it as Response Entity.

import java.util.Date;
 
public class ErrorMessage {
  private Date timestamp;
  private String message;
  private String details;

  public ErrorMessage(){}

  public ErrorMessage(Date timestamp, String message, String details) {
    this.timestamp = timestamp;
    this.message = message;
    this.details = details;
  }
 
    public Date getTimestamp() {
        return timestamp;
    }

 
    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }
 
    public String getMessage() {
        return message;
    }

 
    public void setMessage(String message) {
        this.message = message;
    }

 
    public String getDetails() {
        return details;
    }

 
    public void setDetails(String details) {
        this.details = details;
    }

}

Now use this custom ErrorMessage class as Response Entity in your Exception Handler class.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;


@ControllerAdvice
public class MyExceptionHandler {
  @ExceptionHandler(Exception.class)
  public final ResponseEntity<ErrorMessage> handleAllExceptions(Exception ex, WebRequest request) {
    
    ErrorMessage errorObj = new ErrorMessage(new Date(), ex.getMessage(),
        request.getDescription(false));
    return new ResponseEntity<>(errorObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
  }
}

Handle Your Own Custom Runtime Exception

Earlier in this tutorial, we created our custom UserServiceException, extending RuntimeException.

package com.appsdeveloperblog.app.ws.exceptions;

public class UserServiceException extends RuntimeException{
    
    private static final long serialVersionUID = 5776681206288518465L;
    
    public UserServiceException(String message)
    {
    	  super(message);
    }
}

If this exception is thrown, we can handle it using the below exception handler class. And to make it handle our own custom exception, we will simply specify the name of our exception in the @ExceptionHandler annotation.

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;


@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
  @ExceptionHandler(UserServiceException.class)
  public final ResponseEntity<ErrorMessage> handleSpecificExceptions(Exception ex, WebRequest request) {

    ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage(),
        request.getDescription(false));
    return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
  }
}

Handle More Than One Exception 

If you need a method to handle more than one Exception, then you can specify those exceptions by separating them with a comma inside of the @ExceptionHandler annotation. For example:

@ExceptionHandler(UserNotFoundException.class, RecordExistsException.class)

Here is how you use it within a class:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
  @ExceptionHandler(UserNotFoundException.class, RecordExistsException.class)
  public final ResponseEntity<ErrorMessage> handleUserExceptions(Exception ex, WebRequest request) {

    ErrorMessage errorMessage = new ErrorMessage(new Date(), ex.getMessage(),
        request.getDescription(false));
    return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
  }

}

Handle Exception Inside of @RestController class

You can also handle an exception within the same class it was thrown in if you like. For example, you can have one of the methods in your @RestController class handle an exception message. This will work well, although I prefer not to have methods that handle an exception in my @RestController classes.

Here is an example of a very simple Root Resource class annotated with @RestController annotation. It intentionally throws an exception immediately to demonstrate how it can be handled within the same class.

Notice the @ExceptionHandler within the same class?

Also, the source code of an ErrorMessage class used as a Response Entity can be taken from this same tutorial. It is a few paragraphs above.

@RestController
@RequestMapping("users")
public class UserController {

    @Autowired
    UserService userService;
 

    @PostMapping
    public UserRest createUser(@RequestBody UserDetailsRequestModel requestUserDetails) {
        UserRest returnValue = new UserRest();

// THROW EXCEPTION NOW JUST FOR DEMONSTRATION PURPOSES
                
        if(true) throw new MissingRequiredFieldException("One of the required fields is missing");
/////
        UserDto userDto = new UserDto();
        BeanUtils.copyProperties(requestUserDetails, userDto);

        UserDto createdUser = userService.createUser(userDto);
        BeanUtils.copyProperties(createdUser, returnValue);

        returnValue.setHref("/users/" + createdUser.getUserId());

        return returnValue;
    }


    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingRequiredFieldException.class)
    public ErrorMessage handleBadRequest(HttpServletRequest req, Exception ex) {
       return new ErrorMessage(new Date(), ex.getMessage(), req.getRequestURI());
    }


}

Create @ControllerAdvice Video Tutorial

 

 

I hope this short tutorial on how to handle Exceptions in RESTful Web Services app built with Spring Boot was helpful to you.

To learn more about Spring Boot by following step-by-step video lessons, check out the video courses below; hopefully, one of them will help you take your skills to a new level.


Leave a Reply

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