How to log SpringBoot request validation errors? - java

How to log request validation errors in springboot? Say, we have an API like below and I need to log if there's a validation error in one of the request params?
#GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
public String sayHi(#RequestParam(name = "size") #Max(30) Integer size) {
return "Hi" + size.toString();
}
If the size value is more than 30 then the API returns a validation error. But, along with that, I'm also looking for a way to log such invalid requests

Since you are using springboot , you could use spring boot centralized exception handling using #ControllerAdvice to log these errors or return a custom common response class . The #ControllerAdvice annotation will make it apply globally to all controllers.
In order to catch validation errors for request bodies , we will handle MethodArgumentNotValidExceptions like:
#ControllerAdvice
class ErrorHandlingControllerAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ErrorHandlingControllerAdvice.class);
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
ApiErrorResponse onConstraintValidationException(
ConstraintViolationException e) {
ApiErrorResponse apiErrorResponse = new ApiErrorResponse();
for (ConstraintViolation violation : e.getConstraintViolations()) {
apiErrorResponse.getViolations().add(
new Violation(violation.getPropertyPath().toString(), violation.getMessage()));
}
return apiErrorResponse;
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
ApiErrorResponse onMethodArgumentNotValidException(
MethodArgumentNotValidException e) {
ApiErrorResponse error = new ApiErrorResponse();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
error.getViolations().add(
new Violation(fieldError.getField(), fieldError.getDefaultMessage()));
}
LOGGER.error("Log whatever you want to here");
return error;
}
}

Related

What's the best option/alternative to treat exceptions in spring boot?

Right now i'm using this example of exception handling:
//get an object of type curse by id
//in the service file, this findCurseById() method throws a
//CursaNotFoundException
#GetMapping("/{id}")
public ResponseEntity<curse> getCursaById (#PathVariable("id") Long id) {
curse c = curseService.findCurseById(id);
return new ResponseEntity<>(c, HttpStatus.OK);
}
//so if not found, this will return the message of the error
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(CursaNotFoundException.class)
public String noCursaFound(CursaNotFoundException ex) {
return ex.getMessage();
}
and that's my exception
public class CursaNotFoundException extends RuntimeException {
public CursaNotFoundException(String s) {
super(s);
}
}
in future I want to use Angular as front-end, so I don't really know how I should treat the exceptions in the back-end. For this example let's say, should I redirect the page to a template.html page in the noCursaFound() method, or should I return something else? A json or something? I couldn't find anything helpful. Thanks
I would suggest keeping the error handling at the REST API level and not redirecting to another HTML page on the server side. Angular client application consumes the API response and redirects to template.html if needed.
Also, it would be better if the backend returns an ApiError when an exception occurs with a message and, optionally, an error code:
public class ApiError {
private String message;
private String code;
}
and handle the exceptions in a separate class, ExceptionHandler annotated with #ControllerAdvice:
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(value = CursaNotFoundException.class)
public ResponseEntity cursaNotFoundException(CursaNotFoundException cursaNotFoundException) {
ApiError error = new ApiError();
error.setMessase(cursaNotFoundException.getMessage());
error.setCode(cursaNotFoundException.getCode());
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(value = Exception.class)
public ResponseEntity<> genericException(Exception exception) {
ApiError error = new ApiError();
error.setMessase(exception.getMessage());
error.setCode("GENERIC_ERROR");
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

Validating Rest Controller URL in Spring boot

I am adding a new record in the Postman using the URI localhost:8080//insurance/service/add.
Requirement - I want the application to throw an error in JSON mentioning an error code and customized message if any non-whitespace characters is after the URI. For example, if I want to add a record by using the URI such as localhost:8080//insurance/service/add? or localhost:8080//insurance/service/add* or anything like that, it should throw an error in JSON mentioning the error code and message. How should I proceed?
PS - new with spring boot.
#RestController
#RequestMapping("insurance/service")
public class InsuranceController{
#Autowired
Insurance_Service service;
// Create New Insurance
#PostMapping(path="/add", produces = "application/json")
public String addInsurance(#RequestBody (required=false) Insurance insurance ) {
if(insurance==null)
throw new MissingQueryParam();
this.service.addInsurances(insurance);
return "Insurance added successfully!!!";
}
}
You can use #RestControllerAdvice or #ControllerAdvice to properly handle exceptions with http status.
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(RuntimeException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseMsg handleNotFoundException(Throwable ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
ResponseMsg is coustomised Class to generate customised error response.
In this class you can handle any exception (customized too)

Spring Boot error controller retrieve original request

By default Spring Boot maps /error to BasicErrorController. I want to log the exception along with the request that causes the exception. How can I get the original request in BasicErrorController or a new CustomErrorController. It seems that Spring Boot will make a new request to /error when an exception is thrown and the orginal request info is gone or no way to map the error with the original request.
Get it by:
String url = (String) request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
To avoid any misleading information, Spring Boot DOES NOT make a new request to /error endpoint. Instead, it wraps the exception in the original request and forwards it to /error endpoint. The request will be processed by BasicErrorHandler if you don't provide a custom error handler.
In this case, if you are using an interceptor, the interceptor will be invoked twice - one for the original request and the other for the forwarded request.
To retrieve the original request information, please look into the forwarded request's attributes. Basically, you can get the error message from these attributes javax.servlet.error.message, javax.servlet.error.status_code, org.springframework.web.servlet.DispatcherServlet.EXCEPTION.
And these are some resources that are related to error handling in Spring Boot:
spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
https://www.baeldung.com/exception-handling-for-rest-with-spring
https://www.baeldung.com/spring-boot-custom-error-page
If you are using controller advice to handle your exceptions then method with #ExceptionHandler can inject request as parameter, something like :
#ControllerAdvice
public class YourExceptionHandler
{
#ExceptionHandler
public ResponseEntity handleExceptions(HttpServletRequest request, Exception exception)
{
// use request to populate error object with details like requestId
LOGGER.debug(String.valueOf(request));
LOGGER.error(exception.getMessage(), exception);
}
}
Here is a working example:
#RestController
public class MyErrorController implements ErrorController {
private static final Logger LOG = LoggerFactory.getLogger(MyErrorController.class);
private static final String PATH = "/error";
private final ErrorAttributes errorAttributes;
public MyErrorController(ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#RequestMapping(value = PATH)
public ErrorDTO error(WebRequest webRequest, HttpServletRequest httpServletRequest) {
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
Map<String, Object> attrs = errorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults());
LOG.warn("Forwarded Error Request: {} ", attrs.get("path"), (Throwable)
httpServletRequest.getAttribute("javax.servlet.error.exception"));
ErrorDTO dto = new ErrorDTO();
dto.message = (String) attrs.get("error");
dto.path = (String) attrs.get("path");
dto.timestamp = attrs.get("timestamp").toString();
return dto;
}
}
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
OriginalRequestObject originalRequest = (OriginalRequestObject) exception.getBindingResult().getTarget();
ErrorResponse errorResponse = new ErrorResponse(
status.value(),
originalRequest.getId() + " " + exception.getMessage());
return ResponseEntity.status(status).body(myErrorResponse);
}

Spring #ExceptionHandler does not return content unless body is empty

I'm using Spring #ControllerAdvice to handle exceptions
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { DataIntegrityViolationException.class})
#ResponseBody
public ResponseEntity<String> unknownException(Exception ex, WebRequest req) {
return new ResponseEntity<>(ex.getCause().getMessage(), new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
The problem i'm experiencing is that when the exception occurs (when i send a request via swagger), i do not get an expected exception message, but :
{"error": "no response from server"}
Response Code : 0
Response Body : No Content
I can clearly see in debug mode that the method annotated by #ExceptionHandler is called.
I've experimented with method return types, #ResponseBody, #ResponseStatus annotations and a few other thing that came to mind, but it seems that i only get some non-empty response when i return a ResponseEntity without a body, e.g.
ResponseEntity.noContent().build()
or
ResponseEntity.ok().build()
In such cases i get correct http code and a few headers
Please advise on what i'm doing wrong
Spring version 4.3.9
Spring boot version 1.5.4
Thank you in advance
UPD
I carried on experimenting and this is the solution that worked for me.
It is quite close to one of the answers - i will mark that one as accepted
In short, i just created my own dto class , populated the instance with the exception details i was interested in and returned it directly
My code
#ExceptionHandler(value = { DataIntegrityViolationException.class})
#ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ExceptionDetailHolder unknownException(Exception ex, WebRequest req) {
final Throwable cause = ex.getCause();
return new ExceptionDetailHolder("Error interacting with the database server",
cause.getClass() + ":" + cause.getMessage(),
cause.getCause().getClass() + ":" + cause.getCause().getMessage()
);
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
private class ExceptionDetailHolder {
private String message;
private String exceptionMessage;
private String innerExceptionMessage;
}
Results (which also show the contents of ex.getMessage and ex.getCause().getMessage() as asked by commenters) :
{
"message": "Error interacting with the database server",
"exceptionMessage": "class org.hibernate.exception.ConstraintViolationException:could not execute statement",
"innerExceptionMessage": "class com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:Column 'allow_copay_payments' cannot be null"
}
My way of handling exception is like below, I find the specific exception and then create my own class object ValidationErrorDTO in this case, then populate required fields in that class (ValidationErrorDTO):
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
Throwable throwable = ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
if (throwable instanceof EnumValidationException) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
}
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}

How to respond with HTTP status code in a Spring MVC #RestController #ResponseBody class returning an object?

I'm trying to have a #RestController which takes a #PathVariable return a specific object in JSON format, along with proper status code. So far the way the code is, it will return the object in JSON format because it is using Spring 4 built in Jackson library by default.
However I do not know how to make it so it will give a message to the user saying we want an api variable, then JSON data, then Error code (Or success code depending if all went well). Example output would be:
Please enter api value as parameter (NOTE this can be in JSON as well if needed)
{"id": 2, "api": "3000105000" ... } (NOTE this will be the JSON response object)
Status Code 400 (OR proper status code)
The url with parameter look like this
http://localhost:8080/gotech/api/v1/api/3000105000
The code I have so far:
#RestController
#RequestMapping(value = "/api/v1")
public class ClientFetchWellDataController {
#Autowired
private OngardWellService ongardWellService;
#RequestMapping(value = "/wells/{apiValue}", method = RequestMethod.GET)
#ResponseBody
public OngardWell fetchWellData(#PathVariable String apiValue){
try{
OngardWell ongardWell = new OngardWell();
ongardWell = ongardWellService.fetchOneByApi(apiValue);
return ongardWell;
}catch(Exception ex){
String errorMessage;
errorMessage = ex + " <== error";
return null;
}
}
}
A #RestController is not appropriate for this. If you need to return different types of responses, use a ResponseEntity<?> where you can explicitly set the status code.
The body of the ResponseEntity will be handled the same way as the return value of any #ResponseBody annotated method.
#RequestMapping(value = "/wells/{apiValue}", method = RequestMethod.GET)
public ResponseEntity<?> fetchWellData(#PathVariable String apiValue){
try{
OngardWell ongardWell = new OngardWell();
ongardWell = ongardWellService.fetchOneByApi(apiValue);
return new ResponseEntity<>(ongardWell, HttpStatus.OK);
}catch(Exception ex){
String errorMessage;
errorMessage = ex + " <== error";
return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
}
}
Note that you don't need #ResponseBody on a #RequestMapping method within a #RestController annotated class.
The idiomatic way would be to use an exception handler instead of catching the exception in your regular request handling method. The type of exception determines the response code. (403 for security error, 500 for unexpected platform exceptions, whatever you like)
#ExceptionHandler(MyApplicationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleAppException(MyApplicationException ex) {
return ex.getMessage();
}
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleAppException(Exception ex) {
return ex.getMessage();
}

Categories

Resources