Send error response java

Custom Error Messages in Spring REST API Create global or application-level Exception handlers and return Custom Error Messages in Spring REST APIs. Overview Effective communication is the key to healthy and efficient relationships. Interestingly, the same applies to any Client and Server relationships. The client’s request may succeed or fail on the server. However, […]

Содержание

  1. Custom Error Messages in Spring REST API
  2. Overview
  3. Return a Generic Error Message using @ResponseStatus
  4. Return Error Message Using Custom Error Object
  5. Return Error Message Using HashMap
  6. Handle Bad Request Exceptions
  7. Type Mismatch Exceptions
  8. Bean Validation Exceptions
  9. Handle Media Type Not Supported Exception
  10. Handle Request Body Not Readable Exception
  11. Handle HTTP Request Method Not Supported Exception
  12. Default Exception Handler
  13. Summary
  14. Servlet Exception and Error Handling Example Tutorial
  15. Servlet Exception
  16. Servlet Error
  17. Servlet Exception and Error Handling
  18. Error Handling for REST with Spring
  19. Introduction
  20. 1. Restful API Error / Exception Design
  21. 1.1 Default Exception Message
  22. 2. Spring REST Error Handling
  23. 2.1. Rest API Error Handling Class.
  24. 2.2. ExceptionHandler Annotation
  25. 3. The @ControllerAdvice Annotation
  26. 3.1. ResponseEntityExceptionHandler
  27. 3.2. MethodArgumentTypeMismatchException
  28. 3.3. HttpMessageNotReadable
  29. 4. Handling Custom Exceptions
  30. 5. Default Exception Handler
  31. 6. Spring Boot REST Error Handling
  32. 7. JSR 303 Validation Error (REST API)
  33. Summary

Custom Error Messages in Spring REST API

Create global or application-level Exception handlers and return Custom Error Messages in Spring REST APIs.

Overview

Effective communication is the key to healthy and efficient relationships. Interestingly, the same applies to any Client and Server relationships. The client’s request may succeed or fail on the server. However, the server should provide the most appropriate status code in either of the outcomes.

Although sending a correct status code is enough for a client to take real action based on the outcome of a request, in case of failures, the client may need more details about what went wrong. For example, failure details like the exception type and an error message can help clients log the error or provide appropriate failure messages to their clients.

This article will teach How to handle different failures and return Custom Error Messages from a Spring REST API. If you don’t know how to handle exceptions in Spring REST API, please read Spring Rest Service Exception Handling.

Return a Generic Error Message using @ResponseStatus

The most basic way of returning an error message from a REST API is to use the @ResponseStatus annotation. We can add the error message in the annotation’s reason field. Although we can only return a generic error message, we can specify exception-specific error messages.

Next is an example of a @ControllerAdvice using @ResponseStatus annotations to return exception-specific error messages.

The Exception handler class has three exception handlers, each of which returns a specific HTTP Response Status. Each response status specifies a reason field with a particular error message.

To view the error message in the response, ensure you have turned on include-messages in the server configuration. To learn more about Spring Boot server configurations, please visit Spring Boot Embedded Tomcat Configuration.

Next is an example of a response object the REST API returns. Note that the response object has the specified error message.

Although we can specify exception-specific error messages, it is still not informative. Therefore in the following sections, we will learn how to return a more specific error message from Spring REST API.

Return Error Message Using Custom Error Object

Let’s create a class representing the error message and the status code. We will return an instance of that in case of errors.

Next is the Error class representing the status code and a String message. We use a few Lombok annotations that introduce regular getter and setter methods and a constructor using the final fields.

Custom Response Error Class

Now that we have an error model created, we will use it to return a detailed error message from Controller Advice.

The exception handler returns an instance of the Error class populated with the exception message and HTTP Status Code.

Now, we can throw our Not Found Exception with a custom error message.

When the REST API cannot find the requested resource, we get a detailed error as a response.

Return Error Message Using HashMap

Also, instead of creating a dedicated error class, we can return a detailed error message using a simple HashMap. Next is an example of producing and returning a Custom Error Message using Java HashMap.

Handle Bad Request Exceptions

The Bad Request errors are the Client errors where the client’s request doesn’t meet the requirements of the target server. This section will see how to handle Bad Request exceptions and provide a custom or detailed error response.

Type Mismatch Exceptions

The Type Mismatch Exceptions occur when Spring Controller cannot map the request parameters, path variables, or header values into controller method arguments. This section covers the handling of MethodArgumentTypeMismatchException and TypeMismatchException.

Spring throws MethodArgumentTypeMismatchException when the controller argument doesn’t have a required type. On the other hand, Spring throws TypeMismatchException when there is a type mismatch while setting Bean properties. Also, both exceptions provide a detailed error message that we can use to prepare the Error object.

To demonstrate that, next is an example of Handling MethodArgumentTypeMismatchException and TypeMismatchException and returning a detailed error message in Controller Advice.

Note that the controller advice catches both exceptions; however, the method arguments accept an exception of type TypeMismatchException because it is the parent of the other exception.

Next, the snippet shows a detailed error message when we call a rest endpoint with an incompatible path variable leading to MethodArgumentTypeMismatchException.

Bean Validation Exceptions

The Bean Validation exceptions occur when the request contents do not pass the provided validations.

The BindException occurs when the binding errors are fatal. While the MethodArgumentNotValidException occurs when validations specified by @Valid fail. Note that the MethodArgumentNotValidException is a subclass of BindException. Thus, we can handle them using the same Spring REST API’s exception handler.

Here we have created a List to represent individual binding errors and add that to the response Map. Instead, we can add a List field to the Error class we created in the previous section and populate the list with individual errors.

Spring throws HttpMediaTypeNotSupportedException, when a POST, PUT, or PATCH endpoint on the server cannot handle the content type sent by the client. The REST Controllers on the server specify the content type they can support. When the media type that a client sends doesn’t match, the client gets this exception back.

To demonstrate, next is an example of handling HttpMediaTypeNotSupportedException and returning a custom error response.

As seen in the exception handler above, the instance of HttpMediaTypeNotSupportedException provides detailed information about the incorrect media type we provided and a list of actually supported media types. Thus, we create a custom error message based on the available information.

The above snippet shows a client’s sample error response when it sends a request with an invalid media type.

Handle Request Body Not Readable Exception

Now we will see an example of handling HttpMessageNotReadableException and returning a custom error response. The HttpMessageNotReadableException occurs when the request body is missing or unreadable.

Handle HTTP Request Method Not Supported Exception

The HttpMethodNotSupportedException occurs when the HTTP endpoint on the REST API does not support the HTTP request method. Let’s write an exception handler for HttpMethodNotSupportedException and return a detailed error message.

As seen in the exception handler above, the exception instance provides detailed information about the provided HTTP Method and an array of Supported HTTP Methods. We use it to form a clear error message.

The snippet showed an example response when the client attempted to execute a GET endpoint, while the REST API supports only POST.

Default Exception Handler

Similarly, we can create a default exception handler advice that handles all Exception types. Spring attempts to find the most specific handler when we have multiple exception handlers and falls back to the default handler if there is no suitable handler.

Above is an example of writing a default exception handler that returns an error message by the exception instance and an HTTP Status of 500.

Summary

This detailed tutorial taught us how to Return Custom Error Messages in Spring REST API. Firstly, we understood that Spring returns a generic error message and the most suitable HTTP Status Code by default. However, we can write our exception handlers for specific exceptions using @ControllerAdvice and produce a custom and detailed error response.

For more on Spring and Spring Boot Tutorials, please visit Spring Tutorials.

Источник

Servlet Exception and Error Handling Example Tutorial

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Today we will look into Servlet Exception and Error Handling. Sometime back I wrote a post about Exception Handling in Java but when it comes to web application, we need more than normal exception handling in java.

Servlet Exception

If you notice, doGet() and doPost() methods throw javax.servlet.ServletException and IOException , let’s see what happens when we throw these exception from our application. I will write a simple servlet that will throw the ServletException.

Now when we invoke this servlet through browser with GET method, we get response like below image. Since browser understand only HTML, when our application throw exception, servlet container processes the exception and generate a HTML response. This logic is specific to servlet container. I am using tomcat and getting this error page. If you will use some other servers like JBoss or Glassfish, you might get different error HTML response. The problem with this response is that it’s of no value to user. Also it’s showing our application classes and server details to user that makes no sense to user and it’s not good from security point of view.

Servlet Error

I am sure you must have seen 404 error when you are trying to hit a URL that doesn’t exists. Let’s see how our servlet container responds to 404 error. If we send request for an invalid URL, we get response HTML like below image. Again it’s a generic HTML generated by server on our application behalf and hold little to no value to the user.

Servlet Exception and Error Handling

Servlet API provides support for custom Exception and Error Handler servlets that we can configure in deployment descriptor. The whole purpose of these servlets are to handle the Exception or Error raised by application and send useful HTML response to user. We can provide link to application home page or some details to let user know what went wrong. So first of all we need to create a custom Exception and Error Handler servlet. We can have multiple exception and error handler servlets for the application but for simplicity I will create a single servlet and use it for both exceptions and errors. AppExceptionHandler.java

Let’s see how we can configure it in deployment descriptor and then we will understand it’s implementation and how it works.

As you can see, it’s very easy to specify Exception handler servlets for the application using error-page element. Each error-page element should have either error-code or exception-type element. We define the exception handler servlet in location element. Based on above configuration, if the application throw 404 error or ServletException, it will be handled by AppExceptionHandler servlet. When such exception and error scenario appears, servlet container will invoke the corresponding HTTP method of the Exception Handler servlet and pass the request and response object. Notice that I have provided implementation of both doGet() and doPost() methods so that it can handle GET and POST requests and using a common method to process them. Before servlet container invokes the servlet to handle the exception, it sets some attributes in the request to get useful information about the exception, some of them are javax.servlet.error.exception, javax.servlet.error.status_code, javax.servlet.error.servlet_name and javax.servlet.error.request_uri. For exception, status code is always 500 that corresponds to the “Internal Server Error”, for other types of error we get different error codes such as 404, 403 etc. Using the status code, our implementation presents different types of HTML response to the user. It also provides a hyperlink to the home page of the application. Now when we will hit our servlet that is throwing ServletException, we will get a response like below image. If we try to access an invalid URL that will result in 404 response, we will get response like below image. Doesn’t it look good and helps user to easily understand what happened and provides them a way to go to the correct location. It also avoids sending application sensitive information to the user. We should always have exception handlers in place for our web application. If you want to handle runtime exceptions and all other exceptions in a single exception handler, you can provide exception-type as Throwable.

If there are multiple error-page entries, let’s say one for Throwable and one for IOException and application throws FileNotFoundException then it will be handled by error handler of IOException. You can also use JSP page as exception handler, just provide the location of jsp file rather than servlet mapping. That’s all for servlet exception handling in web application, I hope you liked it.

Check out other articles in this series:

If you’ve enjoyed this tutorial and our broader community, consider checking out our DigitalOcean products which can also help you achieve your development goals.

Источник

Error Handling for REST with Spring

In the earlier post of REST with Spring series, we discussed Spring MVC Content Negotiation.In this article, we will discuss how to implement Spring REST Exception Handling.

Introduction

Exception handling in a RESTful API with a meaningful error message and the status code is a desired and must have feature. A good error message helps API client to take corrective actions. Some application sends the exception stack trace which can be a good option for a typical web application however this is not a very good solution for a REST API. In this post, we will discuss and implement Error Handling with Spring for a REST API.

In this article, we are using Spring Boot to develop and work on our REST API. Spring Boot provides several features which makes it easy and flexible to create REST API’s with minimum configurations. We will look at few methods outlining how to do a better error handling for REST API using Spring Boot.

1. Restful API Error / Exception Design

While designing exception handling in the RESTful API, it’s a good practice to set HTTP status code in the response to communicate why the request failed or showing a success. We should send more information along with HTTP status code. It will help the client understand the error and take any corrective action.

Let’s discuss this response to understand important points while designing response for your REST API.

  • The status represents HTTP status code.
  • error_code represents REST API specific error code.This field is helpful to pass on API / domain specific information.
  • The message field represents human-readable error message.
  • The details section represents error information with complete detail.
  • The information_link field specifies a link for detail information about the error or exception.

I am dividing this article in to 2 section. The first half talks about the importance of a better error handling for the REST API and the second half focused on the Spring Boot built-in feature for REST Exception Handling. We are taking a simple example of an online ecommerce application where our client can create a customer or get customer information by passing the customer id in the request.

1.1 Default Exception Message

Let’s see how the response look like in case REST API do not have a clear response.

This is the default output when there is no custom error response for our REST API. Though it provides a lot of information but it’s difficult for the client API to parse every exception and display the error message to the customer. There should be a better way to communicate these exceptions to the client to show a better error message.

2. Spring REST Error Handling

Spring and Spring Boot provides several options for error/exception handling. Let’s see what are the different options for Error Handling in REST API with Spring.Before we move in to the details, let’s create an Error response class for our Rest exception handling example.

2.1. Rest API Error Handling Class.

We will use this class to rest customer error message from our REST Api.

2.2. ExceptionHandler Annotation

The first approach is to use the ExceptionHandler annotation at the controller level. This annotation specifically handle exceptions thrown by request handling ( @RequestMapping ) methods in the same controller. Let’s take an example where service can throw CustomerNotFound Exception but we like to send a different / customize message to the client API.

We can define as many @RequestMapping in our controller (Having a different mapping for a different exception type).There are multiple problems or drawbacks with the approach.

  • This annotation is only active for the controller.
  • This annotation is not global and we need to add to every controller (not very intuitive).
  • The return type for this is void or String which add a lot of constraints.

Most of the enterprise application work by extending a basic controller (having common controller functionalities). We can overcome @ExceptionHandler limitation by adding it to the base controller. This also has multiple limitations.

  • The base controller is not suitable for all type of controller. We will end up by duplicating out code.
  • Our Controllers may have to extend a third party class not under our control.

[pullquote align=”normal”]With all these limitations, I do not recommend it to use this approach while building your RESTful API [/pullquote]

3. The @ControllerAdvice Annotation

Spring 3.2 introduced @ControllerAdvice annotation which supports global Exception handler mechanism. A controller advice allows you to use exactly the same exception handling techniques but applies them across the application, not just to an individual controller.

If we call our customer API endpoint again with an invalid user id, we will get the following response from the API

Let’s discuss some important points for the @ControllerAdvice annotation:

  1. The @ControllerAdvice annotation is a specialized @Component annotation. We have the flexibility to use this annotation for multiple controller classes (This works based on the Exception and not bind to the Controller).
  2. Spring Boot automatically detects all classes annotated with @ControllerAdvice during startup as part of the classpath scanning.
  3. We can narrow down the selected controllers by using basePackageClasses() , and basePackages() parameters. For more details refer to the ControllerAdvice.

3.1. ResponseEntityExceptionHandler

In the above example we extended ResponseEntityExceptionHandler class. This is a convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods. This is a convenient way while working on Spring based REST API since it allows the developer to specify ResponseEntity as return values. Let’s work on some most common client errors. We will look into few scenarios of a client sending an invalid request.

3.2. MethodArgumentTypeMismatchException

It throws this exception when method arguments are not the expected type

3.3. HttpMessageNotReadable

It throws this exception when API cannot read the HTTP message

Below we can see the answer to a REST call

4. Handling Custom Exceptions

While working on REST API, we may come across multiple use cases when a request is not fulfilled and we want to return a custom exception back to the client API. Let’s take a simple use case when client API call to find a customer by its unique id. Our service call might return a null or empty object if we do not find the object. Here, if not handled correctly, the API will return 200 (OK) response to the client even if no record found. Let’s create a simple example for better clarity:

Here is the sample service file.

In above example, if we send a request with the user id as 1, our REST API send 200 (OK) response.

To handle all similar use cases, we create a custom exception and handle this exception in our GlobalRestExceptionHandler

I will not go into details about handling different Exceptions in the REST API since we can handle all Exceptions in a similar way as explained above. Here is the list of some common exceptions in a REST API.

  • HttpMediaTypeNotSupportedException
  • HttpRequestMethodNotSupportedException
  • TypeMismatchException

5. Default Exception Handler

We can not handle each exception within the system. Let’s create a fallback handler which will handle all exceptions that don’t have specific exception handler.

6. Spring Boot REST Error Handling

Spring Boot provides several features to build RESTful API’s. Spring Boot 1.4 introduced the @RestControllerAdvice annotation for easier exception handling. It is a convenience annotation that is itself annotated with @ControllerAdvice and @ResponseBody. Here is an example.

While using above approach, set following property to true in Spring Boot application.properties file

7. JSR 303 Validation Error (REST API)

The JSR 303 or also known as bean validation is a standard way to validate your incoming data. The @valid annotation throws handleMethodArgumentNotValid error if the incoming data is not valid. In case we like to provide a custom error message, we have the same option to add a separate handler in our GlobalRestExceptionHandler class. This is our simple CustomerController :

We have added bean validation constraints to our Customer class:

Here is the custom spring rest exception handling method in our GlobalRestExceptionHandler class.

When we call our REST controller, we will have a custom error response based on the JSR 303 bean validation.

Summary

It is important to handle and process exceptions properly in the Spring bases REST API. In this post, we covered different options to implement Spring REST Exception Handling. Building a good exception handling workflow for REST API is an iterative and complex process. A good exception handling mechanism allows API client to know what went wrong with the request. The source code is available on the GitHub.

Источник

Во время работы вашего приложения часто будут возникать исключительные ситуации. Когда у вас простое консольное приложение, то все просто – ошибка выводится в консоль. Но как быть с веб-приложением?

Допустим у пользователя отсутсвует доступ, или он передал некорректные данные. Лучшим вариантом будет в ответ на такие ситуации, отправлять пользователю сообщения с описанием ошибки. Это позволит клиенту вашего 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. Этот вариант самый чистый и понятный.

Modifier and Type Field and Description static int SC_ACCEPTED

Status code (202) indicating that a request was accepted for
processing, but was not completed.

static int SC_BAD_GATEWAY

Status code (502) indicating that the HTTP server received an
invalid response from a server it consulted when acting as a
proxy or gateway.

static int SC_BAD_REQUEST

Status code (400) indicating the request sent by the client was
syntactically incorrect.

static int SC_CONFLICT

Status code (409) indicating that the request could not be
completed due to a conflict with the current state of the
resource.

static int SC_CONTINUE

Status code (100) indicating the client can continue.

static int SC_CREATED

Status code (201) indicating the request succeeded and created
a new resource on the server.

static int SC_EXPECTATION_FAILED

Status code (417) indicating that the server could not meet the
expectation given in the Expect request header.

static int SC_FORBIDDEN

Status code (403) indicating the server understood the request
but refused to fulfill it.

static int SC_FOUND

Status code (302) indicating that the resource reside
temporarily under a different URI.

static int SC_GATEWAY_TIMEOUT

Status code (504) indicating that the server did not receive
a timely response from the upstream server while acting as
a gateway or proxy.

static int SC_GONE

Status code (410) indicating that the resource is no longer
available at the server and no forwarding address is known.

static int SC_HTTP_VERSION_NOT_SUPPORTED

Status code (505) indicating that the server does not support
or refuses to support the HTTP protocol version that was used
in the request message.

static int SC_INTERNAL_SERVER_ERROR

Status code (500) indicating an error inside the HTTP server
which prevented it from fulfilling the request.

static int SC_LENGTH_REQUIRED

Status code (411) indicating that the request cannot be handled
without a defined Content-Length.

static int SC_METHOD_NOT_ALLOWED

Status code (405) indicating that the method specified in the
Request-Line is not allowed for the resource
identified by the Request-URI.

static int SC_MOVED_PERMANENTLY

Status code (301) indicating that the resource has permanently
moved to a new location, and that future references should use a
new URI with their requests.

static int SC_MOVED_TEMPORARILY

Status code (302) indicating that the resource has temporarily
moved to another location, but that future references should
still use the original URI to access the resource.

static int SC_MULTIPLE_CHOICES

Status code (300) indicating that the requested resource
corresponds to any one of a set of representations, each with
its own specific location.

static int SC_NO_CONTENT

Status code (204) indicating that the request succeeded but that
there was no new information to return.

static int SC_NON_AUTHORITATIVE_INFORMATION

Status code (203) indicating that the meta information presented
by the client did not originate from the server.

static int SC_NOT_ACCEPTABLE

Status code (406) indicating that the resource identified by the
request is only capable of generating response entities which have
content characteristics not acceptable according to the accept
headers sent in the request.

static int SC_NOT_FOUND

Status code (404) indicating that the requested resource is not
available.

static int SC_NOT_IMPLEMENTED

Status code (501) indicating the HTTP server does not support
the functionality needed to fulfill the request.

static int SC_NOT_MODIFIED

Status code (304) indicating that a conditional GET operation
found that the resource was available and not modified.

static int SC_OK

Status code (200) indicating the request succeeded normally.

static int SC_PARTIAL_CONTENT

Status code (206) indicating that the server has fulfilled
the partial GET request for the resource.

static int SC_PAYMENT_REQUIRED

Status code (402) reserved for future use.

static int SC_PRECONDITION_FAILED

Status code (412) indicating that the precondition given in one
or more of the request-header fields evaluated to false when it
was tested on the server.

static int SC_PROXY_AUTHENTICATION_REQUIRED

Status code (407) indicating that the client MUST first
authenticate itself with the proxy.

static int SC_REQUEST_ENTITY_TOO_LARGE

Status code (413) indicating that the server is refusing to process
the request because the request entity is larger than the server is
willing or able to process.

static int SC_REQUEST_TIMEOUT

Status code (408) indicating that the client did not produce a
request within the time that the server was prepared to wait.

static int SC_REQUEST_URI_TOO_LONG

Status code (414) indicating that the server is refusing to service
the request because the Request-URI is longer
than the server is willing to interpret.

static int SC_REQUESTED_RANGE_NOT_SATISFIABLE

Status code (416) indicating that the server cannot serve the
requested byte range.

static int SC_RESET_CONTENT

Status code (205) indicating that the agent SHOULD reset
the document view which caused the request to be sent.

static int SC_SEE_OTHER

Status code (303) indicating that the response to the request
can be found under a different URI.

static int SC_SERVICE_UNAVAILABLE

Status code (503) indicating that the HTTP server is
temporarily overloaded, and unable to handle the request.

static int SC_SWITCHING_PROTOCOLS

Status code (101) indicating the server is switching protocols
according to Upgrade header.

static int SC_TEMPORARY_REDIRECT

Status code (307) indicating that the requested resource
resides temporarily under a different URI.

static int SC_UNAUTHORIZED

Status code (401) indicating that the request requires HTTP
authentication.

static int SC_UNSUPPORTED_MEDIA_TYPE

Status code (415) indicating that the server is refusing to service
the request because the entity of the request is in a format not
supported by the requested resource for the requested method.

static int SC_USE_PROXY

Status code (305) indicating that the requested resource
MUST be accessed through the proxy given by the
Location field.

Create global or application-level Exception handlers and return Custom Error Messages in Spring REST APIs.

Overview

Effective communication is the key to healthy and efficient relationships. Interestingly, the same applies to any Client and Server relationships. The client’s request may succeed or fail on the server. However, the server should provide the most appropriate status code in either of the outcomes.

Although sending a correct status code is enough for a client to take real action based on the outcome of a request, in case of failures, the client may need more details about what went wrong. For example, failure details like the exception type and an error message can help clients log the error or provide appropriate failure messages to their clients.

This article will teach How to handle different failures and return Custom Error Messages from a Spring REST API. If you don’t know how to handle exceptions in Spring REST API, please read Spring Rest Service Exception Handling.

Return a Generic Error Message using @ResponseStatus

The most basic way of returning an error message from a REST API is to use the @ResponseStatus annotation. We can add the error message in the annotation’s reason field. Although we can only return a generic error message, we can specify exception-specific error messages.

Next is an example of a @ControllerAdvice using @ResponseStatus annotations to return exception-specific error messages.

@ControllerAdvice public class ApplicationExceptionHandler { @ResponseStatus( value = HttpStatus.NOT_FOUND, reason = "Requested Student Not Found") @ExceptionHandler(StudentNotFoundException.class) public void handleException(StudentNotFoundException e) { } @ResponseStatus( value = HttpStatus.BAD_REQUEST, reason = "Received Invalid Input Parameters") @ExceptionHandler(InputValidationException.class) public void handleException(InputValidationException e) { } @ResponseStatus( value = HttpStatus.GATEWAY_TIMEOUT, reason = "Upstream Service Not Responding, Try Again") @ExceptionHandler(ServiceUnavailableException.class) public void handleException(ServiceUnavailableException e) { } }

Code language: Java (java)

The Exception handler class has three exception handlers, each of which returns a specific HTTP Response Status. Each response status specifies a reason field with a particular error message.

To view the error message in the response, ensure you have turned on include-messages in the server configuration. To learn more about Spring Boot server configurations, please visit Spring Boot Embedded Tomcat Configuration.

server: error: include-message: always

Code language: YAML (yaml)

Next is an example of a response object the REST API returns. Note that the response object has the specified error message.

{ "timestamp": "", "status": 404, "error": "Not Found", "message": "Requested Student Not Found", "path": "/students/Jack" }

Code language: JSON / JSON with Comments (json)

Although we can specify exception-specific error messages, it is still not informative. Therefore in the following sections, we will learn how to return a more specific error message from Spring REST API.

Return Error Message Using Custom Error Object

Let’s create a class representing the error message and the status code. We will return an instance of that in case of errors.

Next is the Error class representing the status code and a String message. We use a few Lombok annotations that introduce regular getter and setter methods and a constructor using the final fields.

Custom Response Error Class

@Data @RequiredArgsConstructor public class Error { private final HttpStatus httpStatus; private final String message; }

Code language: Java (java)

Now that we have an error model created, we will use it to return a detailed error message from Controller Advice.

@ExceptionHandler(StudentNotFoundException.class) public ResponseEntity handleException( StudentNotFoundException e) { Error error = new Error(HttpStatus.NOT_FOUND, e.getLocalizedMessage()); return new ResponseEntity<>(error, error.getHttpStatus()); }

Code language: Java (java)

The exception handler returns an instance of the Error class populated with the exception message and HTTP Status Code.

Now, we can throw our Not Found Exception with a custom error message.

throw new StudentNotFoundException ("Student service failed, studentId : " + studentId);

Code language: Java (java)

When the REST API cannot find the requested resource, we get a detailed error as a response.

{ "httpStatus": "NOT_FOUND", "message": "Student service failed, studentId : Jack" }

Code language: JSON / JSON with Comments (json)

Return Error Message Using HashMap

Also, instead of creating a dedicated error class, we can return a detailed error message using a simple HashMap. Next is an example of producing and returning a Custom Error Message using Java HashMap.

@ExceptionHandler(StudentNotFoundException.class) public ResponseEntity<Map<String, String>> handleException(StudentNotFoundException e) { Map<String, String> errorResponse = Map.of( "message", e.getLocalizedMessage(), "status", HttpStatus.NOT_FOUND.toString() ); return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); }

Code language: Java (java)

Handle Bad Request Exceptions

The Bad Request errors are the Client errors where the client’s request doesn’t meet the requirements of the target server. This section will see how to handle Bad Request exceptions and provide a custom or detailed error response.

Type Mismatch Exceptions

The Type Mismatch Exceptions occur when Spring Controller cannot map the request parameters, path variables, or header values into controller method arguments. This section covers the handling of MethodArgumentTypeMismatchException and TypeMismatchException.

Spring throws MethodArgumentTypeMismatchException when the controller argument doesn’t have a required type. On the other hand, Spring throws TypeMismatchException when there is a type mismatch while setting Bean properties. Also, both exceptions provide a detailed error message that we can use to prepare the Error object.

To demonstrate that, next is an example of Handling MethodArgumentTypeMismatchException and TypeMismatchException and returning a detailed error message in Controller Advice.

@ExceptionHandler({ MethodArgumentTypeMismatchException.class, TypeMismatchException.class }) public ResponseEntity<Map<String, String>> handleException(TypeMismatchException e) { Map<String, String> errorResponse = Map.of( "message", e.getLocalizedMessage(), "status", HttpStatus.BAD_REQUEST.toString() ); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); }

Code language: Java (java)

Note that the controller advice catches both exceptions; however, the method arguments accept an exception of type TypeMismatchException because it is the parent of the other exception.

Next, the snippet shows a detailed error message when we call a rest endpoint with an incompatible path variable leading to MethodArgumentTypeMismatchException.

{ "httpStatus": "BAD_REQUEST", "message": "Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'; nested exception is java.lang.NumberFormatException: For input String: "Jack"" }

Code language: JSON / JSON with Comments (json)

Bean Validation Exceptions

The Bean Validation exceptions occur when the request contents do not pass the provided validations.

The BindException occurs when the binding errors are fatal. While the MethodArgumentNotValidException occurs when validations specified by @Valid fail. Note that the MethodArgumentNotValidException is a subclass of BindException. Thus, we can handle them using the same Spring REST API’s exception handler.

@ExceptionHandler({ BindException.class, MethodArgumentNotValidException.class }) public ResponseEntity<Map<String, Object>> handleException(BindException e) { List<String> errors = new ArrayList<>(); e.getFieldErrors() .forEach(err -> errors.add(err.getField() + ": " + err.getDefaultMessage())); e.getGlobalErrors() .forEach(err -> errors.add(err.getObjectName() + ": " + err.getDefaultMessage())); Map<String, Object> errorResponse = Map.of( "error", errors, "message", e.getLocalizedMessage(), "status", HttpStatus.BAD_REQUEST.toString() ); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); }

Code language: Java (java)

Here we have created a List<String> to represent individual binding errors and add that to the response Map. Instead, we can add a List<String> field to the Error class we created in the previous section and populate the list with individual errors.

Spring throws HttpMediaTypeNotSupportedException, when a POST, PUT, or PATCH endpoint on the server cannot handle the content type sent by the client. The REST Controllers on the server specify the content type they can support. When the media type that a client sends doesn’t match, the client gets this exception back.

To demonstrate, next is an example of handling HttpMediaTypeNotSupportedException and returning a custom error response.

@ExceptionHandler(HttpMediaTypeNotSupportedException.class) public ResponseEntity<Map<String, String>> handleException( HttpMediaTypeNotSupportedException e) { String provided = e.getContentType().toString(); List<String> supported = e.getSupportedMediaTypes().stream() .map(MimeType::toString) .collect(Collectors.toList()); String error = provided + " is not one of the supported media types (" + String.join(", ", supported) + ")"; Map<String, String> errorResponse = Map.of( "error", error, "message", e.getLocalizedMessage(), "status", HttpStatus.UNSUPPORTED_MEDIA_TYPE.toString() ); return new ResponseEntity<>(errorResponse, HttpStatus.UNSUPPORTED_MEDIA_TYPE); }

Code language: Java (java)

As seen in the exception handler above, the instance of HttpMediaTypeNotSupportedException provides detailed information about the incorrect media type we provided and a list of actually supported media types. Thus, we create a custom error message based on the available information.

{ "error":"text/plain;charset=UTF-8 is not one of the supported media types ( application/octet-stream, text/plain, application/xml, text/xml, application/x-www-form-urlencoded, application/*+xml, multipart/form-data, multipart/mixed, application/json, application/*+json, */*)", "message":"Content type 'text/plain;charset=UTF-8' not supported", "status":"415 UNSUPPORTED_MEDIA_TYPE" }

Code language: JSON / JSON with Comments (json)

The above snippet shows a client’s sample error response when it sends a request with an invalid media type.

Handle Request Body Not Readable Exception

Now we will see an example of handling HttpMessageNotReadableException and returning a custom error response. The HttpMessageNotReadableException occurs when the request body is missing or unreadable.

@ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity<Map<String, String>> handleException( HttpMessageNotReadableException e) throws IOException { Map<String, String> errorResponse = Map.of( "message", e.getLocalizedMessage(), "status", HttpStatus.BAD_REQUEST.toString() ); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); }

Code language: Java (java)

Handle HTTP Request Method Not Supported Exception

The HttpMethodNotSupportedException occurs when the HTTP endpoint on the REST API does not support the HTTP request method. Let’s write an exception handler for HttpMethodNotSupportedException and return a detailed error message.

@ExceptionHandler(HttpRequestMethodNotSupportedException.class) public ResponseEntity<Map<String, String>> handleException( HttpRequestMethodNotSupportedException e) throws IOException { String provided = e.getMethod(); List<String> supported = List.of(e.getSupportedMethods()); String error = provided + " is not one of the supported Http Methods (" + String.join(", ", supported) + ")"; Map<String, String> errorResponse = Map.of( "error", error, "message", e.getLocalizedMessage(), "status", HttpStatus.METHOD_NOT_ALLOWED.toString() ); return new ResponseEntity<>(errorResponse, HttpStatus.METHOD_NOT_ALLOWED); }

Code language: Java (java)

As seen in the exception handler above, the exception instance provides detailed information about the provided HTTP Method and an array of Supported HTTP Methods. We use it to form a clear error message.

{ "error": "GET is not one of the supported Http Methods (POST)", "message": "Request method 'GET' not supported", "status": "405 METHOD_NOT_ALLOWED" }

Code language: JSON / JSON with Comments (json)

The snippet showed an example response when the client attempted to execute a GET endpoint, while the REST API supports only POST.

Default Exception Handler

Similarly, we can create a default exception handler advice that handles all Exception types. Spring attempts to find the most specific handler when we have multiple exception handlers and falls back to the default handler if there is no suitable handler.

@ExceptionHandler(Exception.class) public ResponseEntity<Map<String, String>> handleException( Exception e) throws IOException { Map<String, String> errorResponse = Map.of( "message", e.getLocalizedMessage(), "status", HttpStatus.INTERNAL_SERVER_ERROR.toString() ); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); }

Code language: Java (java)

Above is an example of writing a default exception handler that returns an error message by the exception instance and an HTTP Status of 500.

Summary

This detailed tutorial taught us how to Return Custom Error Messages in Spring REST API. Firstly, we understood that Spring returns a generic error message and the most suitable HTTP Status Code by default. However, we can write our exception handlers for specific exceptions using @ControllerAdvice and produce a custom and detailed error response.

For more on Spring and Spring Boot Tutorials, please visit Spring Tutorials.


Понравилась статья? Поделить с друзьями:
  • Send error laravel
  • Send error 1103
  • Send cue sheet error
  • Sencha build error
  • Security service 3 error parsing scopedpdu