Exception handler doesn't use the value in #ResponseStatus - java

I have the following controller advice to handle the exceptions within my app globally:
#ControllerAdvice
public class ExceptionHandlingController {
// Convert a predefined exception to an HTTP Status code
#ResponseStatus(value=HttpStatus.BAD_REQUEST) // 400
#ExceptionHandler(ConstraintViolationException.class)
public void ConstraintViolationExceptionHandler() {
//Nothing to do
}
}
The code below is the controller, which tries to save an object to the db (in the service layer). The class that object belongs to, has annotations that fail.
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public void create(#RequestBody CustomUserDetails user, HttpServletResponse response) {
logger.debug("User signup attempt with username: " + username);
userDetailsServices.saveIfNotExists(user);
}
I expect the client to receive a 400 response when ConstraintViolationException is thrown.
When the method returns void , no response is returned. When I change it String and return a random text, I get 404 response back.
{
"timestamp": 1495489172770,
"status": 404,
"error": "Not Found",
"exception": "javax.validation.ConstraintViolationException",
"message": "Validation failed for classes [security.model.CustomUserDetails] during persist time for groups [javax.validation.groups.Default, ]\nList of constraint violations:[\n\tConstraintViolationImpl{interpolatedMessage='must match \"^(?!.*\\..*\\..*)[A-Za-z]([A-Za-z0-9.]*[A-Za-z0-9]){8,15}$\"', propertyPath=username, rootBeanClass=class com.boot.cut_costs.security.model.CustomUserDetails, messageTemplate='{javax.validation.constraints.Pattern.message}'}\n]",
"path": "/signup"
}
How can I make this return a simple BAD REQUEST message as it is defined for the #ExceptionHandler.
Note: ConstraintViolationExceptionHandler is hit!

Related

Java Spring - Handle exception thrown when #RequestBody is null

I have a rest api in Java Spring Boot. I am making a POST request to api. If there is a data in the body it works fine but when no data is passed, it gives a 400 Bad Request response:
{
"timestamp": "2021-05-03T13:32:36.746+0000",
"status": 400,
"error": "Bad Request",
"message": "Required request body is missing: public org.springframework.http.ResponseEntity<com.package.MyResponse> com.package.MyController.getData(com.package.MyRequest)",
"path": "/api/"
}
How can I hide the package information and class name from the message in response? It should return mesage as:
"message": "Required request body is missing."
One option is to set #RequestBody(required=false) and then handle manually inside, but I want to avoid.
It would be great if someone can help. Thank you.
You can create a RestControllerAdvice extend it with ResponseEntityExceptionHandler. In implementation override corresponding method and send your custom message. In your case method will be handleHttpMessageNotReadable from ResponseEntityExceptionHandler.
Here is the sample code:
/**
* Intercept exception to response with error
*/
#RestControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
/**
* {#inheritDoc}
*/
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
// your custom implementation
}
}

How to do Bean Validation with Hibernate Validation in Spring Boot app?

I'm learning about Hibernate Validation in a Spring Boot app and I have a Rest controller and a POST method. And when I make a request, if a field isn't validated successfully, the client should receive 400 Bad Request and in the body a message like this "validation failed". And I try to do this, but the message didn't come.
This is the Body:
{
"timestamp": "2020-06-06T07:56:11.377+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/ibantowallet"
}
And in the console logs I get:
2020-06-06 10:56:11.371 WARN 7552 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity com.dgs.demotrans.api.TransactionController.sendMoneyIbanToWallet(com.dgs.demotrans.request.IbanToWalletRequest): [Field error in object 'ibanToWalletRequest' on field 'fromIban': rejected value [FR9151000 0000 0123 4567 89]; codes [Pattern.ibanToWalletRequest.fromIban,Pattern.fromIban,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [ibanToWalletRequest.fromIban,fromIban]; arguments []; default message [fromIban],[Ljavax.validation.constraints.Pattern$Flag;#3c555785,[a-zA-Z]{2}\d{2}[ ]\d{4}[ ]\d{4}[ ]\d{4}[ ]\d{4}[ ]\d{2}|DE\d{20}]; default message [IBAN validation failed]] ]
So the message is empty, I want the client to receive the specific message "IBAN validation failed" or "Please provide an amount", etc. How can I do that? Thank you in advance!
You would either have to create a ControllerAdvice method that handles MethodArgumentNotValidException, the error thrown by hibernate validator, like in this example:
#ResponseStatus(BAD_REQUEST)
#ResponseBody
#ExceptionHandler(MethodArgumentNotValidException.class)
public CustomError methodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return mapToCustomError(fieldErrors);
}
or you can inject BindingResult in your controller method and check whether validation failed in there:
#PostMapping("/ibantoiban")
public ResponseEntity<String> sendMoneyIbanToIban(#Valid #RequestBody IbanToIbanRequest ibanToIbanRequest, BindingResult bindingResult) {
if (bindingResult.hasErrors()) { /** handle error here */ }
Transaction transaction = transactionService.sendMoneyIbanToIban(ibanToIbanRequest);
HttpHeaders headers = new HttpHeaders();
headers.add("Location", "/ibantoiban" + transaction.getTransactionId().toString());
return new ResponseEntity(headers, HttpStatus.CREATED);
}

How to change Status code in spring boot error response?

I am making a simple rest service that makes some http calls and aggregates data using RestTemplate.
Sometimes i get NotFound error and sometimes BadRequest errors.
I want to respond with the same status code to my client and Spring seems to have this mapping out of the box. the message is okay but the Status code is always 500 Internal Server error.
I Would like to map my status code to the one i am initially receiving
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 500,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
i would like it to be this way
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 400,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
It throws HttpClientErrorException.BadRequest or HttpClientErrorException.NotFound
my code is a simple endpoint :
#GetMapping("/{id}")
public MyModel getInfo(#PathVariable String id){
return MyService.getInfo(id);
}
You can create global exception handling with #ControllerAdvice annotation. Like this:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = YourExceptionTypes.class)
protected ResponseEntity<Object> handleBusinessException(RuntimeException exception, WebRequest request) {
return handleExceptionInternal(exception, exception.getMessage(), new HttpHeaders(), HttpStatus.NOT_ACCEPTABLE, request);
}
}
When an exception is thrown, the handler will catch and transform it to the desired response. The original exception wont be propagated.
The accepted solution with the #ControllerAdvice is insufficient. That surely marks the response with the custom status code for the exception. It does, however, not return the wanted response body as JSON but as only simple string - the message from the exception.
To get the correct status code and the default error body the DefaultErrorAttributes can help.
#ControllerAdvice
public class PackedTemplateNotRecodableExceptionControllerAdvice extends ResponseEntityExceptionHandler {
#Autowired
private DefaultErrorAttributes defaultErrorAttributes;
#ExceptionHandler(PackedTemplateNotRecodableException.class)
public ResponseEntity<Object> handlePackedTemplateNotRecodableException(final RuntimeException exception, final WebRequest webRequest) {
// build the default error response
webRequest.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, HttpStatus.BAD_REQUEST.value(), RequestAttributes.SCOPE_REQUEST);
final Map<String, Object> errorAttributes = defaultErrorAttributes.getErrorAttributes(webRequest, ErrorAttributeOptions.defaults());
// return the error response with the specific response code
return handleExceptionInternal(exception, errorAttributes, new HttpHeaders(), HttpStatus.BAD_REQUEST, webRequest);
}
}
That way you'll receive the wanted error response, e.g. something like this:
{
"timestamp": "2019-07-01T17:56:04.539+0000",
"status": 400,
"error": "Internal Server Error",
"message": "400 Bad Request",
"path": "/8b8a38a9-a290-4560-84f6-3d4466e8d7901"
}
I have spent a lot of time looking into this issue, including solutions from answers here, which didn't work for me (or I didn't implement correctly).
I finally got a breakthrough. Instead of throwing a generic Exception such as throw new Exception(message), I created classes that extends the Exception class for the specific exception type - with their respective HTTP error codes and message
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class BadRequestException extends Exception{
public BadRequestException(String message) {
super(message);
}
}
In your application logic, you can now throw the Bad Request exception with a message like so throw new BadRequestException("Invalid Email"). This will result in an exception thrown thus :
{
"timestamp": "2021-03-01T17:56:04.539+0000",
"status": 400,
"error": "Bad Request",
"message": "Invalid Email",
"path": "path to controller"
}
You can now create other custom exception classes for the different exceptions you want, following the above example and changing the value parameter in the #ResponseStatus, to match the desired response code you want. e.g for a NOT FOUND exception #ResponseStatus (value = HttpStatus.NOT_FOUND), Java provides the different HTTP status codes via the HttpStatus enum.
For more context
I hope this is detailed enough and helps someone :)
Possible duplicate of Spring Resttemplate exception handling Your code needs a controller advice to handle the exceptions from the service it is calling.

Spock + spring boot web - get exception message

So, I'm using spring boot web and I want to test this method:
private void testException(HotelTvApp app) {
throw new InternalServerException("Test message");
}
Returning custom exception:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerException extends RuntimeException {
public InternalServerException(String message) {
super(message);
}
}
A controller that invokes this method returns following JSON data:
{
"timestamp": 1558423837560,
"status": 500,
"error": "Internal Server Error",
"message": "Test message",
"path": "/api/v1/test"
}
I want to write a test that will check if the exception message is correct. I tried this:
def "createRoom() should return 500 if trying to create room with already existing number"() {
given:
def uriRequest = UriComponentsBuilder.fromPath("/api/v1/test")
when:
def perform = mvc.perform(get(uriRequest.toUriString()))
then:
perform.andExpect(status().isInternalServerError())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath('$.message', is("Test message")))
}
But I get this exception:
Caused by: java.lang.AssertionError: Content type not set
How can I check the exception message here?
You can explicitly set the contentType as below
#ExceptionHandler(OrgNotFoundException.class)
public ResponseEntity<String> exceptionHandler(final Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body("Error");
}

consume Rest api with two different responses

I am trying to consume api with two different responses. when providing proper input and headers, it gives one json object. That is working fine with code given below.
but when one of the inputs or headers are missing then api is returning other json which states error details while hitting through Postman but same is not getting achieved by client code. It throws exception but api should return json with error details.
first I tried with postForobject(), then changed to exchange() assuming postForObject returning object and api is not getting expected object format. so tried with String.clas instead of particular class
what can be done to get two different json object by hitting same url?
when success:
{
"contacts": [
{
"input": "98########",
"status": "valid"
}
]
}
when input or header missing:
"errors": [
{
"code": 1###,
"title": "Access denied",
"details": "invalid deatils"
}
]
below is my code:
public static void main(String[] args) throws Exception {
RestTemplate restTemplate = new RestTemplate();
String check_Contact_Async = "https://port/contacts/";
sslByPass();
//headers = setHeaders();
contactsDTO = getInput();
final HttpEntity<ContactsDTO> entity = new HttpEntity<ContactsDTO>(contactsDTO, headers);
try {
//WsResponse res = restTemplate.postForObject(check_Contact_Async, entity, WsResponse.class);
ResponseEntity<String> responseEntity = restTemplate.exchange(
check_Contact_Async, HttpMethod.POST, entity,
String.class);
System.out.println(responseEntity);
} catch (Exception exception) {
System.out.println(exception);
}
}
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:498)
at com.websystique.springmvc.apitest.Example.main(Example.java:120)
any clue will be helpful
Thanks

Categories

Resources