Java Spring exception handling - java

I have multiple controllers whose exceptions are handled in the ControllerAdvice.
All controllers use common exceptions types (like HttpClientException, DBException, etc.).
But there is one specific controller which exceptions should be handled differently.
In my current implementation all methods of this specific controller are wrapped with try-catch and throw CustomException in case of any exception. Then, in ControllerAdvice I process this type of exception.
However, I want to handle all exceptions in ControllerAdvice as well as get rid of CustomException and try-catch in controller methods.
Is there any way to find out the source controller name in the exception advice? I could check it and handle the exception differently.
Or maybe some other solution exist?

Inside of your controller advice, you can provide Handler for your custom exception as below.
#ControllerAdvice
public class CustomGlobalExceptionHandler {
#ExceptionHandler(CustomException.class)
public final ResponseEntity<ApiResponseDTO> manageException(CustomException ex) {
log.error("Error in CustomException: {}", ex.getMessage(), ex);
ApiResponseDTO error = ApiResponseDTO.builder()
.message(ex.getMessage())
.result(0)
.build();
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(CustomException1.class)
public final ResponseEntity<ApiResponseDTO> manageException1(CustomException1 ex) {
log.error("Error in CustomException1: {}", ex.getMessage(), ex);
ApiResponseDTO error = ApiResponseDTO.builder()
.message(ex.getMessage())
.result(0)
.build();
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
#ExceptionHandler(Exception.class)
public final ResponseEntity<ApiResponseDTO> manageException1(Exception ex) {
log.error("Error in Common Exception Handler: {}", ex.getMessage(), ex);
StackTraceElement[] ste = ex.getStackTrace();
String className=ste[ste.length - 1].getClassName();
System.out.println(className);
if(className.equalsIgnoreCase("com.a")){
System.out.println("Do A related stuff");
}else{
System.out.println("Do B related stuff");
}
ApiResponseDTO error = ApiResponseDTO.builder()
.message(ex.getMessage())
.result(0)
.build();
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
}
As mentioned in last block, you can get class name from where this exception thrown and utilizing that name to branching out your stuff.

Related

How to get a 409 for ObjectOptimisticLockingFailureException with Spring Boot?

I know that Spring Boot will render a Thymeleaf template at error/409.html for an exception that leads to a statuscode of 409 CONFLICT by default. However, it seems that ObjectOptimisticLockingFailureException gives a 500 SERVER ERROR by default.
I am able to handle the exception and "manually" redirect to the error/409.html template using a #ControllerAdvice like this:
#ControllerAdvice
public class GlobalControllerAdvice {
#ResponseStatus(HttpStatus.CONFLICT)
#ExceptionHandler({DataIntegrityViolationException.class, ObjectOptimisticLockingFailureException.class})
public ModelAndView handleConflict(HttpServletRequest request, Exception e) {
ModelAndView result = new ModelAndView("error/409");
result.addObject("url", request.getRequestURL());
return result;
}
}
Since ObjectOptimisticLockingFailureException is not part of my own code, I cannot annotate it with #ResponseStatus(HttpStatus.CONFLICT) to have a 409.
Is it possible to map ObjectOptimisticLockingFailureException to 409 and use the default error template mechanism for error codes in Spring Boot?
We can try with ResponseEntity as below
#ExceptionHandler({DataIntegrityViolationException.class, ObjectOptimisticLockingFailureException.class})
public ResponseEntity<ModelAndView> handleConflict(HttpServletRequest request, Exception e) {
ModelAndView result = new ModelAndView("error/409");
result.addObject("url", request.getRequestURL());
return new ResponseEntity<>(result, HttpStatus.CONFLICT);
}
This might work
#Bean(name="simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver
createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r =
new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("ObjectOptimisticLockingFailureException", "error/409");
r.setExceptionMappings(mappings);
return r;
}
Could you catch the exception ObjectOptimisticLockingFailureException and re-throw an exception with 409?
try {
//perform your task here
} catch (ObjectOptimisticLockingFailureException e) {
var ex = new ClientErrorException(409, e);
throw ex;
}

ControllerAdvice conditionally handle exception

I have a controller advice that handles the exceptional behavior in my REST controller and I came across a situation when I have to conditionally process SQLIntegrityConstraintViolationException that have a certain message (the one for duplicate keys), returning a 409, letting the other ones be handled by the default handler (returning a 500 error code).
I am thinking of 2 possible ways to achieve that:
Throwing a new bare-boned Exception on the else branch on my condition, so the handling is done by Spring.
Explicitly calling the general exception handler (like return handleGeneralException(exception) from inside my else branch).
I there a "proper" way to pass on a fraction of exceptions of one kind in my ControllerAdvice to another handler than the "original" one?
EDIT 1:
I would like to do something like this in my ControllerAdvice:
if (exception.getMessage.contains("something")) {
// handle exception
} else {
// pass to other handler
}
Have a custom exception class and then when you throw the SQLIntegrityConstraintViolationException wrap it in your custom exception classs with additional fields whatever you want to be accessible in controller advice. Handle the custom exception in your controller advice class.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(YourCustomException.class)
public final ResponseEntity<ExceptionResponse> handleNotFoundException(YourCustomExceptionex,
WebRequest request) {
ExceptionResponse exceptionResponse = new ExceptionResponse(new Date(), ex.getMessage(),
request.getDescription(false), HttpStatus.NOT_ACCEPTABLE.getReasonPhrase());
return new ResponseEntity<>(exceptionResponse, HttpStatus.CONFLICT);
}
}
While having the try catch block to handle this exception in your code , make sure that you handle DataIntegrityViolationException instead of SQLIntegrityConstraintViolationException if you are using Spring Data JPA. So , if you are using Spring Data Jpa then :
try {
anyRepository.save(new YourModel(..));
} catch (DataIntegrityViolationException e) {
System.out.println("history already exist");in res
throw New YourCustomException("additional msg if you need it ", e);
}
Below code will capture the error message of exception SQLIntegrityConstraintViolationException in ControllerAdbvice without having to handle in code
#ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = DataIntegrityViolationException.class)
public ResponseEntity<ExceptionResponse> dataIntegrityViolationExceptionHandler(Exception ex) {
ExceptionResponse response = new ExceptionResponse();
Throwable throwable = ex.getCause();
while (throwable != null) {
if (throwable instanceof SQLIntegrityConstraintViolationException) {
String errorMessage = throwable.getMessage();
response.setErrors(new ArrayList<>(Arrays.asList(errorMessage)));
}
throwable = throwable.getCause();
}
return new ResponseEntity<Object>(response, HttpStatus.CONFLICT);
}
}

Error not getting propagated from called service to caller service

We have 2 services which have REST controllers.
Caller Service calls like:
try {
response = restTemplate.exchange(urlModifyAttributes, HttpMethod.PUT, new HttpEntity<>(attributesMap, headerMap), parameterizedTypeReference, uriVars);
return response.getBody();
} catch (RestClientResponseException e) {
log.error("Modify attributes failed. Error {}", e.getMessage(), e);
throw new RuntimeException(e.getMessage());
} catch (Exception e) {
log.error("Modify attributes failed. Error: {}", e.getMessage(), e);
throw new RuntimeException(e.getMessage());
}
While the called service throws a RuntimeException like:
throw new RuntimeException("Already modified");
But the caller service is not able to capture the error message "Already modified" using e.getMessage(). Rather e.getMessage() is returning something like "I/O error on PUT request for http....... " Is there a way by which I can capture the error message at the caller service level?
Both services are SpringBoot Applications and have RestControllers
You are handling exception in caller service but in called service it need to send the exception as well. For generic u can refer this or have your own custom impl.
called service:
#Getmapping("/uri")
public ResponseEntity<Object> getIt(#RequestBody MyRequest req) {
try{
//...business logic
return new ResponseEntity<>(response, HttpStatus.OK);
}catch(MyException e){
return new ResponseEntity<>(e, HttpStatus.SOME_EXC_STATUS); //or custom exc
}catch(Exception e){
return new ResponseEntity<>(e, HttpStatus.SOME_EXC_STATUS);
}
}
Then only you will be able to get exception status.
The called service needs to send this in the response. You are making a rest call. Had it been a method, you could have caught it from caller too.
In the response entity of the called service pass the exception message. Either using controller advice or responsestatusexception.

Handling RestClientException and HttpClientErrorException

I am handling few requests by making RESTFul call (Spring RestTemplate) to 3rd parties. In the code, I am trying to handle following condition .
catch (final Exception ex) {
if (ex instanceof HttpClientErrorException) {
HttpClientErrorException hcee = (HttpClientErrorException)ex;
if(hcee.getStatusCode() == NOT_FOUND) {
throw new MyRecordNotFoundException(hcee);
}
}else {
handleRestClientException(ex, Constants.MYAPP);
}
Here is handleRestClientException implementation
protected Exception handleRestClientException(Exception ex, String serviceName) throws Exception{
if (ex instanceof RestClientResponseException) {
RestClientResponseException rcre = (RestClientResponseException) ex;
throw new RestClientResponseException(serviceName, rcre.getRawStatusCode(),
rcre.getStatusText(), rcre.getResponseHeaders(), rcre.getResponseBodyAsByteArray(), null);
} else {
throw new Exception(serviceName, ex);
}
But all org.springframework.web.client.RestTemplate.getForObject(String url, Class responseType, Map urlVariables) throws RestClientException
Which is Parent of HttpClientErrorException
java.lang.Object
java.lang.Throwable
java.lang.Exception
java.lang.RuntimeException
org.springframework.core.NestedRuntimeException
org.springframework.web.client.RestClientException
org.springframework.web.client.RestClientResponseException
org.springframework.web.client.HttpStatusCodeException
org.springframework.web.client.HttpClientErrorException
So, the if condition mentioned in my code never reaches as it is handled.
Could you please help me to handle the each exception in this hierarchy effectively?
You should never do if-else in a catch block to handle different exceptions. The code is unreadable, probably slower in execution and in your example any exception (except for HttpClientErrorException) is handled like a RestClientException.
Handle them with proper catch blocks like this (more specific exceptions first, i.e. HttpClientErrorException before RestClientException:
catch (HttpClientErrorException hcee) {
if (hcee.getStatusCode() == NOT_FOUND) {
throw new MyRecordNotFoundException(hcee);
}
}
catch (final RestClientException rce) {
handleRestClientException(rce, Constants.MYAPP);
}
It would be helpful to many others.
HttpClientErrorException is a subclass of HttpStatusCodeException which is a subclass of RestClientResponseException.
Proper handling includes handling of RestClientResponseException and ResourceAccessException
RestClientException has these two direct subclasses.
RestClientResponseException is thrown when the call is successful but there is invalid response like 500, 400.
RestClientException is thrown when API being called is down or not reachable because of TLS/Proxy.
try {
responseEntity = restTemplate.exchange(url,
HttpMethod.POST, requestEntity,
<ResponseType.class>);
} catch (RestClientResponseException rcre) {
log4jlogs.error(
"Business exception while calling the API! Error message: {}, Response Body: {} ",
rcre.getMessage(), rcre.getResponseBodyAsString(), rcre);
throw rcre; // else throw custom business exception
} catch (ResourceAccessException rac) {
log4jlogs.error(
"System Exception. API not found, Error message: {}, rac.getMessage()",
rac);
throw rac; // else throw custom system exception
}

Why error is not getting caught, even when explicitly thrown?

I want to catch an "Error" in SpringMVC3 using annotated "#ExceptionHandler". I can catch throwable and any exception, but when I tried with "Error" it is not catching the exception. Any idea why? The code below demonstrates the problem.
#Controller
#RequestMapping("/test")
public class TestExceptionController {
static final Logger logger = Logger.getLogger(TestExceptionController.class);
#RequestMapping(method = RequestMethod.GET)
public String processData(int intValue) throws InvalidDataException {
if (intValue < 6) {
try {
throw new Exception();
} catch (Exception e) {
throw new InvalidDataException();
}
}
return "test";
}
#ExceptionHandler(InvalidDataException.class)
public ModelMap handleException(InvalidDataException ex) {
logger.debug("exception catched :" + ex);
return new ModelMap();
}
}
The above code catches, but below code is not catching. Why is it not catching the error?
#Controller
#RequestMapping("/test")
public class TestExceptionController {
static final Logger logger = Logger.getLogger(TestExceptionController.class);
#RequestMapping(method = RequestMethod.GET)
public String processData(int intValue) throws Error{
if (intValue < 6) {
try {
throw new Exception();
} catch (Exception e) {
throw new Error();
}
}
return "test";
}
#ExceptionHandler(Error.class)
public ModelMap handleException(Error ex) {
logger.debug("exception catched :" + ex);
return new ModelMap();
}
}
Actually I looked into the source of spring DispatcherServlet and in line 809 it explains why Error cannot be handled
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
The code is the part where spring processess the ExceptionResolvers may it be annotation or bean based. You can see that Spring only cathces Exception not Throwable. Error is not a subclass of Exception but throwable so you wont be able to handle it with Spring this way. On a related note the annotation is also called #ExceptionHandler so it kind of implies that it wont work with Errors.
From the Error javadocs:
An Error is a subclass of Throwable that indicates serious problems
that a reasonable application should not try to catch. Most such
errors are abnormal conditions. The ThreadDeath error, though a
"normal" condition, is also a subclass of Error because most
applications should not try to catch it.
Catching an Error (or any other Throwable that is not a subclass of Exception) is a bad idea. Spring is doing the right thing by not supporting it.
I also crashed into this, from the code pasted by #peter-szanto seems there is no possibility to handle java.lang.Error with a Spring-registered handler. My use case would be to handle error with a localized page and also to log the error. My workaround is to use web.xml with error-page/error-code 500 defined and as error-page/location a Spring-handled controller (not a JSP) in order to get localization work. Downside is that when controller code runs there is no authentication for the current user. This catches also things Spring could not possibly handle like a wrong URI not mapped to Spring.
For me the below works with Spring 3.0.5 MVC
#ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException{
public NotFoundException() {
super();
}
}
#ExceptionHandler
#RequestMapping(method = RequestMethod.GET, value = "employee/{id}",
headers = "Accept=application/json,application/x-protobuf,application/xml")
public #ResponseBody method(xxxx)
{
..
throw new NotFoundException
...
}
didnt meant to handle Exception instead of Error? Error is very ralely used in Java and has a very few implementing classes.

Categories