Bean creation errors for request-scoped beans - java

When a Spring bean is annotated with SCOPE_REQUEST, it is created and destroyed every time a HTTP request is received by the servlet. If this bean creation fails, a server error is sent back to the caller.
In this trivial example, the creation of the MyInputs bean is dependent on the contents of the HTTP request.
#Configuration
class ApplicationConfiguration {
#Bean
#Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyInputs myInputs(HttpServletRequest request) {
String header1 = request.getHeader("header1");
if (header1 == null) {
throw new MyException("header1 is missing");
}
return new MyInputs(header1);
}
}
If the HTTP request does not contain a required header, a BeanCreationException will be thrown. This is translated into an unhelpful "500 Internal Server Error" response.
I would like to return a more user-friendly response code and body, for example, a "400 Bad Request" with a helpful message. How do I customize this response translation? I cannot find any lifecycle hooks which will allow this.
Note: This is how the request-scoped bean is consumed:
#RestController
public class MyController {
private final Provider<MyInputs> myInputsProvider;
#Autowired
public MyController(Provider<MyInputs> myInputsProvider) {
this.myInputsProvider = myInputsProvider;
}
#GetMapping("/do-stuff")
public void doStuff() {
// Get the inputs for the current request
MyInputs myInputs = myInputsProvider.get();
// ...
}
}

You can use #ControllerAdvice annotation in order to handle exceptions after are thrown.
Also you need to use #ExceptionHandler in order to handle the exception.
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(MyException.class)
public final ResponseEntity<CustomError> handleException(MyException ex, WebRequest request) {
CustomError error = new CustomError();
error.setMessage(ex.getMessage());
error.setStatus(HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(error, null, HttpStatus.BAD_REQUEST);
}
}

Related

Is it possible to have multiple ExceptionHandler in ControllerAdvice for same Exception but different web requests?

I have a task to handle all exceptions in my project via #ControllerAdvice. The project has multiple controller methods. I have a custom exception which is thrown by some of these controller methods.
For handling this exception, I have a class annotated with #ControllerAdvice and I have written some #ExceptionHandler methods.
#EnableWebMvc
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(WebServiceContactException.class)
#ResponseStatus(value = HttpStatus.PRECONDITION_FAILED)
#ResponseBody
public MyResponse dealWebServiceContactExceptionForAddElement(WebServiceContactException e, AddRequest request) {
int errorCode = HttpStatus.PRECONDITION_FAILED.value();
LOGGER.error("Failed to contact service for " + request.getFunctionalId(), e);
auditTrail.reportFailureInfo(e.getMessage());
return MyResponse.Builder.newBuilder(errorCode)
.setDescription(Constants.EXECUTION_FAILED)
.build();
}
#ExceptionHandler(WebServiceContactException.class)
#ResponseStatus(value = HttpStatus.PRECONDITION_FAILED)
#ResponseBody
public MyResponse dealWebServiceContactExceptionForDeleteElement(WebServiceContactException e, DeleteRequest request) {
int errorCode = HttpStatus.PRECONDITION_FAILED.value();
LOGGER.error("Failed to contact service for " + request.getFunctionalId(), e);
auditTrail.reportFailureInfo(e.getMessage());
return MyResponse.Builder.newBuilder(errorCode)
.setDescription(Constants.EXECUTION_FAILED)
.build();
}
}
Doing this throws an IllegalStateException
Caused by: java.lang.IllegalStateException: Ambiguous #ExceptionHandler method mapped for [class com.action.resource.WebServiceContactException]
Is there anyway I can define multiple #ExceptionHandler methods dealing with same exception WebServiceContactException but with different web requests?
PS : I know I can write #ExceptionHandler method without giving request object in argument but I really need that object to populate certain fields in my logs for monitoring purposes.

How to increase timeout AsyncRestTemplate class?

I have developed some async web services with spring framework and REST, I have consumed it from a client created with spring class AsyncRestTemplate. Class return an object ListenableFuture<ResponseEntity<T>> (with the method getForEntity), which brings the value returned by the web service (with the method .get():<T>) . It works fine, however when the web service takes a lot time the method isDone() of ListenableFuture class return a value true, even when the web service has not finished to work.
If I try to recover the web service response with the method get() in the client and It has late a lot of time, I always get the follows message:
"timestamp": "2018-05-29T22:42:26.978+0000",
"status": 500,
"error": "Internal Server Error",
"message": "java.util.concurrent.ExecutionException: org.springframework.web.client.HttpServerErrorException: 503 null",
"path": "/client/result"
Does someone knows how can i solve the problem?. I want the client shows me the web service response, even when the web service takes a lot time (I want to increase the timeout).
The server codes are the following:
Configuration Class:
#Configuration
#EnableAsync
public class ConfigurationClass {
#Bean
public Executor threadPoolTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
}
Controller class:
#RestController
#RequestMapping("/server")
public class ControllerClass {
#GetMapping("/start")
#Async
public CompletableFuture<String> callService() throws InterruptedException{
Thread.sleep(100000L);
return CompletableFuture.completedFuture("OK");
}
}
The client code (consumer) is the following:
#RestController
#RequestMapping("/client")
public class ControllerClass {
private ListenableFuture<ResponseEntity<String>> entity;
#GetMapping("/start")
#Async
public void callService() throws InterruptedException {
AsyncRestTemplate restTemplate = new AsyncRestTemplate();
entity = restTemplate.getForEntity("http://localhost:8080/server/start",
String.class);
}
#GetMapping("/state")
public boolean getState() {
try {
return entity.isDone();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#GetMapping("/result")
public ResponseEntity<String> getResult() {
try {
return entity.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
I tried to increase the property timeout in application.property file, but it did not work.
# SPRING MVC (WebMvcProperties)
spring.mvc.async.request-timeout= 500000 # Amount of time before asynchronous request handling times out.
Thanks for your help, Regards.
For a better maintenance, you can config a AsyncRestTemplate bean:
#Bean
public AsyncRestTemplate asyncRestTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setTaskExecutor(new SimpleAsyncTaskExecutor());
factory.setConnectTimeout(1000);//milliseconds
factory.setReadTimeout(2000);//milliseconds
return new AsyncRestTemplate(factory);
}
And then, autowired this bean:
#Autowired
private AsyncRestTemplate restTemplate;
After that, update your callService:
#GetMapping("/start")
public void callService() throws InterruptedException {
entity = restTemplate.getForEntity("http://localhost:8080/server/start",
String.class);
}
You can remove the #Async Annotation as the AsyncRestTemplate is Asynchronous.

How to catch all unhandled exceptions (i.e. without existing #ExceptionHandler) in Spring MVC?

I want to let HandlerExceptionResolver resolve any Exceptions that I don't explicit catch via #ExceptionHandler annotation.
Anyways, I want to apply specific logic on those exceptions. Eg send a mail notification or log additionally. I can achieve this by adding a #ExceptionHandler(Exception.class) catch as follows:
#RestControllerAdvice
public MyExceptionHandler {
#ExceptionHandler(IOException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Object io(HttpServletRequest req, Exception e) {
return ...
}
#ExceptionHandler(Exception.class)
public Object exception(HttpServletRequest req, Exception e) {
MailService.send();
Logger.logInSpecificWay();
//TODO how to continue in the "normal" spring way with HandlerExceptionResolver?
}
}
Problem: if I add #ExceptionHandler(Exception.class) like that, I can catch those unhandled exceptions.
BUT I cannot let spring continue the normal workflow with HandlerExceptionResolver to create the response ModelAndView and set a HTTP STATUS code automatically.
Eg if someone tries a POST on a GET method, spring by default would return a 405 Method not allowed. But with an #ExceptionHandler(Exception.class) I would swallow this standard handling of spring...
So how can I keep the default HandlerExceptionResolver, but still apply my custom logic?
To provide a complete solution: it works just by extending ResponseEntityExceptionHandler, as that handles all the spring-mvc errors.
And the ones not handled can then be caught using #ExceptionHandler(Exception.class).
#RestControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
MailService.send();
Logger.logInSpecificWay();
return ... custom exception
}
}
Well, I was facing the same problem some time back and have tried several ways like extending ResponseEntityExceptionHandler but all them were solving some problems but creating other ones.
Then I have decided to go with a custom solution which was also allowing me to send additional information and I have written below code
#RestControllerAdvice
public class MyExceptionHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<Object> handleNumberFormatException(NumberFormatException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, "Please enter a valid value"), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, ex.getMessage()), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex) {
return new ResponseEntity<>(getBody(FORBIDDEN, ex, ex.getMessage()), new HttpHeaders(), FORBIDDEN);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
return new ResponseEntity<>(getBody(INTERNAL_SERVER_ERROR, ex, "Something Went Wrong"), new HttpHeaders(), INTERNAL_SERVER_ERROR);
}
public Map<String, Object> getBody(HttpStatus status, Exception ex, String message) {
log.error(message, ex);
Map<String, Object> body = new LinkedHashMap<>();
body.put("message", message);
body.put("timestamp", new Date());
body.put("status", status.value());
body.put("error", status.getReasonPhrase());
body.put("exception", ex.toString());
Throwable cause = ex.getCause();
if (cause != null) {
body.put("exceptionCause", ex.getCause().toString());
}
return body;
}
}
Create classes for exception handling in this way
#RestControllerAdvice
public class MyExceptionHandler extends BaseExceptionHandler {
}
public class BaseExceptionHandler extends ResponseEntityExceptionHandler {
}
Here ResponseEntityExceptionHandler is provided by spring and override the several exception handler methods provided by it related to the requestMethodNotSupported,missingPathVariable,noHandlerFound,typeMismatch,asyncRequestTimeouts ....... with your own exception messages or error response objects and status codes
and have a method with #ExceptionHandler(Exception.class) in MyExceptionHandler where the thrown exception comes finally if it doesn't have a matching handler.
I had the same issue and solved it creating a implementation of the interface HandlerExceptionResolver and removing the generic #ExceptionHandler(Exception.class) from the generic handler method.
.
It works this way:
Spring will try to handle the exception calling MyExceptionHandler first, but it will fail to find a handler because the annotation was removed from the generic handler. Next it will try other implementations of the interface HandlerExceptionResolver. It will enter this generic implementation that just delegates to the original generic error handler.
After that, I need to convert the ResponseEntity response to ModelAndView using MappingJackson2JsonView because this interface expects a ModelAndView as return type.
#Component
class GenericErrorHandler(
private val errorHandler: MyExceptionHandler,
private val objectMapper: ObjectMapper
) : HandlerExceptionResolver {
override fun resolveException(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception): ModelAndView? {
// handle exception
val responseEntity = errorHandler.handleUnexpectedException(ex)
// prepare JSON view
val jsonView = MappingJackson2JsonView(objectMapper)
jsonView.setExtractValueFromSingleKeyModel(true) // prevents creating the body key in the response json
// prepare ModelAndView
val mv = ModelAndView(jsonView, mapOf("body" to responseEntity.body))
mv.status = responseEntity.statusCode
mv.view = jsonView
return mv
}
}

Testing REST endpoints with custom exception handling

I am working on a project with Spring microservices (modules) and I want to test my REST endpoint using MockMvc. My testing works fine for cases where the request is valid but it is not working when requesting a url that is invalid. By not working I mean my custom exception handler (#ControllerAdvice) does not get called, the exception gets thrown and the test fails.
My exception handler and testing class are implemented in different modules.
common-module (ExceptionHandler)
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex, HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
if (ex instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
country-module
This is where my REST endpoint and Testing class are implemented. The common module dependency is included in this module's pom.xml and the packages are scanned through the main class.
CountryApplication.java
#EnableCaching
#EnableDiscoveryClient
#EnableAspectJAutoProxy
#SpringBootApplication(scanBasePackages = {
"com.something1.something2.something3.common.exception",
"com.something1.something2.something3.common.util.logged",
"com.something1.something2.something3.country"
})
public class CountryApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(CountryApplication.class, args);
}
...
}
CountryService.java
This is a method in my Service class.
#GetMapping("/{id:\\d+}")
public CountryDTO getCountryById(#PathVariable("id") Integer id) throws CoreException {
Country countryEntity = this.countryRepository.findOne(id);
// requesting for id that does not exist
if (countryEntity == null) {
throw new CoreException(CoreError.ENTITY_NOT_FOUND);
}
return this.countryMapper.daoToDto(countryEntity);
}
CountryServiceTest.java
#SpringBootTest
#AutoConfigureMockMvc
#AutoConfigureTestDatabase
#RunWith(SpringRunner.class)
public class CountryServiceTest {
...
#Autowired
private MockMvc mockMvc;
#Test
public void getByIdTest() throws Exception {
// Get by id exists
mockMvc.perform(get("/2"))
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andDo(print());
// Get by id not exists. NOT WORKING
mockMvc.perform(get("/100000"))
.andExpect(status().isNotFound())
.andExpect(content().contentType(contentType));
}
}
As I described above, the problem is that at the second request of the test method, the CoreExceptionHandler does not get called and the test fails throwing a:
NestedServletException: Request processing failed; nested exception is com.something1.something2.something3.common.exception.CoreException.
The dependency for the common module is well configured (at least when I am deploying in non-test mode) since I am using it for other things too, plus the ExceptionHandler gets called when I am not testing.
Another strange thing is that when I am deploying my Test, Spring Boot's logs show that the CoreExceptionHandler gets detected. This is the line. Detected #ExceptionHandler methods in coreExceptionHandler
There are two problems as explained below:
(1) ControllerAdvice not being set for MockMvc object in your CountryServiceTest class, which can be done as shown below:
MockMvc mockMvc = standaloneSetup(yourController)
.setHandlerExceptionResolvers(new CoreExceptionHandler())
.build();
(2) Because CoreException is wrapper by NestedServletException by the Spring Container, you need to use exception.getCause() to check your exception as shown below:
#ControllerAdvice
public class CoreExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorMessageDTO> handleException(Exception ex,
HttpServletRequest request) {
// Getting servlet request URL
String uri = request.getRequestURI();
HttpStatus a;
ErrorMessageDTO errorMessage;
//check with exception cause
if (ex.getCause() instanceof CoreException) {
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else if (ex instanceof CoreException) {
//this block will be used only when direct CoreException triggered
CoreException e = (CoreException) ex;
...
errorMessage = new ErrorMessageDTO(e, uri);
} else {
errorMessage = new ErrorMessageDTO(ex, uri);
...
}
return new ResponseEntity<ErrorMessageDTO>(errorMessage, a);
}
}
Also, I suggest not to handle all exception types in a single generic method, which will be very hard to support/maintain, rather split your CoreExceptionHandler using multiple #ExceptionHandler methods/classes.

Spring boot 404 error custom error response ReST

I'm using Spring boot for hosting a REST API. Instead of having the standard error response I would like to always send a JSON response even if a browser is accessing the URL and as well a custom data structure.
I can do this with #ControllerAdvice and #ExceptionHandler for custom exceptions. But I can't find any good ways of doing this for standard and handled errors like 404 and 401.
Are there any good patterns of how to do this?
For those Spring Boot 2 users who don't wanna use #EnableWebMvc
application.properties
server.error.whitelabel.enabled=false
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
ControllerAdvice
#RestControllerAdvice
public class ExceptionResolver {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public HashMap<String, String> handleNoHandlerFound(NoHandlerFoundException e, WebRequest request) {
HashMap<String, String> response = new HashMap<>();
response.put("status", "fail");
response.put("message", e.getLocalizedMessage());
return response;
}
}
Source
It is worked for me in case of #RestControllerAdvice with spring boot
spring.mvc.throw-exception-if-no-handler-found=true
server.error.whitelabel.enabled=false
spring.resources.add-mappings=false
#RestControllerAdvice
public class ErrorHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND )
public String handleNotFoundError(NoHandlerFoundException ex) {
return "path does not exists";
}
}
I've provided the sample solution on how to override response for 404 case. The solution is pretty much simple and I am posting sample code but you can find more details on the original thread: Spring Boot Rest - How to configure 404 - resource not found
First: define Controller that will process error cases and override response:
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value= HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse requestHandlingNoHandlerFound() {
return new ErrorResponse("custom_404", "message for 404 error code");
}
}
Second: you need to tell Spring to throw exception in case of 404 (could not resolve handler):
#SpringBootApplication
#EnableWebMvc
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Summing up all answers and comment, I think the best way to do this is-
First, tell spring boot to throw exception in case of no handler found in application.properties
spring.mvc.throw-exception-if-no-handler-found=true
Then handle NoHandlerFoundException in your application. I handle this by following way
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public void handleNotFoundError(HttpServletResponse response, NoHandlerFoundException ex) {
ErrorDto errorDto = Errors.URL_NOT_FOUND.getErrorDto();
logger.error("URL not found exception: " + ex.getRequestURL());
prepareErrorResponse(response, HttpStatus.NOT_FOUND, errorDto);
}
}
If you are using Swagger then you can view my other answer to exclude swagger URL from this exception handler
404 error is handled by DispatcherServlet. there is a property throwExceptionIfNoHandlerFound, which you can override.
In Application class you can create a new bean:
#Bean
DispatcherServlet dispatcherServlet () {
DispatcherServlet ds = new DispatcherServlet();
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
...and then catch the NoHandlerFoundException exception in
#EnableWebMvc
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorMessageResponse requestHandlingNoHandlerFound(final NoHandlerFoundException ex) {
doSomething(LOG.debug("text to log"));
}
}
You may extend the ResponseEntityExceptionHandler class, which include a lot of common exceptions in a Spring Boot Project. For example, if you wish to use a custom handler for binding exceptions, you may use the following,
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"key\":\"value\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_ACCEPTABLE, request);
}
}
An other example for the http status 404-Not Found,
#ControllerAdvice
public class MyApiExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String responseBody = "{\"errormessage\":\"WHATEVER YOU LIKE\"}";
headers.add("Content-Type", "application/json;charset=utf-8");
return handleExceptionInternal(ex, responseBody, headers, HttpStatus.NOT_FOUND, request);
}
}
Regarding the 404 not found exception you should configure the DispatcherServlet to throw and exception if it doesn't find any handlers, instead of the default behavior. For issues with 404, you may also read this question.
I was having the same issue but fixed it using a different method.
To return 404, 401 and other status in a custom response, you can now add the response status to the custom exception class and call it from your exception handler.
With spring utility class AnnotationUtils, you can get the status of any of the defined custom exceptions with the findAnnotation method and it will return the appropriate status using whatever annotation you defined for the exceptions including not found.
Here's my #RestControllerAdvice
#RestControllerAdvice
public class MainExceptionHandler extends Throwable{
#ExceptionHandler(BaseException.class)
ResponseEntity<ExceptionErrorResponse> exceptionHandler(GeneralMainException e)
{
ResponseStatus status = AnnotationUtils.findAnnotation(e.getClass(),ResponseStatus.class);
if(status != null)
{
return new ResponseEntity<>(new ExceptionErrorResponse(e.getCode(),e.getMessage()),status.code());
}
}
CustomParamsException to return Bad request status
#ResponseStatus(value= HttpStatus.BAD_REQUEST)
public class CustomParamsException extends BaseException {
private static final String CODE = "400";
public CustomParamsException(String message) {
super(CODE, message);
}
}
Details not found to return Not Found Status
#ResponseStatus(value= HttpStatus.NOT_FOUND)
public class DetailsNotException extends BaseException {
private static final String CODE = "400";
public DetailsNotException(String message) {
super(CODE, message);
}
}
A GeneralMainException to extend Excetion
public class GeneralMainException extends Exception {
private String code;
private String message;
public GeneralMainException (String message) {
super(message);
}
public GeneralMainException (String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
#Override
public String getMessage() {
return message;
}
}
You can decide to handle other system exceptions by including it to the controller advice.
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
ExceptionErrorResponse sysError(Exception e)
{
return new ExceptionErrorResponse(""1002", e.getMessage());
}
It seems that you need to introduce an appropriately annotated method, e.g. for unsupported media type (415) it will be:
#ExceptionHandler(MethodArgumentNotValidException)
public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
logger.error('Caught exception', e)
def response = new ExceptionResponse(
error: 'Validation error',
exception: e.class.name,
message: e.bindingResult.fieldErrors.collect { "'$it.field' $it.defaultMessage" }.join(', '),
path: req.servletPath,
status: BAD_REQUEST.value(),
timestamp: currentTimeMillis()
)
new ResponseEntity<>(response, BAD_REQUEST)
}
However it may not be possible since 401 and 404 may be thrown before they reach DispatcherServlet - in this case ControllerAdvice will not work.
You can add custom ErrorPage objects which correlate to the error-page definition in web.xml. Spring Boot provides an example...
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
return new MyCustomizer();
}
// ...
private static class MyCustomizer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"));
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not-found.html"));
}
}
EDIT: While I think the method above will work if you make the error pages rest controllers, an even easier way would be to include a custom ErrorController like the one below...
#Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
return new CustomErrorController(errorAttributes);
}
// ...
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes) {
super(errorAttributes);
}
#Override
#RequestMapping(value = "${error.path:/error}")
#ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
ResponseEntity<Map<String, Object>> error = super.error(request);
HttpStatus statusCode = error.getStatusCode();
switch (statusCode) {
case NOT_FOUND:
return getMyCustomNotFoundResponseEntity(request);
case UNAUTHORIZED:
return getMyCustomUnauthorizedResponseEntity(request);
default:
return error;
}
}
}
Please see Spring Boot REST service exception handling. It shows how to tell the dispatcherservlet to emit exceptions for "no route found" and then how to catch those exceptions. We (the place I work) are using this in production for our REST services right now.
Starting with Spring version 5 can use class ResponseStatusException:
#GetMapping("example")
public ResponseEntity example() {
try {
throw new MyException();
} catch (MyException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "My Exception", e);
}
}
I wanted to have the same error format (json) structure across all possible error scenarios, so I just registered my own ErrorController reusing the code from AbstractErrorController:
#Controller
#RequestMapping(path = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public class ErrorController extends AbstractErrorController {
public ErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers.orderedStream().collect(Collectors.toUnmodifiableList()));
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
final var status = getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
return new ResponseEntity<>(getErrorAttributes(request, ErrorAttributeOptions.defaults()), status);
}
#Override
public String getErrorPath() {
return null;
}
}
with this you dont need any controller advice, all errors go to error method by default

Categories

Resources