I have created a AppErrorController that extends Boot's ErrorController in order to handle status500 errors. The example:
#Controller
public class AppErrorController implements ErrorController {
private static final Logger LOGGER = LogManager.getLogger(AppErrorController.class);
private static final String ERROR = "error";
private static final String ERROR_MESSAGE = "errorMessage";
#RequestMapping(value = "/error")
public String error(Exception e, Model model) {
LOGGER.error("500", e);
model.addAttribute(ERROR_MESSAGE, "Internal server error");
return ERROR;
}
#Override
public String getErrorPath() {
return ERROR;
}
}
I need the error to be logged. But the problem is that Exception e is always null. How to extract the actual error in order to log it?
ADDED
I have a GlobalExceptionHandler, but it never catches '500' errors
#Component
#ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LogManager.getLogger(GlobalExceptionHandler.class);
private static final String ERROR = "error";
#ExceptionHandler(Exception.class)
public String handleException(Exception e) {
LOGGER.error(e);
return ERROR;
}
}
One way to catch exception from jsp layer is to define your own error-page and point it location to you controller. Then you can extract the actual cause and do with it whatever you like.
web.xml
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/error</location>
</error-page>
ErrorController
#Controller
#RequestMapping("/error")
public class ErrorController {
private final Logger log = LoggerFactory.getLogger(ErrorController.class);
#RequestMapping
public String ex(HttpServletRequest request) {
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
throwable.printStackTrace(); //print
log.error(throwable.getMessage(), throwable); // or log
// or save to db
return "error"; //and redirect to some user-friendly page
}
}
Related
I'm trying to test this basic controller class and can’t seem to test the exceptions being mocked in the test. It just doesn’t seem to catch the exception, just returns 200 ok
Update:
I have the exception now being thrown, but the controller advice is not catching them.
Controller
private final Service service;
#GetMapping(value = “/cars/{id}”, produces = APPLICATION_JSON_VALUE)
public ResponseEntity<List<Cars>> getCar(#PathVariable final String carId) {
var car = this.service.getCar(carId);
return ResponseEntity.ok(car);
}
Here is my mapping class
#RestControllerAdvice
public class Mapper {
#ExceptionHandler({CarNotFoundException.class})
public ResponseEntity<Object> notFoundError(final CarNotFoundException ex, final ServletWebRequest request) {
return ResponseEntity.status(NOT_FOUND).contentType(MediaType.APPLICATION_JSON).body(“test”);
}
}
Exception class:
public class CarNotFoundException extends RuntimeException {
public CarNotFoundException(final String msg, final Exception ex) {
super(msg, ex);
}
public CarNotFoundException(final String msg) {
super(msg);
}
}
My Test:
It just keeps returning 200
#WebMvcTest(MyController.class)
#ContextConfiguration(classes={MyApplication.class})
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private Service service;
#Test
void should_return_404_not_found() throws Exception {
when(this.service.getCar(CONSTANT.CAR_ID))
.thenThrow(new CustomNotFoundException("not Found"));
mockMvc.perform(get("/api/cars/98776")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
}
I have a spring-boot application without any controller classes.
How can I write exception handlers for this application. Exception handler classes annotated with #ControllerAdvice doesn't work.
If you are developing web applications, ErrroController is available.
#Controller
#RequestMapping("${server.error.path:${error.path:/error}}")
public class MyErrorController implements ErrorController {
private final ErrorAttributes errorAttributes;
public MyErrorController(final ErrorAttributes errorAttributes) {
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return null;
}
#RequestMapping
public ResponseEntity<Map<String, Object>> error(final HttpServletRequest request) {
final WebRequest webRequest = new ServletWebRequest(request);
final Throwable th = errorAttributes.getError(webRequest);
// ...
// see also: BasicErrorController implementation
}
}
I have a scenario where is an already existing controller and the service throws exceptions which are handled via the #RestControllerAdvice.
Now i have a new class which i have introduced which invokes methods from the above service class in a batch mode. In my class i have to capture the exceptions or successes bundle them up and return. For any exceptions that occur i need to report the HTTP Status and the error message.
Could you let me know if there is any way this can be achieved?
You can create your own Exception class.
public class MyException extends Exception {
private int errorCode;
private String errorMessage;
public MyException(int errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
and you can create new MyException when occurring any exception and throw it. Then you get this exception in the #RestControllerAdvice class.
#RestControllerAdvice
public class ExceptionAdvice {
private ErrorCodeMapper errorCodeMapper;
#Autowired
public ExceptionAdvice(ErrorCodeMapper errorCodeMapper) {
this.errorCodeMapper = errorCodeMapper;
}
#ExceptionHandler(value = MyException.class)
public ResponseEntity handleGenericNotFoundException(MyException e) {
return new ResponseEntity(errorCodeMapper.getStatusCode(e.getErrorCode()));
}
}
and mapper class like below:
#Service
public class ErrorCodeMapper {
public static Map<Integer,HttpStatus> errorCodeMap = new HashMap<>();
public ErrorCodeMapper(){
errorCodeMap.put(100, HttpStatus.BAD_REQUEST);
errorCodeMap.put(101,HttpStatus.OK);
errorCodeMap.put(102,HttpStatus.BAD_REQUEST);
errorCodeMap.put(103,HttpStatus.BAD_REQUEST);
}
HttpStatus getStatusCode(int errorCode){
return errorCodeMap.get(errorCode);
}
}
You can more details to MyException and add the error message to the ResponseEntity.
validate Rest URL in spring boot.
Requirement: If I hit the wrong URL then it should throw a custom exception.
ex. Correct URL is "/fulfillment/600747l/send_to_hub" If I hit "/api/600747l/send_to_hub_1" then it should return exception like
"404:- URL not Found.".
Right now it returning "500 : -
{
"timestamp": 1531995246549,
"status": 500,
"error": "Internal Server Error",
"message": "Invalid Request URL.",
"path": "/api/600747l/send_to_hub_1"
}"
you need to write NewClass with annotation #ControllerAdvice which will redirect all exceptions to this NewClass.
example
Your Custom Exception Class:
#Data
#AllArgsConstructor
#EqualsAndHashCode(callSuper = false)
public class IOApiException extends IOException {
private ErrorReason errorReason;
public IOApiException(String message, ErrorReason errorReason) {
super(message);
this.errorReason = errorReason;
}
}
Now the CustomExceptionHandler Class -
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
Logger logger = LoggerFactory.getLogger(this.getClass());
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = IOApiException.class)
public GlobalErrorResponse handleException(IOApiException e) {
logger.error("UNAUTHORIZED: ", e);
return new GlobalErrorResponse("URL Not Found", HttpStatus.UNAUTHORIZED.value(), e.getErrorReason());
}
//this to handle customErrorResponseClasses
public GlobalErrorResponse getErrorResponseFromGenericException(Exception ex) {
if (ex == null) {
return handleException(new Exception("INTERNAL_SERVER_ERROR"));
}
else if (ex instanceof IOApiException) {
return handleException((IOApiException) ex);
}
}
Now Your error response class:
public class GlobalErrorResponse {
private String message;
#JsonIgnore
private int statusCode;
private ErrorReason reason;
}
ErrorReason Class
public enum ErrorReason {
INTERNAL_SERVER_ERROR,
INVALID_REQUEST_PARAMETER,
INVALID_URL
}
add and register one filter who calls the GlobalExceptionHandler in exception case like this
public class ExceptionHandlerFilter implements Filter {
private final GlobalExceptionHandler globalExceptionHandler;
public ExceptionHandlerFilter(GlobalExceptionHandler globalExceptionHandler) {
this.globalExceptionHandler = globalExceptionHandler;
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception exception) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
GlobalErrorResponse errorResponse = globalExceptionHandler.getErrorResponseFromGenericException(exception);
httpResponse.setStatus(errorResponse.getStatusCode());
response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
}
}
#Override
public void destroy() {
}
}
Like this you can add as many exceptions you want.. and can handle it manually.
As per your question first of all you need to define a base url(e.g.-/api) so that any url must be handled through your controller.Now after base url as shown /api/600747l/send_to_hub_1 #PathVariable int id. This circumstance is important, because Spring documentation said that if method argument annotated with #PathVariable can’t be casted to specified type (in our case to int), it will be exposed as String. Hence it can cause a TypeMismatchException.
To handle this I will use #ExceptionHandler annotation on #Controller level. Such approach suits for this situation as no one else. I just need to make 2 changes in the Controller:
1.Add MessageSource field
2.Add exception handler method
#Autowired
private MessageSource messageSource;
...
#ExceptionHandler(TypeMismatchException.class)
#ResponseStatus(value=HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorInfo handleTypeMismatchException(HttpServletRequest req, TypeMismatchException ex) {
Locale locale = LocaleContextHolder.getLocale();
String errorMessage = messageSource.getMessage("error.bad.smartphone.id", null, locale);
errorMessage += ex.getValue();
String errorURL = req.getRequestURL().toString();
return new ErrorInfo(errorURL, errorMessage);
}
...
In my REST service app, I am planning to create a #ControllerAdvice class to catch controller thrown exceptions and return ResponseEntity objects according to the error type.
But I already have a #RestController class implementing the ErrorController interface to catch all exceptions.
Do these two interfere in any manner?
In which cases will ErrorController be called when #ControllerAdvice exists?
Edit:
The ErrorController code as requested
#RestController
public class ControllerCustomError implements ErrorController{
//error json object
public class ErrorJson {
public Integer status;
public String error;
public String message;
public String timeStamp;
public String trace;
public ErrorJson(int status, Map<String, Object> errorAttributes) {
this.status = status;
this.error = (String) errorAttributes.get("error");
this.message = (String) errorAttributes.get("message");
this.timeStamp = errorAttributes.get("timestamp").toString();
this.trace = (String) errorAttributes.get("trace");
}
}
private static final String PATH = "/error";
#Value("${hybus.error.stacktrace.include}")
private boolean includeStackTrace = false;
#Autowired
private ErrorAttributes errorAttributes;
#RequestMapping(value = PATH)
ErrorJson error(HttpServletRequest request, HttpServletResponse response) {
// Appropriate HTTP response code (e.g. 404 or 500) is automatically set by Spring.
// Here we just define response body.
return new ErrorJson(response.getStatus(), getErrorAttributes(request, includeStackTrace));
}
#Override
public String getErrorPath() {
return PATH;
}
private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
}
}
An implementation of the ErrorController is used to provide a custom whitelabel error page.
A class annotated with #ControllerAdvise is used to add a global exception handling logic for the whole application. Thus, more than one controller in your application.
If in your application there is no mapping found for a request or page then spring will fallback to the 'whitelabel error page'. And in this case it will be the custom implementation of ErrorController