There was an unexpected error type internal server error status 500 spring



Whitelabel Error Page — GreetingController annotation might be wrong #27

Comments

CarloAtDexels commented Dec 29, 2018 •

When the GreetingController has the annotation @Controller annotation the URL to localhost:8080/greeting does not work. Changing the annotation to @RestController (as suggested in https://stackoverflow.com/questions/40797628/spring-boot-thymeleaf-viewresolver-on-rest-controllers) fixes the error.

In the browser the error is shown as:

In the terminal the error:

The text was updated successfully, but these errors were encountered:

dsyer commented Dec 30, 2018

Works for me (Spring Boot configures the view resolver, and the controller in this case is not a @RestController ). You are using the code from the “complete” sample, right?

CarloAtDexels commented Dec 30, 2018

I used the complete sample, added the maven pom and then run Application. But if it occurs only for me I’ll close this issue.

dsyer commented Dec 30, 2018

When you say “added the maven pom” what does that mean exactly? There is already a pom.xml in the complete sample. If you use it, I expect it will work.

CarloAtDexels commented Dec 31, 2018

I got it to work and figured out what went wrong!

I used the IntelliJ functionality VCS -> Checkout from version control -> Git to add an existing project. This does not add the pom.xml. Also the folder structure is different from the complete sample. It sets the main folder as the project root. Thymeleaf tries to search for the template under src/main/resources/templates when the template is under main/resources/templates. This caused the org.thymeleaf.exceptions.TemplateInputException

Footer

© 2023 GitHub, Inc.

You can’t perform that action at this time.

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.

Источник

Spring Boot REST API – обработка исключений. Часть 1

В этой статье — обзор способов обработки исключений в Spring Boot.

Приложение

Мы рассмотрим простое REST API приложение с одной сущностью Person и с одним контроллером.

При старте приложения выполняется скрипт data.sql, который добавляет в базу данных H2 одну строку — Person c То есть Person c в базе отсутствует.

При попытке запросить Person c id=2:

метод контроллера getPerson() выбрасывает исключение — в данном случае наше пользовательское MyEntityNotFoundException:

BasicErrorController

По умолчанию все исключения попадают на адрес /error в BasicErrorController, в метод error():

Если поставить в этом методе break point, то будет понятно, из каких атрибутов собирается ответное JSON сообщение.

Проверим ответ по умолчанию, запросив с помощью клиента Postman отсутствующий Person, чтобы выбросилось MyEntityNotFoundException:

Причем для того, чтобы поле message было непустым, в application.properties нужно включить свойство:

Обратите внимание, что поле status JSON-тела ответа дублирует реальный http-код ответа. В Postman он виден:

Поле message заполняется полем message выброшенного исключения.

Независимо от того, какое исключение выбросилось: пользовательское или уже существующее, ответ стандартный — в том смысле, что набор полей одинаковый. Меняется только внутренняя часть и, возможно, код ответа (он не обязательно равен 500, некоторые существующие в Spring исключения подразумевают другой код).

Но структура ответа сохраняется.

Не пользовательское исключение

Например, если изменить код, убрав пользовательское MyEntityNotFoundException, то при отсутствии Person исключение будет все равно выбрасываться, но другое:

findById() возвращает тип Optional, а Optional.get() выбрасывает исключение NoSuchElementException с другим сообщением:

в итоге при запросе несуществующего Person:

ответ сохранит ту же структуру, но поменяется поле message:

Вернем обратно пользовательское исключение MyEntityNotFoundException.

Попробуем поменять ответ, выдаваемый в ответ за запрос. Статус 500 для него явно не подходит.

Рассмотрим способы изменения ответа.

@ResponseStatus

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

Для этого аннотируем наше исключение:

Теперь ответ будет таким:

@ControllerAdvice

Есть еще более мощный способ изменить ответ — @ControllerAdvice, и он имеет больший приоритет, чем @ResponseStatus.

В @ControllerAdvice можно не только изменить код ответа, но и тело. К тому же один обработчик можно назначить сразу для нескольких исключений.

Допустим мы хотим, чтобы ответ на запрос несуществующего Person имел такую структуру:

Для этого создадим обработчик в @ControllerAdvice, который перехватывает наше исключение MyEntityNotFoundException:

Теперь в ответ на запрос

мы получаем статус 404 с телом:

Но помимо MyEntityNotFoundException, наш обработчик поддерживает и javax.persistence.EntityNotFoundException (см. код выше).

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

Это исключение EntityNotFoundException возникает в методе updatePerson() в контроллера. А именно, когда мы обращаемся с помощью метода PUT к несуществующей сущности в попытке назначить ей имя:

В этом случае мы тоже получим ответ с новой структурой:

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

Последовательность проверок

Обратите внимание, что MyEntityNotFoundException мы «обработали» дважды — изменили код с помощью @ResponseStatus (1) и прописали в @ContollerAdvice — тут изменили как код, так и тело ответа (2). Эти обработки могли быть противоречивы, но существует приоритет:

  • Когда выбрасывается исключение MyEntityNotFoundException, сначала Spring проверяет @ControllerAdvice-класс. А именно, нет ли в нем обработчика, поддерживающего наше исключение. Если обработчик есть, то исключение в нем и обрабатывается. В этом случае код @ResponseStatus значения не имеет, и в BasicErrorController исключение тоже не идет.
  • Если исключение не поддерживается в @ControllerAdvice-классе, то оно идет в BasicErrorController. Но перед этим Spring проверяет, не аннотировано ли исключение аннотацией @ResponseStatus. Если да, то код ответа меняется, как указано в @ResponseStatus. Далее формируется ответ в BasicErrorController.
  • Если же первые два условия не выполняются, то исключение обрабатывается сразу в BasicErrorController — там формируется стандартный ответ со стандартным кодом (для пользовательских исключений он равен 500).

Но и стандартный ответ можно изменить, для этого нужно расширить класс DefaultErrorAttributes.

Попробуем это сделать.

Изменение DefaultErrorAttributes

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

В Map errorAttributes перечисляются поля ответа. Мы взяли их из родительского метода и добавили свое поле newAttribute.

Чтобы выполнить проверку, надо убрать @ControllerAdvice, поскольку он самый приоритетный и с ним мы даже не дойдем до BasicErrorController со «стандартными» полями.

Далее запросим ресурс:

В JSON-ответе появилось дополнительное поле.

ResponseStatusException

Рассмотрим еще один вариант, позволяющий сразу протолкнуть код ответа и сообщение стандартные поля, не прописывая обработку пользовательских или встроенных исключений. А вместо этого просто выбросив специально предназначенное исключение ResponseStatusException.

Изменим код метода контроллера getPerson():

Теперь тут не выбрасывается ни MyEntityNotFoundException, ни java.util.NoSuchElementException. А выбрасывается ResponseStatusException с заданным сообщением и кодом ответа.

Теперь при запросе

ответ будет таким:

Как код, так и сообщение появилось в полях стандартного ответа.

ResponseStatusException не вступает в конкуренцию ни со способом @ControllerAdvice, ни с @ResponseStatus — просто потому, что это другое исключение.

Итоги

Код примера доступен на GitHub. В следующей части мы унаследуем RestExceptionHandler от ResponseEntityExceptionHandler. Это класс-заготовка, которая уже обрабатывает ряд исключений.

Источник

Error handling for a Spring-based REST API

Spring Boot provides pretty nifty defaults to handle exceptions and formulate a helpful response in case anything goes wrong. Still, for any number of reasons, an exception can be thrown at runtime and the consumers of your API may get a garbled exception message (or worse, no message at all) with a 500 Internal Server Error response.

Such a scenario is undesirable, because of the

  • usability concerns Although relevant, the default exception message may not be helpful to the consumers of your API.
  • security concerns The exception message may expose the internal details of your application to anyone using the API.

This is a pretty common occurrence and customizing the error response so that it is easy to comprehend is often one of the requirements of the API design. Like many other niceties, Spring Boot does a lot of heavy lifting for you; it does not send binding errors (due to validation failure), exceptions, or stacktrace in a response unless you configure them otherwise (see server.error keys under Server Properties available for a Spring application).

In this post, we’ll explore some of the ways to customize error responses returned by a REST API. We’ll also cover some usecases when Spring Security comes into the picture.

The code written for this post uses:

  • Java 14
  • Spring Boot 2.3.2
  • Postgres 13
  • Maven 3.6.3

You can launch an instance of Postgres with Docker using the following Compose file.

Execute the following command to launch the container.

Configure the project

Generate a Spring Boot project with Spring Initializr, and add spring-boot-starter-web , spring-boot-starter-data-jdbc , and postgresql as dependencies.

Your pom.xml would look like this.

Rename application.properties to application.yml , open the file, and add the following database configuration.

Create an API

Say, you want to save a Book object, described by the following entity.

The id will be of type SERIAL in Postgres which will be automatically incremented by the database.

Create the required table using the following SQL statement.

Note that the genre field is backed by an enum described as follows.

Define a BookRepository to perform database operations.

Create a BookController to expose some endpoints.

Launch the application and try to access a non-existent book.

The operation failed, obviously, and you received a JSON response with some useful fields. These fields are managed by DefaultErrorAttributes class and the response is formed by an implementation of HandlerExceptionResolver . However, we can already see what is amiss here.

  • There is no message clarifying what exactly went wrong.
  • It is the client that passed the incorrect id but the status code indicates it is a server error.
  • Since the record was not found, a 404 Not found would’ve been the accurate status.

Send the correct status using ResponseStatusException

The fastest way to address the issue with status is to throw Spring provided ResponseStatusException which accepts an HttpStatus .

Launch the application and try the request again.

We are getting the correct status now but it’d be useful to let the client know the cause of this issue. Also, it’d be useful to throw relevant custom exceptions instead of the same exception everywhere.

Exception handling with @ControllerAdvice and @ExceptionHandler

If you examine ResponseStatusException , you’d notice that it saves the message in a variable reason . We’d prefer to map this to the message key in the response above. Let’s create a class RestResponse that can hold this response.

Let’s also create a RestResponseBuilder that can provide a fluent API to create RestResponse objects from a variety of inputs.

We can configure a @ControllerAdvice that can trigger methods to handle ResponseStatusException . This method has to be annotated by @ExceptionHandler which specifies what type of exceptions a method can handle.

In the above codeblock, handleStatusException will be invoked whenever a ResponseStatusException is thrown and instead of the boilerplate Spring response, an instance of RestResponse would be returned with the reason.

Note that we’re also injecting a WebRequest instance to get the path . Besides path , a WebRequest can provide a whole lot of other details about the request and client. Also, you may want to log the exceptions in the handler method, else Spring will not print them on the logs.

At this point, you may continue to throw ResponseStatusException throughout your application or you can choose to extend it to define custom exceptions with specific HttpStatus . But what about exceptions that are thrown by a third-party?

Handling Exceptions thrown by a third-party

One approach is to rethrow such exceptions as ResponseStatusException ; this can be done wherever you encounter them. Another way is to write handler methods to intercept them in the @RestControllerAdvice above. It makes things a bit cleaner, but you can’t handle every exception out there. To deal with this, you can write a generic exception handler that may handle Exception class.

To get you started, Spring offers a ResponseEntityExceptionHandler class that provides a huge number of handlers for the exceptions thrown by Spring. You can extend this class and implement your handlers on top of it. Even better, you can override the existing handlers to customize their behavior. Let’s modify RestErrorHandler as follows.

A lot of things are going on here.

  • handleResponseStatusException method specifically handles ResponseStatusException
  • handleStatusException method handles exceptions when the status is not an error status (the statuses in 1xx, 2xx and 3xx series)
  • handleEveryException method handles all other exceptions and sets their status as 500 Internal Server Error
  • we’re also overriding handleExceptionInternal to translate the exceptions thrown by Spring to return RestResponse
  • finally, we’ve defined handleAllExceptions handler that serves as a catch-all. If no specific error handler is found for an exception, this method will be invoked.

To test this, launch the application and try to put a new book with genre as kids .

Since we’ve not configured any constant named kids in the Genre enum, Jackson will serialize the genre field as null which would violate NOT NULL constraint in the database. The application will throw a DataAccessException as a result. Since there’s no handler defined for this exception in RestErrorHandler class, the handleAllExceptions handler method will be invoked, sending the response seen above.

Error Handling for Spring Security

What happens if you add Spring Security in our application? After adding the JWT-based authentication (from the post Securing Spring Boot APIs with JWT Authentication) in our application, try to hit any BookController endpoint.

You’d notice that the exception handler that we configured earlier is not being invoked and we’re getting the default response. This happens because our custom advice is invoked after Spring Security’s servlet filters have verified the user. Since the user authentication failed, the handlers were never invoked.

Handle Authentication failure with AuthenticationEntryPoint

AuthenticationEntryPoint ’s commence method is called when an AuthenticationException is thrown. You can implement this interface to return a customized response.

You can be as generic or versatile in handling different types of AuthenticationException s as you need. Configure this entrypoint in the security configuration as follows.

Handle Authorization failure with AccessDeniedHandler

To handle authorization failures, you can implement the AccessDeniedHandler interface.

Similar to the AuthenticationEntryPoint approach, you can handle different scenarios that can lead to authorization failure. Call the handle method implemented above whenever such scenarios are encountered. An example is given below.

For this to work, you’ll have to inject the CustomAccessDeniedHandler in CustomAuthorizationFilter through the security configuration, as follows.

Launch the application again and try accessing an endpoint without any authentication details.

A 401 Unauthenticated error was sent, informing that authentication details were not sufficient for this request. Now, add an expired Bearer token in an Authorization header and send the request again.

This time, a 403 Forbidden error was sent indicating that even though the authentication was successful, the token was invalid. Generate a new token by sending a login request.

You’ll receive a response 200 OK with an Authorization header that contains a token.

Using this token, try sending the request again.

You’ll receive a 200 OK with a list of books saved in the database, as expected.

Источник

Читайте также:  Error dev sda unrecognised disk label
Оцените статью
toolgir.ru
Adblock
detector