Http status 500 internal server error spring

declaration: package: org.springframework.http, enum: HttpStatus

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
  • 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 name
    NullPointerException — if the argument is null
  • value

    public int value()

    Return the integer value of this status code.

    Specified by:
    value in interface HttpStatusCode
  • 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 interface HttpStatusCode
    See Also:
    • RFC 2616
  • is2xxSuccessful

    public boolean is2xxSuccessful()

    Whether this status code is in the Successful class (2xx).

    Specified by:
    is2xxSuccessful in interface HttpStatusCode
    See Also:
    • RFC 2616
  • is3xxRedirection

    public boolean is3xxRedirection()

    Whether this status code is in the Redirection class (3xx).

    Specified by:
    is3xxRedirection in interface HttpStatusCode
    See Also:
    • RFC 2616
  • is4xxClientError

    public boolean is4xxClientError()

    Whether this status code is in the Client Error class (4xx).

    Specified by:
    is4xxClientError in interface HttpStatusCode
    See Also:
    • RFC 2616
  • is5xxServerError

    public boolean is5xxServerError()

    Whether this status code is in the Server Error class (5xx).

    Specified by:
    is5xxServerError in interface HttpStatusCode
    See Also:
    • RFC 2616
  • isError

    public boolean isError()

    Whether this status code is in the Client or Server Error class

    Specified by:
    isError in interface HttpStatusCode
    See Also:
    • RFC 2616
    • RFC 2616
      (4xx or 5xx).
    • HttpStatusCode.is4xxClientError()
    • HttpStatusCode.is5xxServerError()
  • toString

    Return a string representation of this status code.

    Overrides:
    toString in class Enum<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, or null if not found
    Since:
    5.0

Содержание

  1. There was an unexpected error (type=Internal Server Error, status=500).
  2. Root Cause
  3. How to reproduce this issue
  4. Solution
  5. How to Return HTTP Status Codes in a Spring Boot Application
  6. Introduction
  7. What Are HTTP Status Codes?
  8. Return HTTP Status Codes in Spring Boot
  9. Returning Response Status Codes with @ResponseStatus
  10. Free eBook: Git Essentials
  11. Returning Response Status Codes with ResponseEntity
  12. Returning Response Status Codes with ResponseStatusException
  13. Custom Exception Classes and Returning HTTP Status Codes
  14. 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:

  1. Informational (1xx): Indicates that the request was received and the process is continuing. It alerts the sender to wait for a final response.
  2. Successful (2xx): Indicates that the request was successfully received, understood, and accepted.
  3. Redirection (3xx): Indicates that further action must be taken to complete the request.
  4. Client Errors (4xx): Indicates that an error occurred during the request processing and it is the client who caused the error.
  5. 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+.

Понравилась статья? Поделить с друзьями:
  • Http status 500 internal server error confluence
  • Http status 403 forbidden call of duty mobile как исправить
  • Http status 401 ошибка
  • Http status 400 bad request что это означает как исправить
  • Http state waiting closing for error 32 broken pipe