Throwing custom errors as Json when a parameter is invalid - java

I am working on an API and need to throw and exception that looks like this
"error": "sortBy parameter is invalid"
}
if the sort by parameter is not one of my predetermined values,
i have a few parameters to do this for
here is what my controller looks like
#GetMapping("/api/posts")
public ResponseEntity<List<Post>> getPostResponse(#RequestParam String tag, Optional<String> sortBy,
Optional<String> direction) throws InvalidSortBy {
RestTemplate postResponseTemplate = new RestTemplate();
URI postUri = UriComponentsBuilder.fromHttpUrl("urlHere")
.queryParam("tag", tag)
.queryParamIfPresent("sortBy", sortBy)
.queryParamIfPresent("direction", direction)
.build()
.toUri();
ResponseEntity<PostResponse> response = postResponseTemplate.getForEntity(postUri, PostResponse.class);
ResponseEntity<List<Post>> newResponse = responseService.createResponse(response, sortBy, direction);
return newResponse;
}
}
ive remove the url but it works for sorting the incoming data but i need to validate and throw correct errors, im just really not sure how to do it in the format required, as json, any help appreciated

First you need to handle your exception and resolve it based on error, I would suggest you raise error codes for known application exception and resolve them in your exception handler (either by using #ControllerAdvice or #RestControllerAdvice), once you have translated error code to respective message send them as json you can refer below thread for more details on following SO thread
How to throw an exception back in JSON in Spring Boot
#ExceptionHandler
#ExceptionHandler to tell Spring which of our methods should be
invoked for a given exception
#RestControllerAdvice
Using #RestControllerAdvice which contains #ControllerAdvice to
register the surrounding class as something each #Controller should be
aware of, and #ResponseBody to tell Spring to render that method's
response as JSON

Related

Spring Boot - Remove external "/error" endpoint access

Introduction
I have a custom ErrorController implementation to handle all exceptions and create a custom error message:
#RestController
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public ResponseEntity handleError(HttpServletRequest request) {
HttpStatus status = HttpStatus.valueOf((Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
String body = ... // Code to calculate the body based on the request
return ResponseEntity.status(status.value()).body(body);
}
#Override
public String getErrorPath() {
return "/error";
}
}
Problem Description
However, this also enables access to the path /error which I would like to disable.
When trying to access https://localhost:8443/error, a NullPointerException is thrown by the HttpStatus.valueOf() method, because the status code could not be extracted. As a result, an Internal Server Error (500) is created, which is run through my custom controller, creating a custom 500 error response.
Temporary Fix
As a workaround, I can check if the status code attribute exists, and handle that case separately. But it is a work-around and not an actual fix.
The Question
What I would like is to disable the /error mapping from external access. If attempted, the result should be Not Found (404) which is then run through my custom controller.
Is the #RequestMapping("/error") necessary or could this be implemented differently?
Edits
Spring Boot version is 2.1.2.RELEASE
The server.error.whitelabel.enabled property is set to false. The issue does not seem to be related with it.

How to return proper error response for content-type text/csv?

I have a servlet where in general I return text/csv as response. So a plain comma separated string.
BUT: in case of exceptions, I'd want to show just some error text as response. But instead Spring generates a custom ResponseEntity object, then tries to convert this response to csv which obviously fails.
Is it possible to replace the requested format to format=json, and then just return the default Spring error response?
#RestController
public class CsvServlet {
#RequestMapping(value = "/test", produces = "text/csv")
#ResponseBody
public String errorCsv(HttpServletRequest request) {
return "some, plain, text";
}
}
Usage: localhost:8080/test?format=csv
When having spring.security.enabled=true, this will first validate the basic auth credentials. If they fail, spring will automatically redirect to /error servlet.
Thereby BasicErrorController.error() method comes in, catching the error and generating a ResponseEntity with error attributes like timestamp, exception, path, etc.
Problem: now an Object of type ResponseEntity is returned. This is fine as long as the format parameter is either ?format=json/xml. As the response can then be properly converted.
But in my special case, where I request ?format=csv, the conversation will fail, leading to:
Response could not be created:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not
find acceptable representation
This is partially true, moreover misleading because the user should directly see that the authentication credentials have been invalid. Because in general I'm accepting csv, but Spring and the mappers don't know how to convert a ResponseEntity to a plain format like csv.
Question: how can I preserve the original exception? So that I could just return a plain text error message. It would also be fine if I could return a application/json response in this case!
Workaround as follows for the moment: override the /error servlet handler, and if the ?format parameter is not either json/xml, just return the original http status error without a body.
Thus spring cannot fail on converting the body into plain format and return the response correctly (but without exception details in the body).
#Controller
public class WorkaroundBasicErrorController extends BasicErrorController {
//default constructor from ErrorMvcAutoConfiguration
public DefaultBasicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
super(errorAttributes, serverProperties.getError(), errorViewResolversProvider.getIfAvailable());
}
#RequestMapping
#ResponseBody
#Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
String format = request.getParameter("format");
return (StringUtils.containsAny(format, "json", "xml"))
? super.error(request)
: new ResponseEntity<>(getStatus(request)); //neglect body for plain formats
}
}
Try to use a void #ExceptionHandler, and write error message directly in HttpResponse

How to validate request parameters in Spring REST controller

I am working on a Spring REST application.
This application has only REST controllers, no view part.
I want to know how can I validate a #RequestParam
For example
#RequestMapping(value = "", params = "from", method = RequestMethod.GET)
public List<MealReadingDTO> getAllMealReadingsAfter(#RequestParam(name = "from", required = true) Date fromDate) {
......
......
}
In the above example, my goal is to validate the Date. Suppose someone pass an invalid value, then I should be able to handle that situation.
Now it is giving and exception with 500 status.
PS
My question is not just about Date validation.
Suppose, there is a boolean parameter and someone passes tru instead of true by mistake, I should be able to handle this situation as well.
Thanks in advance :)
Spring will fail with an 500 status code, because it cannot parse the value.
The stages of request handling are:
receive request
identify endpoint
parse request params / body values and bind them to the detected objects
validate values if #Validated is used
enter method call with proper parameters
In your case the flow fails at the parse (3) phase.
Most probably you receive a BindException.
You may handle these cases by providing an exception handler for your controller.
#ControllerAdvice
public class ControllerExceptionHandler {
#ExceptionHandler(BindException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public YourErrorObject handleBindException(BindException e) {
// the details which field binding went wrong are in the
// exception object.
return yourCustomErrorData;
}
}
Otherwise when parsing is not functioning as expected (especially a hussle with Dates), you may want to add your custom mappers / serializers.
Most probably you have to configure Jackson, as that package is responsible for serializing / deserializing values.

Spring Boot - Error Controller to handle either JSON or HTML

I have a spring boot application.
I have a custom error controller, that is mapped to using ErrorPage mappings. The mappings are largely based on HTTP Status codes, and normally just render a HTML view appropriately.
For example, my mapping:
#Configuration
class ErrorConfiguration implements EmbeddedServletContainerCustomizer {
#Override public void customize( ConfigurableEmbeddedServletContainer container ) {
container.addErrorPages( new ErrorPage( HttpStatus.NOT_FOUND, "/error/404.html" ) )
}
And my error controller:
#Controller
#RequestMapping
public class ErrorController {
#RequestMapping( value = "/error/404.html" )
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public String pageNotFound( HttpServletRequest request ) {
"errors/404"
}
This works fine - If I just enter a random non-existent URL then it renders the 404 page.
Now, I want a section of my site, lets say /api/.. that is dedicated to my JSON api to serve the errors as JSON, so if I enter a random non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to have a #ControllerAdvice that specifically caught a class of custom API exceptions I had defined and returned JSON, and in my standard ErrorController checking the URL and throwing an apprpriate API exception if under that API URL space (but that didn't work, as the ExceptionHandler method could not be invoked because it was a different return type from the original controller method).
Is this something that has been solved?
The problem was my own fault. I was trying to work out why my #ExceptionHandler was not able to catch my exception and return JSON - As I suggested at the end of my question, I thought I was having problems because of conflicting return types - this was incorrect.
The error I was getting trying to have my exception handler return JSON was along the lines of:
"exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
"message": "Could not find acceptable representation"
I did some more digging/experimenting to try to narrow down the problem (thinking that the issue was because I was in the Spring error handling flow and in an ErrorController that was causing the problem), however the problem was just because of the content negotiation stuff Spring does.
Because my errorPage mapping in the web.xml was mapping to /error/404.html, Spring was using the suffix to resolve the appropriate view - so it then failed when I tried to return json.
I have been able to resolve the issue by changing my web.xml to /error/404 or by turning off the content negotiation suffix option.
Now, I want a section of my site, lets say /api/.. that is dedicated
to my JSON api to serve the errors as JSON, so if I enter a random
non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to
have a #ControllerAdvice that specifically caught a class of custom
API exceptions I had defined and returned JSON, and in my standard
ErrorController checking the URL and throwing an apprpriate API
exception if under that API URL space (but that didn't work, as the
ExceptionHandler method could not be invoked because it was a
different return type from the original controller method).
I think you need to rethink what you are trying to do here. According to HTTP response codes here
The 404 or Not Found error message is an HTTP standard response code
indicating that the client was able to communicate with a given
server, but the server could not find what was requested.
So when typing a random URL you may not want to throw 404 all the time. If you are trying to handle a bad request you can do something like this
#ControllerAdvice
public class GlobalExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ErrorResponse> noRequestHandlerFoundExceptionHandler(NoHandlerFoundException e) {
log.debug("noRequestHandlerFound: stacktrace={}", ExceptionUtils.getStackTrace(e));
String errorCode = "400 - Bad Request";
String errorMsg = "Requested URL doesn't exist";
return new ResponseEntity<>(new ErrorResponse(errorCode, errorMsg), HttpStatus.BAD_REQUEST);
}
}
Construct ResponseEntity that suites your need.

Serializing Jersey exceptions from filter throws error

I currently have a RESTful webservice running Jersey. I recently added a filter that does some auth stuff, and it works in the happy-path case. However, when I need to throw an error from within this filter, instead of serializing the exception into a pretty json string it, it throws a 500 with the following error:
javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message
body writer for Java class myclass, and Java type class myclass, and MIME media type
application/octet-stream was not found
The thing is, I don't want to write anything to application/octet-stream. My service only uses application/json. This is not a problem in my actual Resource classes, where I can specify the#Produces annotation. Error responses thrown from the body of a resource will serialize properly.
My question, then, is: How do I control what MIME type is used for exceptions thrown while filtering?
You need to build an exceptionmapper to handle the exceptions and turn them in to JSON. Something like:
#Provider
public class UnexpectedExceptionMapper implements ExceptionMapper<Exception>
{
#Override
public Response toResponse(final Exception exception)
{
ResponseBuilder builder = Response.status(Status.BAD_REQUEST)
.entity(jsonError(exception))
.type(MediaType.APPLICATION_JSON);
return builder.build();
}
private String jsonError(final Exception exception)
{
return "{\"error\":\"" + exception.getMessage() + "\"}";
}
}

Categories

Resources