Hi have a simple spring controller that returns ResponseStatusException in case of error.
#RestController
#RequestMapping("/process")
public class ProcessWorkitemController {
#Autowired MyService service;
#GetMapping("/{id}")
public ResponseEntity<?> findById(#PathVariable Long id) {
Optional<MyObj> opt = service.getObj(id);
opt.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, String.format("Process id %s not found", id), null));
return ResponseEntity.ok(opt.get());
}
}
This works perfctely if invoked with SoapUI: it shows me a Json as follows:
{
"timestamp": "2021-01-20T10:59:48.082+0000",
"status": 404,
"error": "Not Found",
"message": "Process id 1 not found",
"path": "/workspace-manager/process/1"
}
When I call the same service with the browser it shows the whitelabel error page with my custmo error message.
I want a JSON as response even on the browser, is this possible?
Thanks in advance.
I would avoid using ResponseStatusException, especially when building an application that needs a unified way of handling exceptions. What I would do instead is the following:
Create a new class and annotate with an #RestControllerAdvice. This will effectively be the main entrypoint for exception handling of your app.
Create methods that handle different types of exceptions and return a response using the message and/or information from the exceptions.
Simply throw such an exception either from the service layer or from the controller layer and let the exception handler handle it.
A simple example is the following (disregard the inner classes -- only there in order to save space).
Exception handler:
#RestControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(ServiceException.class)
public ResponseEntity<ExceptionResponse> handleException(ServiceException e) {
return ResponseEntity
.status(e.getStatus())
.body(new ExceptionResponse(e.getMessage(), e.getStatus().value()));
}
public static class ExceptionResponse {
private final String message;
public String getMessage() { return message; }
private final Integer code;
public Integer getCode() { return code; }
public ExceptionResponse(String message, Integer code) {
this.message = message;
this.code = code;
}
}
}
Base ServiceException:
package com.ariskourt.test.controllers;
import org.springframework.http.HttpStatus;
public abstract class ServiceException extends RuntimeException{
public ServiceException(String message) {
super(message);
}
public abstract HttpStatus getStatus();
}
Controller
#RestController
#RequestMapping("/process")
public class ProcessController {
#GetMapping("/{id}")
public ResponseEntity<?> findById(#PathVariable Long id) {
return Optional
.of(1L)
.filter(number -> number.equals(id))
.map(ResponseEntity::ok)
.orElseThrow(() -> new ProcessNotFoundException(String.format("Process with id %s not found", id)));
}
}
With all that in place, you should a unified way of handling exceptions which also always returns the correct message not matter the client.
Related
I have an endpoint:
#GetMapping("/v2/tw/{id}")
public TwDto getTw(Authentication auth, #PathVariable Long id) {
}
When I want to fetch data with Postman with wrong parameter type /v2/tw/variableNoNumber, then I want to have some logs in service, I want to get some validation error. How to do that?
Should I add eg. #NumberFormat? It doesn't work.
I use #ControllerAdvice, my project is quite old, everything is validated properly but THIS case is weird for me....
You can write your custom exception handler.
This is your custom entity.
public class ApiException {
private final String message;
private final HttpStatus httpStatus;
public ApiException(String message, HttpStatus httpStatus ) {
this.message = message;
this.httpStatus = httpStatus;
this.timestamp = timestamp;
}
public String getMessage() {
return message;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
}
This is your exception class.
public class ApiRequestException extends RuntimeException {
public ApiRequestException(String message) {
super(message);
}
public ApiRequestException(String message, Throwable cause) {
super(message, cause);
}
}
And finally ApiExceptionHandler
#ControllerAdvice
public class ApiExceptionHandler {
#ExceptionHandler(value = {ApiRequestException.class})
public ResponseEntity<Object> handleApiRequestException(ApiRequestException e) {
HttpStatus badRequest = HttpStatus.BAD_REQUEST;
ApiException apiException = new ApiException(e.getMessage(), badRequest));
return new ResponseEntity<>(apiException, badRequest);
}
}
For example, if you want to validate the ID you can write as below.
throw new ApiRequestException("Case by id" + TwDto.getId+ " was not found!");
I think I found solution for my problem - instead of throwing 400 with some message maybe it would be sufficient to add:
#GetMapping("/v2/tw/{id:\d+}")
public TwDto getTw(Authentication auth, #PathVariable Long id) {
}
Then I get 404 error Not found and I think this approach is better than returning 400 error without any message... What do you think?
you can use the annotation #Valid for the parameter and handle the exception with the ConstraintViolationException. For more information and detail see here:
https://medium.com/#aamine/customized-input-validation-in-spring-boot-1927aa440bc6
I have some exception in order to handling errors in my restful spring backend, Here is example of an exception:
#ResponseStatus(value = HttpStatus.FORBIDDEN)
public class IllegalUserAgentException extends RuntimeException
{
public IllegalUserAgentException(String exception)
{
super(exception);
}
}
When i throw this exception from service (in domain driven architecture), Spring returning below json error
{
"timestamp": 1552127820802,
"status": 403,
"error": "Forbidden",
"exception": "com.example.exception.IllegalUserAgentException",
"message": "test",
"path": "/path/somePath"
}
As you can see, Spring added an attribute with "exception" name, I want to remove this attribute.
I added server.error.include-exception=false flag, but not work.
Any solution?
In spring-boot you can do it via defining a #Component which extends DefaultErrorAttributes, then override the getErrorAttributes function to remove the "exception".
#Component
public class CustomErrorAttributes extends DefaultErrorAttributes {
#Override
public Map<String, Object> getErrorAttributes(
WebRequest webRequest,
boolean includeStackTrace
) {
Map<String, Object> errorAttributes
= super.getErrorAttributes(webRequest, includeStackTrace);
errorAttributes.remove("exception");
return errorAttributes;
}
}
There is also another alternatives such as using #ControllerAdvice which you can peruse further.
I would suggest you to make a Response Class.
Add try catch in your service class and return this object instead.
In catch section instantiate ResponseDto object and add the message accordingly.This will gracefully handle your exception. It is scalable because you can wrap any other exception message as well.
public class ResponseDto {
#JsonProperty("url")
private String url;
#JsonProperty("status")
private int status;
#JsonProperty("userMsg")
private String UserMsg;
}
I am trying to handle all Types of exceptions using #ExceptionHandler(Exception.class). But it's not handling all types of exception.
When I am trying to access wrong HTTP method from postman/ browser I am not getting any response blank page is coming.
Can please any one tell me why I am not getting any response or tell me if I am doing something wrong in my code?
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionMessage> handleAllExceptionMethod(Exception ex,WebRequest requset,HttpServletResponse res) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setStatus(res.getStatus());
exceptionMessageObj.setError(ex.getLocalizedMessage());
exceptionMessageObj.setException(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
return new ResponseEntity<ExceptionMessage>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
Either override ResponseEntityExceptionHandler#handleExceptionInternal()or don't extend ResponseEntityExceptionHandler.
#Order(Ordered.HIGHEST_PRECEDENCE) on a #ControllerAdvice should work before ResponseEntityExceptionHandler is invoked as per this answer which suggests that Spring Framework 4.3.7 is needed.
This will handle the exceptions raised from within the controller method.
If you send a request for which there is no mapping the controller method will not be invoked at all thus the #ExceptionHandler will be obsolete in that case.
Maybe this article on creating custom handlers may help: article
Using RequestMapping you can create different responses for every Http code. In this example I show how to control errors and give a response accordingly.
This is the RestController with the service specification
#RestController
public class User {
#RequestMapping(value="/myapp/user/{id}", method = RequestMethod.GET)
public ResponseEntity<String> getId(#PathVariable int id){
if(id>10)
throw new UserNotFoundException("User not found");
return ResponseEntity.ok("" + id);
}
#ExceptionHandler({UserNotFoundException.class})
public ResponseEntity<ErrorResponse> notFound(UserNotFoundException ex){
return new ResponseEntity<ErrorResponse>(
new ErrorResponse(ex.getMessage(), 404, "The user was not found") , HttpStatus.NOT_FOUND);
}
}
Within the getId method there is a little logic, if the customerId < 10 It should response the Customer Id as part of the body message but an Exception should be thrown when the customer is bigger than 10 in this case the service should response with an ErrorResponse.
public class ErrorResponse {
private String message;
private int code;
private String moreInfo;
public ErrorResponse(String message, int code, String moreInfo) {
super();
this.message = message;
this.code = code;
this.moreInfo = moreInfo;
}
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
public String getMoreInfo() {
return moreInfo;
}
}
And finally I'm using an specific Exception for a "Not Found" error
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
I have a Request Mapping -
#RequestMapping("/fetchErrorMessages")
public #ResponseBody int fetchErrorMessages(#RequestParam("startTime") String startTime,#RequestParam("endTime") String endTime) throws Exception
{
if(SanityChecker.checkDateSanity(startTime)&&SanityChecker.checkDateSanity(endTime))
{
return 0;
}
else
{
throw new NotFoundException("Datetime is invalid");
}
}
If the startTime and endTime are invalid, I want to throw a 500 error but return the exception string in JSON. However, I get a HTML Page instead saying
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Dec 20 10:49:37 IST 2017
There was an unexpected error (type=Internal Server Error, status=500).
Datetime is invalid
I instead wanted to return 500 with a JSON
{"error":"Date time format is invalid"}
How do I go about this?
Suppose you have a custom Exception class NotFoundException and its implementations something like this:
public class NotFoundException extends Exception {
private int errorCode;
private String errorMessage;
public NotFoundException(Throwable throwable) {
super(throwable);
}
public NotFoundException(String msg, Throwable throwable) {
super(msg, throwable);
}
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException(String message, int errorCode) {
super();
this.errorCode = errorCode;
this.errorMessage = message;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return errorMessage;
}
#Override
public String toString() {
return this.errorCode + " : " + this.getErrorMessage();
}
}
Now you want to throw some exception from controller. If you throw a exception then you must catch it from a standard Error Handler class, say for example in spring they provide #ControllerAdvice annotation to apply to make a class Standard Error Handler. When it is applied to a class then this spring component (I mean the class you annotated) can catch any exception thrown from controller. But We need to map exception class with proper method. So we defined a method with your exception NotFoundException handler something like below.
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public Object processValidationError(NotFoundException ex) {
String result = ex.getErrorMessage();
System.out.println("###########"+result);
return ex;
}
}
You want to sent http status to internal server error(500), so here we used #ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR). Since you used Spring-boot so you do not need to make a json string except a simple annotation #ResponseBody can do that for you automagically.
Create a custom exception.
public class SecurityException extends RuntimeException {
private static final long serialVersionUID = -7806029002430564887L;
private String message;
public SecurityException() {
}
public SecurityException(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Create a custom response entity.
public class SecurityResponse {
private String error;
public SecurityResponse() {
}
public SecurityResponse(String error) {
this.error = error;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
Create a ControllerAdvice with ExceptionHandler for custom exception, it will handle the custom exception, populate and return the custom response as below.
#ControllerAdvice
public class SecurityControllerAdvice {
#ExceptionHandler(SecurityException.class)
#ResponseBody
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public SecurityResponse handleSecurityException(SecurityException se) {
SecurityResponse response = new SecurityResponse(se.getMessage());
return response;
}
}
Throw the custom exception based on your condition.
throw new SecurityException("Date time format is invalid");
Now run and test you app. E.G. :
you can create NotFoundException class with #ResponseStatus annotation like below:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class NotFoundException extends RuntimeException {
public NotFoundException() {
}
public NotFoundException(String message) {
super(message);
}
}
Javax has a interface name as ExceptionMapper. Please refer the below code snippet, For every RuntimeException in your application it will map it to a Json Response entity.
public class RuntimeExceptionMapper implements ExceptionMapper <RuntimeException> {
#Override
public Response toResponse(RuntimeException exception) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setMessage(exception.getMessage);
if (exception== null) {
logger.error("Exception Details Not found");
} else {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(errorResponse )
.type(MediaType.APPLICATION_JSON)
.header("trace-id", "1234").build();
}
}
}
This is how I did it in my application:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class ExceptionHandlingControllerAdvice {
#ExceptionHandler(ExecutionRestrictionViolationException.class)
public ResponseEntity<String> handleExecutionRestrictionViolationException(ExecutionRestrictionViolationException ex) {
return response("Invalid Query", ex.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY);
}
private static String createJson(String message, String reason) {
return "{\"error\" : \"" + message + "\"," +
"\"reason\" : \"" + reason + "\"}";
}
private static ResponseEntity<String> response(String message,
String reason,
HttpStatus httpStatus) {
String json = createJson(message, reason);
return new ResponseEntity<>(json, httpStatus);
}
}
Explanation:
You create a controller Advice, mark it with a special annotation and define just like any other bean (in my case it was a java configuration, but it doesn't really matter)
For each Exception you would like to handle like this - define a handler that will generate a response in a format you want
There is a static method createJson - you can use a different way, it also doesn't matter really.
Now this is only one way to work (its available in more recent spring boot versions) - but there are others:
All the methods I'm aware of (and even more) are listed here.
Spring provides a few ways to do this, some more sensible than others depending on your situation.
(Great tutorial here on several options. https://www.baeldung.com/spring-exceptions-json)
My favorite is this one because I want to send back a proper error message and an appropriate http response without creating a superclass or creating helper methods in a utility class or copying boilerplate everywhere.
If you want to inform the caller that the event caused an error (and in proper JSON), use Spring's ResponseStatusException. It gives you access to the httpReponse object so you can also send back a response other than 'ok'.
It wants an exception as one of it's parameters. For one of my scenarios I wanted to inform the caller that they were trying to register a user that already existed. Typically, looking up a user isn't supposed to throw an exception but in this case I created my own exception and I throw it back to the caller in a ResponseStatusException like so:
#PostMapping("/register")
public ResponseEntity register(#RequestBody AccountUserDto user) {
UserDetails userExists = userDetailsService.loadUserByEmail(user.getEmail());
if (userExists != null) {
UserExistsException exc = new UserExistsException("Error: Email address " + user.getEmail() + " is already in use.");
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "User Exists", exc);
}
....(fall through and create user)
I use Jersey API for my REST service. My question is: Is there a more elegant way of returning exceptions in a JSON form? Is it better to concern myself with creating a json object myself and attaching it to the response directly?
This is a simplified example of one of the methods in the service. As you see, I use HashMap only because the method may throw an exception, in which case I need to return information about It.
#Path("/admin")
public class AdminService {
#Context
HttpServletRequest request;
#POST
#Produces(MediaType.APPLICATION_JSON)
public Map<Integer, String> createCompany(Company company){
Map<Integer, String> map = new HashMap<>();
try{
AdminFacade adminFacade = (AdminFacade)Utility.getFacade(request);
adminFacade.createCompany(company);
map.put(1,"success");
} catch (ExceptionREST e) {
map.put(e.getErrorNumber(), e.getMessage());
} finally {
return map;
}
}
}
You can create a class like the one below to represent an error,
#JsonPropertyOrder({ "code", "field", "message" })
public class ErrorInfo {
private String code;
private String field;
private String message;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
You can create a class which extends an exception like this,
public class InvalidInputException extends RuntimeException {
private static final long serialVersionUID = -5027121014723838738L;
private List<ErrorInfo> errors;
public List<ErrorInfo> getErrors() {
return this.errors;
}
public InvalidInputException(List<ErrorInfo> errors) {
super();
this.errors = errors;
}
public InvalidInputException(String message, List<ErrorInfo> errors) {
super(message);
this.errors = errors;
}
}
And have a exception mapper, where you can convert the List to json and return to the user with http status code 400 (Bad Request).
#Provider
public class InvalidInputExceptionMapper implements ExceptionMapper<InvalidInputException> {
#Override
#Produces(MediaType.APPLICATION_JSON)
public Response toResponse(InvalidInputException e) {
ResponseBuilder rb = Response.status(Status.BAD_REQUEST);
rb.entity(e.getErrors());
return rb.build();
}
}
Http Response will be,
HTTP/1.1 400 BAD REQUEST
{
"errors": [{
"error": {
"code": "100",
"field": null,
"message": "Name is required"
},
"error": {
"code": "100",
"field": null,
"message": "Age is required"
}
}]
}
I believe it is quite popular that people use http response status code to handle the error. E.g. 404 status is not found 5xx is server internal error e.t.c.
You can easily set the error code by using the Response object.
Instead of returning a map, return a Response object.
#Path("/admin")public class AdminService {
#Context
HttpServletRequest request;
#POST
#Produces(MediaType.APPLICATION_JSON)
public Response createCompany(Company company){
Map<Integer, String> map = new HashMap<>();
try{
AdminFacade adminFacade = (AdminFacade)Utility.getFacade(request);
Company commpany=adminFacade.createCompany(company);//this entity annotated by XmlRootElement
Response response=Response.ok().entity(company).build();
} catch (ExceptionREST e) {
response=Response.status(404).build();
} return response;
}}
To make the Restful api more robust, some will return an OK response to prevent "smart redirect" from the server and output some weird html.
you can refer here for a list of http status code and what it mean.
For Java EE Response class, you can refer the official javadoc
You can wrap your error into a class, say I have an ErrorData class which has status, message and stacktrace. Everytime an exception occurs, I throw a GeneralAppException with the errordata object.
public class GeneralAppException extends WebApplicationException {
public GeneralAppException(ErrorData er) {
super(Response.status(er.getStatusCode()).
entity(er).type(MediaType.APPLICATION_JSON).build());
}
}
I have another class which has all the known errors, eg.
public static final ErrorData NODATAFOUND = new ErrorData(Response.Status.NOT_FOUND.getStatusCode(),"No data was found for given query");
public static final ErrorData CODEERROR = new ErrorData(502,"CodeError");
Your catch can look like
catch (ExceptionREST e) {
throw new GeneralAppException(ErrorData.NODATAFOUND);
}
Reference used : https://jersey.java.net/documentation/latest/representations.html#d0e6665