How to Handle Exceptions in Spring Boot

Handle Exception
0 13

When you are developing an enterprise applications, handling exceptions and errors in APIs is very important. APIs should always send the proper response to the client whether its a HTTP Status OK or at any exception level a proper 4XX status should be sent. In this journal entry, we will be learning on how we can properly handle exceptions in Spring Boot applications.

Before we move forward and dig deep dive into learning lets first try to understand the following annotations.

Controller Advice

The annotation @ControllerAdvice is used to handle the exceptions globally. In other words, we can say that the class annotated with this annotation is a single point of contact and responsible to handle the exceptions of the application globally.

Exception Handler

The annotation @ExceptionHandler is used to handle the specific exception type and send the proper custom response to the client.

Now that we have a basic understanding of these two annotations, let’s understand the use of these two via example.

The following code snippet will help you create @ControllerAdvice class.

package in.developersjournal.demo.handleexception;

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

@ControllerAdvice
public class UserExceptionController {
}

Now lets define a class that extends the RuntimeException class.

package in.developersjournal.demo.handleexception.exception;

public class UserNotFoundException extends RuntimeException {
    private static final long serialVersionUID = 1L;
}

Define the @ExceptionHandler method to handle the exceptions as shown below in the code snippet. The method using this annotation should be written in the @ControllerAdvice class file.

@ExceptionHandler(value = UserNotFoundException.class)
public ResponseEntity<Object> exception(UserNotFoundException exception) {
}

Now, the below given code will throw the exception from the main REST API controller.

@RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
public ResponseEntity<Object> updateUser(@PathVariable String id) {
    throw new UserNotFoundException();
}

Now, that you have seen the patches of the code for handling exceptions in Spring Boot application. Given below for your reference is the complete code. For quick understanding we have used the PUT API request call for updating the User. While trying to update the User, if the requested user is not found, then we will be returning a proper error response message as “User not found”.

UserNotFoundException.java

package in.developersjournal.demo.handleexception.exception;

public class UserNotFoundException extends RuntimeException {
    private static final long serialVersionUID = 1L;
}

UserExceptionController.java

The Controller Advice class to handle the exception globally is given below. We can define any number of Exception Handler methods in this class file.

package in.developersjournal.demo.handleexception.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 UserExceptionController {
    @ExceptionHandler(value = UserNotFoundException.class)
    public ResponseEntity<Object> exception(UserNotFoundException exception) {
        return new ResponseEntity<>("User not found", HttpStatus.CONFLICT);
    }
}

UsersController.java

The code of Users REST API controller is given below. This controller can have any number of @RequestMapping which relates to the User object.

package in.developersjournal.demo.handleexception.controller;

import in.developersjournal.demo.handleexception.exception.UserNotFoundException;
import in.developersjournal.demo.handleexception.model.User;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
public class UsersController {

    private static Map<Integer, User> userRepo = new HashMap<>();
    static {
        User dev1 = new User();
        dev1.setId(1);
        dev1.setName("John");
        userRepo.put(dev1.getId(), dev1);

        User dev2 = new User();
        dev2.setId(2);
        dev2.setName("Maria");
        userRepo.put(dev2.getId(), dev2);
    }


    @RequestMapping(value = "/users/{id}", method = RequestMethod.PUT)
    public ResponseEntity<Object> updateUser(@PathVariable String id, @RequestBody User user) {
        int userId = Integer.parseInt(id);

        if(!userRepo.containsKey(userId))
            throw new UserNotFoundException();
        userRepo.remove(userId);
        user.setId(userId);
        user.setName(user.getName());
        userRepo.put(userId, user);
        return new ResponseEntity<>("User is updated successfully", HttpStatus.OK);
    }
}

User.java

Model class (POJO) of the User object.

package in.developersjournal.demo.handleexception.model;

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Now that all the class files have been generated, to run and use these files we need a starting point. Given below is the code for main Spring Boot application specifying the main method.

package in.developersjournal.demo.handleexception;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HandleexceptionApplication {
	public static void main(String[] args) {
		SpringApplication.run(HandleexceptionApplication.class, args);
	}
}

To run the Spring Boot application we can use either Maven or Gradle commands depending on which one you have chosen while creating the project.

For Maven, you can use the following command −

mvn clean install

After “BUILD SUCCESS”, you can find the JAR file under the target directory.

For Gradle, you can use the following command −

gradle clean build

After “BUILD SUCCESSFUL”, you can find the JAR file under the build/libs directory.

You can run the JAR file by using the following command −

java -jar <JAR-FILE-NAME>

This will start the application on the default port of Tomcat (8080).

Using the POSTMAN application hit the below URL with the request type set as PUT and passing the parameter “name” of the User object as a JSON body parameter. Screenshots for your reference:

URL: http://localhost:8080/users/3

Figure #1: PUT request call via POSTMAN
Figure #2: Response when User is not found
Figure #3: Response when User is found and successfully updated