Http ошибки java

I am using Apache HttpClient and would like to communicate HTTP errors (400 Bad Request, 404 Not Found, 500 Server Error, etc.) via the Java exception mechanism to the calling code. Is there an exception in the Java standard library or in a widely used library that would be appropriate to use or to subclass for this purpose?

The alternative is to check status return codes. This appears to be the HttpClient design philosophy, but since these errors are truly exceptional in my app, I would like to have the stack trace and other nice exception things set up for me when they happen.

asked Oct 17, 2008 at 13:56

Steven Huwig's user avatar

If it’s not an Exception in HttpClient design philosophy, but an Exception in your code, then create your own Exception classes. ( As a subclass of org.apache.commons.httpclient.HttpException )

answered Oct 17, 2008 at 14:04

asalamon74's user avatar

asalamon74asalamon74

6,1209 gold badges46 silver badges60 bronze badges

1

Quick answer

In Spring you have exactly what you want:

  • HttpClientErrorException — Exception thrown when an HTTP 4xx is received.
  • HttpServerErrorException — Exception thrown when an HTTP 5xx is received.

And a recommended practice

Minimally, you should differentiate exceptions related to business logic (e.g., insufficient balance, email address is not valid) from other exceptions (e.g., server not available, unsupported media type, SQLException).

In our REST API, we have a library for Java clients that parses responses and throws only three different exceptions:

  • 400, 401, 403, 404, 409, 422: throw MyBusinessException, which contains a message that can be shown to the end user. The message comes in the response body (exception handling on the service side), but if not present we have a default message specific to each status code.
  • 405, 412, 415: throw HttpClientErrorException with a message that is specific to each status code.
  • other 4xx codes: throw HttpClientErrorException with a generic message.
  • 5xx codes: throw HttpServerErrorException with a generic message.

All these exceptions are unchecked.

answered Nov 1, 2017 at 15:34

Paulo Merson's user avatar

Paulo MersonPaulo Merson

13.3k8 gold badges79 silver badges72 bronze badges

I found this exception on Apache HTTP Client 4.5.7:

...
import org.apache.http.client.HttpResponseException;

...
StatusLine statusLine = response.getStatusLine();
if(statusLine.getStatusCode() != 200) {
    throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
...

The sample of result is:

Exception in thread "main" org.apache.http.client.HttpResponseException: status code: 400, reason phrase: Bad Request

answered Jan 27, 2019 at 17:28

Wendel's user avatar

WendelWendel

2,81929 silver badges28 bronze badges

I’d say this depends on what you are using the HTTPClient for. For example, the PayPal SDK uses HttpClient to transmit API calls to the PayPal server, but fails to check the HTTP response code when it’s done. I patched my copy so that if the response code isn’t 200 it throws a PayPal FatalException, with an appropriate message. That’s because the caller isn’t interested in the HTML or any of the details of this HTTP post, and isn’t even interested in the fact that we’re using HTTP as a transport. If the call is successful then the body of the response contains transaction details, which are extracted and placed into a response object; otherwise it contains HTML which is useless. HTTP is just the transport in this case, so certain response codes indicate errors which can be reported using exceptions. Because this is part of the PayPal SDK, I used a PayPal Exception class. In some other system or library, I’d use a subtype of whatever exceptions that library already uses. For example, if I were writing a GMail library, which accesses GMail accounts, I’d probably create a GMailException class, and subclass that for the different kinds of exceptions the library runs into. Alternatively, you can use something like IOException.

The reason HttpClient makes you check response codes is because the response may be useful even if the response code is not 200. Some websites put useful text on a 404 page, either providing something useful for the user to do, or a search form, or just a helpful error message. Depending on your use case you may want to just show the response content rather than throw an exception.

answered Oct 17, 2008 at 14:20

Mr. Shiny and New 安宇's user avatar

There is org.apache.commons.httpclient.HttpException if you want a library exception. We have also sometimes created our own for specific purposes, both creating an exception for specific HTTP status codes and a generic one for and unexpected status code.

answered Oct 17, 2008 at 14:12

Instantsoup's user avatar

InstantsoupInstantsoup

14.8k5 gold badges35 silver badges41 bronze badges

2

I am using Apache HttpClient and would like to communicate HTTP errors (400 Bad Request, 404 Not Found, 500 Server Error, etc.) via the Java exception mechanism to the calling code. Is there an exception in the Java standard library or in a widely used library that would be appropriate to use or to subclass for this purpose?

The alternative is to check status return codes. This appears to be the HttpClient design philosophy, but since these errors are truly exceptional in my app, I would like to have the stack trace and other nice exception things set up for me when they happen.

asked Oct 17, 2008 at 13:56

Steven Huwig's user avatar

If it’s not an Exception in HttpClient design philosophy, but an Exception in your code, then create your own Exception classes. ( As a subclass of org.apache.commons.httpclient.HttpException )

answered Oct 17, 2008 at 14:04

asalamon74's user avatar

asalamon74asalamon74

6,1209 gold badges46 silver badges60 bronze badges

1

Quick answer

In Spring you have exactly what you want:

  • HttpClientErrorException — Exception thrown when an HTTP 4xx is received.
  • HttpServerErrorException — Exception thrown when an HTTP 5xx is received.

And a recommended practice

Minimally, you should differentiate exceptions related to business logic (e.g., insufficient balance, email address is not valid) from other exceptions (e.g., server not available, unsupported media type, SQLException).

In our REST API, we have a library for Java clients that parses responses and throws only three different exceptions:

  • 400, 401, 403, 404, 409, 422: throw MyBusinessException, which contains a message that can be shown to the end user. The message comes in the response body (exception handling on the service side), but if not present we have a default message specific to each status code.
  • 405, 412, 415: throw HttpClientErrorException with a message that is specific to each status code.
  • other 4xx codes: throw HttpClientErrorException with a generic message.
  • 5xx codes: throw HttpServerErrorException with a generic message.

All these exceptions are unchecked.

answered Nov 1, 2017 at 15:34

Paulo Merson's user avatar

Paulo MersonPaulo Merson

13.3k8 gold badges79 silver badges72 bronze badges

I found this exception on Apache HTTP Client 4.5.7:

...
import org.apache.http.client.HttpResponseException;

...
StatusLine statusLine = response.getStatusLine();
if(statusLine.getStatusCode() != 200) {
    throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
}
...

The sample of result is:

Exception in thread "main" org.apache.http.client.HttpResponseException: status code: 400, reason phrase: Bad Request

answered Jan 27, 2019 at 17:28

Wendel's user avatar

WendelWendel

2,81929 silver badges28 bronze badges

I’d say this depends on what you are using the HTTPClient for. For example, the PayPal SDK uses HttpClient to transmit API calls to the PayPal server, but fails to check the HTTP response code when it’s done. I patched my copy so that if the response code isn’t 200 it throws a PayPal FatalException, with an appropriate message. That’s because the caller isn’t interested in the HTML or any of the details of this HTTP post, and isn’t even interested in the fact that we’re using HTTP as a transport. If the call is successful then the body of the response contains transaction details, which are extracted and placed into a response object; otherwise it contains HTML which is useless. HTTP is just the transport in this case, so certain response codes indicate errors which can be reported using exceptions. Because this is part of the PayPal SDK, I used a PayPal Exception class. In some other system or library, I’d use a subtype of whatever exceptions that library already uses. For example, if I were writing a GMail library, which accesses GMail accounts, I’d probably create a GMailException class, and subclass that for the different kinds of exceptions the library runs into. Alternatively, you can use something like IOException.

The reason HttpClient makes you check response codes is because the response may be useful even if the response code is not 200. Some websites put useful text on a 404 page, either providing something useful for the user to do, or a search form, or just a helpful error message. Depending on your use case you may want to just show the response content rather than throw an exception.

answered Oct 17, 2008 at 14:20

Mr. Shiny and New 安宇's user avatar

There is org.apache.commons.httpclient.HttpException if you want a library exception. We have also sometimes created our own for specific purposes, both creating an exception for specific HTTP status codes and a generic one for and unexpected status code.

answered Oct 17, 2008 at 14:12

Instantsoup's user avatar

InstantsoupInstantsoup

14.8k5 gold badges35 silver badges41 bronze badges

2

Время на прочтение
9 мин

Количество просмотров 72K

image

Часто на практике возникает необходимость централизованной обработки исключений в рамках контроллера или даже всего приложения. В данной статье разберём основные возможности, которые предоставляет Spring Framework для решения этой задачи и на простых примерах посмотрим как всё работает. Кому интересна данная тема — добро пожаловать под кат!

Изначально до Spring 3.2 основными способами обработки исключений в приложении были HandlerExceptionResolver и аннотация @ExceptionHandler. Их мы ещё подробно разберём ниже, но они имеют определённые недостатки. Начиная с версии 3.2 появилась аннотация @ControllerAdvice, в которой устранены ограничения из предыдущих решений. А в Spring 5 добавился новый класс ResponseStatusException, который очень удобен для обработки базовых ошибок для REST API.

А теперь обо всём по порядку, поехали!

Обработка исключений на уровне контроллера — @ExceptionHandler

С помощью аннотации @ExceptionHandler можно обрабатывать исключения на уровне отдельного контроллера. Для этого достаточно объявить метод, в котором будет содержаться вся логика обработки нужного исключения, и проаннотировать его.

В качестве примера разберём простой контроллер:

@RestController
public class Example1Controller {

    @GetMapping(value = "/testExceptionHandler", produces = APPLICATION_JSON_VALUE)
    public Response testExceptionHandler(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws BusinessException {
        if (exception) {
            throw new BusinessException("BusinessException in testExceptionHandler");
        }
        return new Response("OK");
    }

    @ExceptionHandler(BusinessException.class)
    public Response handleException(BusinessException e) {
        return new Response(e.getMessage());
    }

}

Тут я сделал метод testExceptionHandler, который вернёт либо исключение BusinessException, либо успешный ответ — всё зависит от того что было передано в параметре запроса. Это нужно для того, чтобы можно было имитировать как штатную работу приложения, так и работу с ошибкой.

А вот следующий метод handleException предназначен уже для обработки ошибок. У него есть аннотация @ExceptionHandler(BusinessException.class), которая говорит нам о том что для последующей обработки будут перехвачены все исключения типа BusinessException. В аннотации @ExceptionHandler можно прописать сразу несколько типов исключений, например так: @ExceptionHandler({BusinessException.class, ServiceException.class}).

Сама обработка исключения в данном случае примитивная и сделана просто для демонстрации работы метода — по сути вернётся код 200 и JSON с описанием ошибки. На практике часто требуется более сложная логика обработки и если нужно вернуть другой код статуса, то можно воспользоваться дополнительно аннотацией @ResponseStatus, например @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR).

Пример работы с ошибкой:

Пример штатной работы:

Основной недостаток @ExceptionHandler в том что он определяется для каждого контроллера отдельно, а не глобально для всего приложения. Это ограничение можно обойти если @ExceptionHandler определен в базовом классе, от которого будут наследоваться все контроллеры в приложении, но такой подход не всегда возможен, особенно если перед нами старое приложение с большим количеством легаси.

Обработка исключений с помощью HandlerExceptionResolver

HandlerExceptionResolver является общим интерфейсом для обработчиков исключений в Spring. Все исключений выброшенные в приложении будут обработаны одним из подклассов HandlerExceptionResolver. Можно сделать как свою собственную реализацию данного интерфейса, так и использовать существующие реализации, которые предоставляет нам Spring из коробки. Давайте разберем их для начала:

ExceptionHandlerExceptionResolver — этот резолвер является частью механизма обработки исключений с помощью аннотации @ExceptionHandler, о которой я уже упоминал ранее.

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

Основной недостаток заключается в том что возвращается только код статуса, а на практике для REST API одного кода часто не достаточно. Желательно вернуть клиенту еще и тело ответа с описанием того что произошло. Эту проблему можно решить с помощью ModelAndView, но не нужно, так как есть способ лучше.

ResponseStatusExceptionResolver — позволяет настроить код ответа для любого исключения с помощью аннотации @ResponseStatus.

В качестве примера я создал новый класс исключения ServiceException:

@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public class ServiceException extends Exception {

    public ServiceException(String message) {
        super(message);
    }

}

В ServiceException я добавил аннотацию @ResponseStatus и в value указал что данное исключение будет соответствовать статусу INTERNAL_SERVER_ERROR, то есть будет возвращаться статус-код 500.

Для тестирования данного нового исключения я создал простой контроллер:

@RestController
public class Example2Controller {

    @GetMapping(value = "/testResponseStatusExceptionResolver", produces = APPLICATION_JSON_VALUE)
    public Response testResponseStatusExceptionResolver(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws ServiceException {
        if (exception) {
            throw new ServiceException("ServiceException in testResponseStatusExceptionResolver");
        }
        return new Response("OK");
    }

}

Если отправить GET-запрос и передать параметр exception=true, то приложение в ответ вернёт 500-ю ошибку:

Из недостатков такого подхода — как и в предыдущем случае отсутствует тело ответа. Но если нужно вернуть только код статуса, то @ResponseStatus довольно удобная штука.

Кастомный HandlerExceptionResolver позволит решить проблему из предыдущих примеров, наконец-то можно вернуть клиенту красивый JSON или XML с необходимой информацией. Но не спешите радоваться, давайте для начала посмотрим на реализацию.

В качестве примера я сделал кастомный резолвер:

@Component
public class CustomExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView modelAndView = new ModelAndView(new MappingJackson2JsonView());
        if (ex instanceof CustomException) {
            modelAndView.setStatus(HttpStatus.BAD_REQUEST);
            modelAndView.addObject("message", "CustomException was handled");
            return modelAndView;

        }
        modelAndView.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        modelAndView.addObject("message", "Another exception was handled");
        return modelAndView;
    }

}

Ну так себе, прямо скажем. Код конечно работает, но приходится выполнять всю работу руками: сами проверяем тип исключения, и сами формируем объект древнего класса ModelAndView. На выходе конечно получим красивый JSON, но в коде красоты явно не хватает.

Такой резолвер может глобально перехватывать и обрабатывать любые типы исключений и возвращать как статус-код, так и тело ответа. Формально он даёт нам много возможностей и не имеет недостатков из предыдущих примеров. Но есть способ сделать ещё лучше, к которому мы перейдем чуть позже. А сейчас, чтобы убедиться что всё работает — напишем простой контроллер:

@RestController
public class Example3Controller {

    @GetMapping(value = "/testCustomExceptionResolver", produces = APPLICATION_JSON_VALUE)
    public Response testCustomExceptionResolver(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws CustomException {
        if (exception) {
            throw new CustomException("CustomException in testCustomExceptionResolver");
        }
        return new Response("OK");
    }

}

А вот и пример вызова:

Видим что исключение прекрасно обработалось и в ответ получили код 400 и JSON с сообщением об ошибке.

Обработка исключений с помощью @ControllerAdvice

Наконец переходим к самому интересному варианту обработки исключений — эдвайсы. Начиная со Spring 3.2 можно глобально и централизованно обрабатывать исключения с помощью классов с аннотацией @ControllerAdvice.

Разберём простой пример эдвайса для нашего приложения:

@ControllerAdvice
public class DefaultAdvice {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response> handleException(BusinessException e) {
        Response response = new Response(e.getMessage());
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

}

Как вы уже догадались, любой класс с аннотацией @ControllerAdvice является глобальным обработчиком исключений, который очень гибко настраивается.
В нашем случае мы создали класс DefaultAdvice с одним единственным методом handleException. Метод handleException имеет аннотацию @ExceptionHandler, в которой, как вы уже знаете, можно определить список обрабатываемых исключений. В нашем случае будем перехватывать все исключения BusinessException.

Можно одним методом обрабатывать и несколько исключений сразу: @ExceptionHandler({BusinessException.class, ServiceException.class}). Так же можно в рамках эдвайса сделать сразу несколько методов с аннотациями @ExceptionHandler для обработки разных исключений.
Обратите внимание, что метод handleException возвращает ResponseEntity с нашим собственным типом Response:

public class Response {

    private String message;

    public Response() {
    }

    public Response(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

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

}

Таким образом у нас есть возможность вернуть клиенту как код статуса, так и JSON заданной структуры. В нашем простом примере я записываю в поле message описание ошибки и возвращаю HttpStatus.OK, что соответствует коду 200.

Для проверки работы эдвайса я сделал простой контроллер:

@RestController
public class Example4Controller {

    @GetMapping(value = "/testDefaultControllerAdvice", produces = APPLICATION_JSON_VALUE)
    public Response testDefaultControllerAdvice(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws BusinessException {
        if (exception) {
            throw new BusinessException("BusinessException in testDefaultControllerAdvice");
        }
        return new Response("OK");
    }

}

В результате, как и ожидалось, получаем красивый JSON и код 200:

А что если мы хотим обрабатывать исключения только от определенных контроллеров?
Такая возможность тоже есть! Смотрим следующий пример:

@ControllerAdvice(annotations = CustomExceptionHandler.class)
public class CustomAdvice {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<Response> handleException(BusinessException e) {
        String message = String.format("%s %s", LocalDateTime.now(), e.getMessage());
        Response response = new Response(message);
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

}

Обратите внимание на аннотацию @ControllerAdvice(annotations = CustomExceptionHandler.class). Такая запись означает что CustomAdvice будет обрабатывать исключения только от тех контроллеров, которые дополнительно имеют аннотацию @CustomExceptionHandler.

Аннотацию @CustomExceptionHandler я специально сделал для данного примера:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomExceptionHandler {
}

А вот и исходный код контроллера:

@RestController
@CustomExceptionHandler
public class Example5Controller {

    @GetMapping(value = "/testCustomControllerAdvice", produces = APPLICATION_JSON_VALUE)
    public Response testCustomControllerAdvice(@RequestParam(required = false, defaultValue = "false") boolean exception)
            throws BusinessException {
        if (exception) {
            throw new BusinessException("BusinessException in testCustomControllerAdvice");
        }
        return new Response("OK");
    }

}

В контроллере Example5Controller присутствует аннотация @CustomExceptionHandler, а так же на то что выбрасывается то же исключение что и в Example4Controller из предыдущего примера. Однако в данном случае исключение BusinessException обработает именно CustomAdvice, а не DefaultAdvice, в чём мы легко можем убедиться.

Для наглядности я немного изменил сообщение об ошибке в CustomAdvice — начал добавлять к нему дату:

На этом возможности эдвайсов не заканчиваются. Если мы посмотрим исходный код аннотации @ControllerAdvice, то увидим что эдвайс можно повесить на отдельные типы или даже пакеты. Не обязательно создавать новые аннотации или вешать его на уже существующие.

Исключение ResponseStatusException.

Сейчас речь пойдёт о формировании ответа путём выброса исключения ResponseStatusException:

@RestController
public class Example6Controller {

    @GetMapping(value = "/testResponseStatusException", produces = APPLICATION_JSON_VALUE)
    public Response testResponseStatusException(@RequestParam(required = false, defaultValue = "false") boolean exception) {
        if (exception) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "ResponseStatusException in testResponseStatusException");
        }
        return new Response("OK");
    }

}

Выбрасывая ResponseStatusException можно также возвращать пользователю определённый код статуса, в зависимости от того, что произошло в логике приложения. При этом не нужно создавать кастомное исключение и прописывать аннотацию @ResponseStatus — просто выбрасываем исключение и передаём нужный статус-код. Конечно тут возвращаемся к проблеме отсутствия тела сообщения, но в простых случаях такой подход может быть удобен.

Пример вызова:

Резюме: мы познакомились с разными способами обработки исключений, каждый из которых имеет свои особенности. В рамках большого приложения можно встретить сразу несколько подходов, но при этом нужно быть очень осторожным и стараться не переусложнять логику обработки ошибок. Иначе получится что какое-нибудь исключение обработается не в том обработчике и на выходе ответ будет отличаться от ожидаемого. Например если в приложении есть несколько эдвайсов, то при создании нового нужно убедиться, что он не сломает существующий порядок обработки исключений из старых контроллеров.
Так что будьте внимательны и всё будет работать замечательно!

Ссылка на исходники из статьи

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.

Enum Constant and Description

ACCEPTED

202 Accepted, see HTTP/1.1 documentation.

BAD_GATEWAY

502 Bad Gateway, see HTTP/1.1 documentation.

BAD_REQUEST

400 Bad Request, see HTTP/1.1 documentation.

CONFLICT

409 Conflict, see HTTP/1.1 documentation.

CREATED

201 Created, see HTTP/1.1 documentation.

EXPECTATION_FAILED

417 Expectation Failed, see HTTP/1.1 documentation.

FORBIDDEN

403 Forbidden, see HTTP/1.1 documentation.

FOUND

302 Found, see HTTP/1.1 documentation.

GATEWAY_TIMEOUT

504 Gateway Timeout, see HTTP/1.1 documentation.

GONE

410 Gone, see HTTP/1.1 documentation.

HTTP_VERSION_NOT_SUPPORTED

505 HTTP Version Not Supported, see HTTP/1.1 documentation.

INTERNAL_SERVER_ERROR

500 Internal Server Error, see HTTP/1.1 documentation.

LENGTH_REQUIRED

411 Length Required, see HTTP/1.1 documentation.

METHOD_NOT_ALLOWED

405 Method Not Allowed, see HTTP/1.1 documentation.

MOVED_PERMANENTLY

301 Moved Permanently, see HTTP/1.1 documentation.

NETWORK_AUTHENTICATION_REQUIRED

511 Network Authentication Required, see RFC 6585: Additional HTTP Status Codes.

NO_CONTENT

204 No Content, see HTTP/1.1 documentation.

NOT_ACCEPTABLE

406 Not Acceptable, see HTTP/1.1 documentation.

NOT_FOUND

404 Not Found, see HTTP/1.1 documentation.

NOT_IMPLEMENTED

501 Not Implemented, see HTTP/1.1 documentation.

NOT_MODIFIED

304 Not Modified, see HTTP/1.1 documentation.

OK

200 OK, see HTTP/1.1 documentation.

PARTIAL_CONTENT

206 Reset Content, see HTTP/1.1 documentation.

PAYMENT_REQUIRED

402 Payment Required, see HTTP/1.1 documentation.

PRECONDITION_FAILED

412 Precondition Failed, see HTTP/1.1 documentation.

PRECONDITION_REQUIRED

428 Precondition required, see RFC 6585: Additional HTTP Status Codes.

PROXY_AUTHENTICATION_REQUIRED

407 Proxy Authentication Required, see HTTP/1.1 documentation.

REQUEST_ENTITY_TOO_LARGE

413 Request Entity Too Large, see HTTP/1.1 documentation.

REQUEST_HEADER_FIELDS_TOO_LARGE

431 Request Header Fields Too Large, see RFC 6585: Additional HTTP Status Codes.

REQUEST_TIMEOUT

408 Request Timeout, see HTTP/1.1 documentation.

REQUEST_URI_TOO_LONG

414 Request-URI Too Long, see HTTP/1.1 documentation.

REQUESTED_RANGE_NOT_SATISFIABLE

416 Requested Range Not Satisfiable, see HTTP/1.1 documentation.

RESET_CONTENT

205 Reset Content, see HTTP/1.1 documentation.

SEE_OTHER

303 See Other, see HTTP/1.1 documentation.

SERVICE_UNAVAILABLE

503 Service Unavailable, see HTTP/1.1 documentation.

TEMPORARY_REDIRECT

307 Temporary Redirect, see HTTP/1.1 documentation.

TOO_MANY_REQUESTS

429 Too Many Requests, see RFC 6585: Additional HTTP Status Codes.

UNAUTHORIZED

401 Unauthorized, see HTTP/1.1 documentation.

UNSUPPORTED_MEDIA_TYPE

415 Unsupported Media Type, see HTTP/1.1 documentation.

USE_PROXY

305 Use Proxy, see HTTP/1.1 documentation.

This page lists the exceptions provided by the endpoints library as well as
HTTP status codes supported by Cloud Endpoints Frameworks.

In many situations, you might want to use common HTTP status codes to indicate
the success or failure of a user’s API request. For example, if a user is
attempting to retrieve an entity which doesn’t exist, you might want to send an
HTTP 404 status code to say that no entity exists with ID: entityId.

You can send common HTTP status codes by throwing an exception provided
by the endpoints library as follows:

Exceptions provided by Endpoints Frameworks

Endpoints Frameworks provides the following exceptions, corresponding
to specific HTTP status codes:

Exception Corresponding HTTP status code
com.google.api.server.spi.response.BadRequestException HTTP 400
com.google.api.server.spi.response.UnauthorizedException HTTP 401
com.google.api.server.spi.response.ForbiddenException HTTP 403
com.google.api.server.spi.response.NotFoundException HTTP 404
com.google.api.server.spi.response.ConflictException HTTP 409
com.google.api.server.spi.response.InternalServerErrorException HTTP 500
com.google.api.server.spi.response.ServiceUnavailableException HTTP 503

Supported HTTP status codes

Cloud Endpoints Frameworks supports a subset of HTTP status codes
in API responses. The following table describes the supported codes.

HTTP status codes Support
HTTP 2xx HTTP 200 is typically assumed by Endpoints Frameworks if the API method returns successfully.
If the API method response type is void or the return value of the API method is null , HTTP 204 is set instead.
HTTP 3xx HTTP 3xx codes aren’t supported. Use of any HTTP 3xx codes results in an HTTP 404 response.
HTTP 4xx Only the following HTTP 4xx codes are supported:

  • 400
  • 401
  • 403
  • 404
  • 409
  • 410
  • 412
  • 413

Any other HTTP 4xx codes are returned as error 404, except for the following:

  • 405 is returned as 501
  • 408 is returned as 503
HTTP 5xx All HTTP 5xx status codes are converted to HTTP 503 in the client response.

Creating your own exception classes

If you want to create other exception classes for other HTTP status codes, you
need to subclass com.google.api.server.spi.ServiceException. The
following snippet shows how to create an exception class that represents an
HTTP 408 status code:

Using unsupported HTTP status codes

It is possible to use HTTP status codes that aren’t in the preceding supported
list. Note that if you decide to do so, it might have unintended consequences
for client libraries that access the API. To use unsupported error codes,
create your own exception class, as described in the previous section, and
add a new servlet initialization parameter enableExceptionCompatibility to
EndpointsServlet:

  <servlet>
    <servlet-name>com.google.api.server.spi.EndpointsServlet</servlet-name>
    <servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
    <init-param>
      <param-name>services</param-name>
      <param-value>...</param-value>
    </init-param>
    <init-param>
      <param-name>enableExceptionCompatibility</param-name>
      <param-value>true</param-value>
    </init-param>
  </servlet>

Автор оригинала: Justin Albano.

1. введение

REST-это архитектура без состояния, в которой клиенты могут получать доступ к ресурсам на сервере и управлять ими. Как правило, службы REST используют HTTP для объявления набора ресурсов, которыми они управляют, и предоставляют API, который позволяет клиентам получать или изменять состояние этих ресурсов.

В этом уроке мы познакомимся с некоторыми из лучших практик обработки ошибок REST API, включая полезные подходы для предоставления пользователям соответствующей информации, примеры из крупномасштабных веб-сайтов и конкретную реализацию с использованием примера приложения Spring REST.

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

Обработка ошибок для ОТДЫХА с пружиной

Spring ResponseStatusException

Когда клиент делает запрос к HTTP — серверу — и сервер успешно получает запрос – сервер должен уведомить клиента, был ли запрос успешно обработан или нет . HTTP выполняет это с помощью пяти категорий кодов состояния:

  • 100-уровневый (информационный) — Сервер подтверждает запрос
  • 200-уровень (Успех) — Сервер выполнил запрос, как и ожидалось
  • 300-уровень (Перенаправление) — Клиенту необходимо выполнить дальнейшие действия для завершения запроса
  • 400-level (Ошибка клиента) — Клиент отправил неверный запрос
  • 500-level (Server error) — Серверу не удалось выполнить допустимый запрос из-за ошибки с сервером

Основываясь на коде ответа, клиент может предположить результат конкретного запроса.

3. Обработка Ошибок

Первым шагом в обработке ошибок является предоставление клиенту правильного кода состояния. Кроме того, нам может потребоваться предоставить дополнительную информацию в теле ответа.

3.1. Основные Ответы

Самый простой способ обработки ошибок-это ответить соответствующим кодом состояния .

Некоторые распространенные коды ответов включают в себя:

  • 400 Плохой запрос — Клиент отправил недопустимый запрос — например, отсутствует требуемое тело запроса или параметр
  • 401 Неавторизованный — Клиенту не удалось пройти аутентификацию на сервере
  • 403 Forbidden — Клиент аутентифицирован, но не имеет разрешения на доступ к запрошенному ресурсу
  • 404 Не найден — Запрошенный ресурс не существует
  • 412 Ошибка предварительного условия — одно или несколько условий в полях заголовка запроса оцениваются как ложные.
  • 500 Внутренняя ошибка сервера — На сервере произошла общая ошибка
  • 503 Услуга Недоступна — Запрошенная услуга недоступна

Будучи базовыми, эти коды позволяют клиенту понять широкий характер возникшей ошибки. Например, мы знаем, если получаем ошибку 403, что у нас нет разрешений на доступ к запрошенному ресурсу.

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

500 ошибок сигнализируют о том, что при обработке запроса на сервере возникли некоторые проблемы или исключения. Как правило, эта внутренняя ошибка не является делом нашего клиента.

Следовательно, чтобы свести к минимуму такого рода ответы клиенту, мы должны усердно пытаться обрабатывать или улавливать внутренние ошибки и отвечать другими соответствующими кодами состояния, где это возможно . Например, если исключение возникает из-за того, что запрошенный ресурс не существует, мы должны выставить это как ошибку 404, а не 500.

Это не означает, что 500 никогда не должны быть возвращены, только то, что он должен использоваться для непредвиденных условий – таких как отключение службы – которые мешают серверу выполнить запрос.

3.2. Ответы На Весенние ошибки По Умолчанию

Эти принципы настолько распространены, что Spring кодифицировала их в своем механизме обработки ошибок по умолчанию .

Чтобы продемонстрировать это, предположим, что у нас есть простое приложение Spring REST , которое управляет книгами, с конечной точкой для извлечения книги по ее идентификатору:

curl -X GET -H "Accept: application/json" http://localhost:8082/spring-rest/api/book/1

Если нет книги с идентификатором 1, мы ожидаем, что наш контроллер выдаст исключение BookNotFoundException . Выполняя GET на этой конечной точке, мы видим, что это исключение было выброшено, и тело ответа:

{
    "timestamp":"2019-09-16T22:14:45.624+0000",
    "status":500,
    "error":"Internal Server Error",
    "message":"No message available",
    "path":"/api/book/1"
}

Обратите внимание, что этот обработчик ошибок по умолчанию включает метку времени возникновения ошибки, код состояния HTTP, заголовок (поле error ), сообщение (по умолчанию пустое) и URL-адрес, по которому произошла ошибка.

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

Кроме того, обратите внимание, что Spring автоматически возвращает код состояния HTTP 500 при вызове нашего BookNotFoundException . Хотя некоторые API будут возвращать код состояния 500 или другие общие коды, как мы увидим с API Facebook и Twitter — для всех ошибок ради простоты лучше всего использовать наиболее конкретный код ошибки, когда это возможно .

В нашем примере мы можем добавить @ControllerAdvice так, чтобы при возникновении BookNotFoundException наш API возвращал статус 404 для обозначения Not Found вместо 500 Internal Server Error .

3.3. Более Подробные Ответы

Как видно из приведенного выше примера Spring, иногда кода состояния недостаточно, чтобы показать специфику ошибки. При необходимости мы можем использовать тело ответа, чтобы предоставить клиенту дополнительную информацию. При предоставлении подробных ответов мы должны включать:

  • Error — Уникальный идентификатор ошибки
  • Сообщение — Краткое читаемое человеком сообщение
  • Детализация — более подробное объяснение ошибки

Например, если клиент отправляет запрос с неверными учетными данными, мы можем отправить ответ 401 с телом:

{
    "error": "auth-0001",
    "message": "Incorrect username and password",
    "detail": "Ensure that the username and password included in the request are correct"
}

Поле error не должно совпадать с кодом ответа . Вместо этого это должен быть код ошибки, уникальный для нашего приложения. Как правило, нет никакого соглашения для поля error , ожидайте, что оно будет уникальным.

Обычно это поле содержит только буквенно-цифровые символы и соединительные символы, такие как тире или подчеркивание. Например, 0001 , auth-0001 и incorrect-user-pass являются каноническими примерами кодов ошибок.

Часть тела message обычно считается презентабельной на пользовательских интерфейсах. Поэтому мы должны перевести это название, если мы поддерживаем интернационализацию . Таким образом, если клиент отправляет запрос с заголовком Accept-Language , соответствующим французскому языку, значение title должно быть переведено на французский.

Часть detail предназначена для использования разработчиками клиентов , а не конечным пользователем , поэтому перевод не требуется.

Кроме того, мы также можем предоставить URL — адрес — например, поле help , по которому клиенты могут перейти для получения дополнительной информации:

{
    "error": "auth-0001",
    "message": "Incorrect username and password",
    "detail": "Ensure that the username and password included in the request are correct",
    "help": "https://example.com/help/error/auth-0001"
}

Иногда мы можем захотеть сообщить более чем об одной ошибке для запроса . В этом случае мы должны вернуть ошибки в список:

{
    "errors": [
        {
            "error": "auth-0001",
            "message": "Incorrect username and password",
            "detail": "Ensure that the username and password included in the request are correct",
            "help": "https://example.com/help/error/auth-0001"
        },
        ...
    ]
}

И когда возникает единственная ошибка, мы отвечаем списком, содержащим один элемент. Обратите внимание, что ответ с несколькими ошибками может быть слишком сложным для простых приложений. Во многих случаях достаточно ответить на первую или самую значительную ошибку.

3.4. Стандартизированные Органы реагирования

В то время как большинство API REST следуют аналогичным соглашениям, специфика обычно варьируется, включая имена полей и информацию, включенную в тело ответа. Эти различия затрудняют единообразную обработку ошибок библиотеками и фреймворками.

Стремясь стандартизировать обработку ошибок REST API, |/IETF разработала RFC 7807 , который создает обобщенную схему обработки ошибок .

Эта схема состоит из пяти частей:

  1. тип — Идентификатор URI, который классифицирует ошибку
  2. заголовок — Краткое, читаемое человеком сообщение об ошибке
  3. status — Код ответа HTTP (необязательно)
  4. подробно — Читаемое человеком объяснение ошибки
  5. экземпляр — URI, идентифицирующий конкретное возникновение ошибки

Вместо того чтобы использовать наше пользовательское тело ответа на ошибку, мы можем преобразовать ваше тело в:

{
    "type": "/errors/incorrect-user-pass",
    "title": "Incorrect username or password.",
    "status": 401,
    "detail": "Authentication failed due to incorrect username or password.",
    "instance": "/login/log/abc123"
}

Обратите внимание, что поле type классифицирует тип ошибки, в то время как instance идентифицирует конкретное возникновение ошибки аналогично классам и объектам соответственно.

Используя URI, клиенты могут следовать этим путям, чтобы найти дополнительную информацию об ошибке таким же образом, как ссылки HATEOAS могут использоваться для навигации по REST API.

Придерживаться RFC 7807 необязательно, но выгодно, если требуется однородность.

4. Примеры

Описанные выше методы являются общими для некоторых наиболее популярных API REST. В то время как конкретные имена полей или форматов могут варьироваться между сайтами, общие шаблоны почти универсальны .

4.1. Твиттер

Например, давайте отправим запрос GET без предоставления необходимых аутентификационных данных:

curl -X GET https://api.twitter.com/1.1/statuses/update.json?include_entities=true

API Twitter отвечает ошибкой со следующим телом:

{
    "errors": [
        {
            "code":215,
            "message":"Bad Authentication data."
        }
    ]
}

Этот ответ включает в себя список, содержащий одну ошибку, с ее кодом ошибки и сообщением. В случае Twitter нет подробного сообщения, и общая ошибка — а не более конкретная ошибка 401 — используется для обозначения того, что аутентификация не удалась.

Иногда более общий код состояния проще реализовать, как мы увидим в нашем весеннем примере ниже. Это позволяет разработчикам перехватывать группы исключений и не различать код состояния, который должен быть возвращен. Однако, когда это возможно, следует использовать наиболее конкретный код состояния .

4.2. Facebook

Подобно Twitter, Facebook Graph REST API также включает подробную информацию в свои ответы.

Например, давайте выполним POST-запрос для аутентификации с помощью API Facebook Graph:

curl -X GET https://graph.facebook.com/oauth/access_token?client_id=foo&client_secret=bar&grant_type=baz

Мы получаем следующую ошибку:

{
    "error": {
        "message": "Missing redirect_uri parameter.",
        "type": "OAuthException",
        "code": 191,
        "fbtrace_id": "AWswcVwbcqfgrSgjG80MtqJ"
    }
}

Как и Twitter, Facebook также использует общую ошибку — а не более конкретную ошибку 400-го уровня — для обозначения сбоя. В дополнение к сообщению и числовому коду Facebook также включает в себя поле type , которое классифицирует ошибку, и идентификатор трассировки ( fbtrace_id ), который действует как внутренний идентификатор поддержки .

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

В этой статье мы рассмотрели некоторые из лучших практик обработки ошибок REST API, в том числе:

  • Предоставление конкретных кодов состояния
  • Включение дополнительной информации в органы реагирования
  • Единообразная обработка исключений

Хотя детали обработки ошибок будут варьироваться в зависимости от приложения, эти общие принципы применимы почти ко всем API REST и должны соблюдаться, когда это возможно .

Это не только позволяет клиентам последовательно обрабатывать ошибки, но и упрощает код, который мы создаем при реализации REST API.

Код, на который ссылается эта статья, доступен на GitHub .

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 Thrown\n");
    }

    @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 %s\n",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.

Понравилась статья? Поделить с друзьями:
  • Http ошибка при загрузке файла 413
  • Https прокси ошибка сертификата электронный бюджет
  • Http ошибка доступ запрещен
  • Https ошибка 12157 при подключении
  • Http ошибка 602