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.
When a restful web service call is made to a Spring Boot MVC application, It shows error “Whitelabel Error Page – There was an unexpected error (type=Internal Server Error, status=500).” in the browser. The Internal Server Error is a popular error in spring boot application if a server side error occurs and is not in position to server the request.
A RuntimeException such as NullPointerException will be thrown in the controller class from the server side. The unexpected error causes Internal Server Error in the spring boot application.
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Fri Jan 31 18:12:15 IST 2020
There was an unexpected error (type=Internal Server Error, status=500).
No message available
2020-01-31 18:12:15.311 ERROR 39501 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
at com.yawintutor.TestController.postLogin(TestController.java:65) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_101]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_101]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_101]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_101]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:320) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:118) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:158) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.2.1.RELEASE.jar:5.2.1.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.3.RELEASE.jar:5.2.3.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:367) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:860) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1598) [tomcat-embed-core-9.0.30.jar:9.0.30]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.30.jar:9.0.30]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_101]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_101]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.30.jar:9.0.30]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_101]
Root Cause
The controller class is responsible for processing the request from the client. In controller class, the request is processed and the response is sent back to the user. The Json request is processed in RestController class in the restful web service and sends Json response to the so called application.
In some cases the controller class will not process the request because of some code bug or data issue. Instead, a Runtime Exception is thrown. The request to the calling application can not be servered. The internal server error created with http error code is 500, in this case.
How to reproduce this issue
Create a controller class in the Spring boot mvc application. Throw a Runtime Exception from the controller class method. If a call is made to the method, then the exception will be thrown. The example below will throw a NullPointerException from the postLogin method. This will create the Internal Server Error.
package com.yawintutor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping
public class TestController {
@RequestMapping(value = "/login", method = RequestMethod.POST)
public @ResponseBody String postLogin(String user) {
String userName = user.trim();
return userName;
}
}
Solution
Check the controller class method and find any logical error such as data issue, data missing, invalid data. Make sure that the controller method returns a correct response. If required, add a null check or add try catch block.
package com.yawintutor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping
public class TestController {
@RequestMapping(value = "/login", method = RequestMethod.POST)
public @ResponseBody String postLogin(String user) {
String userName = null;
if(user !=null) {
userName = user.trim();
}
return userName;
}
}
Время на прочтение
5 мин
Количество просмотров 33K
Часто при работе с микросервисами, построенными с помощью технологии 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. Надеюсь, что все было просто и понятно. Спасибо за внимание и пишите комментарии! Буду рад вашим замечаниям и уточнениям!
When building a Spring REST API, it is important to handle all possible errors that might occur. One of the most common errors that can happen is the Internal server error (500). In this guide, we will discuss how to handle this error and provide some code examples.
Understanding Internal server error (500)
The Internal server error (500) is a generic error message indicating that something has gone wrong on the server-side. This error can occur due to various reasons such as a programming error, database connection error, or any other underlying issue that is beyond the control of the client.
Handling Internal server error (500)
Handling Internal server error (500) is crucial as it can cause a poor user experience and affect the reputation of your application. Here are some ways to handle this error:
1. Use @ExceptionHandler
With Spring, we can use the @ExceptionHandler annotation to handle exceptions thrown by our application. We can create a controller advice class that will handle all the exceptions thrown by our application, including the Internal server error.
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
// Custom error message
String errorMessage = "Internal server error occurred. Please try again later.";
// Create a custom response object
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, errorMessage);
return new ResponseEntity<>(errorResponse, new HttpHeaders(), errorResponse.getStatus());
}
}
In the above example, we have created a RestExceptionHandler class that extends ResponseEntityExceptionHandler. We have defined a handleAllExceptions method that will catch all the exceptions thrown by our application, including the Internal server error. We have created a custom error message and a custom response object that will be returned to the client.
2. Use try-catch block
Another way to handle the Internal server error is to use a try-catch block in our controller method. We can catch the exception and return a custom error message and HTTP status code to the client.
@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable("id") Long id) {
try {
User user = userService.getUserById(id);
return new ResponseEntity<>(user, HttpStatus.OK);
} catch (Exception e) {
// Custom error message
String errorMessage = "Internal server error occurred. Please try again later.";
// Create a custom response object
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, errorMessage);
return new ResponseEntity<>(errorResponse, new HttpHeaders(), errorResponse.getStatus());
}
}
In the above example, we have created a getUserById method that retrieves a user by their ID. We have used a try-catch block to catch any exceptions that might occur. If an exception occurs, we return a custom error message and HTTP status code to the client.
Conclusion
Handling Internal server error (500) is an important aspect of building a Spring REST API. By using @ExceptionHandler or try-catch block, we can provide a better user experience and improve the reputation of our application.
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)