Return incorrect error response when handle exception - spring-boot

Spring Boot
#RestController
public class ProductsController {
#PostMapping("/product")
public ResponseEntity<Product> createProduct(#RequestBody String payload) {
Product product = new Gson().fromJson(payload, Product.class);
checkProduct(product);
product.setCreated(new Date());
if (product.getPrice() == null) {
throw new InvalidParameterException("Invalid price");
}
logger.info("createProduct: product:\n" + product);
return new ResponseEntity<Product>(product, HttpStatus.OK);
}
Here my custom handler of exception InvalidParameterException:
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(InvalidParameterException.class)
protected ResponseEntity<Object> handleInvalidParameterException(InvalidParameterException ex,
WebRequest request) {
ErrorResponse error = new ErrorResponse(ex.getMessage());
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status,
WebRequest request) {
List<String> details = new ArrayList<>();
for(ObjectError error : ex.getBindingResult().getAllErrors()) {
details.add(error.getDefaultMessage());
}
ErrorResponse error = new ErrorResponse("Validation Failed", details);
return new ResponseEntity(error, HttpStatus.BAD_REQUEST);
}
}
But when start request without price:
# Create product without price
POST :BASE_URL:URL_SUFFIX/product
:HEADERS
{
"name": "product name_1"
}
response is:
{
"timestamp": "2020-09-08T23:25:25.514+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/api/v1/product"
}
But result must be:
{
"timestamp": "2020-09-08T23:25:25.514+00:00",
"status": 400,
"error": "Bad request",
"message": "Invalid price",
"path": "/api/v1/product"
}
Why not correct error response?
I got error:
org.springframework.http.converter.HttpMessageNotWritableException: No converter found for return value of type: class myproject.model.ErrorResponse
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:230) ~[spring-webmvc-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:219) ~[spring-webmvc-

Related

Customizing NoHandlerException response with ControllerAdvice

I try to implement a custom error response in a spring boot rest application for 404 errors.
I read many of the solutions presented in stackoverflow, without success.
When I call an invalid entry point I obtain this result:
{
"timestamp": "2022-06-22T10:38:41.114+00:00",
"status": 404,
"error": "Not Found",
"path": "/ws-facturx/fx2"
}
But i'd like to have a response that should look like this:
{
"operationId": "u044eZg2gHwtadqxB5CVv6aeMBjj0w",
"status": "ERROR",
"operation": "webserviceName",
"clientName": "ACME Inc",
"errorMessage": "Error message from Server",
"createdAt": "2022-06-22T09:15:04.844+00:00"
}
I first tried to use #RestControllerAdvice to intercept the exception when they are thrown.
#ExceptionHandler(value = {AppServiceException.class, NoHandlerFoundException.class, ServletServiceException.class })
public ResponseEntity<Object> handleAppServiceException(Exception ex,
WebRequest req) throws JsonProcessingException {
FacturxDto request = context.getFacturxDtoContext();
ErrorMessage errorMessage = errorMessageBuilder(request, ex);
return new ResponseEntity<>(errorMessage, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
I also modified my application.properties :
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
If i call a non defined entry point I do not reach this method. I tried to use an interceptor.
I firs added a class for adding interceptor to InterceptorRegistry:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final ApplicationExchangeContext context;
public WebMvcConfig(ApplicationExchangeContext context) {
this.context = context;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ApplicationInterceptor(context)).addPathPatterns("/**");
}
}
My ApplicationInterception looks like this:
#Component
public class ApplicationInterceptor implements HandlerInterceptor {
private final ApplicationExchangeContext context;
#Autowired
public ApplicationInterceptor(ApplicationExchangeContext context) {
this.context = context;
}
//unimplemented methods comes here. Define the following method so that it
//will handle the request before it is passed to the controller.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in preHandle");
return true;
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
System.out.println(request);
System.out.println(response);
if (response.getStatus() == HttpStatus.NOT_FOUND.value()) {
// si on a un 404
System.out.println(handler);
String requestData = request.getReader().lines().collect(Collectors.joining());
System.out.println(requestData);
Gson gson = new Gson();
FacturxDto facturxDto = gson.fromJson(requestData, FacturxDto.class);
context.setFacturxDtoContext(facturxDto);
throw new ServletServiceException("404...");
}
System.out.println("Done in afterCompletion");
}
}
On the preHandle, i do reach the catch part of the code block but i do not access the RestControllerAdvice method that should handle this exception and build my expected object.
The exception is thrown. But i do not return it to user. Instead I do have an HTML page.

Spring zuul proxy appends extra json response along with service response json

Spring zuul proxy appends extra json response along with service response json
Below is the zuul configuration
zuul:
sensitiveHeaders:
routes:
api-gateway:
url: http://localhost:8099
abc-management:
url: http://localhost:8098
Below is the response json
{
"status": "P200",
"message": "Orders fetched successfully",
"timeStamp": "2020-09-30T16:01:42.116275Z",
"data": {
"orders": [
{
"order_id": "11312553751504",
"status_reason": null
}
]
},
"requestId": 0
}{
"timestamp": "2020-09-30T16:01:42.122+0000",
"status": 200,
"error": "OK",
"message": "",
"path": "/api-gateway/orders"
}
The extra json
{
"timestamp": "2020-09-30T16:01:42.122+0000",
"status": 200,
"error": "OK",
"message": "",
"path": "/api-gateway/orders"
}
is appended by zuul proxy, is this a bug or a misconfiguration
was overriding PostFilter, when that part is removed. It fixed the issue
The reason is because an exception occurred in filter. and response has written twice, the response data and the /error response.
ApplicationFilterChain.java
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
StandardWrapperValve.java
private void exception(Request request, Response response,
Throwable exception) {
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setError();
}
BasicErrorController.java
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
if these is an Error occurred in filter, then will be redirect to /error page.
this is a example:
#Component
public class MyFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
throw new RuntimeException("error");
}
}
#RestController
public class IndexController {
#GetMapping("/user")
public User user(#RequestParam int id) {
return new User(id, "user" + id);
}
}
Request from postman, the response:
{
"id": 2,
"name": "user2"
}{
"timestamp": "2022-02-15T11:06:16.094+00:00",
"status": 200,
"error": "OK",
"path": "/user"
}
image

Spring ConstraintViolation not caught

I'm using Spring Boot, and I have created a custom PathVariable validator, which I use as follows:
#GetMapping(value = "/test/{id}")
public ResponseEntity<Void> test(#PathVariable("id")
#Valid
#MyGUID(message = "ID_INVALID")
String id) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
The validor seems to do its job well - it passes when input is valid, and throws exception when not. The problem is, that my custom exception handler doesn't catch the ConstraintViolationException that is thrown. This is the top line of the stack trace:
javax.validation.ConstraintViolationException: ID_INVALID
Then I have my exeception handler class:
#ControllerAdvice
public class RestExceptionController extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
List<String> errorDetailsCodes = new ArrayList<>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
errorDetailsCodes.add(violation.getMessage());
}
ErrorResponse errorResponse = new ErrorResponse(
status.name(),
status.value(),
ex.hashCode(),
errorDetailsCodes
);
logException(ex);
return new ResponseEntity<>(errorResponse, headers, status);
}
#ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorResponse> handleGeneralException(Exception ex) {
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.name(),
HttpStatus.INTERNAL_SERVER_ERROR.value(),
ex.hashCode()
);
logException(ex);
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
It is strange to me that despite the fact I have both a specific and a general exception handler, they are both skipped, and the system throws its own error:
{
"timestamp": 1568554746642,
"status": 500,
"error": "Internal Server Error",
"message": "ID_INVALID",
"path": "/api/logged-in-user/test/123"
}
How can I catch this exception and return my custom response?
Thanks.

Spring boot custom exception not getting printed in logs

I am trying to implement a custom exception for my Spring boot REST project. The custom exception gets called but shows no impact in the way error message is displayed.
This is the POJO I'm using for my custom errors:
public class ApiError {
private HttpStatus status;
private String message;
private List<String> errors;
public ApiError(HttpStatus status, String message, List<String> errors) {
super();
this.status = status;
this.message = message;
this.errors = errors;
}
public ApiError(HttpStatus status, String message, String error) {
super();
this.status = status;
this.message = message;
errors = Arrays.asList(error);
}
}
This is the exception handler I wrote:
#ControllerAdvice
#EnableWebMvc
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
System.out.println("Custom exception!!");
//List<String> details = new ArrayList<>();
//details.add(ex.getLocalizedMessage());
//System.out.println("Localize message:: "+ex.getLocalizedMessage());
// ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
//request.getDescription(false));
ApiError error = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR,"Server Error", request.getDescription(false));
return new ResponseEntity<Object>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Additionally, I'm defined the following method within my controller:
#RequestMapping(value = "/model", params = "number", method = RequestMethod.GET, produces = "application/json")
List<Model> getModel(HttpServletRequest request,#RequestParam(value = "codeNumber") String number) throws Exception{
List<Model> model = null;
try {
model = niiService.getModel(number);
}catch(RuntimeException e){
new Exception(e);
}
return model;
}
However, in stead of my custom POJO, I'm seeing the following exception:
{
"timestamp": 1547013989124,
"status": 500,
"error": "Internal Server Error",
"message": "Request processing failed; nested exception is org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure\n\nThe last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.",
"path": "/model"
}
I was expecting the following JSON structure in stead:
{
"status": 500,
"message": "Server Error"
..
}
Please let me know, what I am missing to get the error response in the way I wanted.
You are missing the throw in front of the custom exception.
Also make sure that you are catching the right exception inside your controller.
Change
catch (RuntimeException e) {
//custom exception
new Exception(e);
}
To
catch (RuntimeException e) {
//custom exception
throw new Exception(e);
}
I missed adding getter and setter to APIError class. Hence, the response was not coming in the way I expected.

Spring boot Exception custom handling - Unexpected HTTP status

I am trying to implement some custom exception handlers in my spring boot application which will be able to handle custom exceptions and display appropiate message and status code.
My issue : Getting http status = 500 even though the response body is according to my custom handler.
Code :
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ BadRequestValidationFailureException.class, Exception.class })
public ResponseEntity<Object> handleAll(Exception ex, WebRequest request) {
ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getMessage());
return new ResponseEntity<Object>( apiError, new HttpHeaders(), HttpStatus.BAD_REQUEST );
}
And throwing exception as :
throw new BadRequestValidationFailureException( "ERROR_CODE", "THIS IS THE MESSAGE" );
The output is :
{
"timestamp": "2018-09-20T17:44:01.502Z",
"status": 500,
"error": "Internal Server Error",
"exception": "com.hotstar.payment.exception.BadRequestValidationFailureException",
"message": "[ ERROR_CODE ] THIS IS THE MESSAGE",
"path": "/my/api/path"
}
The weird thing is that the http response status is 500.
Please help.
Got the solution. Had to set another annotation :
#ResponseStatus(value = HttpStatus.BAD_REQUEST, code = HttpStatus.BAD_REQUEST, reason = "some reason")
Add this to handleAll method.
I've made good experiences with the following pattern:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
private static final Map<MyProjectErrorCode, HttpStatus> CODE_STATUS_MAP = new EnumMap<>(MyProjectErrorCode.class);
static {
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_ILLEGAL_PROPERTY, HttpStatus.BAD_REQUEST);
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_FOO, HttpStatus.BAD_REQUEST);
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_THIRDPARTYX_CLIENT, HttpStatus.INTERNAL_SERVER_ERROR);
CODE_STATUS_MAP.put(MyProjectErrorCode.MYPROJ_UNKNOWN, HttpStatus.INTERNAL_SERVER_ERROR);
CODE_STATUS_MAP.put(MyProjectErrorCode.THIRDPARTYX_BAR, HttpStatus.BAD_REQUEST);
CODE_STATUS_MAP.put(MyProjectErrorCode.THIRDPARTYX_UNKNOWN, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(MyProjectException.class)
public ResponseEntity<ErrorResponse> handleMyProjectException(MyProjectException ex) {
ErrorResponse errorResponse = createErrorResponse(ex.getErrorCode(), ex.getMessage());
HttpStatus httpStatus = determineHttpStatus(ex.getErrorCode());
return handleErrorResponse(errorResponse, httpStatus);
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
MyProjectErrorCode errorCode = MyProjectErrorCode.MYPROJ_ILLEGAL_PROPERTY;
ErrorResponse errorResponse = createErrorResponse(errorCode, ex.getMessage());
HttpStatus httpStatus = determineHttpStatus(errorCode);
return handleErrorResponse(errorResponse, httpStatus);
}
#ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) {
MyProjectErrorCode errorCode = MyProjectErrorCode.MYPROJ_UNKNOWN;
ErrorResponse errorResponse = createErrorResponse(errorCode, ex.getMessage());
HttpStatus httpStatus = determineHttpStatus(errorCode);
return handleErrorResponse(errorResponse, httpStatus);
}
private ResponseEntity<ErrorResponse> handleErrorResponse(ErrorResponse errorResponse, HttpStatus httpStatus) {
return new ResponseEntity<>(errorResponse, httpStatus);
}
private ErrorResponse createErrorResponse(MyProjectErrorCode errorCode, String message) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setErrorCode(errorCode.name());
errorResponse.setErrorMessage(message);
return errorResponse;
}
private HttpStatus determineHttpStatus(MyProjectErrorCode errorCode) {
return CODE_STATUS_MAP.getOrDefault(errorCode, HttpStatus.INTERNAL_SERVER_ERROR);
}
The client can get the HttpStatus from the Http response - no need to add it to the JSON body.
The project specific MyProjectErrorCode enum allows you to communicate to the clients a detailed error code. The client can analyze this error code and take the appropriate action or display a localized (specific or generic) error message based on the error code.
MyProjectErrorCode also allows you to communicate if the error was created in your code (starting with MYPROJ_) or if the error is forwarded from the third party 'x' service (starting with THIRDPARTYX_).
You can also create subclasses of MyProjectException and ErrorResponse to transport more specific data for specific cases - just add an additional expcetion handler method for that exception.

Resources