Unable to Handle Exception with #ErrorHandler Annotation - java

I'm new to Spring Boot. I'm trying to handle a exception when a value is null. Basically, its a value that I'm suppose to get from another external service (using restTemplate.exchange() method) which is currently down, so a null value gets assigned to that variable. Here's the code for the same service:
ResponseEntity<AuthenticationResponse> respNode = null;
try {
respNode = restTemplate.exchange(url, HttpMethod.POST, httpEntity, new ParameterizedTypeReference<AuthenticationResponse>() {});
} catch (Exception e) {
if (respNode == null) {
log.info("Unable to reach: {} ", url);
throw new AuthServerException("Auth Server Not Found");
}
}
I created a custom Exception to handle the scenario if the value is null. The Exception is thrown and I'm able to read it in the logs, but I'm not able to handle it.
AuthServerException.java
package com.nokia.sp.module.myVerify.portalapp.exception;
public class AuthServerException extends NullPointerException {
private static final long serialVersionUID = 1L;
public AuthServerException(String message) {
super(message);
}
}
Controller:
#Controller
#RequiredArgsConstructor
public class LandingPageController {
#ExceptionHandler(AuthServerException.class)
public ModelAndView AuthServerExceptionHandler(AuthServerException e) {
log.info(e.getMessage());
ModelAndView mav = new ModelAndView();
mav.setViewName("auth-server-handler");
return mav;
}
}
Am I missing something or have I made any syntax Error? Please do help me with the same. Thank you!

Related

What's the best option/alternative to treat exceptions in spring boot?

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);
}
}

Unable to catch error when using RestTemplate getForObject

I'm using RestTemplate to call my webservice's health actuator endpoint from another webservice of mine to see if the webservice is up. If the webservice is up, all works fine, but when it's down, I get an error 500, "Internal Server Error". If my webservice is down, I'm trying to catch that error to be able to handle it, but the problem I'm having is that I can't seem to be able to catch the error.
I've tried the following and it never enters either of my catch sections
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getBankAccountConnectorHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
String result = null;
try {
result = restTemplate.getForObject(uri, String.class);
} catch (HttpClientErrorException exception) {
System.out.println("callToRestService Error :" + exception.getResponseBodyAsString());
} catch (HttpStatusCodeException exception) {
System.out.println( "callToRestService Error :" + exception.getResponseBodyAsString());
}
System.out.println(result);
}
}
I've also tried doing it this way, but same results. It never seems to enter my error handler class.
public class NotFoundException extends RuntimeException {
}
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse) throws IOException {
return (httpResponse.getStatusCode().series() == CLIENT_ERROR || httpResponse.getStatusCode().series() == SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
if (httpResponse.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
// handle SERVER_ERROR
System.out.println("Server error!");
} else if (httpResponse.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
// handle CLIENT_ERROR
System.out.println("Client error!");
if (httpResponse.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new NotFoundException();
}
}
}
}
#Service
public class DepositService {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(3000))
.setReadTimeout(Duration.ofMillis(3000))
.build();
}
private static void getAccountHealth() {
final String uri = "http://localhost:9996/health";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
String result = null;
result = restTemplate.getForObject(uri, String.class);
System.out.println(result);
}
}
Any suggestions as to how I can call my webservice's health actuator from another webservice and catch if that webservice is down?
It looks like the getForObject doesn't throw either of the exceptions you are catching. From the documentation, it throws RestClientException. The best method I have found for identifying thrown exceptions is to catch Exception in the debugger and inspect it to find out if it's useful.
With the second method, I'm not sure why you would create a bean method for the RestTemplate and then create one with new. You probably should inject your RestTemplate and initialise the ResponseErrorHandler with the RestTemplateBuilder::errorHandler method.
Internal serve error throw HttpServerErrorException You should catch this exception if you want to handle it However the better way to do that is using error handler you can see the following posts to see how to do that:
spring-rest-template-error-handling
spring-boot-resttemplate-error-handling

Spring MVC error handling with response status code

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(...) {
...
}

Rest Api exception handling

I am having different projects for Service and Web. I would like to know how to handle when specific exception comes from Services. For example I am handling DuplicateDataException as follows at Service side:
public void serviceFunction()
{
try
{
//code
}catch(DuplicateDataException e)
{
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(e.getMessage()).build();
}}
At UI side: controller class is calling the service function through Rest API
#RequestMapping(value = "/addNew", method = RequestMethod.POST)
public ModelAndView addNew(Object obj) {
try {
restTemplate.exchange(url, HttpMethod.POST, httpEntity,
Object.class);
LOGGER.info("Object Created Successfully");
} catch (Exception e) {
return ModelAndView("PageName", "param","value");
}
}
At UI side I am getting Internal Server Error, Instead I would like to get the entity error message value which was set at service side.
As a kind of best practice try to catch your exceptions in your service code and throw an RuntimeException("An error occured") or a self defined Exception which extends Java's RuntimeException. Then you can define a global ExceptionHandler for all of your controllers and return your own error page like:
#ControllerAdvice
public class MyExceptionHandler {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(Exeption.class)
public ModelAndView handleFileNotFoundException(Exception exception){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("yourView");
modelAndView.addObject("exception", exception);
return modelAndView;
}
}

Customizing Zuul Exception

I have a scenario in Zuul where the service that the URL is routed too might be down . So the reponse body gets thrown with 500 HTTP Status and ZuulException in the JSON body response.
{
"timestamp": 1459973637928,
"status": 500,
"error": "Internal Server Error",
"exception": "com.netflix.zuul.exception.ZuulException",
"message": "Forwarding error"
}
All I want to do is to customise or remove the JSON response and maybe change the HTTP status Code.
I tried to create a exception Handler with #ControllerAdvice but the exception is not grabbed by the handler.
UPDATES:
So I extended the Zuul Filter I can see it getting into the run method after the error has been executed how do i change the response then. Below is what i got so far. I read somewhere about SendErrorFilter but how do i implement that and what does it do?
public class CustomFilter extends ZuulFilter {
#Override
public String filterType() {
return "post";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
final RequestContext ctx = RequestContext.getCurrentContext();
final HttpServletResponse response = ctx.getResponse();
if (HttpStatus.INTERNAL_SERVER_ERROR.value() == ctx.getResponse().getStatus()) {
try {
response.sendError(404, "Error Error"); //trying to change the response will need to throw a JSON body.
} catch (final IOException e) {
e.printStackTrace();
} ;
}
return null;
}
Added this to the class that has #EnableZuulProxy
#Bean
public CustomFilter customFilter() {
return new CustomFilter();
}
We finally got this working [Coded by one of my colleague]:-
public class CustomErrorFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(CustomErrorFilter.class);
#Override
public String filterType() {
return "post";
}
#Override
public int filterOrder() {
return -1; // Needs to run before SendErrorFilter which has filterOrder == 0
}
#Override
public boolean shouldFilter() {
// only forward to errorPath if it hasn't been forwarded to already
return RequestContext.getCurrentContext().containsKey("error.status_code");
}
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
Object e = ctx.get("error.exception");
if (e != null && e instanceof ZuulException) {
ZuulException zuulException = (ZuulException)e;
LOG.error("Zuul failure detected: " + zuulException.getMessage(), zuulException);
// Remove error code to prevent further error handling in follow up filters
ctx.remove("error.status_code");
// Populate context with new response values
ctx.setResponseBody(“Overriding Zuul Exception Body”);
ctx.getResponse().setContentType("application/json");
ctx.setResponseStatusCode(500); //Can set any error code as excepted
}
}
catch (Exception ex) {
LOG.error("Exception filtering in custom error filter", ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
}
The Zuul RequestContext doesn't contain the error.exception as mentioned in this answer.
Up to date the Zuul error filter:
#Component
public class ErrorFilter extends ZuulFilter {
private static final Logger LOG = LoggerFactory.getLogger(ErrorFilter.class);
private static final String FILTER_TYPE = "error";
private static final String THROWABLE_KEY = "throwable";
private static final int FILTER_ORDER = -1;
#Override
public String filterType() {
return FILTER_TYPE;
}
#Override
public int filterOrder() {
return FILTER_ORDER;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
final RequestContext context = RequestContext.getCurrentContext();
final Object throwable = context.get(THROWABLE_KEY);
if (throwable instanceof ZuulException) {
final ZuulException zuulException = (ZuulException) throwable;
LOG.error("Zuul failure detected: " + zuulException.getMessage());
// remove error code to prevent further error handling in follow up filters
context.remove(THROWABLE_KEY);
// populate context with new response values
context.setResponseBody("Overriding Zuul Exception Body");
context.getResponse().setContentType("application/json");
// can set any error code as excepted
context.setResponseStatusCode(503);
}
return null;
}
}
I had the same problem and was able to solve it in simpler way
Just put this into you Filter run() method
if (<your condition>) {
ZuulException zuulException = new ZuulException("User message", statusCode, "Error Details message");
throw new ZuulRuntimeException(zuulException);
}
and SendErrorFilter will deliver to the user the message with the desired statusCode.
This Exception in an Exception pattern does not look exactly nice, but it works here.
Forwarding is often done by a filter, in this case the request does not even reach a controller. This would explain why your #ControllerAdvice does not work.
If you forward in the controller than the #ControllerAdvice should work.
Check if spring creates an instance of the class annotated with #ControllerAdvice. For that place a breakpoint in the class and see whether it is hit.
Add a breakpoint also in the controller method where the forwarding should happen. May be you accidently invoke another controller method than you inspect ?
These steps should help you resolve the issue.
In your class annotated with #ControllerAdvice add an ExceptionHandler method annotated with #ExceptionHandler(Exception.class), that should catch every Exception.
EDIT :
You can try to add your own filter that converts the error response returned by the Zuulfilter. There you can change the response as you like.
How the error response can be customized is explained here :
exception handling for filter in spring
Placing the filter correctly may be a little tricky.
Not exactly sure about the correct position, but you should be aware of the order of your filters and the place where you handle the exception.
If you place it before the Zuulfilter, you have to code your error handling after calling doFilter().
If you place it after the Zuulfilter, you have to code your error handling before calling doFilter().
Add breakpoints in your filter before and after doFilter() may help to find the correct position.
Here are the steps to do it with #ControllerAdvice:
First add a filter of type error and let it be run before the SendErrorFilter in zuul itself.
Make sure to remove the key associated with the exception from the RequestContext to prevent the SendErrorFilter from executing.
Use RequestDispatcher to forward the request to the ErrorController -- explained below.
Add a #RestController class and make it extends AbstractErrorController, and re-throw the exception again (add it in the step of executing your new error filter with (key, exception), get it from the RequestContext in your controller).
The exception will now be caught in your #ControllerAdvice class.
The simplest solution is to follow first 4 steps.
1. Create your own CustomErrorController extends
AbstractErrorController which will not allow the
BasicErrorController to be called.
2. Customize according to your need refer below method from
BasicErrorController.
<pre><code>
#RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request,
isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}
</pre></code>
4. You can control whether you want exception / stack trace to be printed or not can do as mentioned below:
<pre><code>
server.error.includeException=false
server.error.includeStacktrace=ON_TRACE_PARAM
</pre></code>
====================================================
5. If you want all together different error response re-throw your custom exception from your CustomErrorController and implement the Advice class as mentioned below:
<pre><code>
#Controller
#Slf4j
public class CustomErrorController extends BasicErrorController {
public CustomErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, serverProperties.getError(), errorViewResolvers);
log.info("Created");
}
#Override
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
throw new CustomErrorException(String.valueOf(status.value()), status.getReasonPhrase(), body);
}
}
#ControllerAdvice
public class GenericExceptionHandler {
// Exception handler annotation invokes a method when a specific exception
// occurs. Here we have invoked Exception.class since we
// don't have a specific exception scenario.
#ExceptionHandler(CustomException.class)
#ResponseBody
public ErrorListWsDTO customExceptionHandle(
final HttpServletRequest request,
final HttpServletResponse response,
final CustomException exception) {
LOG.info("Exception Handler invoked");
ErrorListWsDTO errorData = null;
errorData = prepareResponse(response, exception);
response.setStatus(Integer.parseInt(exception.getCode()));
return errorData;
}
/**
* Prepare error response for BAD Request
*
* #param response
* #param exception
* #return
*/
private ErrorListWsDTO prepareResponse(final HttpServletResponse response,
final AbstractException exception) {
final ErrorListWsDTO errorListData = new ErrorListWsDTO();
final List<ErrorWsDTO> errorList = new ArrayList<>();
response.setStatus(HttpStatus.BAD_REQUEST.value());
final ErrorWsDTO errorData = prepareErrorData("500",
"FAILURE", exception.getCause().getMessage());
errorList.add(errorData);
errorListData.setErrors(errorList);
return errorListData;
}
/**
* This method is used to prepare error data
*
* #param code
* error code
* #param status
* status can be success or failure
* #param exceptionMsg
* message description
* #return ErrorDTO
*/
private ErrorWsDTO prepareErrorData(final String code, final String status,
final String exceptionMsg) {
final ErrorWsDTO errorDTO = new ErrorWsDTO();
errorDTO.setReason(code);
errorDTO.setType(status);
errorDTO.setMessage(exceptionMsg);
return errorDTO;
}
}
</pre></code>
This is what worked for me. RestExceptionResponse is the class which is used within the #ControllerAdvice, so we have an identical exception response in case of internal ZuulExceptions.
#Component
#Log4j
public class CustomZuulErrorFilter extends ZuulFilter {
private static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";
#Override
public String filterType() {
return ERROR_TYPE;
}
#Override
public int filterOrder() {
return SEND_ERROR_FILTER_ORDER - 1; // Needs to run before SendErrorFilter which has filterOrder == 0
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable ex = ctx.getThrowable();
return ex instanceof ZuulException && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}
#Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
ZuulException ex = (ZuulException) ctx.getThrowable();
// log this as error
log.error(StackTracer.toString(ex));
String requestUri = ctx.containsKey(REQUEST_URI_KEY) ? ctx.get(REQUEST_URI_KEY).toString() : "/";
RestExceptionResponse exceptionResponse = new RestExceptionResponse(HttpStatus.INTERNAL_SERVER_ERROR, ex, requestUri);
// Populate context with new response values
ctx.setResponseStatusCode(500);
this.writeResponseBody(ctx.getResponse(), exceptionResponse);
ctx.set(SEND_ERROR_FILTER_RAN, true);
}
catch (Exception ex) {
log.error(StackTracer.toString(ex));
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
private void writeResponseBody(HttpServletResponse response, Object body) throws IOException {
response.setContentType("application/json");
try (PrintWriter writer = response.getWriter()) {
writer.println(new JSonSerializer().toJson(body));
}
}
}
The output looks like this:
{
"timestamp": "2020-08-10 16:18:16.820",
"status": 500,
"error": "Internal Server Error",
"path": "/service",
"exception": {
"message": "Filter threw Exception",
"exceptionClass": "com.netflix.zuul.exception.ZuulException",
"superClasses": [
"com.netflix.zuul.exception.ZuulException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "com.netflix.zuul.exception.ZuulException: Forwarding error",
"exceptionClass": "org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
"superClasses": [
"org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException",
"java.lang.RuntimeException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "Forwarding error",
"exceptionClass": "com.netflix.zuul.exception.ZuulException",
"superClasses": [
"com.netflix.zuul.exception.ZuulException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": {
"message": "Load balancer does not have available server for client: template-scalable-service",
"exceptionClass": "com.netflix.client.ClientException",
"superClasses": [
"com.netflix.client.ClientException",
"java.lang.Exception",
"java.lang.Throwable",
"java.lang.Object"
],
"stackTrace": null,
"cause": null
}
}
}
}
}

Categories

Resources