Java Full Stack Spring boot & React app: Backend REST API / 4 - Exception handling
Hello,
In this previous tutorial, we secured our backend RESTful API using Spring Security and JWT. Now we are going to handle errors/exceptions in the RESTful API.
Problem
Until now, all eventual error messages thrown in our RESTful API are not user-friendly. For example, when you try to log in with invalid credentials, you should have a similar message in Postman:
In the following lines, we'll use one of the smart solutions Spring provides to make the API throw understandable error messages.
Solution: Spring Global Exception Handling
1 - ExceptionResponse.java
This java class will be the custom error message class to return by the API when an error occurs. Create it in the com.codeurinfo.easytransapi.exception package.
package com.codeurinfo.easytransapi.exception;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import org.springframework.http.HttpStatus;
public class ExceptionResponse {
private HttpStatus status;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
private LocalDateTime timestamp;
private String message;
private String description;
public ExceptionResponse() {
this.timestamp = LocalDateTime.now();
}
public ExceptionResponse(HttpStatus status) {
this();
this.status = status;
this.message = "An error occured";
}
public ExceptionResponse(String message) {
this();
this.message = message;
}
public ExceptionResponse(HttpStatus status, String message, Throwable ex) {
this();
this.status = status;
this.message = message;
this.description = ex.getLocalizedMessage();
}
public LocalDateTime getTimestamp() {
return this.timestamp;
}
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
// getters and setters
}
2 - GlobalControllerExceptionHandler.java
Let's create this class in com.codeurinfo.easytransapi.exception package to handle all the errors in the RESTful API.
package com.codeurinfo.easytransapi.exception;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler({ DataIntegrityViolationException.class })
@ResponseStatus(value = HttpStatus.CONFLICT)
public ExceptionResponse handleDIVException(
DataIntegrityViolationException e
) {
return new ExceptionResponse(
HttpStatus.CONFLICT,
"Data integrity error.",
e
);
}
@ExceptionHandler({ Exception.class })
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ExceptionResponse handleException(Exception e) {
return new ExceptionResponse(
HttpStatus.INTERNAL_SERVER_ERROR,
e.getLocalizedMessage(),
e
);
}
@ExceptionHandler({ AccessDeniedException.class })
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public ExceptionResponse handleForbiddenException(AccessDeniedException e) {
return new ExceptionResponse(
HttpStatus.FORBIDDEN,
e.getLocalizedMessage(),
e
);
}
}
Spring @RestControllerAdvice annotation is used on a global class to make all its methods annotated with @ExceptionHandler be accessible across all the controllers in the RESTful API.
@ExceptionHandler annotation is used upon methods to handle specific Exceptions.
Thus, we use @ExceptionHandler({ DataIntegrityViolationException.class }) on the handleDIVException method to handle all DataIntegrityViolation Exceptions occurred in @RepositoryRestResource annotated repository interfaces.
@ResponseStatus is used to send the corresponding HTTP status code for the JSON response.
For unknown exceptions handling, we use @ExceptionHandler({ Exception.class }) on the handleException method and provide a HttpStatus.INTERNAL_SERVER_ERROR status code to return.
Similarly, we use @ExceptionHandler({ AccessDeniedException.class }) and provide HttpStatus.FORBIDDEN on the handleForbiddenException method to handle URL access denied exception.
3 - Tests
1 - Log in with bad credentials
- POST request : localhost:8000/api/auth/login
- Body:
{
"userName": "user",
"password": "user1"
}
- Response
2 - Try to access an unauthorized resource
Suppose you logged in with user/user good credentials, following our API logic, a user with username user is not allowed to make a routeByName search request
- POST request: localhost:8000/api/routes/search/routesByNa..
- Provide Authorization token value (token generated when log in was successful)
- Response
In the next post, we'll add new features to our EasyTrans app. Hope you learned something new. If so, don't forget to hit the Like button and subscribe to this blog to be up to date with new posts.