Enum Constant Details
-
CONTINUE
100 Continue
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.2.1
-
SWITCHING_PROTOCOLS
public static final HttpStatus SWITCHING_PROTOCOLS
101 Switching Protocols
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.2.2
-
PROCESSING
public static final HttpStatus PROCESSING
102 Processing
.- See Also:
-
- WebDAV
-
CHECKPOINT
public static final HttpStatus CHECKPOINT
103 Checkpoint
.- See Also:
-
- A proposal for supporting
resumable POST/PUT HTTP requests in HTTP/1.0
- A proposal for supporting
-
OK
200 OK
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.3.1
-
CREATED
201 Created
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.3.2
-
ACCEPTED
202 Accepted
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.3.3
-
NON_AUTHORITATIVE_INFORMATION
public static final HttpStatus NON_AUTHORITATIVE_INFORMATION
203 Non-Authoritative Information
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.3.4
-
NO_CONTENT
public static final HttpStatus NO_CONTENT
204 No Content
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.3.5
-
RESET_CONTENT
public static final HttpStatus RESET_CONTENT
205 Reset Content
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.3.6
-
PARTIAL_CONTENT
public static final HttpStatus PARTIAL_CONTENT
206 Partial Content
.- See Also:
-
- HTTP/1.1: Range Requests, section 4.1
-
MULTI_STATUS
public static final HttpStatus MULTI_STATUS
207 Multi-Status
.- See Also:
-
- WebDAV
-
ALREADY_REPORTED
public static final HttpStatus ALREADY_REPORTED
208 Already Reported
.- See Also:
-
- WebDAV Binding Extensions
-
IM_USED
226 IM Used
.- See Also:
-
- Delta encoding in HTTP
-
MULTIPLE_CHOICES
public static final HttpStatus MULTIPLE_CHOICES
300 Multiple Choices
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.4.1
-
MOVED_PERMANENTLY
public static final HttpStatus MOVED_PERMANENTLY
301 Moved Permanently
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.4.2
-
FOUND
302 Found
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.4.3
-
MOVED_TEMPORARILY
302 Moved Temporarily
.- See Also:
-
- HTTP/1.0, section 9.3
-
SEE_OTHER
303 See Other
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.4.4
-
NOT_MODIFIED
public static final HttpStatus NOT_MODIFIED
304 Not Modified
.- See Also:
-
- HTTP/1.1: Conditional Requests, section 4.1
-
USE_PROXY
305 Use Proxy
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.4.5
-
TEMPORARY_REDIRECT
public static final HttpStatus TEMPORARY_REDIRECT
307 Temporary Redirect
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.4.7
-
PERMANENT_REDIRECT
public static final HttpStatus PERMANENT_REDIRECT
308 Permanent Redirect
.- See Also:
-
- RFC 7238
-
BAD_REQUEST
public static final HttpStatus BAD_REQUEST
400 Bad Request
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.1
-
UNAUTHORIZED
public static final HttpStatus UNAUTHORIZED
401 Unauthorized
.- See Also:
-
- HTTP/1.1: Authentication, section 3.1
-
PAYMENT_REQUIRED
public static final HttpStatus PAYMENT_REQUIRED
402 Payment Required
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.2
-
FORBIDDEN
403 Forbidden
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.3
-
NOT_FOUND
404 Not Found
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.4
-
METHOD_NOT_ALLOWED
public static final HttpStatus METHOD_NOT_ALLOWED
405 Method Not Allowed
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.5
-
NOT_ACCEPTABLE
public static final HttpStatus NOT_ACCEPTABLE
406 Not Acceptable
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.6
-
PROXY_AUTHENTICATION_REQUIRED
public static final HttpStatus PROXY_AUTHENTICATION_REQUIRED
407 Proxy Authentication Required
.- See Also:
-
- HTTP/1.1: Authentication, section 3.2
-
REQUEST_TIMEOUT
public static final HttpStatus REQUEST_TIMEOUT
408 Request Timeout
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.7
-
CONFLICT
409 Conflict
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.8
-
GONE
410 Gone
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.9
-
LENGTH_REQUIRED
public static final HttpStatus LENGTH_REQUIRED
411 Length Required
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.10
-
PRECONDITION_FAILED
public static final HttpStatus PRECONDITION_FAILED
412 Precondition failed
.- See Also:
-
- HTTP/1.1: Conditional Requests, section 4.2
-
PAYLOAD_TOO_LARGE
public static final HttpStatus PAYLOAD_TOO_LARGE
413 Payload Too Large
.- Since:
- 4.1
- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.11
-
REQUEST_ENTITY_TOO_LARGE
413 Request Entity Too Large
.- See Also:
-
- HTTP/1.1, section 10.4.14
-
URI_TOO_LONG
public static final HttpStatus URI_TOO_LONG
414 URI Too Long
.- Since:
- 4.1
- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.12
-
REQUEST_URI_TOO_LONG
414 Request-URI Too Long
.- See Also:
-
- HTTP/1.1, section 10.4.15
-
UNSUPPORTED_MEDIA_TYPE
public static final HttpStatus UNSUPPORTED_MEDIA_TYPE
415 Unsupported Media Type
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.13
-
REQUESTED_RANGE_NOT_SATISFIABLE
public static final HttpStatus REQUESTED_RANGE_NOT_SATISFIABLE
416 Requested Range Not Satisfiable
.- See Also:
-
- HTTP/1.1: Range Requests, section 4.4
-
EXPECTATION_FAILED
public static final HttpStatus EXPECTATION_FAILED
417 Expectation Failed
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.5.14
-
I_AM_A_TEAPOT
public static final HttpStatus I_AM_A_TEAPOT
418 I'm a teapot
.- See Also:
-
- HTCPCP/1.0
-
INSUFFICIENT_SPACE_ON_RESOURCE
-
METHOD_FAILURE
-
DESTINATION_LOCKED
-
UNPROCESSABLE_ENTITY
public static final HttpStatus UNPROCESSABLE_ENTITY
422 Unprocessable Entity
.- See Also:
-
- WebDAV
-
LOCKED
423 Locked
.- See Also:
-
- WebDAV
-
FAILED_DEPENDENCY
public static final HttpStatus FAILED_DEPENDENCY
424 Failed Dependency
.- See Also:
-
- WebDAV
-
TOO_EARLY
425 Too Early
.- Since:
- 5.2
- See Also:
-
- RFC 8470
-
UPGRADE_REQUIRED
public static final HttpStatus UPGRADE_REQUIRED
426 Upgrade Required
.- See Also:
-
- Upgrading to TLS Within HTTP/1.1
-
PRECONDITION_REQUIRED
public static final HttpStatus PRECONDITION_REQUIRED
428 Precondition Required
.- See Also:
-
- Additional HTTP Status Codes
-
TOO_MANY_REQUESTS
public static final HttpStatus TOO_MANY_REQUESTS
429 Too Many Requests
.- See Also:
-
- Additional HTTP Status Codes
-
UNAVAILABLE_FOR_LEGAL_REASONS
public static final HttpStatus UNAVAILABLE_FOR_LEGAL_REASONS
451 Unavailable For Legal Reasons
.- Since:
- 4.3
- See Also:
-
- An HTTP Status Code to Report Legal Obstacles
-
INTERNAL_SERVER_ERROR
public static final HttpStatus INTERNAL_SERVER_ERROR
500 Internal Server Error
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.6.1
-
NOT_IMPLEMENTED
public static final HttpStatus NOT_IMPLEMENTED
501 Not Implemented
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.6.2
-
BAD_GATEWAY
public static final HttpStatus BAD_GATEWAY
502 Bad Gateway
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.6.3
-
SERVICE_UNAVAILABLE
public static final HttpStatus SERVICE_UNAVAILABLE
503 Service Unavailable
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.6.4
-
GATEWAY_TIMEOUT
public static final HttpStatus GATEWAY_TIMEOUT
504 Gateway Timeout
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.6.5
-
HTTP_VERSION_NOT_SUPPORTED
public static final HttpStatus HTTP_VERSION_NOT_SUPPORTED
505 HTTP Version Not Supported
.- See Also:
-
- HTTP/1.1: Semantics and Content, section 6.6.6
-
VARIANT_ALSO_NEGOTIATES
public static final HttpStatus VARIANT_ALSO_NEGOTIATES
506 Variant Also Negotiates
- See Also:
-
- Transparent Content Negotiation
-
INSUFFICIENT_STORAGE
public static final HttpStatus INSUFFICIENT_STORAGE
507 Insufficient Storage
- See Also:
-
- WebDAV
-
LOOP_DETECTED
public static final HttpStatus LOOP_DETECTED
508 Loop Detected
- See Also:
-
- WebDAV Binding Extensions
-
BANDWIDTH_LIMIT_EXCEEDED
public static final HttpStatus BANDWIDTH_LIMIT_EXCEEDED
509 Bandwidth Limit Exceeded
-
NOT_EXTENDED
public static final HttpStatus NOT_EXTENDED
510 Not Extended
- See Also:
-
- HTTP Extension Framework
-
NETWORK_AUTHENTICATION_REQUIRED
public static final HttpStatus NETWORK_AUTHENTICATION_REQUIRED
511 Network Authentication Required
.- See Also:
-
- Additional HTTP Status Codes
Method Details
-
values
Returns an array containing the constants of this enum class, in
the order they are declared.- Returns:
- an array containing the constants of this enum class, in the order they are declared
-
valueOf
Returns the enum constant of this class with the specified name.
The string must match exactly an identifier used to declare an
enum constant in this class. (Extraneous whitespace characters are
not permitted.)- Parameters:
name
— the name of the enum constant to be returned.- Returns:
- the enum constant with the specified name
- Throws:
IllegalArgumentException
— if this enum class has no constant with the specified nameNullPointerException
— if the argument is null
-
value
public int value()
Return the integer value of this status code.
- Specified by:
value
in interfaceHttpStatusCode
-
series
Return the HTTP status series of this status code.
- See Also:
-
HttpStatus.Series
-
getReasonPhrase
public String getReasonPhrase()
Return the reason phrase of this status code.
-
is1xxInformational
public boolean is1xxInformational()
Whether this status code is in the Informational class (
1xx
).- Specified by:
is1xxInformational
in interfaceHttpStatusCode
- See Also:
-
- RFC 2616
-
is2xxSuccessful
public boolean is2xxSuccessful()
Whether this status code is in the Successful class (
2xx
).- Specified by:
is2xxSuccessful
in interfaceHttpStatusCode
- See Also:
-
- RFC 2616
-
is3xxRedirection
public boolean is3xxRedirection()
Whether this status code is in the Redirection class (
3xx
).- Specified by:
is3xxRedirection
in interfaceHttpStatusCode
- See Also:
-
- RFC 2616
-
is4xxClientError
public boolean is4xxClientError()
Whether this status code is in the Client Error class (
4xx
).- Specified by:
is4xxClientError
in interfaceHttpStatusCode
- See Also:
-
- RFC 2616
-
is5xxServerError
public boolean is5xxServerError()
Whether this status code is in the Server Error class (
5xx
).- Specified by:
is5xxServerError
in interfaceHttpStatusCode
- See Also:
-
- RFC 2616
-
isError
public boolean isError()
Whether this status code is in the Client or Server Error class
- Specified by:
isError
in interfaceHttpStatusCode
- See Also:
-
- RFC 2616
- RFC 2616
(4xx
or5xx
). HttpStatusCode.is4xxClientError()
HttpStatusCode.is5xxServerError()
-
toString
Return a string representation of this status code.
- Overrides:
toString
in classEnum<HttpStatus>
-
valueOf
public static HttpStatus valueOf(int statusCode)
Return the
HttpStatus
enum constant with the specified numeric value.- Parameters:
statusCode
— the numeric value of the enum to be returned- Returns:
- the enum constant with the specified numeric value
- Throws:
IllegalArgumentException
— if this enum has no constant for the specified numeric value
-
resolve
Resolve the given status code to an
HttpStatus
, if possible.- Parameters:
statusCode
— the HTTP status code (potentially non-standard)- Returns:
- the corresponding
HttpStatus
, ornull
if not found - Since:
- 5.0
Содержание
- There was an unexpected error (type=Internal Server Error, status=500).
- Root Cause
- How to reproduce this issue
- Solution
- How to Return HTTP Status Codes in a Spring Boot Application
- Introduction
- What Are HTTP Status Codes?
- Return HTTP Status Codes in Spring Boot
- Returning Response Status Codes with @ResponseStatus
- Free eBook: Git Essentials
- Returning Response Status Codes with ResponseEntity
- Returning Response Status Codes with ResponseStatusException
- Custom Exception Classes and Returning HTTP Status Codes
- Conclusion
There was an unexpected error (type=Internal Server Error, status=500).
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.
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.
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.
Источник
How to Return HTTP Status Codes in a Spring Boot Application
Introduction
All Software Engineers that rely on external/third-party services or tools over HTTP would like to know whether their requests have been accepted, and if not — what’s going on.
Your role as an API developer is to provide a good experience for your users, and amongst other things — satisfy this demand. Making it easy for other developers to determine whether your API returns an error or not gets you a long way, and in the former case — letting other developers know why gets you even farther.
Is the error caused by an internal service of the API? Did they send an unparseable value? Did the server processing these requests outright crash?
Narrowing the possibilities of failure allows developers using your service do their job more efficiently. This is where HTTP status codes come into play, with a short message in the response’s body, describing what’s going on.
In this guide, we’ll take a look at how to return different HTTP Status Codes in Spring Boot, while developing a REST API.
What Are HTTP Status Codes?
Simply put, an HTTP Status Code refers to a 3-digit code that is part of a server’s HTTP Response. The first digit of the code describes the category in which the response falls. This already gives a hint to determine whether the request was successful or not. The Internet Assigned Numbers Authority (IANA) maintains the official registry of HTTP Status Codes. Below are the different categories:
- Informational (1xx): Indicates that the request was received and the process is continuing. It alerts the sender to wait for a final response.
- Successful (2xx): Indicates that the request was successfully received, understood, and accepted.
- Redirection (3xx): Indicates that further action must be taken to complete the request.
- Client Errors (4xx): Indicates that an error occurred during the request processing and it is the client who caused the error.
- Server Errors (5xx): Indicates that an error occurred during request processing but that it was by the server.
While the list is hardly exhaustive, here are some of the most common HTTP codes you’ll be running into:
Code | Status | Description |
200 | OK | The request was successfully completed. |
201 | Created | A new resource was successfully created. |
400 | Bad Request | The request was invalid. |
401 | Unauthorized | The request did not include an authentication token or the authentication token was expired. |
403 | Forbidden | The client did not have permission to access the requested resource. |
404 | Not Found | The requested resource was not found. |
405 | Method Not Allowed | The HTTP method in the request was not supported by the resource. For example, the DELETE method cannot be used with the Agent API. |
500 | Internal Server Error | The request was not completed due to an internal error on the server side. |
503 | Service Unavailable | The server was unavailable. |
Return HTTP Status Codes in Spring Boot
Spring Boot makes the development of Spring-based applications so much easier than ever before, and it automatically returns appropriate status codes. If the request went through just fine, a 200 OK is returned, while a 404 Not Found is returned if the resource isn’t found on the server.
Nevertheless, there are many situations in which we would like to decide on the HTTP Status Code that will be returned in the response ourselves and Spring Boot provides multiple ways for us to achieve that.
Let’s start up a skeleton project via Spring Initializr:
Or via the Spring CLI:
We’ll have a simple controller, TestController :
Here, we’ll create a few request handlers that return different status codes, through a few different approaches.
Returning Response Status Codes with @ResponseStatus
This annotation takes as an argument, the HTTP Status Code, to be returned in the response. Spring makes our job easier by providing an enum containing all the HTTP status codes. It is a very versatile annotation and can be used in controllers at a class or method-level, on Custom Exception Classes, and on classes annotated with @ControllerAdvice (at class or method level).
It works the same way in both classes annotated with @ControllerAdvice and those annotated with @Controller . It is usually coupled with the @ResponseBody annotation in both cases. When used at the class-level, all class methods will result in a response with the specified HTTP status code. All method-level @ResponseStatus annotations override the class-level code and if no @ResponseStatus is associated with a method that doesn’t throw an exception — a 200 is returned by default:
The class-level @ResponseStatus becomes the default code to be returned for all methods, unless a method overrides it. The /classlevel request handler isn’t associated with a method-level status, so the class-level status kicks in, returning a 503 Service Unavailable if someone hits the endpoint. On the other hand, the /methodlevel endpoint returns an 200 OK :
@ResponseStatus works differently when used on Custom Exception classes. Here, the HTTP Status code specified will be the one returned in the response when an exception of that type is thrown but is not caught. We will have a closer look at all this in the code in a later section.
Additionally, you can specify a reason , which automatically triggers the HttpServletResponse.sendError() method, which means that whatever you return won’t come to pass:
Free eBook: Git Essentials
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Though, to actually get the reason to be sent via the sendError() method, you’ll have to set the include-message property within application.properties :
Now, if we send a request to /methodlevel :
This is probably the simplest way to return an HTTP status, but also a rigid one. We can’t really alter the status codes manually, through code here. This is where the ResponseEntity class kicks in.
Returning Response Status Codes with ResponseEntity
The ResponseEntity class is used when we one to programmatically specify all aspects of an HTTP response. This includes the headers, body, and, of course, the status code. This way is the most verbose way of returning an HTTP response in Spring Boot but also the most customizable. Many prefer to use the @ResponseBody annotation coupled with @ResponseStatus as they’re simpler. A ResponseEntity object can be created using one of the several constructors or via the static builder method:
The main advantage of using a ResponseEntity is that you can tie it in with other logic, such as:
Here, we’ve generated a random integer within a range of 1 and 10, and returned a status code depending on the random integer. By checking if the randomInt is greater than 9 , we’ve given the client a 10% probability of seeing the «I am a teapot» April Fool’s status code, added to the RFC2324.
Sending several requests to this endpoint will eventually return:
Returning Response Status Codes with ResponseStatusException
A class used to return status codes in exceptional cases is the ResponseStatusException class. It is used to return a specific message and the HTTP status code that will be returned when an error occurs. It is an alternative to using @ExceptionHandler and @ControllerAdvice . Exception handling using ResponseStatusException is considered to be more fine-grained. It avoids the creation of unnecessary additional Exception classes and reduces tight coupling between the status codes and the exception classes themselves:
It behaves much like when we set the reason via a @ResponseStatus since the underlying mechanism is the same — the sendError() method:
Custom Exception Classes and Returning HTTP Status Codes
Finally, another way to handle exceptions is via the @ResponseStatus and @ControllerAdvice annotations and custom exception classes. Although ResponseStatusException is preferred, if for whatever reason it isn’t in the picture, you can always use these.
Let’s add two request handlers that throw new custom exceptions:
Now, let’s define these exceptions and their own default @ResponseStatus codes (which override the class-level status):
Finally, we’ll create a @ControllerAdvice controller, which is used to set up how Spring Boot manages exceptions:
Finally, when we fire up a few HTTP requests, the endpoint that returns the CaughCustomException will be formatted according to the @ControllerAdvice , while the UnCaughtCustomException won’t:
Conclusion
In this guide, we’ve taken a look at how to return HTTP Status Codes in Spring Boot using @ResponseStatus , ResponseEntity and ResponseStatusException , as well as how to define custom exceptions and handle them both via @ControllerAdvice and without it.
Источник
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;
}
}
Во время работы вашего приложения часто будут возникать исключительные ситуации. Когда у вас простое консольное приложение, то все просто – ошибка выводится в консоль. Но как быть с веб-приложением?
Допустим у пользователя отсутсвует доступ, или он передал некорректные данные. Лучшим вариантом будет в ответ на такие ситуации, отправлять пользователю сообщения с описанием ошибки. Это позволит клиенту вашего API скорректировать свой запрос.
В данной статье разберём основные возможности, которые предоставляет SpringBoot для решения этой задачи и на простых примерах посмотрим как всё работает.
@ExceptionHandler
@ExceptionHandler
позволяет обрабатывать исключения на уровне отдельного контроллера. Для этого достаточно объявить метод в контроллере, в котором будет содержаться вся логика обработки нужного исключения, и пометить его аннотацией.
Для примера у нас будет сущность Person
, бизнес сервис к ней и контроллер. Контроллер имеет один эндпойнт, который возвращает пользователя по логину. Рассмотрим классы нашего приложения:
Сущность Person
:
package dev.struchkov.general.sort;
import java.text.MessageFormat;
public class Person {
private String lastName;
private String firstName;
private Integer age;
//getters and setters
}
Контроллер PersonController
:
package dev.struchkov.example.controlleradvice.controller;
import dev.struchkov.example.controlleradvice.domain.Person;
import dev.struchkov.example.controlleradvice.service.PersonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@Slf4j
@RestController
@RequestMapping("api/person")
@RequiredArgsConstructor
public class PersonController {
private final PersonService personService;
@GetMapping
public ResponseEntity<Person> getByLogin(@RequestParam("login") String login) {
return ResponseEntity.ok(personService.getByLoginOrThrown(login));
}
@GetMapping("{id}")
public ResponseEntity<Person> getById(@PathVariable("id") UUID id) {
return ResponseEntity.ok(personService.getById(id).orElseThrow());
}
}
И наконец PersonService
, который будет возвращать исключение NotFoundException
, если пользователя не будет в мапе persons
.
package dev.struchkov.example.controlleradvice.service;
import dev.struchkov.example.controlleradvice.domain.Person;
import dev.struchkov.example.controlleradvice.exception.NotFoundException;
import lombok.NonNull;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@Service
public class PersonService {
private final Map<UUID, Person> people = new HashMap<>();
public PersonService() {
final UUID komarId = UUID.randomUUID();
people.put(komarId, new Person(komarId, "komar", "Алексей", "ertyuiop"));
}
public Person getByLoginOrThrown(@NonNull String login) {
return people.values().stream()
.filter(person -> person.getLogin().equals(login))
.findFirst()
.orElseThrow(() -> new NotFoundException("Пользователь не найден"));
}
public Optional<Person> getById(@NonNull UUID id) {
return Optional.ofNullable(people.get(id));
}
}
Перед тем, как проверить работу исключения, давайте посмотрим на успешную работу эндпойнта.
Все отлично. Нам в ответ пришел код 200, а в теле ответа пришел JSON нашей сущности. А теперь мы отправим запрос с логином пользователя, которого у нас нет. Посмотрим, что сделает Spring по умолчанию.
Обратите внимание, ошибка 500 – это стандартный ответ Spring на возникновение любого неизвестного исключения. Также исключение было выведено в консоль.
Как я уже говорил, отличным решением будет сообщить пользователю, что он делает не так. Для этого добавляем метод с аннотацией @ExceptionHandler
, который будет перехватывать исключение и отправлять понятный ответ пользователю.
@RequestMapping("api/person")
@RequiredArgsConstructor
public class PersonController {
private final PersonService personService;
@GetMapping
public ResponseEntity<Person> getByLogin(@RequestParam("login") String login) {
return ResponseEntity.ok(personService.getByLoginOrThrown(login));
}
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorMessage> handleException(NotFoundException exception) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
}
Вызываем повторно наш метод и видим, что мы стали получать понятное описание ошибки.
Но теперь вернулся 200 http код, куда корректнее вернуть 404 код.
Однако некоторые разработчики предпочитают возвращать объект, вместо ResponseEntity<T>
. Тогда вам необходимо воспользоваться аннотацией @ResponseStatus
.
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NotFoundException.class)
public ErrorMessage handleException(NotFoundException exception) {
return new ErrorMessage(exception.getMessage());
}
Если попробовать совместить ResponseEntity<T>
и @ResponseStatus
, http-код будет взят из ResponseEntity<T>
.
Главный недостаток @ExceptionHandler
в том, что он определяется для каждого контроллера отдельно. Обычно намного проще обрабатывать все исключения в одном месте.
Хотя это ограничение можно обойти если @ExceptionHandler
будет определен в базовом классе, от которого будут наследоваться все контроллеры в приложении, но такой подход не всегда возможен, особенно если перед нами старое приложение с большим количеством легаси.
HandlerExceptionResolver
Как мы знаем в программировании магии нет, какой механизм задействуется, чтобы перехватывать исключения?
Интерфейс HandlerExceptionResolver
является общим для обработчиков исключений в Spring. Все исключений выброшенные в приложении будут обработаны одним из подклассов HandlerExceptionResolver
. Можно сделать как свою собственную реализацию данного интерфейса, так и использовать существующие реализации, которые предоставляет нам Spring из коробки.
Давайте разберем стандартные для начала:
ExceptionHandlerExceptionResolver
— этот резолвер является частью механизма обработки исключений помеченных аннотацией @ExceptionHandler
, которую мы рассмотрели выше.
DefaultHandlerExceptionResolver
— используется для обработки стандартных исключений Spring и устанавливает соответствующий код ответа, в зависимости от типа исключения:
Exception | HTTP Status Code |
---|---|
BindException | 400 (Bad Request) |
ConversionNotSupportedException | 500 (Internal Server Error) |
HttpMediaTypeNotAcceptableException | 406 (Not Acceptable) |
HttpMediaTypeNotSupportedException | 415 (Unsupported Media Type) |
HttpMessageNotReadableException | 400 (Bad Request) |
HttpMessageNotWritableException | 500 (Internal Server Error) |
HttpRequestMethodNotSupportedException | 405 (Method Not Allowed) |
MethodArgumentNotValidException | 400 (Bad Request) |
MissingServletRequestParameterException | 400 (Bad Request) |
MissingServletRequestPartException | 400 (Bad Request) |
NoSuchRequestHandlingMethodException | 404 (Not Found) |
TypeMismatchException | 400 (Bad Request) |
Мы можем создать собственный HandlerExceptionResolver
. Назовем его CustomExceptionResolver
и вот как он будет выглядеть:
package dev.struchkov.example.controlleradvice.service;
import dev.struchkov.example.controlleradvice.exception.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class CustomExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
final ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
if (e instanceof NotFoundException) {
modelAndView.setStatus(HttpStatus.NOT_FOUND);
modelAndView.addObject("message", "Пользователь не найден");
return modelAndView;
}
modelAndView.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
modelAndView.addObject("message", "При выполнении запроса произошла ошибка");
return modelAndView;
}
}
Мы создаем объект представления – ModelAndView
, который будет отправлен пользователю, и заполняем его. Для этого проверяем тип исключения, после чего добавляем в представление сообщение о конкретной ошибке и возвращаем представление из метода. Если ошибка имеет какой-то другой тип, который мы не предусмотрели в этом обработчике, то мы отправляем сообщение об ошибке при выполнении запроса.
Так как мы пометили этот класс аннотацией @Component
, Spring сам найдет и внедрит наш резолвер куда нужно. Посмотрим, как Spring хранит эти резолверы в классе DispatcherServlet
.
Все резолверы хранятся в обычном ArrayList
и в случае исключнеия вызываются по порядку, при этом наш резолвер оказался последним. Таким образом, если непосредственно в контроллере окажется @ExceptionHandler
обработчик, то наш кастомный резолвер не будет вызван, так как обработка будет выполнена в ExceptionHandlerExceptionResolver
.
Важное замечание. У меня не получилось перехватить здесь ни одно Spring исключение, например MethodArgumentTypeMismatchException
, которое возникает если передавать неверный тип для аргументов @RequestParam
.
Этот способ был показан больше для образовательных целей, чтобы показать в общих чертах, как работает этот механизм. Не стоит использовать этот способ, так как есть вариант намного удобнее.
@RestControllerAdvice
Исключения возникают в разных сервисах приложения, но удобнее всего обрабатывать все исключения в каком-то одном месте. Именно для этого в SpringBoot предназначены аннотации @ControllerAdvice
и @RestControllerAdvice
. В статье мы рассмотрим @RestControllerAdvice
, так как у нас REST API.
На самом деле все довольно просто. Мы берем методы помеченные аннотацией @ExceptionHandler
, которые у нас были в контроллерах и переносим в отдельный класс аннотированный @RestControllerAdvice
.
package dev.struchkov.example.controlleradvice.controller;
import dev.struchkov.example.controlleradvice.domain.ErrorMessage;
import dev.struchkov.example.controlleradvice.exception.NotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
@RestControllerAdvice
public class ExceptionApiHandler {
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorMessage> notFoundException(NotFoundException exception) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorMessage> mismatchException(MethodArgumentTypeMismatchException exception) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
}
За обработку этих методов класса точно также отвечает класс ExceptionHandlerExceptionResolver
. При этом мы можем здесь перехватывать даже стандартные исключения Spring, такие как MethodArgumentTypeMismatchException
.
На мой взгляд, это самый удобный и простой способ обработки возвращаемых пользователю исключений.
Еще про обработку
Все написанное дальше относится к любому способу обработки исключений.
Запись в лог
Важно отметить, что исключения больше не записываются в лог. Если помимо ответа пользователю, вам все же необходимо записать это событие в лог, то необходимо добавить строчку записи в методе обработчике, например вот так:
@ExceptionHandler(NotFoundException.class)
public ResponseEntity<ErrorMessage> handleException(NotFoundException exception) {
log.error(exception.getMessage(), exception);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ErrorMessage(exception.getMessage()));
}
Перекрытие исключений
Вы можете использовать иерархию исключений с наследованием и обработчики исключений для всей своей иерархии. В таком случае обработка исключения будет попадать в самый специализированный обработчик.
Допустим мы бросаем NotFoundException
, как в примере выше, который наследуется от RuntimeException
. И у вас будет два обработчика исключений для NotFoundException
и RuntimeException
. Исключение попадет в обработчик для NotFoundException
. Если этот обработчик убрать, то попадет в обработчик для RuntimeException
.
Резюмирую
Обработка исключений это важная часть REST API. Она позволяет возвращать клиентам информационные сообщения, которые помогут им скорректировать свой запрос.
Мы можем по разному реализовать обработку в зависимости от нашей архитектуры. Предпочитаемым способом считаю вариант с @RestControllerAdvice
. Этот вариант самый чистый и понятный.
I have written about Spring’s support for web response error handling a few times before (e.g. about custom error responses and how they can be generalized). This time, we will take a look at what Spring Boot has to offer.
Start by creating a simple controller:
import org.springframework.web.bind.annotation.*; @RestController public class GreetingController { @RequestMapping(path = "/greet", method = RequestMethod.GET) String greet(@RequestParam("name") String name) { return String.format("Hello %s!", name); } }
First, we verify that the controller is working (the -v
flag is curl’s verbose flag):
$ curl -v localhost:8080/greet?name=Mattias [...] < HTTP/1.1 200 OK [...] Hello Mattias!
Good, it works as expected. Now, let us introduce an error. Since the @RequestParam
by default require the presence of the specified parameter, you will get an error if a request is made without it (json_pp
is the convenient JSON Pure Perl tool for pretty printing JSON):
curl -v localhost:8080/greet | json_pp [...] < HTTP/1.1 400 Bad Request [...] { "timestamp" : 1413313361387, "exception" : "org.springframework.web.bind.MissingServletRequestParameterException", "status" : 400, "error" : "Bad Request", "path" : "/greet", "message" : "Required String parameter 'name' is not present" }
Nice! Not only did we get a 400 Bad Request
HTTP status code as one would expect. More importantly, Spring Boot returned an informative response body with data that may be very useful for client developers:
- A time stamp when the error occured, i.e.
System.currentTimeMillis()
. - The kind of exception that occurred.
- The HTTP status code (same as the response status code).
- The HTTP status code description (derived from the response status code).
- The path that was requested.
- And a message which elaborates the problem further.
What happens if we make a request that has the required name
parameter, but no value?
$ curl -v localhost:8080/greet?name | json_pp [...] < HTTP/1.1 500 Internal Server Error [...] { "timestamp" : 1413314685675, "exception" : "java.lang.IllegalArgumentException", "status" : 500, "error" : "Internal Server Error", "path" : "/greet", "message" : "The 'name' parameter must not be null or empty" }
The value of the exception
field has been replaced with IllegalArgumentException
and the message
field corresponds exactly to the exception message stated by the controller above. Similarily, the HTTP status code in the response header and body is updated to 500 Internal Server Error
, which in some way is correct (in the sense that the server throws an exception it did not handle). However, I argue that 400 Bad Request
is more suitable, since the error occurs because the client did not provide all the required information. So how can we change this?
It turns out that the solution is pretty simple once you know it. The following lines added to your controller will do the trick:
@ExceptionHandler void handleIllegalArgumentException(IllegalArgumentException e, HttpServletResponse response) throws IOException { response.sendError(HttpStatus.BAD_REQUEST.value()); }
Repeat the request:
$ curl -v localhost:8080/greet?name | json_pp [...] < HTTP/1.1 400 Bad Request [...] { "timestamp" : 1413315159949, "exception" : "java.lang.IllegalArgumentException", "status" : 400, "error" : "Bad Request", "path" : "/greet", "message" : "The 'name' parameter must not be null or empty" }
Voila! The HTTP status code is now 400 Bad Request
, both in the response header as well as in the response body.
If you would like to return the same HTTP status code for multiple exception, you can declare the exceptions in the @ExceptionHandler
annotation instead of passing them as a method parameters:
@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class}) void handleBadRequests(HttpServletResponse response) throws IOException { response.sendError(HttpStatus.BAD_REQUEST.value()); }
Lastly, if you add the @ExceptionHandler
snippets in a controller, it will only work for that particular controller. But if you add them to a separate class annotated with the @ControllerAdvice
annotation it will work for all controllers (or a subset of them).
Update
As of Spring Boot version 1.2.0.RC1 issues 1731 and 1762 have been resolved which enables the possibility to set a custom error message in the response:
@ExceptionHandler(IllegalArgumentException.class) void handleBadRequests(HttpServletResponse response) throws IOException { response.sendError(HttpStatus.BAD_REQUEST.value(), "Please try again and with a non empty string as 'name'"); }
Whatever message is passed to the sendError()
method will be copied to the response body:
$ curl -v localhost:8080/greet?name | json_pp [...] < HTTP/1.1 400 Bad Request [...] { "timestamp" : 1413315159949, "exception" : "java.lang.IllegalArgumentException", "status" : 400, "error" : "Bad Request", "path" : "/greet", "message" : "Please try again and with a non empty string as 'name'" }
Considerations
If you would like to validate more complex requests, you can benefit from using the standard JSR-303 Bean Validation API together with Spring Validation. Please see the Validating Form Input Getting Started Guide for a working example.
Acknowledgements
All credits go to Andy Wilkinsson and Dave Syer for answering the issue I filed against Spring Boot regarding this matter.
References
- Exception Handling in Spring MVC at the spring.io/blog.
- @ControllerAdvice in the Spring reference docs.
Dependency
Spring Boot version 1.1+.