Good day,
I am working on spring rest api and i would like to sure everything is working fine. I would like to log abnormal behaviors like nullPointerException or database connection error or any Exception that could raise and not handled or not assumed.
I would like to catch any unhandled exception and show a beautifull message to user instead of printing stack trace.
for this i found a solution on internet that is extend ResponseEntityExceptionHandler and override handleExceptionInternal method.
I also like to log 404 errors to see if someone trying to attack on my server.
I have also added this line in properties file : spring.mvc.throw-exception-if-no-handler-found=true
and here is the code for handleExceptionInternal
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
GenericResponse response = new GenericResponse();
response.setMessage("Internal error occured, " + ex.getStackTrace()[0]);
System.out.println("big exceptions");
return new ResponseEntity(response, headers, status);
}
My problem is when i am passing incorrect route like /abc this code is running fine, But when i throw null pointer exception from controllers method this method is not catching it.
thanks.
- All Implemented Interfaces:
Serializable
- Direct Known Subclasses:
HttpServerErrorException.BadGateway,HttpServerErrorException.GatewayTimeout,HttpServerErrorException.InternalServerError,HttpServerErrorException.NotImplemented,HttpServerErrorException.ServiceUnavailable
Exception thrown when an HTTP 5xx is received.
- Since:
- 3.0
- Author:
- Arjen Poutsma
- See Also:
-
DefaultResponseErrorHandler- Serialized Form
-
Nested Class Summary
Nested Classes
static final classstatic final classstatic final classstatic final classstatic final class -
Constructor Summary
Constructors
Constructor with a status code and status text, headers, content, and a
prepared message.Constructor with a status code only.
Constructor with a status code and status text.
Constructor with a status code and status text, and content.
Constructor with a status code and status text, headers, and content.
-
Method Summary
Variant of
create(String, HttpStatusCode, String, HttpHeaders, byte[], Charset)
with an optional prepared message.Create an
HttpServerErrorExceptionor an HTTP status specific subclass.Methods inherited from class java.lang.Throwable
addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
-
Constructor Details
-
HttpServerErrorException
public HttpServerErrorException(HttpStatusCode statusCode)
Constructor with a status code only.
-
HttpServerErrorException
Constructor with a status code and status text.
-
HttpServerErrorException
Constructor with a status code and status text, and content.
-
-
Method Details
Время на прочтение
5 мин
Количество просмотров 32K
Часто при работе с микросервисами, построенными с помощью технологии Spring Boot, можно видеть стандартный вывод ошибок подобный этому:
{
"timestamp": 1510417124782,
"status": 500,
"error": "Internal Server Error",
"exception": "com.netflix.hystrix.exception.HystrixRuntimeException",
"message": "ApplicationRepository#save(Application) failed and no fallback available.",
"path": "/application"
}
Такой вывод может быть излишним и ненужным клиентам вашего сервиса. Если вы хотите упростить жизнь сторонним сервисам в случае ошибки, то как раз об этом и пойдет речь в данном посте.
Начнем мы с построения небольшого сервиса с одним контроллером. Наш сервис будет принимать запрос на получение пользователя и в случае успеха отдавать данные по пользователю. В случае провала к нам возвращается ошибка. Начнем с простого и далее в статье будем усовершенствовать проект.
Итак, первое, что нам понадобится, это пользователь:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String firstName;
private String lastName;
}
Здесь я использовал библиотеку lombok. Аннотация Data подставляет геттеры и сеттеры в класс. Остальные аннотации добавляют пустой конструктор и конструктор с параметрами. Если вы хотите повторить данный пример у себя в IntelliJ Idea, то вам необходимо поставить галочку в пункте enable annotation processing, либо написать все руками.
Далее нам понадобится сервис (для краткости репозиторий создавать не будем):
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserService {
private static final Map<Integer, User> userStorage
= new HashMap<>();
static {
userStorage.put(1, new User(1, "Petr", "Petrov"));
userStorage.put(2, new User(2, "Ivan", "Ivanov"));
userStorage.put(3, new User(3, "Sergei", "Sidorov"));
}
public User get(int id) {
return userStorage.get(id);
}
}
Ну и, конечно, сам контроллер:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("user")
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("{id}")
public User get(@PathVariable(name = "id") int id) {
return userService.get(id);
}
}
Итак, у нас есть почти полноценный сервис с пользователями. Запускаем его и смотрим.
При запросе на URL localhost:8080/user/1 нам возвращается json в таком формате:
{
"id": 1,
"firstName": "Petr",
"lastName": "Petrov"
}
Все отлично. Но что будет, если сделать запрос на URL localhost:8080/user/4 (у нас всего 3 пользователя)? Правильный ответ: мы получим статус 200 и ничего в ответе. Ситуация не особо приятная. Ошибки нет, но и запрашиваемого объекта тоже нет.
Давайте улучшим наш сервис и добавим в него выбрасывание ошибки в случае неудачи. Для начала создадим исключение:
public class ThereIsNoSuchUserException extends RuntimeException { }
Теперь добавим пробрасывание ошибки в сервис:
public User get(int id) {
User user = userStorage.get(id);
if (user == null) {
throw new ThereIsNoSuchUserException();
}
return user;
}
Сделаем перезапуск сервиса и снова посмотрим, что будет при запросе несуществующего пользователя:
{
"timestamp": 1510479979781,
"status": 500,
"error": "Internal Server Error",
"exception": "org.faoxis.habrexception.ThereIsNoSuchUserException",
"message": "No message available",
"path": "/user/4"
}
Это уже лучше. Гораздо более информативно и код статуса не 200. Такую ситуацию клиент на своей стороне уже сможет успешно и легко обработать. Но, как говорится, есть один нюанс. Ошибки могут быть совершенно разными, и клиенту нашего сервиса придется ставить кучу условных операторов и исследовать, что у нас пошло не так и как это можно поправить. Получается немного грубо с нашей стороны.
Как раз для таких случаев и была придумана аннотация ResponseStatus. Подставим ее на место нашего исключения и на практике посмотрим, как это работает:
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "There is no such user")
public class ThereIsNoSuchUserException extends RuntimeException {
}
Повторим запрос и посмотрим результат:
{
"timestamp": 1510480307384,
"status": 404,
"error": "Not Found",
"exception": "org.faoxis.habrexception.ThereIsNoSuchUserException",
"message": "There is no such user",
"path": "/user/4"
}
Отлично! Код статуса и сообщение поменялись. Теперь клиент сможет определись по коду ответа причину ошибки и даже уточнить ее по полю message. Но все же есть проблема. Большинство полей клиенту могут быть просто не нужны. Например, код ответа как отдельное поле может быть излишним, поскольку мы его и так получаем с кодом ответа. С этим нужно что-то делать.
К счастью, со spring boot сделать последний шаг к нашему успешному оповещению об ошибке не так сложно.
Все, что для этого требуется, разобрать пару аннотаций и один класс:
- Аннотация ExceptionHandler. Используется для обработки собственных и каких-то специфичных исключений. Далее в примере будет понятно, что это значит. На всякий случай ссылка на документацию.
- Аннотация ControllerAdvice. Данная аннотация дает «совет» группе констроллеров по определенным событиям. В нашем случае — это обработка ошибок. По умолчанию применяется ко всем контроллерам, но в параметрах можно указать отпределенную группу. Подбронее тут.
- Класс ResponseEntityExceptionHandler. Данный класс занимается обработкой ошибок. У него куча методов, название которых построенно по принципу handle + название исключения. Если мы хотим обработать какое-то базовое исключение, то наследуемся от этого класса и переопределяем нужный метод.
Давайте теперь посмотрим, как все это обЪединить и построить наше уникальное и неповторимое сообщение об ошибке:
import lombok.AllArgsConstructor;
import lombok.Data;
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.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class AwesomeExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ThereIsNoSuchUserException.class)
protected ResponseEntity<AwesomeException> handleThereIsNoSuchUserException() {
return new ResponseEntity<>(new AwesomeException("There is no such user"), HttpStatus.NOT_FOUND);
}
@Data
@AllArgsConstructor
private static class AwesomeException {
private String message;
}
}
Сделаем все тот же запрос и увидим статус ответа 404 и наше сообщение с единственным полем:
{
"message": "There is no such user"
}
Аннотацию ResponseStatus над нашим исключением можно смело убирать.
В итоге у нас получилось приложение, в котором обработка ошибок настраивается максимально гибко и просто. Полный проект можно найти в репозитории github. Надеюсь, что все было просто и понятно. Спасибо за внимание и пишите комментарии! Буду рад вашим замечаниям и уточнениям!
I am new at Spring and trying to learn with tutorial. And I have a problem. I converted to xml based config to annotation based config. this is tutorial.
And I receive HTTP 500 error. This is full stacktrace.
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
root cause
java.lang.NullPointerException com.ulotrix.spring.service.PersonServiceImpl.listPersons(PersonServiceImpl.java:35)
com.ulotrix.spring.controller.PersonController.listPersons(PersonController.java:28)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Project Structure
AppConfig.java
@EnableWebMvc
@Configuration
@ComponentScan({ "com.ulotrix.spring" })
public class AppConfig extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Bean
public SessionFactory sessionFactory() {
LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(dataSource());
builder.scanPackages("com.ulotrix.spring.model");
builder.addProperties(getHibernationProperties());
return builder.buildSessionFactory();
}
private Properties getHibernationProperties() {
Properties prop = new Properties();
prop.put("hibernate.format_sql", "true");
prop.put("hibernate.show_sql", "true");
prop.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return prop;
}
@Bean(name = "dataSource")
public BasicDataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/deneme_db2");
ds.setUsername("root");
ds.setPassword("xxxx");
return ds;
}
@Bean
public HibernateTransactionManager txManger() {
return new HibernateTransactionManager(sessionFactory());
}
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
PersonService.java
public interface PersonService {
public void addPerson(Person p);
public void updatePerson(Person p);
public List<Person> listPersons();
public Person getPersonById(int id);
public void removePerson(int id);
}
PersonServiceImpl.java
@Service("personService")
public class PersonServiceImpl implements PersonService {
private PersonDAO personDAO;
public void setPersonDAO(PersonDAO personDAO) {
this.personDAO = personDAO;
}
@Override
@Transactional
public void addPerson(Person p) {
this.personDAO.addPerson(p);
}
@Override
@Transactional
public void updatePerson(Person p) {
this.personDAO.updatePerson(p);
}
@Override
@Transactional
public List<Person> listPersons() {
return this.personDAO.listPersons();
}
@Override
@Transactional
public Person getPersonById(int id) {
return this.personDAO.getPersonById(id);
}
@Override
@Transactional
public void removePerson(int id) {
this.personDAO.removePerson(id);
}
}
PersonController.java
@Controller
public class PersonController {
private PersonService personService;
@Autowired(required = true)
@Qualifier(value = "personService")
public void setPersonService(PersonService ps) {
this.personService = ps;
}
@RequestMapping(value = "/persons", method = RequestMethod.GET)
public String listPersons(Model model) {
model.addAttribute("person", new Person());
model.addAttribute("listPersons", this.personService.listPersons());
return "person";
}
//For add and update person both
@RequestMapping(value = "/person/add", method = RequestMethod.POST)
public String addPerson(@ModelAttribute("person") Person p) {
if(p.getId() == 0) {
this.personService.addPerson(p);
}else {
this.personService.updatePerson(p);
}
return "redirect:/persons";
}
@RequestMapping(value = "/remove/{id}")
public String removePerson(@PathVariable("id") int id) {
this.personService.removePerson(id);
return "redirect:/persons";
}
@RequestMapping(value = "/edit/{id}")
public String editPerson(@PathVariable("id") int id, Model model) {
model.addAttribute("person", this.personService.getPersonById(id));
model.addAttribute("listPersons", this.personService.listPersons());
return "person";
}
}
PersonDAO.java
public interface PersonDAO {
public void addPerson(Person p);
public void updatePerson(Person p);
public void removePerson(int id);
public List<Person> listPersons();
public Person getPersonById(int id);
}
PersonDAOImpl.java
@Component
public class PersonDAOImpl implements PersonDAO {
private static final Logger logger = LoggerFactory.getLogger(PersonDAOImpl.class);
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sf) {
this.sessionFactory = sf;
}
@Override
public void addPerson(Person p) {
Session session = this.sessionFactory.getCurrentSession();
session.persist(p);
logger.info("Person saved successfully, Person Details="+p);
}
@Override
public void updatePerson(Person p) {
Session session = this.sessionFactory.getCurrentSession();
session.update(p);
logger.info("Person updated successfully, Person Details="+p);
}
@SuppressWarnings("unchecked")
@Override
public List<Person> listPersons() {
Session session = this.sessionFactory.getCurrentSession();
List<Person> personList = session.createQuery("from Person").list();
for(Person p: personList) {
logger.info("Person List::"+p);
}
return personList;
}
@Override
public Person getPersonById(int id) {
Session session = this.sessionFactory.getCurrentSession();
Person p = (Person) session.load(Person.class, new Integer(id));
logger.info("Person loaded successfully, Person details="+p);
return p;
}
@Override
public void removePerson(int id) {
Session session = this.sessionFactory.getCurrentSession();
Person p = (Person) session.load(Person.class, new Integer(id));
if(null != p) {
session.delete(p);
}
logger.info("Person deleted successfully, person details="+p);
}
}
More Exception
exception
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)

This topic might become the most important for running the application without any interference. Also, it is very helpful in making other developers understand our code flow easily. Even finding out the origin of errors & exceptions becomes very irritating, if we have not handled them properly in our code. Sometimes we have to debug the whole flow of code to find it out & resolve accordingly. In this way Exception handling plays an important role in our software development engagement.
In this topic ‘How to handle Exceptions & Errors in Spring Boot?’ we will learn handling of Errors & Exceptions step by step covering all aspects. However, Spring Boot has made our job very easy by handling most common exception at the framework level internally. Even we can observe the fascination of Spring Boot while learning & implementing the exceptions. In my opinion, every developer should go through this topic and subsequently apply the concepts learned in the real project. Let’s discuss ‘How to handle Exceptions & Errors in Spring Boot?’ accordingly.
What Can You Expect from This Article as a Whole?
Once you complete going through this article, you will be able to answer :
1) What are the importance & benefits of implementing Exception Handling in a Spring Boot Application?
2) How does Spring Boot’s inbuilt Exception/Error handling work internally?
3) Further, How does predefined BasicErrorController work in different scenarios?
4) How to display a meaningful custom error/exception pages?
5) Additionally, What is an Http Response Status Code?
6) Also, What are some most commonly used status codes?
7) How to create a specific error page with respect to a Http Response Status Code?

9) Consequently, How to add custom error attributes in a custom Error Controller?
10) How does a predefined ErrorController handles exception raised by a REST call?
11) How can we customize error status code & error details attribute?
12) In the end, How can we customize all error attributes?
13) Use of annotations @ControllerAdvice, @RestControllerAdvice, @ExceptionHandler, @ResponseStatus, @ResponseBody etc.
14) Last but not the Least, ‘How to handle Exceptions & Errors in Spring Boot?’.
How does Spring Boot application handle errors/exceptions internally via inbuilt functionality?
To illustrate the inbuilt exception handling in a Spring Boot Project, we will consider the most commonly used flows which are Spring Boot MVC and Spring Boot REST. Both flows work based on a Controller, either it is a normal controller or a RestController. Also in both the cases, any request first interacts with DispatcherServlet. Further, any request interacts with the DispatcherServlet before sending the response to the client, whether it is returning a successful result or failure after raising any exception. If it returns any failure result, DispatcherServlet calls a predefined functional interface ErrorController. In this case BasicErrorController (an implementation class of interface ErrorController) handles the request. BasicErrorController has below two methods to handle such type of requests.
♦ errorHtml() – This method is called when the request comes from a browser (MVC call)
♦ error() – This method is called when the request comes from non-browser medium such as postman tool/client app/other app (REST call)
BasicErrorController shows error/exception because of below default path at @RequestMapping annotation.
♦ @RequestMapping(“${server.error.path:${error.path:/error}}”)
Finally a default whitelabel Error page with some status code appears on the screen in case of MVC call. Similarly, if it is REST call, you will receive an error message as a JSON response in the form of default error attributes like status, error, message, timestamp etc. Further If we want to display a meaningful custom page on the screen, then what will we do? Check it in the next section.
How to display meaningful Custom error/exception pages?
Instead of getting Whitelabel Error Page, we can just create ‘error.html’ page under src/main/resources/templates folder as below.
error.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org/">
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" >
</head>
<body>
<div class="container">
<h3>SOMTHING WENT WRONG! PLZ CONTACT MAINTENANCE TEAM</h3>
<table class="table table-bordered">
<tr>
<td>DATE</td>
<td th:text="${timestamp}"></td>
</tr> <tr>
<td>status</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>error</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>path</td>
<td th:text="${path}"></td>
</tr>
<tr>
<td>Trace</td>
<td th:text="${trace}"></td>
</tr>
</table>
</div>
</body>
</html>
On creating our custom error.html page we can get a meaningful error information separated by default error attributes.
error.html will handle all the errors with status 4xx & 5xx. For example 400, 401 , 403 , 404 , .. 500 , 501 etc. only error.html page will be executed.
What is Http Response Status code?
In a client-server request-response model Http Response Status code is a three digit code which is provided by a server to the client. This Status code indicates whether the requested action is successful or any error occurred during the processing. For example, when a search engine or website visitor makes a request to a web server, a three digit HTTP Response Status Code is returned. This code indicates what is about to happen. A response code of 200 means “OK” which indicates that the request has succeeded. Similarly, other status codes have some meaning.
Generally Http Status Codes come under below five categories.
Informational responses (100–199) Successful responses (200–299) Redirects (300–399) Client errors (400–499) : denotes Errors in Java Server errors (500–599) : denotes Exceptions in Java
The below status codes are defined by section 10 of RFC 2616. You can find an updated specification in RFC 7231.
♥ If you receive a response that is not in this list, it is a non-standard response, possibly custom to the server’s software. If error status code is of type 4xx or 5xx, aforementioned error.html will handle that request.
What are some commonly used Http Response Status codes?
OK : 200
CREATED : 201
NO_CONTENT : 204
MULTIPLE_CHOICES : 300
NOT_MODIFIED : 304
PERMANENT_REDIRECT : 308
BAD_REQUEST : 400
UNAUTHORIZED : 401
PAYMENT_REQUIRED : 402
FORBIDDEN : 403
NOT_FOUND : 404
METHOD_NOT_ALLOWED : 405
PROXY_AUTHENTICATION_REQUIRED : 407
REQUEST_TIMEOUT : 408
CONFLICT : 409
URI_TOO_LONG : 414
UNSUPPORTED_MEDIA_TYPE : 415
TOO_MANY_REQUESTS : 429
REQUEST_HEADER_FIELDS_TOO_LARGE : 431
INTERNAL_SERVER_ERROR : 500
NOT_IMPLEMENTED : 501
BAD_GATEWAY : 502
SERVICE_UNAVAILABLE : 503
GATEWAY_TIMEOUT : 504
HTTP_VERSION_NOT_SUPPORTED : 505
NETWORK_AUTHENTICATION_REQUIRED : 511
How to create a specific error page for a particular Http Response Status code?
Aforementioned error.html will handle all the request irrespective of a particular Http Response Status Code. It works as a generic error page. Suppose we want to display a separate error page in case of status code-404. Additionally, in case of status code-500 we want to display another distinct error page. Then how will we create these pages?
Step#1: Create a sub-folder ‘error’ under ‘src/main/resources/templates‘ folder.
Step#2: Create an html page with the same name as error status code in ‘StatusCode.html’ format. For example, for status code 404 create ‘404.html’ and place it under ‘error’ sub-folder.
Your folder structure should look like below screen.
If no page with specific status code found, it will display error.html by default. If the controller is throwing any exception, then by default Http Status code will be 500 which is ‘INTERNAL SERVER ERROR’. To change error code to any other status code, apply @ResponseStatus(code= HttpStatus.StatusCodeName) on top of your custom exception.
Even we can create a single page for a error status code 400-499 as ‘4xx.html’. Similarly, for 500-599 as ‘5xx.html’.
How to create a custom exception and map it to an error page with specific Http Response Status code?
To illustrate this requirement, let’s assume that we have an Invoice Processing application. Further in this application we have one controller with a getInvoice() method to serve the request as given below. We will take some random integers in if condition. Therefore this method will sometimes provide successful response. But sometimes it will also throw an exception. We will call it InvoiceNotFoundException and create it as a custom exception. Additionally we want a custom 404.html display this exception.
InvoiceController.java
package com.dev.spring.exception.controller;import java.util.Random;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import com.dev.spring.exception.custom.ImplementationNotFoundException;
import com.dev.spring.exception.custom.InvoiceNotFoundException;
@Controller
public class InvoiceController {
@GetMapping("/getInvoice")
public String getInvoice() {
if(new Random().nextInt(10)>5)
throw new InvoiceNotFoundException("Invoice Not Found!");
return "showInvoice";
}
}
Creating Custom Exception
Create a class InvoiceNotFoundException which will extend java.lang.RuntimeException. Then apply @ResponseStatus(code= HttpStatus.NOT_FOUND) on top of it as below.
InvoiceNotFoundException.java
package com.dev.spring.exception.custom;import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code= HttpStatus.NOT_FOUND)
public class InvoiceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public InvoiceNotFoundException() {
super();
}
public InvoiceNotFoundException(String message) {
super(message);
}
}
Here httpStatus.NOT_FOUND indicates error status code 404. Therefore we will create a custom 404.html page so that it can be called when InvoiceNotFoundException occurs.
Creating Custom Error Page (404.html)
Create 404.html as below and place it inside ‘src/main/resources/templates/error’ folder as aforementioned.
404.html
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org/">
<head>
<meta charset="ISO-8859-1">
<title>Insert title here</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" >
</head>
<body>
<div class="container">
<h4>SOME RESOURCE NOT FOUND! PLEASE CONTACT MAINTENANCE TEAM</h4>
<table class="table table-bordered">
<tr>
<td>DATE</td>
<td th:text="${timestamp}"></td>
</tr> <tr>
<td>STATUS</td>
<td th:text="${status}"></td>
</tr>
<tr>
<td>ERROR</td>
<td th:text="${error}"></td>
</tr>
<tr>
<td>PATH</td>
<td th:text="${path}"></td>
</tr>
</table>
</div>
</body>
</html>
Whenever exception occurs, 404.html will be called and it will display the error description as in below meaningful format.
How to write custom Error Controller in Spring Boot?
Instead of utilizing the ErrorController functionalities already provided by Spring Boot, we can implement our own Error Controller. Since already provided ErrorController.class is an interface, we can create an implementer class of it to make it possible. If we define implementation class of ErrorController(I) then Spring Boot selects our custom class on priority to show up the errors/exceptions. Further in this case error.hml, 4xx.html, 5xx.html or any other error page will not work at all. Spring Boot by default provides some error attributes like timestamp, status, message, path, exception, trace etc. We can use them in our custom controller via @Autowired. Now to read values of attributes for current request, use below lines of code.
ServletWebRequest swr = new ServletWebRequest(request);
Map<String, Object> errors= errorAttribues.getErrorAttributes(swr,true);
Custom Error Controller to Get output in HTML format
To illustrate a custom error controller which will display the error output in HTML format, below is the code.
CustomErrorController.java
package com.dev.spring.exception.controller;import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
@Controller
public class CustomErrorController implements ErrorController {
@Autowired
private ErrorAttributes errorAttributes;
@Override
public String getErrorPath() {
return "/error"; //mandatory path
}
@RequestMapping("/error") //mandatory mapping
public @ResponseBody String handleError(HttpServletRequest req) {
ServletWebRequest servletWebRequest = new ServletWebRequest(req);
@SuppressWarnings("deprecation")
Map<String, Object> errors = errorAttributes.getErrorAttributes(servletWebRequest, true);
StringBuilder builder = new StringBuilder();
builder.append("<html><body>");
builder.append("<h2>ERROR SUMMARY</h2>");
builder.append("<table border='1.5'>");
errors.forEach((key, value) -> {
builder.append("<tr>").append("<td>").append(key).append("</td>").append("<td>").append(value).append("</td>")
.append("</tr>");
});
builder.append("</table>");
builder.append("</body></html>");
return builder.toString();
}
}
Output
Below is the Output format.
Custom Error Controller to Get output in JSON format
To illustrate a custom error controller which will display the error output in JSON format, below is the code.
♥ Remember that Default output of @ResponseBody in Spring Boot is either String or JSON format. Any non-string return type in method will provide output in JSON format. Therefore, make sure that your controller method return type is a non-string (Lis/Set/Map)
Also apply @ResponseBody over method.
CustomErrorControllerWithJSONResponse.java
package com.dev.spring.exception.controller;import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.ServletWebRequest;
@Controller
public class CustomErrorControllerWithJSONResponse implements ErrorController {
@Autowired
private ErrorAttributes errorAttributes;
@RequestMapping("/error")
public @ResponseBody Map<String, Object> handleError(HttpServletRequest req)
{
ServletWebRequest webRequest = new ServletWebRequest(req);
@SuppressWarnings("deprecation")
Map<String, Object> errors = errorAttributes.getErrorAttributes(webRequest, true);
return errors;
}
@Override
public String getErrorPath() {
// TODO Auto-generated method stub
return null;
}
}
Output
Below is the Output format.
♥ To see JSON output use Firefox, JSON View extension with Google Chrome or even Postman tool.
How to add Custom Error Attributes in Custom Error Controller ?
Sometimes we may get a requirement to display some additional attribute with the error details. In that case we can just add the value of attribute in key-value format in the Map itself as shown below. Here we are only providing method code. Subsequently rest part of Custom Controller code will be as it is as in previous code example.
Adding Custom Error Attribute to Custom Error Controller
@RequestMapping("/error")
public @ResponseBody Map<String, Object> handleError(HttpServletRequest req)
{
ServletWebRequest webRequest = new ServletWebRequest(req);
@SuppressWarnings("deprecation")
Map<String, Object> errors = errorAttributes.getErrorAttributes(webRequest, true);
errors.put("Error Output Format", "JSON");
return errors;
}
Output
Below is the output.
How does predefined ErrorController handles exception raised by a REST call by default ?
To illustrate this, let’s create a Spring Boot Starter project which will implement a simple REST call step by step. Consider an Invoice Processing use case where we will have a RestController as ‘InvoiceRestController’ with a method as getInvoice(). In addition we will have a model class as ‘Invoice’ and a Custom exception class as ‘InvoiceNotFoundException’.
Step#1 : Create a Spring Boot Starter project in STS(Spring Tool Suite)
While creating Starter Project select ‘Spring Web’, ‘Lombok’ and ‘Spring Boot DevTools’ as starter project dependencies. Even If you don’t know how to create Spring Boot Starter Project, Kindly visit Internal Link. Also, if you want to know more about Lombok, then visit a separate article on ‘Lombok‘.
Step#2 : Create Model class as Invoice.java
Invoice.java
package com.dev.spring.exception.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Invoice {
private Integer id;
private String name;
private Double amount;
private String number;
}
Step#3 : Create Controller class as InvoiceRestController.java
Here we are intentionally throwing a InvoiceNotFoundException if value of invoice id is 24.
InvoiceRestController.java
package com.dev.spring.exception.controller;import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.dev.spring.exception.custom.InvoiceNotFoundException;
import com.dev.spring.exception.entity.Invoice;
@RestController
public class InvoiceRestController {
@GetMapping("/find/{id}")
public ResponseEntity<Invoice> getInvoice(@PathVariable Integer id){
if(id ==24) {
throw new InvoiceNotFoundException("Invoice with id: " +id +" does not exist!");
}
return ResponseEntity.ok(new Invoice(id,"INV001",2378.75,"CRTQW224"));
}
}
Step#4 : Create Custom Exception class as InvoiceNotFoundException.java
InvoiceNotFoundException.java
package com.dev.spring.exception.custom;public class InvoiceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public InvoiceNotFoundException() {
super();
}
public InvoiceNotFoundException(String message) {
super(message);
}
}
Testing the Exception
Enter URL ‘http://localhost:8080/find/24‘ using Postman tool, select ‘GET’ method and then click on ‘Send’ button. Consequently you will see output something like below. Here you can choose any browser of your choice to hit the URL.
{
"timestamp": "2020-12-29T19:32:29.056+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "com.dev.spring.exception.custom.InvoiceNotFoundException: Invoice with id: 24 does not exist!rntat com.dev.spring.exception.controller.InvoiceRestController.getInvoice(InvoiceRestController.java:18)rntat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)rntat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)rntat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)rntat java.base/java.lang.reflect.Method.invoke(Method.java:564)rntat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)rntat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)rntat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)rntat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)rntat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)rntat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)rntat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)rntat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)rntat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)rntat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)rntat javax.servlet.http.HttpServlet.service(HttpServlet.java:626)rntat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)rntat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)rntat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)rntat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)rntat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)rntat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)rntat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)rntat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)rntat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)rntat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)rntat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:888)rntat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597)rntat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)rntat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)rntat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)rntat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)rntat java.base/java.lang.Thread.run(Thread.java:832)rn",
"message": "Invoice with id: 24 does not exist!",
"path": "/find/24"
}
Conclusion
Although from the above output it is clear that by default Spring Boot’s predefined ErrorController provides Status code as ‘505’ and error as ‘Internal Server Error’ on any type of exception. Further we can customize the status code & error as per our wish.
How can we customize error status code & error details attribute?
Further to customize error status code & error we can just apply annotation @ResponseStatus(code= HttpStatus.StatusName) on top of custom controller itself as below.
InvoiceNotFoundException.java
package com.dev.spring.exception.custom;import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code= HttpStatus.NOT_FOUND)
public class InvoiceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public InvoiceNotFoundException() {
super();
}
public InvoiceNotFoundException(String message) {
super(message);
}
}
Customized Output
{
"timestamp": "2020-12-29T20:17:21.207+00:00",
"status": 404,
"error": "Not Found",
"trace": "com.dev.spring.exception.custom.InvoiceNotFoundException: Invoice with id: 24 does not exist!rntat com.dev.spring.exception.controller.InvoiceRestController.getInvoice(InvoiceRestController.java:18)rntat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)rntat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)rntat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)rntat java.base/java.lang.reflect.Method.invoke(Method.java:564)rntat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)rntat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)rntat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)rntat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)rntat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)rntat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)rntat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)rntat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)rntat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)rntat org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)rntat javax.servlet.http.HttpServlet.service(HttpServlet.java:626)rntat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)rntat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)rntat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)rntat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)rntat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)rntat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)rntat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)rntat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)rntat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)rntat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)rntat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)rntat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)rntat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)rntat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)rntat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:888)rntat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1597)rntat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)rntat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)rntat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)rntat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)rntat java.base/java.lang.Thread.run(Thread.java:832)rn",
"message": "Invoice with id: 24 does not exist!",
"path": "/find/24"
}
How can we customize all error attributes ?
To achieve this we will create one new Exception Handler class by using below annotations accordingly.
@ControllerAdvice : At Handler class level
@ExceptionHandler : At method level
@ResponseBody : At method level
♦ Although we can optionally use @RestControllerAdvice at class level. Further in that case we don’t need to apply @ResponseBody at method level. In order to get more details on these annotations, kindly visit Annotations on Spring Boot Errors & Exceptions.
Step#1 : Write a new model class as ‘ErrorType.java’
In order to customize all attributes, we will create a model class as ‘ErrorType.java’ and define all desired attributes as the fields. We will also need object of this class in our handler class.
ErrorType.java
package com.dev.spring.exception.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ErrorType {
private String message;
private String code;
private String error;
private String staus;
}
Step#2 : Write a new handler class as ‘InvoiceExceptionhandler.java’
We have to write a handler class and apply aforementioned annotations as below.
InvoiceExceptionHandler.java
package com.dev.spring.exception.handler;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.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.dev.spring.exception.custom.InvoiceNotFoundException;
import com.dev.spring.exception.entity.ErrorType;
//@RestControllerAdvice
@ControllerAdvice
public class InvoiceExceptionHandler {
@ExceptionHandler(InvoiceNotFoundException.class)
@ResponseBody
public ResponseEntity<ErrorType> handleInvoiceNotFoundException(
InvoiceNotFoundException ine){
return new ResponseEntity<ErrorType>(
new ErrorType(
ine.getMessage(),
"INVOICE_NOT_FOUND",
"DATA NOT FOUND FOR REQUESTED ID",
"406"),
HttpStatus.NOT_ACCEPTABLE);
}
}
Step#3 : Write a new custom exception class and Rest Controller
You can use InvoiceNotFoundException.java from previous section. Additionally, make sure that @ResponseStatus is not applied on top of it. Similarly, use InvoiceRestController from the previous section itself.
Testing the customized error attributes
Enter URL ‘http://localhost:8080/find/24‘ using Postman tool, select ‘GET’ method and then click on ‘Send’ button. Consequently you will see output something like below. Here you can choose any browser of your choice to hit the URL.
Summary
After going through all the theoretical & examples part of ‘How to handle Exceptions & Errors in Spring Boot?’, finally, we are able to implement Exceptions/Errors handling in a Spring Boot project. Of course, In this article we have thoroughly learned about the Spring Boot Exception handling. Similarly, we expect from you to further extend these examples and implement them in your project accordingly. You can also check other details on Exception Handling in Spring MVC from spring.io. In addition, If there is any update in future, we will also update the article accordingly. Moreover, Feel free to provide your comments in comments section.









