In my Spring Boot Application, I'm currently leveraging the resources/public/error/404.html custom error page to show (automagically) this 404 page errors on invalid URLS.
Is there an easy way to retain this auto functionality, and add a simple log message (with the invalid URL) for every such 404 ?
Ideally with as little code as possible I would want some like :
//Some code
LOGGER.warn("Invalid URL " + request.url);
//Some more code
You need to define a custom ErrorViewResolver:
#Component
public class MyErrorViewResolver implements ErrorViewResolver {
#Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
if (HttpStatus.NOT_FOUND == status) {
LoggerFactory.getLogger(this.getClass()).error("error 404 for url " + model.get("path"));
return new ModelAndView("error404", model);
}
else {
return new ModelAndView("error", model);
}
}
}
This MyErrorViewResolver will be automatically called in the BasicErrorController class.
For a 404 error, the view "error404" will be displayed.
For the other errors, the view "error" will be displayed.
Views must be in the "templates" folder (resources/templates/error404.html).
Add NotFoundException handler.
public class BaseController {
// Logger declaration
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse handleNotFoundError(HttpServletRequest req, NotFoundException exception) {
List<Error> errors = Lists.newArrayList();
errors.add(new Error(String.valueOf(exception.getCode()), exception.getMessage()));
log.error(exception);
return new ErrorResponse(errors);
}
}
Related
Right now i'm using this example of exception handling:
//get an object of type curse by id
//in the service file, this findCurseById() method throws a
//CursaNotFoundException
#GetMapping("/{id}")
public ResponseEntity<curse> getCursaById (#PathVariable("id") Long id) {
curse c = curseService.findCurseById(id);
return new ResponseEntity<>(c, HttpStatus.OK);
}
//so if not found, this will return the message of the error
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(CursaNotFoundException.class)
public String noCursaFound(CursaNotFoundException ex) {
return ex.getMessage();
}
and that's my exception
public class CursaNotFoundException extends RuntimeException {
public CursaNotFoundException(String s) {
super(s);
}
}
in future I want to use Angular as front-end, so I don't really know how I should treat the exceptions in the back-end. For this example let's say, should I redirect the page to a template.html page in the noCursaFound() method, or should I return something else? A json or something? I couldn't find anything helpful. Thanks
I would suggest keeping the error handling at the REST API level and not redirecting to another HTML page on the server side. Angular client application consumes the API response and redirects to template.html if needed.
Also, it would be better if the backend returns an ApiError when an exception occurs with a message and, optionally, an error code:
public class ApiError {
private String message;
private String code;
}
and handle the exceptions in a separate class, ExceptionHandler annotated with #ControllerAdvice:
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(value = CursaNotFoundException.class)
public ResponseEntity cursaNotFoundException(CursaNotFoundException cursaNotFoundException) {
ApiError error = new ApiError();
error.setMessase(cursaNotFoundException.getMessage());
error.setCode(cursaNotFoundException.getCode());
return new ResponseEntity(error, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(value = Exception.class)
public ResponseEntity<> genericException(Exception exception) {
ApiError error = new ApiError();
error.setMessase(exception.getMessage());
error.setCode("GENERIC_ERROR");
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
help me anybody Please in this issue.
The project, I am working on is old mvc, and is not going to be change to rest, So have to deal with "what we have :) ".
this is my controller method, the class of which is anotated #Controller
#RequestMapping(method=RequestMethod.POST)
public String createSomething(#RequestBody somejson, Model m) throws Exception {
SomeCustomListenerClass listener = new SomeCustomListenerClass(m);
AnnotherClass ac = somejson.toNotification(someService, anotherService, listener);
try {
ac = someService.createSomething(ac, listener);
m.addAttribute("success", true);
m.addAttribute("notificationId", ac.getId());
}
catch(SawtoothException ex) {
return handleError(ex, "Create Notification", listener);
}
return "structured";
}
and this one is handleError method body
private String handleError(Exception ex, String operation, SomeCustomListenerClass listener) {
if (!listener.hasErrors()) {
log.error("Unexpected error getting notification detail", ex);
listener.error("notification.controllerException", operation);
}
return "error";
}
Now I am getting the right errors in the client side, say in browser, but also getting the status code 500
now my boss says that we have to get 400, when validation errors hapens, not 500, as is now.
So, Please help me guys, how to overcome to this problem.
You can extend your exceptions and throw them on your controller:
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Your exception message")
public class YourCustomException extends RuntimeException {
}
Or you can use an ExceptionControllerHandler:
#Controller
public class ExceptionHandlingController {
// #RequestHandler methods
...
// Exception handling methods
// Convert a predefined exception to an HTTP Status code
#ResponseStatus(value=HttpStatus.CONFLICT,
reason="Data integrity violation") // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void conflict() {
// Nothing to do
}
// Specify name of a specific view that will be used to display the error:
#ExceptionHandler({SQLException.class,DataAccessException.class})
public String databaseError() {
// Nothing to do. Returns the logical view name of an error page, passed
// to the view-resolver(s) in usual way.
// Note that the exception is NOT available to this view (it is not added
// to the model) but see "Extending ExceptionHandlerExceptionResolver"
// below.
return "databaseError";
}
// Total control - setup a model and return the view name yourself. Or
// consider subclassing ExceptionHandlerExceptionResolver (see below).
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
Try the #ExceptionHandler annotation or #ControllerAdvice to create custom exception handling mechanisms:
https://www.tutorialspoint.com/spring_boot/spring_boot_exception_handling.htm
add #ResponseStatus(HttpStatus.BAD_REQUEST) on top of handleError(...) method.
#ExceptionHandler({ Throwable.class })
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleError(...) {
...
}
I am trying to create a controller so that when a user goes to a non-existing URL, he/she will be mapped to a custom error page "error.jsp".
Currently, my Exception Handler Controller looks as followed:
#ControllerAdvice
public class ExceptionHandlerController {
private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlerController.class);
#ExceptionHandler(value = {Exception.class, RuntimeException.class})
public String defaultErrorHandler(Exception e) {
logger.error("Unhandled exception: ", e);
return "error";
}
#ExceptionHandler(NoHandlerFoundException.class)
public String handle(Exception e) {
logger.error("No handler found!", e);
return "error";
}
}
However, when I run my web app and visit a nonexisting URL, I get redirected to the default browser page saying '404 this page cannot be page.
Does anyone have any thoughts or suggestions as to why this is not working?
From the javaDoc of NoHandlerFoundException
By default when the DispatcherServlet can't find a handler for a
request it sends a 404 response. However if its property
"throwExceptionIfNoHandlerFound" is set to true this exception is
raised and may be handled with a configured HandlerExceptionResolver.
To solve this, You need to make sure you did these 2 things.
Creating SimpleMappingExceptionResolver and registering as a bean
#Bean
HandlerExceptionResolver customExceptionResolver () {
SimpleMappingExceptionResolver s = new SimpleMappingExceptionResolver();
Properties p = new Properties();
//mapping spring internal error NoHandlerFoundException to a view name.
p.setProperty(NoHandlerFoundException.class.getName(), "error-page");
s.setExceptionMappings(p);
//uncomment following line if we want to send code other than default 200
//s.addStatusCode("error-page", HttpStatus.NOT_FOUND.value());
//This resolver will be processed before default ones
s.setOrder(Ordered.HIGHEST_PRECEDENCE);
return s;
}
Set setThrowExceptionIfNoHandlerFound as true in your dispatcherServlet.
p
public class AppInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
....
......
#Override
protected FrameworkServlet createDispatcherServlet (WebApplicationContext wac) {
DispatcherServlet ds = new DispatcherServlet(wac);
//setting this flag to true will throw NoHandlerFoundException instead of 404 page
ds.setThrowExceptionIfNoHandlerFound(true);
return ds;
}
}
Refer complete example here.
I am running a simple spring-boot web application api. The problem is when I throw an exception, or spring throws an exception, the exception is always thrown in Http, springs default error page.
Is there a way to get the errors to default to another mediatype, say, JSON?
Basically I always want json, even on errors.
I do not want to have to write a custom #ExceptionHandler for each exception type as that is just plain terrible..
Update: Here is what I am currently trying:
#ControllerAdvice
#EnableAutoConfiguration
public class ErrorWritter extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
String response = "{\"status\":\""
+ status.toString()
+ "\",\"generic message\":\""
+ status.getReasonPhrase()
+ "\",\"specific message\":\""
+ ex.getMessage()
+ "\" }";
return new ResponseEntity<Object>(response, headers, status);
}
}
This doesn't seem to do anything however. Is there something I need to do in order to get spring to recognize that I want it to use this?
Please note: I am using Java config and NOT xml config.
There is pretty good info in the following article:
http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
you can create a model for your error such as:
public class ErrorInfo {
public final String url;
public final String ex;
public ErrorInfo(String url, Exception ex) {
this.url = url;
this.ex = ex.getLocalizedMessage();
}
}
And an error handler that uses that returns a representation of that model:
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MyBadDataException.class)
#ResponseBody ErrorInfo handleBadRequest(HttpServletRequest req, Exception ex) {
return new ErrorInfo(req.getRequestURL(), ex);
}
If you want more details on how the #ExceptionHandler works in spring, look at the spring docs:
http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/mvc.html#mvc-exceptionhandlers
I'm using the following exception handler in Spring 4.0.3 to intercept exceptions and display a custom error page to the user:
#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?
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 {
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). If you want to override it you just need to map the /error path to your own #Controller.
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;
}
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.
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);
}
}
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 -- return type for HTML
DefaultErrorAttributes -- data used to render HTML template (and JSON response)
BasicErrorController.java -- Spring Boot source from which this example was derived
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?
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.