I would like to throw different exceptions with different Strings (giving details of the error via message) with a very specific JSON structure, something like:
[
{
"error": [
{
"statusCode": 400,
"customMessage": "xxx",
"timestamp": "time"
}
]
}
]
Is this possible to achieve with Spring Boot? I haven't found how to do it.
Create an ErrorHandler(as per your naming convention) class and extend spring class ResponseEntityExceptionHandler which receives all method exceptions. Check spring documentation for more details on this class.
In your class add handleTechincalException method as below for required exceptions.
#ExceptionHandler({ Exception.class, AbcException.class, XyzException.class })
public ResponseEntity<Object> handleTechnicalException(Exception e, WebRequest webRequest) {
// prepare response entity object as required based on type of exception
// return ResponseEntity<Object>
}
Related
How can I validate the actual Exception in a spring-webflux test?
The following worked in the old spring-web environment, but migrating to netty and spring-webflux, the MvcResult cannot be resolved anymore (NullPointerException):
#SpringBootTest
#AutoConfigureWebTestClient
public class ApiTest {
#Test
public void test() throws Exception {
webTestClient.get()
.uri("/api?test=123")
.exchange()
.expectStatus().isBadRequest()
.expectBody().consumeWith(rsp -> {
//throws NPE
Exception ex = ((MvcResult) rsp.getMockServerResult()).getResolvedException();
assertTrue(ex instanceof ResponseStatusException);
});
}
}
#RestController
public class ApiController {
#PostMapping("/api")
public String test(#RequestParam String test) {
if (test.matches("[0-9]+"))
throw new ResponseStatusException(HttpStatus.BadRequest, "Prohibited characters");
}
}
How could I still validate the real exception class?
The main purpose of the WebTestClient is to test endpoints using fluent API to verify responses. There is no magic deserialization or error handling happening but you can get access to the raw responses (status, headers, body).
In your example you will not get MvcResult or ResponseStatusException but you could get access to the raw body using rsp.getResponseBody() that would look like
{
"timestamp": "2022-05-17T17:57:07.041+00:00",
"path": "/api",
"status": 400,
"error": "Bad Request",
"requestId": "4fa648d"
}
You could use expectBody().consumeWith(rsp -> { ... }) to get access to the request and response or expectBody(String.class).value(body -> { ... })to get just body. As an alternative use some fluent API to validate result JSON.expectBody().json()or.expectBody().jsonPath()` to check specific fields only.
In addition you could still deserialize body explicitly using .expectBody(Response.class).value(body -> {...}).
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
}
}
I am posting a POJO where I get an error saying the field is not included.
Asset POJO
public class Asset {
private MultipartFile[] files;
private String name;
private String meta;
//Constructor/Getters n Setters
}
Resource Method
#PostMapping("asset")
public ResponseEntity uploadAsset(#RequestParam("asset") Asset asset) {
System.out.println(asset);
return new ResponseEntity(HttpStatus.ACCEPTED);
}
PostMan JSON Body
{
"asset" : {
"files": [
"#/home/Downloads/1.jpeg",
"#/home/Downloads/2.jpeg"
],
"name": "assetName",
"meta": "assetMeta"
}
}
PostMan JSON Response
{
"timestamp": "2019-10-29T20:46:19.536+0000",
"status": 400,
"error": "Bad Request",
"message": "Required Asset parameter 'asset' is not present",
"path": "/asset"
}
I don't understand why I get the Required Asset parameter 'asset' is not present message when I have it in the JSON body. Any ideas on this?
Use #RequestBody rather than #RequestParam
public ResponseEntity uploadAsset(#RequestBody Asset asset) {
Based on your payload, Spring is expecting an object that looks like this:
public class SomeClass {
private Asset asset;
}
Change your payload to look like this:
{
"files": [
"#/home/Downloads/1.jpeg",
"#/home/Downloads/2.jpeg"
],
"name": "assetName",
"meta": "assetMeta"
}
RequestParam
Annotation which indicates that a method parameter should be bound to a web request parameter.
RequestBody
Annotation indicating a method parameter should be bound to the body of the web request. The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request. Optionally, automatic validation can be applied by annotating the argument with #Valid.
HttpMessageConverter
Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
You need to check converter dependency. because you using application/json.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
Q : Missing request param when it is included in body
A : Use #RequestBody annotation.
I tried #Jordans answer and the endpoint was called with all values set to null :(
Doing more research I came across this statement https://stackoverflow.com/a/51982230/2199102 and tried it out.
Combining #Jordans answer and then the annotation change, I was able to get the answer I wanted
I have a simple webservice that returns content either as json or as plain text (depending on the clients' accept http header).
Problem: if an error occurs during text/plain request, Spring somehow returns a 406 Not Acceptable. Which is kind of wrong, because spring could as well just write the error out as plain error text, and moreover should absolutely preserve the 400 error status:
#RestController
public class TestServlet {
#PostMapping(value = "/test", produces = {APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE, "text/csv"})
public Object post() {
throw new BadRequestException("bad req");
}
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String msg) {
super(msg);
}
}
POST request with accept=application/json:
{
"timestamp": "2018-07-30T14:26:02",
"status": 400,
"error": "Bad Request",
"message": "bad req",
"path": "/test"
}
BUT with accept=text/csv (or text/plain) shows an empty response with status 406 Not Acceptable.
I also noticed the DispatcherServlet.processDispatchResult() is called twice: first with my BadRequest exception, 2nd time with HttpMediaTypeNotAcceptableException. So clearly the rendering of my custom exception fails, but why?
The problem is the restrictive Accept header allowing only one content type as response. In case of an error, Spring MVC needs to handle the BadRequestException and produce the required content type using a registered HttpMessageConverter.
By default Spring Boot has no message converter to produce text/plain directly from any object. You may register an ObjectToStringHttpMessageConverter (as a bean should work for Spring Boot) to allow this and you will get the result of BadRequestException.toString() as response body.
I assume a similar problem for text/csv but I am not sure how your setup for CSV message conversion looks like.
The condition written in "produces" determines the media type to use for the response to be "text/csv". So For a success scenario it works fine, **
but when you go for rendering an exception with a JSON body that
becomes a problem and gives you a 406 instead.
**
in latest versions of spring framework the problem fixes, but in old versions,as mentioned in Spring JIRA comments you should remove HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribute from request
the code might be like this :
#RestControllerAdvice
public class ExampleControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<?> handleException(HttpServletRequest request, Exception e) {
request.removeAttribute(
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return new ResponseEntity<?>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
we can handle exception in advice
#ControllerAdvice
class ExceptionHandler{
#ExceptionHandler(value = {HttpMediaTypeNotAcceptableException.class})
public ResponseEntity handleMediaTypeException(HttpMediaTypeNotAcceptableException e) {
APIErrorResponse apiErrorResponse = new APIErrorResponse();
apiErrorResponse.setErrorCode("set custom code here");
apiErrorResponse.setErrorMessage("set custom meggage here/ here we can use message from object of exception i.e e.getMessage()");
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
After reading some blog posts about making a custom exception handler for Spring, I wrote the following class:
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<Object> exceptionHandler(Exception e) {
HashMap<String, Object> msg = new HashMap<>(2);
msg.put("error", HttpStatus.PRECONDITION_FAILED.value());
msg.put("message", "Something went wrong");
return new ResponseEntity<>(msg, HttpStatus.BAD_REQUEST);
}
}
The intent is to send msg in the JSON response instead of giving away the Spring exception what was thrown for whatever reason.
This class isn't working, however.
When I hit, say, and invalid endpoint for my server API, I get the default response payload:
{
"timestamp": 1449238700342,
"status": 405,
"error": "Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message": "Request method 'POST' not supported",
"path": "/bad_enpoint"
}
What am I missing?
Thanks.
Your handler will not be called because you want to map Exception to your custom error response but Spring MVC most likely already has one exception handler registered for Exception class. It also has one that handles HttpRequestMethodNotSupportedException for sure.
It is not a great idea however, to overwrite entire Spring MVC exception handling/mapping anyway. You should only care about specific exceptions - ones that you define.
Please read this article for a bit more insight into Spring MVC exception handling.
You don't need to extend ResponseEntityExceptionHandler to make it work.
Setting two HttpStatuses is reaaaaaly bad idea.
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<String> exceptionHandler(Exception e) {
return new ResponseEntity<>("Something went wrong", HttpStatus.BAD_REQUEST);
}
}