So I have this API:
public Map<String, Object> myFunc(#RequestBody #Valid MyPrivateEntity body) {}
Which is marked with #RequestBody and #Valid
The thing is, if I omit the body when calling this API, I get the following error message:
{
"title": "Failed to parse request",
"detail": "Required request body is missing: public com.privatePackage.misc.service.rest.MyPrivateEntity com.privatePackage.misc.service.rest.MyPrivateResource.myFunc(java.lang.String, com.privatePackage.misc.service.rest.MyPrivateEntity)",
"status": 400
}
I don't want the error message to include class names and paths, instead just "Required request body is missing".
How can I do that?
Thanks
Try this code
#ExceptionHandler(BindException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST) // return 400 if validate fail
public String handleBindException(BindException e) {
// return message of first error
String errorMessage = "Request not found";
if (e.getBindingResult().hasErrors())
e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
return errorMessage;
}
Or use this way
public Map<String, Object> myFunc(
#RequestBody #Valid MyPrivateEntity body,
BindingResult bindingResult) { // add this parameter
// When there is a BindingResult, the error is temporarily ignored for manual handling
// If there is an error, block it
if (bindingResult.hasErrors())
throw new Exception("...");
}
Reference:
https://www.baeldung.com/spring-boot-bean-validation
If you need more control on only this endpoint then I'll suggest to mark request body optional and check in the method if it's null then return whatever message you want to show.
#RequestBody(required = false)
Try #ControllerAdvice to customise your message.
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
// paste custom hadling here
}
}
Reference:
https://ittutorialpoint.com/spring-rest-handling-empty-request-body-400-bad-request/
Related
I'm new to springboot and am working on a legacy project. I'v searched but didn't find answer.
The current working project uses #Valid #RequestBody to validate body, and #ExceptionHandler to catch the exception.
Now the new requirement is dealing with request headers, and I will use the headers(to log for example) regardless of body is valid or not.
The question is I don't konw how to get the headers when #ExceptionHandler hits. Similiar question is How to get the #RequestBody in an #ExceptionHandler (Spring REST) but I cannot figure out how to get headers from injected RequestContext(I even cannot resolve getRequestBody() method in the answer).
The minimal, working and reproducible code is pushed to github.
The Model:
public class Book {
#NotBlank(message = "Title is mandatory")
public String Title;
}
the Controller
#RestController
public class BooksController {
final Log log = LogFactory.getLog(getClass());
#PostMapping("/")
public String add(#RequestHeader("id") String value, #Valid #RequestBody final Book book) {
log.info(value); // want this value even when hit #ExceptionHandler
return "added " + book.Title;
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public String handleInvalidRequest(final MethodArgumentNotValidException e) {
//log headers; ??
return e.getMessage();
}
}
the Client
curl -H "id:123" -H "Content-Type: application/json" http://localhost:8080 --data '{"Title":""}'
Additonal Information
jdk 11, springboot 2.5.3, intellij idea 2021.
model's code is in a library that i cannot change, so the validation logic is unknown to me.
I guess there should be a lot of ways to solve, such as defining some customized middlewares or handles, but I'm not familiar with those and I want to see a solution with least code changes. Thanks!
Inside #ExceptionalHandler you can accept HttpServletRequest to get headers
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public String handleInvalidRequest(final MethodArgumentNotValidException e, final HttpServletRequest req) {
//log headers; ??
req.getHeader("My-Header");
return e.getMessage();
}
I have an entity with some constraint :
#FieldMustBeEqualToAnotherField(
field = "sentCharges.currency",
fieldToCompare = "settlementAmount.currency",
message = "Sent charges currency must be null or equal to settlement amount currency")
public class MT103ApiDTO {
....
}
One exception handler :
#RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<Feedback> feedbacks = new LinkedList<>();
ex.getBindingResult().getFieldErrors().forEach(fields -> {
feedbacks.add(new Feedback().setCode(INVALID_FORMAT)
.setLabel(fields.getDefaultMessage())
.setSeverity(ERROR)
.setType(BUS)
.setSource(fields.getField()));
});
return handleExceptionInternal(ex, createAndLogError(ex, feedbacks), headers, BAD_REQUEST, request);
}
}
My probem is when I throw the MethodArgumentNotValidException I catch it but on ex.getBindingResult().getFieldErrors() I have nothing, is all time empty but on stacktrace i can see the names of fields in error
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.soprabanking.ibd.digitalswiftmanager.model.api.MessageApiDTO com.soprabanking.ibd.digitalswiftmanager.resource.MT103Resource.generate(com.soprabanking.ibd.digitalswiftmanager.model.api.MT103ApiDTO): [Error in object 'MT103ApiDTO': codes [FieldMustBeNullIfAnotherFieldIsIntoList.MT103ApiDTO,FieldMustBeNullIfAnotherFieldIsIntoList]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [MT103ApiDTO.,]; arguments []; default message [],sentCharges,chargeBearer,[Ljava.lang.String;#2bd828f4]; default message [Sent charges must be null if charge bearer code is SHA or BEN]]
I dont understand where is the problem... if someone have an answer.
Thx everyone and sorry for my bad english....
How can i strip down the excessive information from MethodArgumentNotValidException and keep only the required "default message" ??
I am experimenting with Validation annotations- #NotNull, #NotBlank, and #NotEmpty
I have configured a custom error message as below:-
#NotNull(message = "Aaaah !!!! firstname cannot be empty !!!")
private String firtName;
My Exception handler is :-
#RestControllerAdvice
public class ControllerAdviceClass {
#ExceptionHandler(value = MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity handleValidationException(MethodArgumentNotValidException ex)
{
return new ResponseEntity(ex.getMessage() , HttpStatus.BAD_REQUEST);
}
}
But the exception message i see on swagger is :-
Validation failed for argument [0] in public cosmosdb.User cosmosdb.CosmosController.postResult(cosmosdb.User):
[Field error in object 'user' on field 'firstName': rejected value [null]; codes [NotNull.user.firstName,NotNull.firstName,NotNull.java.lang.String,NotNull];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.firstName,firstName]; arguments []; default message [firstName]];
default message [Aaaah !!!! firstname cannot be empty !!!]]
I want to see only the default message * [Aaaah !!!! firstname cannot be empty !!!]] * and remove the extra bunkum.
I had a similar experience with adjusting the default messages to something more meaningful.
You have to implement a javax.validation.MessageInterpolation interface. From there you'll be able to interpolate your default message.
I used this site as a reference to solve my issue. https://www.baeldung.com/spring-validation-message-interpolation
#Override
protected ResponseEntity<Object>
handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
logError(ex, HttpStatus.BAD_REQUEST);
Map<String, String> errorMap = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
errorMap.put(error.getField(),error.getDefaultMessage());
});
return new ResponseEntity<>(errorMap, HttpStatus.BAD_REQUEST);
}
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<Object>(ex.getFieldError().getDefaultMessage(), HttpStatus.BAD_REQUEST);
}
To create custom error, we need to know what kind of Exception error it is. The problem is I cannot determine what kind of error the "request body is missing one" is. At first I thought it was categorized as MethodArgumentNotValidException , but it doesn't catch the error.
I create the Controller Advice for the error
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers, HttpStatus status, WebRequest request){
MyObject<Object> error = MyObject.failure("Invalid Parameter");
log.error("argument invalid", exception);
return new ResponseEntity<Object>(error, new HttpHeaders(), HttpStatus.OK);
}
The controller
#PostMapping(value = "/tes")
public MyObject<MyRes> myTest(#Valid #RequestBody MyReq req, HttpServletRequest hsReq) throws Exception{
return myService.updateTestData(req);
}
I used Postman to call the API.
* First Trial with bracket
* Second Trial - without bracket
No error occurred.
My question is, how to handle this error, When no request body attached at all in the request. I want to return the "invalid param" error in this case too.
This might be late but you might want to try adding this on your ControllerAdvice class, that's how I caught the Required request body is missing error when trying to send an empty request.
#ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<Object> handleMissingRequestBody(HttpMessageNotReadableException ex) {
return new ResponseEntity<Object>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
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);
}