Spring response entity error

declaration: package: org.springframework.http, class: ResponseEntity

Type Parameters:
T — the body type

public class ResponseEntity<T>
extends HttpEntity<T>

Extension of HttpEntity that adds an HttpStatusCode status code.
Used in RestTemplate as well as in @Controller methods.

In RestTemplate, this class is returned by
getForEntity() and
exchange():

 ResponseEntity<String> entity = template.getForEntity("https://example.com", String.class);
 String body = entity.getBody();
 MediaType contentType = entity.getHeaders().getContentType();
 HttpStatus statusCode = entity.getStatusCode();
 

This can also be used in Spring MVC as the return value from an
@Controller method:

 @RequestMapping("/handle")
 public ResponseEntity<String> handle() {
   URI location = ...;
   HttpHeaders responseHeaders = new HttpHeaders();
   responseHeaders.setLocation(location);
   responseHeaders.set("MyResponseHeader", "MyValue");
   return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED);
 }
 

Or, by using a builder accessible via static methods:

 @RequestMapping("/handle")
 public ResponseEntity<String> handle() {
   URI location = ...;
   return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body("Hello World");
 }
 
Since:
3.0.2
Author:
Arjen Poutsma, Brian Clozel
See Also:
  • getStatusCode()
  • RestOperations.getForEntity(String, Class, Object...)
  • RestOperations.getForEntity(String, Class, java.util.Map)
  • RestOperations.getForEntity(URI, Class)
  • RequestEntity
  • Nested Class Summary

    Nested Classes

    static interface 

    Defines a builder that adds a body to the response entity.

    static interface 

    Defines a builder that adds headers to the response entity.

  • Field Summary

  • Constructor Summary

    Constructors

    Create a ResponseEntity with a status code only.

    Create a ResponseEntity with headers and a status code.

    Create a ResponseEntity with a body and status code.

    Create a ResponseEntity with a body, headers, and a raw status code.

    Create a ResponseEntity with a body, headers, and a status code.

  • Method Summary

    accepted()

    Create a builder with an ACCEPTED status.

    created(URI location)

    Create a new builder with a CREATED status
    and a location header set to the given URI.

    boolean

    Return the HTTP status code of the response.

    int

    int

    hashCode()

    noContent()

    notFound()

    A shortcut for creating a ResponseEntity with the given body
    and the OK status, or an empty body and a
    NOT FOUND status in case of an
    Optional.empty() parameter.

    ok()

    Create a builder with the status set to OK.

    ok(T body)

    A shortcut for creating a ResponseEntity with the given body
    and the status set to OK.

    status(int status)

    Create a builder with the given status.

    Create a builder with the given status.

    toString()

  • Constructor Details

    • ResponseEntity

      Create a ResponseEntity with a status code only.

      Parameters:
      status — the status code
    • ResponseEntity

      Create a ResponseEntity with a body and status code.

      Parameters:
      body — the entity body
      status — the status code
    • ResponseEntity

      Create a ResponseEntity with headers and a status code.

      Parameters:
      headers — the entity headers
      status — the status code
    • ResponseEntity

      Create a ResponseEntity with a body, headers, and a status code.

      Parameters:
      body — the entity body
      headers — the entity headers
      status — the status code
    • ResponseEntity

      Create a ResponseEntity with a body, headers, and a raw status code.

      Parameters:
      body — the entity body
      headers — the entity headers
      rawStatus — the status code value
      Since:
      5.3.2
  • Method Details

    • getStatusCode

      Return the HTTP status code of the response.

      Returns:
      the HTTP status as an HttpStatus enum entry
    • getStatusCodeValue

      Return the HTTP status code of the response.

      Returns:
      the HTTP status as an int value
      Since:
      4.3
    • equals

      Overrides:
      equals in class HttpEntity<T>
    • hashCode

      public int hashCode()

      Overrides:
      hashCode in class HttpEntity<T>
    • toString

      Overrides:
      toString in class HttpEntity<T>
    • status

      Create a builder with the given status.

      Parameters:
      status — the response status
      Returns:
      the created builder
      Since:
      4.1
    • status

      Create a builder with the given status.

      Parameters:
      status — the response status
      Returns:
      the created builder
      Since:
      4.1
    • ok

      Create a builder with the status set to OK.

      Returns:
      the created builder
      Since:
      4.1
    • ok

      A shortcut for creating a ResponseEntity with the given body
      and the status set to OK.

      Parameters:
      body — the body of the response entity (possibly empty)
      Returns:
      the created ResponseEntity
      Since:
      4.1
    • of

      A shortcut for creating a ResponseEntity with the given body
      and the OK status, or an empty body and a
      NOT FOUND status in case of an
      Optional.empty() parameter.

      Returns:
      the created ResponseEntity
      Since:
      5.1
    • of

      Parameters:
      body — the problem detail to use
      Returns:
      the created builder
      Since:
      6.0
    • created

      Create a new builder with a CREATED status
      and a location header set to the given URI.

      Parameters:
      location — the location URI
      Returns:
      the created builder
      Since:
      4.1
    • accepted

      Create a builder with an ACCEPTED status.

      Returns:
      the created builder
      Since:
      4.1
    • noContent

      Create a builder with a NO_CONTENT status.

      Returns:
      the created builder
      Since:
      4.1
    • badRequest

      Returns:
      the created builder
      Since:
      4.1
    • notFound

      Create a builder with a NOT_FOUND status.

      Returns:
      the created builder
      Since:
      4.1
    • unprocessableEntity

      Returns:
      the created builder
      Since:
      4.1.3
    • internalServerError

      Returns:
      the created builder
      Since:
      5.3.8

I have created a Spring Restful Service and Spring MVC application.

Restful Service ::
Restful service returns an entity if its existing in DB. If it doesn’t exist It returns a custom Exception information in ResponseEntity object.

It is working as expected tested using Postman.

@GetMapping(value = "/validate/{itemId}", produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ResponseEntity<MyItem> validateItem(@PathVariable Long itemId, @RequestHeader HttpHeaders httpHeaders) {

    MyItem myItem = myitemService.validateMyItem(itemId);
    ResponseEntity<MyItem> responseEntity = null;
    if (myItem == null) {
        throw new ItemNotFoundException("Item Not Found!!!!");
    }
    responseEntity = new ResponseEntity<MyItem>(myItem, headers, HttpStatus.OK);
    return responseEntity;
}

If the requested Entity does not exist Restful Service returns below.

@ExceptionHandler(ItemNotFoundException.class)
public ResponseEntity<ExceptionResponse> itemNotFEx(WebRequest webRequest, Exception exception) {
    System.out.println("In CREEH::ItemNFE");
    ExceptionResponse exceptionResponse = new ExceptionResponse("Item Not Found Ex!!!", new Date(), webRequest.getDescription(false));
    ResponseEntity<ExceptionResponse> responseEntity = new ResponseEntity<ExceptionResponse>(exceptionResponse, HttpStatus.NOT_FOUND);
    return responseEntity;
}

But when I am calling the above service from a spring MVC application using RestTemplate, It is returning a valid object if it exists.

If the requested object does not exist Restful service is returning the exception information but its not reaching the calling(spring MVC) application.

Spring MVC application calls Restful Web Service using Rest template

String url = "http://localhost:8080/ItemServices/items/validate/{itemId}";
ResponseEntity<Object> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Object.class, uriParms);
int restCallStateCode = responseEntity.getStatusCodeValue();

Всем привет! Проверяя задания в учебном центре моей компании, обнаружил, что двумя словами описать то, как можно избавиться от ResponseEntity<?> в контроллерах не получится, и необходимо написать целую статью. Для начала, немного введения.

ВАЖНО! Статья написана для новичков в программировании и Spring в часности, которые знакомы со Spring на базовом уровне.

Что такое ResponseEntity<>? Представим ситуацию — у нас есть интернет магазин. И, при примитивной реализации, мы переходим по продуктам, передавая его Id в качестве параметра@RequestParam. Например, наш код выглядит таким образом:

    @ResponseBody
    @GetMapping("/products")
    public Product getProduct(@RequestParam Long id){
        return productsService.findById(id);
    }

При запросе через адресную строку браузера, вывод будет в виде JSON, таким:

{"id":1,"title":"Milk","price":100}

Однако, если мы обратимся к продукту, который у нас отсутствует, например с id=299, то получим следующую картину:

Для пользователя, или даже для фронтендщика, будет абсолютно непонятно, что пошло не так и в чём проблема. Совершая тот же запрос через Postman, ситуация яснее не будет:

{
    "timestamp": "2022-06-30T18:21:03.634+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/app/api/v1/products"
}

И вот тут мы переходим к ResponseEntity<>. Этот объект представляет собой оболочку для Java классов, благодаря которой мы в полной мере сможем реализовать RESTfull архитектуру. Суть использования сводится к тому, чтобы вместо прямого возвращаемого типа данных в контроллере, использовать оболочку ResponseEntity<> и возвращать конечному пользователю, или, что скорее всего вероятно — фронту, JSON, который бы более-менее подробно описывал ошибку. Выглядит такой код примерно так:

    @GetMapping("/products")
    public ResponseEntity<?> getProductRe(Long id){
        try {
            Product product = productsService.findById(id).orElseThrow();
            return new ResponseEntity<>(product, HttpStatus.OK);
        } catch (Exception e){
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }

Что здесь происходит? Вместо строгого типа Product, мы ставим ResponseEntity<?>, где под ? понимается любой Java объект. Конструктор ResponseEntity позволяет перегружать этот объект, добавляя в него не только наш возвращаемый тип, но и статус, чтобы фронтенд мог понимать, что именно пошло не так. Например, при корректном исполнении программы, передавая id=1, мы увидим просто успешно переданный объект Product с кодом 200, а вот в случае продукта с id = 299 результат уже будет такой:

Всё ещё не красиво, но уже хотя бы понятно, что продукт не найден. Мы имеем статус код 404 и фронт уже как-то может с этим работать. Это здорово, но нам бы хотелось более конкретного описания ошибки и результата. Давайте, в таком случае, создадим новый класс:

public class AppError {
    private int statusCode;
    private String message;

    public int getStatusCode() {
        return statusCode;
    }

    public void setStatusCode(int statusCode) {
        this.statusCode = statusCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public AppError() {
    }

    public AppError(int statusCode, String message) {
        this.statusCode = statusCode;
        this.message = message;
    }
}

Это будет вспомогательный класс. Его задача — принять наше сообщение и переслать его фронту вместе со статусом 404. Как мы это сделаем? Очень просто:

    @GetMapping("/products")
    public ResponseEntity<?> getProduct(Long id){
        try {
            Product product = productsService.findById(id).orElseThrow();
            return new ResponseEntity<>(product, HttpStatus.OK);
        } catch (Exception e){
            return new ResponseEntity<>(new AppError(HttpStatus.NOT_FOUND.value(), 
                    "Product with id " + id + " nor found"),
                    HttpStatus.NOT_FOUND);
        }
    }

В этом примере, если мы ловим ошибку, просто отдаём в конструктор ResponseEntity наш кастомный объект и статус 404. Теперь, если мы попробуем получить продукт с id = 299, то ответ будет таким:

{
    "statusCode": 404,
    "message": "Product with id 299 nor found"
}

Отлично! Этого мы и хотели. Стало понятно, в чём проблема. Фронтенд легко распарсит этот JSON и обработает наше сообщение. Однако, сам метод контроллера теперь выглядит не слишком красиво. Да и когда сталкиваешься с чужим кодом, любой из нас сразу хотел бы видеть тип объекта, который будет возвращаться, а не какой-то там ResponseEntity со знаком вопроса в скобочках. Тут мы и переходим к основному материалу статьи.

Как избавиться от ResponseEntity в сигнатуре метода контроллера, при этом сохранив информативность возвращаемой ошибки?

Для этого нам понадобиться глобальный обработчик ошибок, который нам любезно предоставляется в пакете со спрингом. Для начала, создадим какой-то свой кастомный Exception, в котором унаследуемся от RuntimeException:

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

Здесь ничего особенного. Интересное начинается дальше. Давайте внимательно посмотрим на листинг этого класса:

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    @ExceptionHandler
    public ResponseEntity<AppError> catchResourceNotFoundException(ResourceNotFoundException e) {
        log.error(e.getMessage(), e);
        return new ResponseEntity<>(new AppError(HttpStatus.NOT_FOUND.value(), e.getMessage()), HttpStatus.NOT_FOUND);
    }
 }

Начнём сверху вниз. @ControllerAdvice используется для глобальной обработки ошибок в приложении Spring. То есть любой Exception, выпадающий в нашем приложении, будет замечен нашим ControllerAdvice. @Slf4j используется для логгирования, заострять внимание мы на этом не будем. Далее создаём собственный класс, назвать его можем как угодно. И вот тут уже интересное — аннотация@ExceptionHandlerнад методом. Эта аннотация позволяет нам указать, что мы хотим перехватывать и обрабатывать исключения определённого типа, если они возникают, и зашивать их в ResponseEntity, чтобы вернуть ответ нашему фронту. В аргументах метода указываем, какую именно ошибку мы собираемся ловить. В данном случае, это наш кастомный ResourceNotFoundException. И возвращать мы будем точно такой же ResponseEntity, как и в примере выше, однако прописываем мы его уже всего 1 раз — в этом классе. Спринг на этапе обработки этой ошибки самостоятельно поймёт, что в методе нашего контроллера вместо нашего класса Product нужно будет вернуть ResponseEntity.

Теперь мы можем убрать из контроллера все ResponseEntity:

    @GetMapping("/products")
    public Product getProduct(Long id){
            return productsService.findById(id);
    }

А логику появления ошибки перенести в сервисный слой:

    public Product findById(Long id) {
        return productsRepository.findById(id).orElseThrow(
                () -> new ResourceNotFoundException("Product with id " + id + " not found"));
    }

Теперь, если продукт не будет найден, выбросится ResourceNotFoundException. Наш глобальный обработчик исключений поймает это исключение, самостоятельно преобразует его в ResponseEntity и вместо Product’a вернут JSON с подробным описанием ошибки, как и прежде:

{
    "statusCode": 404,
    "message": "Product with id 299 not found"
}

Таким образом, мы избавились от ResponseEntity и кучи лишнего кода, переписываемого из метода в метод, при этом сохранив всю функциональность, которую нам предоставляет ResponseEntity.

Использование Spring ResponseEntity для управления HTTP-ответом

1. Вступление

Используя Spring, у нас обычно есть много способов достичь той же цели, включая тонкую настройку HTTP-ответов.

В этом коротком руководстве мы увидим, как установить текст, статус и заголовки HTTP-ответа с помощьюResponseEntity.

Дальнейшее чтение:

2. ResponseEntityс

ResponseEntityrepresents the whole HTTP response: status code, headers, and body. Из-за этого мы можем использовать его для полной настройки HTTP-ответа.

Если мы хотим использовать его, мы должны вернуть его из конечной точки; Весна позаботится обо всем остальном.

ResponseEntity — это общий тип. В результате мы можем использовать любой тип в качестве тела ответа:

@GetMapping("/hello")
ResponseEntity hello() {
    return new ResponseEntity<>("Hello World!", HttpStatus.OK);
}

Поскольку мы указываем статус ответа программно, мы можем вернуться с разными кодами состояния для разных сценариев:

@GetMapping("/age")
ResponseEntity age(
  @RequestParam("yearOfBirth") int yearOfBirth) {

    if (isInFuture(yearOfBirth)) {
        return new ResponseEntity<>(
          "Year of birth cannot be in the future",
          HttpStatus.BAD_REQUEST);
    }

    return new ResponseEntity<>(
      "Your age is " + calculateAge(yearOfBirth),
      HttpStatus.OK);
}

Кроме того, мы можем установить заголовки HTTP:

@GetMapping("/customHeader")
ResponseEntity customHeader() {
    HttpHeaders headers = new HttpHeaders();
    headers.add("Custom-Header", "foo");

    return new ResponseEntity<>(
      "Custom header set", headers, HttpStatus.OK);
}

Кроме того,ResponseEntityprovides two nested builder interfaces:HeadersBuilder и его подинтерфейсBodyBuilder. Следовательно, мы можем получить доступ к их возможностям с помощью статических методовResponseEntity.

Простейший случай — это ответ с телом и кодом ответа HTTP 200:

@GetMapping("/hello")
ResponseEntity hello() {
    return ResponseEntity.ok("Hello World!");
}

Для самых популярных кодов состояния HTTP мы получаем статические методы:

BodyBuilder accepted();
BodyBuilder badRequest();
BodyBuilder created(java.net.URI location);
HeadersBuilder noContent();
HeadersBuilder notFound();
BodyBuilder ok();

Кроме того, мы можем использовать методыBodyBuilder status(HttpStatus status) иBodyBuilder status(int status) для установки любого статуса HTTP.

Наконец, с помощьюResponseEntity<T> BodyBuilder.body(T body) мы можем установить тело ответа HTTP:

@GetMapping("/age")
ResponseEntity age(@RequestParam("yearOfBirth") int yearOfBirth) {
    if (isInFuture(yearOfBirth)) {
        return ResponseEntity.badRequest()
            .body("Year of birth cannot be in the future");
    }

    return ResponseEntity.status(HttpStatus.OK)
        .body("Your age is " + calculateAge(yearOfBirth));
}

Мы также можем установить пользовательские заголовки:

@GetMapping("/customHeader")
ResponseEntity customHeader() {
    return ResponseEntity.ok()
        .header("Custom-Header", "foo")
        .body("Custom header set");
}

Следовательно,BodyBuilder.body() возвращаетResponseEntity вместоBodyBuilder,, это должен быть последний вызов.

Обратите внимание, что с помощьюHeaderBuilder мы не можем установить какие-либо свойства тела ответа.

При возврате объектаResponseEntity<T> из контроллера мы можем получить какое-то исключение или ошибку при обработке запроса и хотели бы получитьreturn error-related information to the user represented as some other type let’s say E.

Spring 3.2 обеспечивает поддержку глобального@ExceptionHandler with the new @ControllerAdvice annotation, обрабатывающего такие сценарии. Для получения более подробной информации обратитесь к нашей существующей статьеhere.

While ResponseEntity is very powerful, we shouldn’t overuse it. В простых случаях есть другие варианты, которые удовлетворяют наши потребности, и они приводят к гораздо более чистому коду.

3. альтернативы

3.1. @ResponseBodyс

В классических приложениях Spring MVC конечные точки обычно возвращают визуализированные HTML-страницы. Иногда нам нужно только вернуть фактические данные, например, когда мы используем конечную точку с AJAX.

В таких случаях мы можем пометить метод обработчика запросов@ResponseBody иSpring treats the result value of the method as the HTTP response body.

3.2. @ResponseStatusс

Когда конечная точка успешно возвращается, Spring предоставляет ответ HTTP 200 (ОК). Если конечная точка выдает исключение, Spring ищет обработчик исключений, который сообщает, какой HTTP-статус использовать.

Мы можем пометить эти методы с помощью @ResponseStatus. Следовательно, Springreturns with a custom HTTP status.

3.3. Манипулировать ответом напрямую

Spring также позволяет нам напрямую обращаться к объектуjavax.servlet.http.HttpServletResponse; нам нужно только объявить его как аргумент метода:

@GetMapping("/manual")
void manual(HttpServletResponse response) throws IOException {
    response.setHeader("Custom-Header", "foo");
    response.setStatus(200);
    response.getWriter().println("Hello World!");
}

Поскольку Spring предоставляет абстракции и дополнительные возможности над базовой реализацией,we shouldn’t manipulate the response this way.

4. Заключение

В этой статье мы увидели несколько способов с их преимуществами и недостатками для манипулирования HTTP-ответом в Spring.

В части 1 мы рассмотрели варианты обработки исключений, выбрасываемых в контроллере.

Самый гибкий из них — @ControllerAdvice — он позволяет изменить как код, так и тело стандартного ответа при ошибке. Кроме того, он позволяет в одном методе обработать сразу несколько исключений — они перечисляются над методом.

В первой части мы создавали @ControllerAdvice с нуля, но в Spring Boot существует заготовка — ResponseEntityExceptionHandler, которую можно расширить. В ней уже обработаны многие исключения, например: NoHandlerFoundException, HttpMessageNotReadableException, MethodArgumentNotValidException и другие (всего десяток-другой исключений).

Приложение

Обрабатывать исключения будем в простом Spring Boot приложении из первой части. Оно предоставляет REST API для сущности Person:

@Entity
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Size(min = 3, max = 10)
    private String name;
    
}

Только в этот раз поле name аннотировано javax.validation.constraints.Size.

А также перед аргументом Person в методах контроллера стоит аннотация @Valid:

@RestController
@RequestMapping("/persons")
public class PersonController {

    @Autowired
    private PersonRepository personRepository;

    @GetMapping
    public List<Person> listAllPersons() {
        List<Person> persons = personRepository.findAll();
        return persons;
    }

    @GetMapping(value = "/{personId}")
    public Person getPerson(@PathVariable("personId") long personId) {
        return personRepository.findById(personId).orElseThrow(() -> new MyEntityNotFoundException(personId));
    }

    @PostMapping
    public Person createPerson(@RequestBody @Valid Person person) {
        return personRepository.save(person);
    }

    @PutMapping("/{id}")
    public Person updatePerson(@RequestBody @Valid Person person, @PathVariable long id) {
        Person oldPerson = personRepository.getOne(id);
        oldPerson.setName(person.getName());
        return personRepository.save(oldPerson);
    }

}

Аннотация @Valid заставляет Spring проверять валидность полей объекта Person, например условие @Size(min = 3, max = 10). Если пришедший в контроллер объект не соответствует условиям, то будет выброшено MethodArgumentNotValidException — то самое, для которого в ResponseEntityExceptionHandler уже задан обработчик. Правда, он выдает пустое тело ответа. Вообще все обработчики из ResponseEntityExceptionHandler выдают корректный код ответа, но пустое тело.

Мы это исправим. Поскольку для MethodArgumentNotValidException может возникнуть несколько ошибок (по одной для каждого поля сущности Person), добавим в наше пользовательское тело ответа список List с ошибками. Он предназначен именно для MethodArgumentNotValidException (не для других исключений).

Итак, ApiError по сравнению с 1-ой частью теперь содержит еще список errors:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiError {
    private String message;
    private String debugMessage;

    @JsonInclude(JsonInclude.Include.NON_NULL)
    private List<String> errors;

    public ApiError(String message, String debugMessage){
        this.message=message;
        this.debugMessage=debugMessage;
    }
}

Благодаря аннотации @JsonInclude(JsonInclude.Include.NON_NULL) этот список будет включен в ответ только в том случае, если мы его зададим. Иначе ответ будет содержать только message и debugMessage, как в первой части.

Класс обработки исключений

Например, на исключение MyEntityNotFoundException ответ не поменяется, обработчик такой же, как в первой части:

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

   ...

    @ExceptionHandler({MyEntityNotFoundException.class, EntityNotFoundException.class})
    protected ResponseEntity<Object> handleEntityNotFoundEx(MyEntityNotFoundException ex, WebRequest request) {
        ApiError apiError = new ApiError("Entity Not Found Exception", ex.getMessage());
        return new ResponseEntity<>(apiError, HttpStatus.NOT_FOUND);
    }
   ...
}

Но в отличие от 1 части, теперь RestExceptionHandler расширяет ResponseEntityExceptionHandler.  А значит, он наследует различные обработчики исключений, и мы их можем переопределить. Сейчас они все возвращают пустое тело ответа, хотя и корректный код.

HttpMessageNotReadableException

Переопределим обработчик, отвечающий за HttpMessageNotReadableException. Это исключение возникает тогда, когда тело запроса, приходящего в метод контроллер, нечитаемое — например, некорректный JSON.

За это исключение отвечает метод handleHttpMessageNotReadable(), его и переопределим:

@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
                                                              HttpHeaders headers, HttpStatus status, WebRequest request) {
    ApiError apiError = new ApiError("Malformed JSON Request", ex.getMessage());
    return new ResponseEntity(apiError, status);
}

Проверим ответ, сделав запрос с некорректным JSON-телом запроса (он пойдет в метод updatePerson() контроллера):

PUT localhost:8080/persons/1
{
   11"name": "alice"
}

Получаем ответ с кодом 400 (Bad Request) и телом:

{
    "message": "Malformed JSON Request",
    "debugMessage": "JSON parse error: Unexpected character ('1' (code 49)): was expecting double-quote to start field name; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('1' (code 49)): was expecting double-quote to start field namen at [Source: (PushbackInputStream); line: 2, column: 5]"
}

Теперь ответ содержит не только корректный код, но и тело с информативными сообщениями. Если бы мы не переопределяли обработчик, вернулся бы только код 400.

А если бы не расширяли класс ResponseEntityExceptionHandler, все эти обработчики в принципе не были бы задействованы и вернулся бы стандартный ответ из BasicErrorController:

{
    "timestamp": "2021-03-01T16:53:04.197+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "JSON parse error: Unexpected character ('1' (code 49)): was expecting double-quote to start field name; nested exception is com.fasterxml.jackson.core.JsonParseException: Unexpected character ('1' (code 49)): was expecting double-quote to start field namen at [Source: (PushbackInputStream); line: 2, column: 5]",
    "path": "/persons/1"
}

MethodArgumentNotValidException

Как говорилось выше, чтобы выбросилось это исключение, в контроллер должен прийти некорректный Person. В смысле корректный JSON, но условие @Valid чтоб не выполнялось: например, поле name имело бы неверную длину (а она должна быть от 3 до 10, как указано в аннотации @Size).

Попробуем сделать запрос с коротким name:

POST http://localhost:8080/persons
{ 
   "name": "al" 
}

Получим ответ:

{
    "message": "Method Argument Not Valid",
    "debugMessage": "Validation failed for argument [0] in public ru.sysout.model.Person ru.sysout.controller.PersonController.createPerson(ru.sysout.model.Person): [Field error in object 'person' on field 'name': rejected value [al]; codes [Size.person.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.name,name]; arguments []; default message [name],10,3]; default message [размер должен находиться в диапазоне от 3 до 10]] ",
    "errors": [
        "размер должен находиться в диапазоне от 3 до 10"
    ]
}

Тут пошел в ход список ошибок, который мы добавили в ApiError. Мы его заполняем в переопределенном обработчике исключения:

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                              HttpHeaders headers, HttpStatus status, WebRequest request) {
    List<String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(x -> x.getDefaultMessage())
            .collect(Collectors.toList());

    ApiError apiError = new ApiError("Method Argument Not Valid", ex.getMessage(), errors);
    return new ResponseEntity<>(apiError, status);
}

Вообще говоря, стандартный ответ, выдаваемый BasicErrorController, тоже будет содержать этот список ошибок по полям, если в application.properties включить свойство:

server.error.include-binding-errors=always

В этом случае (при отсутствии нашего RestExceptionHandler  с @ControlleAdvice) ответ будет таким:

{
    "timestamp": "2021-03-01T17:15:37.134+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "Validation failed for object='person'. Error count: 1",
    "errors": [
        {
            "codes": [
                "Size.person.name",
                "Size.name",
                "Size.java.lang.String",
                "Size"
            ],
            "arguments": [
                {
                    "codes": [
                        "person.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                },
                10,
                3
            ],
            "defaultMessage": "размер должен находиться в диапазоне от 3 до 10",
            "objectName": "person",
            "field": "name",
            "rejectedValue": "al",
            "bindingFailure": false,
            "code": "Size"
        }
    ],
    "path": "/persons/"
}

Мы просто сократили информацию.

MethodArgumentTypeMismatchException

Полезно знать еще исключение MethodArgumentTypeMismatchException, оно возникает, если тип аргумента неверный. Например, наш метод контроллера получает Person по id:

@GetMapping(value = "/{personId}")
   public Person getPerson(@PathVariable("personId") Long personId) throws EntityNotFoundException {
       return personRepository.getOne(personId);
   }

А мы передаем не целое, а строковое значение id:

GET http://localhost:8080/persons/mn

Тут то и возникает исключение MethodArgumentTypeMismatchException. Давайте его обработаем:

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<Object> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex,HttpStatus status,
                                                                  WebRequest request) {
    ApiError apiError = new ApiError();
    apiError.setMessage(String.format("The parameter '%s' of value '%s' could not be converted to type '%s'",
            ex.getName(), ex.getValue(), ex.getRequiredType().getSimpleName()));
    apiError.setDebugMessage(ex.getMessage());
    return new ResponseEntity<>(apiError, status);
}

Проверим ответ сервера (код ответа будет 400):

{
    "message": "The parameter 'personId' of value 'mn' could not be converted to type 'long'",
    "debugMessage": "Failed to convert value of type 'java.lang.String' to required type 'long'; nested exception is java.lang.NumberFormatException: For input string: "mn""
}

NoHandlerFoundException

Еще одно полезное исключение — NoHandlerFoundException. Оно возникает, если на данный запрос не найдено обработчика.

Например, сделаем запрос:

GET http://localhost:8080/pers

По данному адресу у нас нет контроллера, так что возникнет NoHandlerFoundException.  Добавим обработку исключения:

@Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
                                                               HttpStatus status, WebRequest request) {
    return new ResponseEntity<Object>(new ApiError("No Handler Found", ex.getMessage()), status);
}

Только учтите, для того, чтобы исключение выбрасывалось, надо задать свойства в файле application.properties:

spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false

Проверим ответ сервера (код ответа 404):

{
    "message": "No Handler Found",
    "debugMessage": "No handler found for GET /pers"
}

Если же не выбрасывать NoHandlerFoundException и не пользоваться нашим обработчиком, то ответ от BasicErrorController довольно непонятный, хотя код  тоже 404:

{
    "timestamp": "2021-03-01T17:35:59.204+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/pers"
}

Обработчик по умолчанию

Этот обработчик будет ловить исключения, не пойманные предыдущими обработчиками:

@ExceptionHandler(Exception.class)
    protected ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
        ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, "prosto exception", ex);
        return new ResponseEntity<>(apiError, HttpStatus.INTERNAL_SERVER_ERROR);
    }

Заключение

Мы рассмотрели:

  • как сделать обработку исключений в едином классе, аннотированном @ControllerAdvice;
  • как переопределить формат  JSON-ответа, выдаваемого при возникновении исключения;
  • как воспользоваться классом-заготовкой ResponseEntityExceptionHandler и переопределить его обработчики так, чтобы тело ответов не было пустым;

Обратите внимание, что все не переопределенные методы ResponseEntityExceptionHandler будут выдавать пустое тело ответа.

Код примера доступен на GitHub.

Learn How to return a Specific HTTP Response Status Code from a Controller.

Overview

When a client sends an HTTP request to a server, the server returns an HTTP Response Status. This HTTP response status indicates the client’s request status. Using the response code, the client knows if the server failed, rejected or successfully processed the request.

In Spring-based applications, the underlying framework automatically adds appropriate HTTP Response status codes to every response the server returns. In addition, Spring also provides ways of Customising Spring Controller’s HTTP Response Statuses. In the following sections, we will have a quick overview of the HTTP Status codes and their meaning and learn different ways of returning specific HTTP Response Statuses in Spring.

What is HTTP Response Status Code?

As stated earlier, an HTTP Status is a special code server issues to let its client know the status of its request. These response status codes are three-digit numbers. Next is how these response statuses are categorised.

  • 1xx: All the status codes starting from 100 to 199 are informational.
  • 2xx: These status codes represent the successful completion of the request.
  • 3xx: Represent redirection. That means further action is needed to fulfil the request.
  • 4xx: Request unsuccessful because the request is invalid or cannot be completed
  • 5xx: The request is valid, but the server cannot be fulfilled it because of a problem in the server.

As discussed earlier, Spring Controllers automatically include appropriate status codes in each response. For example, they return 200 when request processing is completed without exception, 404 when the resource in the request is not found, or 500 when the server runs into an exception.

Although these default status codes are correct, they are very high-level. Sometimes, we may want to customise this behaviour or return a more specific status code that the client expects. Thankfully, Spring offers a few ways to customise the HTTP response status codes. Let’s learn those ways with examples.

Sending Specific Response Status Codes

As stated earlier, Spring can send one of the standard Response status codes from the Controller. However, Spring also provides flexible ways to send Custom HTTP Response Status codes or more specific status codes from the server.

The basic way of sending response status is to use the ResponseEntity object, which a controller returns. The Controller can set a specific response status in the Response.

Alternatively, we can use @ResponseStatus annotation to specify the desired status code. The controllers, exception handler methods, and exception classes can use this annotation.

Also, we can throw ResponseStatusException from a Controller to send back a specific HTTP error status code. We can throw this exception with a status code when the request results in an exception.

Send Status Code using ResponseEntity

We can return an instance of ResponseEntity from a Spring Controller, where the response entity object wraps the controller response. We can send a specific HTTP Response Status Code with this response entity instance.

Spring allows a controller to return an object from a controller method. Also, Spring automatically wraps the returned instance into a response along with a default status code.

@PostMapping("/students") public Student postStudent(@RequestBody Student student) { log.info("Request to create student: {}", student); return service.addNewStudent(student); }

Code language: Java (java)

Thus, when we execute an HTTP POST request on the above Controller that returns an instance of the Student object, we get a response status of 200.

> curl -i --location --request POST 'localhost:8080/students' --header 'Content-Type: application/json' --data-raw '{ "firstName" : "n", "lastName" : "b", "year" : 2011 }' HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Wed, 03 Mar 2021 21:52:50 GMT {"studentId":18,"firstName":"n","lastName":"b","year":2011}%

Code language: Bash (bash)

Next is an example of a Controller using ResponseEntity to return a Custom HTTP Response Status Code. Modify the same Controller to wrap the student instance in ResponseEntity.

@PostMapping("/students") public ResponseEntity<Student> postStudent( @RequestBody Student student) { log.info("Request to create student: {}", student); Student newStudent = service.addNewStudent(student); return new ResponseEntity<>(student, HttpStatus.CREATED); }

Code language: Java (java)

Note that being an HTTP POST endpoint, the Controller returns a more specific response status of 201. HTTP Response Status Code 201 denotes an entity is created on the server.

When we execute the endpoint, we get the correct response status.

> curl -i --location --request POST 'localhost:8080/students' --header 'Content-Type: application/json' --data-raw '{ "firstName" : "n", "lastName" : "b", "year" : 2011 }' HTTP/1.1 201 Content-Type: application/json Transfer-Encoding: chunked Date: Wed, 03 Mar 2021 22:00:19 GMT {"studentId":19,"firstName":"n","lastName":"b","year":2011}%

Code language: Bash (bash)

Send Status Code using @ResponseStatus

Spring provides @ResponseStatus annotation, the most flexible way of returning a specific response status. Also, we can specify the reason that leads to the status. The reason is useful when the server returns unsuccessful statuses.

The @ResponseStatus annotation can be used on any method that returns a response to the client. Thus we can use it on controllers or exception handler methods. Moreover, we can also use this annotation on an exception class, and Spring will transform the exception to the specified status code.

Using @ResponseStatus on Exception Class

When we create custom exceptions in Spring or a Spring Boot application, we can associate the exception with a specific Response Status and an optional error message.

@ResponseStatus( value = HttpStatus.NOT_FOUND, reason = "Requested student does not exist" ) public class StudentNotFoundException extends RuntimeException { public StudentNotFoundException(Throwable t) { super(t); } }

Code language: Java (java)

When StudentNotFoundException is thrown out of a controller, Spring will automatically return the specified HTTP Response Status Code.

@GetMapping("/students/{id}") public Student getStudent(@PathVariable Long studentId) { Student student = service.getStudent(studentId); if (student == null) { throw new StudentNotFoundException( "Student not found, studentId: " + studentId); } return student; }

Code language: Java (java)

Using @ResponseStatus on @ExceptionHandler

We can also use @ResponseStatus annotation on a @ExceptionHandler method.

@ExceptionHandler({MyCustomException.class}) @ResponseStatus( value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "this is the reason" ) public void handle() { }

Code language: Java (java)

Using @ResponseStatus on Controller

Alternatively, we can add @ResponseStatus on a controller endpoint method. We have seen the Controller returning a specific status code as part of ResponseEntity.

However, the @ResponseStatus annotation controller can send a specific status code without returning any response.

Next is an example of a Controller with a void return type that returns a custom status code.

@PostMapping("/students") @ResponseStatus(HttpStatus.CREATED) public void postStudent(@RequestBody Student student) { log.info("Request to create student: {}", student); service.addNewStudent(student); }

Code language: Java (java)

Send Status Code using ResponseStatusException

Spring provides ResponseStatusException that a controller can throw with a specific status code and error message.

@PostMapping("/students") public void postStudent(@RequestBody Student student) { log.info("Request to create student: {}", student); try { repository.save(student); } catch (InvalidStudentException e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST); } catch (StudentServiceException e){ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR); } }

Code language: Java (java)

The Controller can treat each exception differently and set a different response status for them.

Summary

This tutorial explained different ways of returning custom HTTP Response Status Codes from the Spring controller. Firstly, we sent a status code by returning ResponseEntity with a custom status code. Then we used @ResponseStatus annotations on the Controller, @ExceptionHandler methods, and custom annotations. Finally, we learnt how to throw ResponseStatusException with a specific HTTP Response Status Code from a controller.

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


Table of Contents

The ResponseEntity object is Spring’s wrapper around the request response. It inherits from the HttpEntity object and contains the Http response code (httpstatus), the response header (header), and the response body (body). A Spring MVC interface to get user information usually we return the entity directly (with @RestController).

1
2
3
4
5
6
@GetMapping("/user")
public User userinfo() {
    User user = new User();
    user.setUsername("felord.cn");
    return user;
}

is equivalent to using ResponseEntity as the return value of the controller.

1
2
3
4
5
6
    @GetMapping("/userinfo")
    public ResponseEntity<User> user() {
        User user = new User();
        user.setUsername("felord.cn");
        return ResponseEntity.ok(user);
    }

But we can do much more when using ResponseEntity.

Customizing the response code

The ResponseEntity.ok above already contains the return 200 Http response code, we can also customize the return response code with ResponseEntity.status(HttpStatus|int).

Customizing the response body

The response body that places the response is usually the data of our interface, here is an example.

1
2
ResponseEntity.status(HttpStatus.OK)
               .body(Object)

Typically we specify the response headers for the Spring MVC interface by setting the header(), consumes, produces() attributes in the @RequestMapping and its Restful family of annotations. If you are using ResponseEntity, you can set it via a chain call.

1
2
3
4
5
6
ResponseEntity.status(HttpStatus.OK)
               .allow(HttpMethod.GET)
               .contentType(MediaType.APPLICATION_JSON)
               .contentLength(1048576)
               .header("My-Header","felord.cn")
               .build();

All standard request headers have their corresponding setting methods, you can also set custom request headers with header(String headerName, String... headerValues) to set a custom request header.

General principles

Let’s look at an abstract interface for handling return values of Spring MVC controller interfaces HandlerMethodReturnValueHandler .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public interface HandlerMethodReturnValueHandler {

    /**
     * 支持的返回值类型
     */
    boolean supportsReturnType(MethodParameter returnType);

    /**
     *  将数据绑定到视图,并设置处理标志以指示已直接处理响应,后续的其它方法就不处理了,优先级非常高
     */
    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;

}

One of its key implementations, HttpEntityMethodProcessor, is the processor that handles controller methods that return a type of HttpEntity. It will give the three kinds of information carried by ResponseEntity to the ServletServerHttpResponse object to render the view and set the processing flag to indicate that the response has been processed directly, and other subsequent methods will not be processed, with a very high priority.

Practical use

Usually you write a download file interface is to get the HttpServletResponse object, and then configure the Content-Type to write a stream inside. If you use ResponseEntity will be more simple and elegant.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@GetMapping("/download")
public ResponseEntity<Resource> load() {
    ClassPathResource classPathResource = new ClassPathResource("application.yml");
    String filename = classPathResource.getFilename();
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.setContentDisposition(ContentDisposition.inline().filename(filename, StandardCharsets.UTF_8).build());
    return ResponseEntity.ok()
            .headers(httpHeaders)
            .body(classPathResource);
}

Above is an example of downloading the Spring Boot configuration file application.yml. There are three main steps.

  • Wrap the file to be downloaded into an org.springframework.core.io.Resource object, which has a number of implementations. Here we use ClassPathResource, other InputStreamResource, PathResource are common implementations.
  • Then configure the download file request header Content-Disposition. It has two modes for downloading: inline means show the file content directly in the browser; attachment means download as a file. Please don’t forget the file extension, e.g. application.yml here. If you don’t specify Content-Disposition, you’ll need to set the corresponding Content-Type according to the file extension, which can be a bit tricky.
  • Finally, the ResponseEntity<Resource> is assembled and returned.

The above interface corresponds to the following effect.

spring

See org.springframework.http.converter.ResourceHttpMessageConverter for the principle

Summary

Today we have shared the role and mechanism of ResponseEntity in Spring and also shared an alternative way to download files. Interested to see the source code implementation.

Reference https://felord.cn/responseEntity.html

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:

$ spring init -d=web

We’ll have a simple controller, TestController:

@Controller
public class 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:

@Controller
@ResponseBody
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
public class TestController {
    
    @GetMapping("/classlevel")
    public String serviceUnavailable() {
        return "The HTTP Status will be SERVICE_UNAVAILABLE (CODE 503)n";
    }

    @GetMapping("/methodlevel")
    @ResponseStatus(code = HttpStatus.OK, reason = "OK")
    public String ok() {
        return "Class Level HTTP Status Overriden. The HTTP Status will be OK (CODE 200)n";
    }    
}

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:

$ curl -i 'http://localhost:8080/classlevel'

HTTP/1.1 503
Content-Type: text/plain;charset=UTF-8
Content-Length: 55
Date: Thu, 17 Jun 2021 06:37:37 GMT
Connection: close

The HTTP Status will be SERVICE_UNAVAILABLE (CODE 503)
$ curl -i 'http://localhost:8080/methodlevel'

HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 73
Date: Thu, 17 Jun 2021 06:41:08 GMT

Class Level HTTP Status Overriden. The HTTP Status will be OK (CODE 200)

@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:

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!

    @GetMapping("/methodlevel")
    @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Resource was not found on the server")
    public String notFound() {
        return "";
    }

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:

server.error.include-message=always

Now, if we send a request to /methodlevel:

$ curl -i http://localhost:8080/methodlevel
HTTP/1.1 404
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Jun 2021 16:52:28 GMT

{"timestamp":"2021-06-29T16:52:28.894+00:00","status":404,"error":"Not Found","message":"Resource was not found on the server","path":"/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:

@Controller
@ResponseBody
public class TestController {
    
    @GetMapping("/response_entity")
    public ResponseEntity<String> withResponseEntity() {
        return ResponseEntity.status(HttpStatus.CREATED).body("HTTP Status will be CREATED (CODE 201)n");
    }   
}

The main advantage of using a ResponseEntity is that you can tie it in with other logic, such as:

@Controller
@ResponseBody
public class TestController {
    
    @GetMapping("/response_entity")
    public ResponseEntity<String> withResponseEntity() {
        int randomInt = new Random().ints(1, 1, 11).findFirst().getAsInt();
        if (randomInt < 9) {
            return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body("Expectation Failed from Client (CODE 417)n");   
        } else {
            return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body("April Fool's Status Code (CODE 418)n");
        }
    }   
}

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:

$ curl -i 'http://localhost:8080/response_entity'

HTTP/1.1 418
Content-Type: text/plain;charset=UTF-8
Content-Length: 36
Date: Tue, 29 Jun 2021 16:36:21 GMT

April Fool's Status Code (CODE 418)

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:

@Controller
@ResponseBody
public class TestController {

    @GetMapping("/rse")
    public String withResponseStatusException() {
        try {
            throw new RuntimeException("Error Occurred");
        } catch (RuntimeException e) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "HTTP Status will be NOT FOUND (CODE 404)n");
        }
    }   
}

It behaves much like when we set the reason via a @ResponseStatus since the underlying mechanism is the same — the sendError() method:

$ curl -i http://localhost:8080/rse
HTTP/1.1 404
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Jun 2021 17:00:23 GMT

{"timestamp":"2021-06-29T17:01:17.874+00:00","status":404,"error":"Not Found","message":"HTTP Status will be NOT FOUND (CODE 404)n","path":"/rse"}

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:

@Controller
@ResponseBody
@ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
public class TestController {

    @GetMapping("/caught")
    public String caughtException() {
        throw new CaughtCustomException("Caught Exception Thrownn");
    }

    @GetMapping("/uncaught")
    public String unCaughtException() {
        throw new UnCaughtException("The HTTP Status will be BAD REQUEST (CODE 400)n");
    }

}

Now, let’s define these exceptions and their own default @ResponseStatus codes (which override the class-level status):

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class CaughtCustomException extends RuntimeException{
    public CaughtCustomException(String message) {
        super(message);
    }
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class UnCaughtException extends RuntimeException {
    public UnCaughtException(String message) {
        super(message);
    }
}

Finally, we’ll create a @ControllerAdvice controller, which is used to set up how Spring Boot manages exceptions:

@ControllerAdvice
@ResponseBody
public class TestControllerAdvice {

    @ExceptionHandler(CaughtCustomException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleException(CaughtCustomException exception) {
        return String.format("The HTTP Status will be Internal Server Error (CODE 500)n %sn",exception.getMessage()) ;
    }
}

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:

$ curl -i http://localhost:8080/caught
HTTP/1.1 500
Content-Type: text/plain;charset=UTF-8
Content-Length: 83
Date: Tue, 29 Jun 2021 17:10:01 GMT
Connection: close

The HTTP Status will be Internal Server Error (CODE 500)
 Caught Exception Thrown


$ curl -i http://localhost:8080/uncaught
HTTP/1.1 400
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Jun 2021 17:10:06 GMT
Connection: close

{"timestamp":"2021-06-29T17:10:06.264+00:00","status":400,"error":"Bad Request","message":"The HTTP Status will be BAD REQUEST (CODE 400)n","path":"/uncaught"}

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.

Понравилась статья? Поделить с друзьями:
  • Sql error 1062 sqlstate 23000
  • Sql error 25006 error cannot execute update in a read only transaction
  • Sql error code 242
  • Spring kafka error handler
  • Sql error 1062 23000 duplicate entry 1 for key users primary