Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 297 Vote(s) - 3.49 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to handle exceptions in Spring MVC differently for HTML and JSON requests

#1
I'm using the following exception handler in Spring 4.0.3 to intercept exceptions and display a custom error page to the user:

<!-- Language: java -->

@ControllerAdvice
public class ExceptionHandlerController
{
@ExceptionHandler(value = Exception.class)
public ModelAndView handleError(HttpServletRequest request, Exception e)
{
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
}

But now I want a different handling for JSON requests so I get JSON error responses for this kind of requests when an exception occurred. Currently the above code is also triggered by JSON requests (Using an `Accept: application/json` header) and the JavaScript client doesn't like the HTML response.

How can I handle exceptions differently for HTML and JSON requests?
Reply

#2
The controlleradvice annotation has several properties that can be set, since spring 4. You can define multiple controller advices applying different rules.

One property is "annotations. Probably you can use a specific annotation on the json request mapping or you might find another property more usefull?
Reply

#3
The best way to do this (especially in servlet 3) is to register an error page with the container, and use that to call a Spring `@Controller`. That way you get to handle different response types in a standard Spring MVC way (e.g. using `@RequestMapping` with produces=... for your machine clients).

I see from your other question that you are using Spring Boot. If you upgrade to a snapshot (1.1 or better in other words) you get this behaviour out of the box (see [`BasicErrorController`](

[To see links please register here]

)). If you want to override it you just need to map the /error path to your own `@Controller`.
Reply

#4
Use @ControllerAdvice
Let the exception handler send a DTO containing the field errors.

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();

return processFieldErrors(fieldErrors);
}

This code is of this website:http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
Look there for more info.
Reply

#5
As you have the HttpServletRequest, you should be able to get the request "Accept" header. Then you could process the exception based on it.

Something like:

String header = request.getHeader("Accept");
if(header != null && header.equals("application/json")) {
// Process JSON exception
} else {
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
Reply

#6
The ControllerAdvice annotation has an element/attribute called basePackage which can be set to determine which packages it should scan for Controllers and apply the advices. So, what you can do is to separate those Controllers handling normal requests and those handling AJAX requests into different packages then write 2 Exception Handling Controllers with appropriate ControllerAdvice annotations. For example:

@ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
@ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {
Reply

#7
Since i didn't find any solution for this, i wrote some code that manually checks the `accept` header of the request to determine the format. I then check if the user is logged in and either send the complete stacktrace if he is or a short error message.

I use ResponseEntity to be able to return both JSON or HTML like [here][1].
Code:

@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {

final HttpHeaders headers = new HttpHeaders();
Object answer; // String if HTML, any object if JSON
if(jsonHasPriority(request.getHeader("accept"))) {
logger.info("Returning exception to client as json object");
headers.setContentType(MediaType.APPLICATION_JSON);
answer = errorJson(ex, isUserLoggedIn());
} else {
logger.info("Returning exception to client as html page");
headers.setContentType(MediaType.TEXT_HTML);
answer = errorHtml(ex, isUserLoggedIn());
}
final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return new ResponseEntity<>(answer, headers, status);
}

private String errorHtml(Exception e, boolean isUserLoggedIn) {
String error = // html code with exception information here
return error;
}

private Object errorJson(Exception e, boolean isUserLoggedIn) {
// return error wrapper object which will be converted to json
return null;
}

/**
* @param acceptString - HTTP accept header field, format according to HTTP spec:
* "mime1;quality1,mime2;quality2,mime3,mime4,..." (quality is optional)
* @return true only if json is the MIME type with highest quality of all specified MIME types.
*/
private boolean jsonHasPriority(String acceptString) {
if (acceptString != null) {
final String[] mimes = acceptString.split(",");
Arrays.sort(mimes, new MimeQualityComparator());
final String firstMime = mimes[0].split(";")[0];
return firstMime.equals("application/json");
}
return false;
}

private static class MimeQualityComparator implements Comparator<String> {
@Override
public int compare(String mime1, String mime2) {
final double m1Quality = getQualityofMime(mime1);
final double m2Quality = getQualityofMime(mime2);
return Double.compare(m1Quality, m2Quality) * -1;
}
}

/**
* @param mimeAndQuality - "mime;quality" pair from the accept header of a HTTP request,
* according to HTTP spec (missing mimeQuality means quality = 1).
* @return quality of this pair according to HTTP spec.
*/
private static Double getQualityofMime(String mimeAndQuality) {
//split off quality factor
final String[] mime = mimeAndQuality.split(";");
if (mime.length <= 1) {
return 1.0;
} else {
final String quality = mime[1].split("=")[1];
return Double.parseDouble(quality);
}
}

[1]:

[To see links please register here]

Reply

#8
The trick is to have a REST controller with two mappings, one of which specifies `"text/html"` and returns a valid HTML source. The example below, which was tested in **Spring Boot 2.0**, assumes the existence of a separate template named `"error.html"`.

@RestController
public class CustomErrorController implements ErrorController {

@Autowired
private ErrorAttributes errorAttributes;

private Map<String,Object> getErrorAttributes( HttpServletRequest request ) {
WebRequest webRequest = new ServletWebRequest(request);
boolean includeStacktrace = false;
return errorAttributes.getErrorAttributes(webRequest,includeStacktrace);
}

@GetMapping(value="/error", produces="text/html")
ModelAndView errorHtml(HttpServletRequest request) {
return new ModelAndView("error.html",getErrorAttributes(request));
}

@GetMapping(value="/error")
Map<String,Object> error(HttpServletRequest request) {
return getErrorAttributes(request);
}

@Override public String getErrorPath() { return "/error"; }

}


----------


References
----------

* [ModelAndView](

[To see links please register here]

) -- return type for HTML
* [DefaultErrorAttributes](

[To see links please register here]

) -- data used to render HTML template (and JSON response)
* [BasicErrorController.java](

[To see links please register here]

) -- Spring Boot source from which this example was derived
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through