- 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 bodystatus
— the status code
-
ResponseEntity
Create a
ResponseEntity
with headers and a status code.- Parameters:
headers
— the entity headersstatus
— the status code
-
ResponseEntity
Create a
ResponseEntity
with a body, headers, and a status code.- Parameters:
body
— the entity bodyheaders
— the entity headersstatus
— the status code
-
ResponseEntity
Create a
ResponseEntity
with a body, headers, and a raw status code.- Parameters:
body
— the entity bodyheaders
— the entity headersrawStatus
— 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 classHttpEntity<T>
-
hashCode
public int hashCode()
- Overrides:
hashCode
in classHttpEntity<T>
-
toString
- Overrides:
toString
in classHttpEntity<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.
Code language: Java (java)
@PostMapping("/students") public Student postStudent(@RequestBody Student student) { log.info("Request to create student: {}", student); return service.addNewStudent(student); }
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.
Code language: Bash (bash)
> 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}%
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.
Code language: Java (java)
@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); }
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.
Code language: Bash (bash)
> 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}%
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.
Code language: Java (java)
@ResponseStatus( value = HttpStatus.NOT_FOUND, reason = "Requested student does not exist" ) public class StudentNotFoundException extends RuntimeException { public StudentNotFoundException(Throwable t) { super(t); } }
When StudentNotFoundException is thrown out of a controller, Spring will automatically return the specified HTTP Response Status Code.
Code language: Java (java)
@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; }
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.
Code language: Java (java)
@PostMapping("/students") @ResponseStatus(HttpStatus.CREATED) public void postStudent(@RequestBody Student student) { log.info("Request to create student: {}", student); service.addNewStudent(student); }
Send Status Code using ResponseStatusException
Spring provides ResponseStatusException that a controller can throw with a specific status code and error message.
Code language: Java (java)
@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); } }
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
).
|
|
is equivalent to using ResponseEntity
as the return value of the controller.
|
|
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.
|
|
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.
|
|
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
.
|
|
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.
|
|
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 useClassPathResource
, otherInputStreamResource
,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 specifyContent-Disposition
, you’ll need to set the correspondingContent-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.
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:
- Informational (1xx): Indicates that the request was received and the process is continuing. It alerts the sender to wait for a final response.
- Successful (2xx): Indicates that the request was successfully received, understood, and accepted.
- Redirection (3xx): Indicates that further action must be taken to complete the request.
- Client Errors (4xx): Indicates that an error occurred during the request processing and it is the client who caused the error.
- Server Errors (5xx): Indicates that an error occurred during request processing but that it was by the server.
While the list is hardly exhaustive, here are some of the most common HTTP codes you’ll be running into:
Code | Status | Description |
200 | OK | The request was successfully completed. |
201 | Created | A new resource was successfully created. |
400 | Bad Request | The request was invalid. |
401 | Unauthorized | The request did not include an authentication token or the authentication token was expired. |
403 | Forbidden | The client did not have permission to access the requested resource. |
404 | Not Found | The requested resource was not found. |
405 | Method Not Allowed | The HTTP method in the request was not supported by the resource. For example, the DELETE method cannot be used with the Agent API. |
500 | Internal Server Error | The request was not completed due to an internal error on the server side. |
503 | Service Unavailable | The server was unavailable. |
Return HTTP Status Codes in Spring Boot
Spring Boot makes the development of Spring-based applications so much easier than ever before, and it automatically returns appropriate status codes. If the request went through just fine, a 200 OK
is returned, while a 404 Not Found
is returned if the resource isn’t found on the server.
Nevertheless, there are many situations in which we would like to decide on the HTTP Status Code that will be returned in the response ourselves and Spring Boot provides multiple ways for us to achieve that.
Let’s start up a skeleton project via Spring Initializr:
Or via the Spring CLI:
$ 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.