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.
Related
I have a service A that makes post request to another controller (B). This is my service making post request. Controller B is not in the same project as service A, so B throws Bad request (400) and service A turns 400 request to WebApplicationException:
WebClient client = tokenAuth.addAuthentication(WebClient.create(url))
.type(AccelaradMediaType.SMR_IMAGE_SHARE_V3_JSON)
.accept(AccelaradMediaType.SMR_SHARE_RESULT_JSON);
String response = client.post(body, String.class);
catch (WebApplicationException e) {
//get message from exception and print
}
And this is other controller(B) that my service is making post request:
#POST
#Path("/shares")
#Consumes({AccelaradMediaType.SMR_IMAGE_SHARE_V3_JSON, AccelaradMediaType.SMR_IMAGE_SHARE_V3_XML})
#Produces({AccelaradMediaType.SMR_SHARE_RESULT_JSON, AccelaradMediaType.SMR_SHARE_RESULT_XML})
public ShareResult shareV3() {
ShareResult result = null;
try {
result = shareStudies();
}
catch (StudyShareException e) {
LOG.error(e.getMessage(), e);
throw new BadRequestException(e.getMessage());
}
return result;
}
public ShareResult shareStudies() {
try {
//some logic
}
catch (InvitationException e) {
String message = "Invitation is pending";
throw new StudyShareException(message, e);
}
}
And here are StudyShareException class and BadRequestException class:
public class StudyShareException extends Exception {
public StudyShareException(String message, Throwable cause) {
super(message, cause);
}
}
public class BadRequestException extends WebApplicationException {
private static final long serialVersionUID = 1L;
public BadRequestException(String message) {
this(message, null);
}
public BadRequestException(String message, Throwable cause) {
super(cause, Response.status(Response.Status.BAD_REQUEST).entity(message).type(MediaType.TEXT_PLAIN).build());
}
}
When service A makes a post request, it does go into the catch block and controller B prints out error in the stack trace with "Invitation is pending" message.
My goal is to print out "Invitation is pending" from service A as well. I tried e.getResponse() or e.getResponse().getEntity() or e.getMessage(), nothing has worked. Is it even possible to get custom message from service A? If so, how can I achieve this?
Why are you catching WebApplicationException in service A when service B is throwing StudyShareException? You need to catch the correct exception in service A
try {
WebClient client = tokenAuth.addAuthentication(WebClient.create(url))
.type(AccelaradMediaType.SMR_IMAGE_SHARE_V3_JSON)
.accept(AccelaradMediaType.SMR_SHARE_RESULT_JSON);
String response = client.post(body, String.class);
catch (StudyShareException e) {
//get message from exception and print
}
Now if you are trying to catch all exceptions extending WebApplicationException then you should make StudyShareException extend WebApplicationException as well.
That being said, perhaps you shouldn't catch any exception at all just let service A just bubble up the exception thrown in service B. However, that's up to you, you may want to throw a different message or a different exception in service A.
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.
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);
}
}
We're using org.springframework.web.reactive.function.client.WebClient with
reactor.netty.http.client.HttpClient as part of Spring 5.1.9 to make requests using the exchange() method. The documentation for this method highlights the following:
... when using exchange(), it is the responsibility of the application
to consume any response content regardless of the scenario (success,
error, unexpected data, etc). Not doing so can cause a memory leak.
Our use of exchange() is rather basic, but the documentation for error scenarios is unclear to me and I want to be certain that we are correctly releasing resources for all outcomes. In essence, we have a blocking implementation which makes a request and returns the ResponseEntity regardless of the response code:
try {
...
ClientResponse resp = client.method(method).uri(uri).syncBody(body).exchange().block();
ResponseEntity<String> entity = resp.toEntity(String.class).block();
return entity;
} catch (Exception e) {
// log error details, return internal server error
}
If I understand the implementation, exchange() will always give us a response if the request was successfully dispatched, regardless of response code (e.g. 4xx, 5xx). In that scenario, we just need to invoke toEntity() to consume the response. My concern is for error scenarios (e.g. no response, low-level connection errors, etc). Will the above exception handling catch all other scenarios and will any of them have a response that needs to be consumed?
Note: ClientResponse.releaseBody() was only introduced in 5.2
The response have to be consumed when the request was made, but if you can't do the request probably an exception was be throwed before, and you will no have problems with response.
In the documentation says:
NOTE: When using a ClientResponse through the WebClient exchange() method, you have to make sure that the body is consumed or released by using one of the following methods:
body(BodyExtractor)
bodyToMono(Class) or bodyToMono(ParameterizedTypeReference)
bodyToFlux(Class) or bodyToFlux(ParameterizedTypeReference)
toEntity(Class) or toEntity(ParameterizedTypeReference)
toEntityList(Class) or toEntityList(ParameterizedTypeReference)
toBodilessEntity()
releaseBody()
You can also use bodyToMono(Void.class) if no response content is expected. However keep in mind the connection will be closed, instead of being placed back in the pool, if any content does arrive. This is in contrast to releaseBody() which does consume the full body and releases any content received.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html
You can try to use .retrieve() instead .exchange() and handle errors as your preference.
public Mono<String> someMethod() {
return webClient.method(method)
.uri(uri)
.retrieve()
.onStatus(
(HttpStatus::isError), // or the code that you want
(it -> handleError(it.statusCode().getReasonPhrase())) //handling error request
)
.bodyToMono(String.class);
}
private Mono<? extends Throwable> handleError(String message) {
log.error(message);
return Mono.error(Exception::new);
}
In this example I used Exception but you can create some exception more specific and then use some exception handler to return the http status that you want.
Is not recommended to use block, a better way is pass the stream forward.
create some exception classes
Autowired ObjectMapper
Create a method that returns Throwable
Create a custom class for Error.
return webClient
.get()
.uri(endpoint)
.retrieve()
.bodyToMono(Model.class)
.onErrorMap(WebClientException.class, this::handleHttpClientException);
private Throwable handleHttpClientException(Throwable ex) {
if (!(ex instanceof WebClientResponseException)) {
LOG.warn("Got an unexpected error: {}, will rethrow it", ex.toString());
return ex;
}
WebClientResponseException wcre = (WebClientResponseException)ex;
switch (wcre.getStatusCode()) {
case NOT_FOUND -> throw new NotFoundException(getErrorMessage(wcre));
case BAD_REQUEST -> throw new BadRequestException(getErrorMessage(wcre));
default -> {
LOG.warn("Got a unexpected HTTP error: {}, will rethrow it", wcre.getStatusCode());
LOG.warn("Error body: {}", wcre.getResponseBodyAsString());
return ex;
}
}
}
private String getErrorMessage(WebClientResponseException ex) {
try {
return mapper.readValue(ex.getResponseBodyAsString(), HttpErrorInfo.class).getMessage();
} catch (IOException ioe) {
return ex.getMessage();
}
}
Not able to use #ControllerAdvice and #AfterThrowing when method surrounded by try and catch block.
I can explain step by step
Step 1: In my spring application all handlers(methods) are handle exception by try and catch block.
Step 2: So requirement i need to trigger a email when the exception occurs in all handlers methods. But my application having 100's of methods. So try with #ControllerAdvice to handle the exceptions by using #ExceptionHandler annotation. I know that it wont work because we already handling our exception in catch block. So it cant look at #ControllerAdvice.
Step 3: I try with Aop #AfterThrowing advice also. It is not working. So i cant able to remove all catch blocks in entire application code. It is very difficultly to do that.
But my question is that
Is there any way in spring to handle the exception even we are handling it.
We returning back status code like 400 . In Spring they any Advice to identify status code.
Because are retiring ResponseEntity Status back as response.
#RequestMapping(value = "/service/getDetails", method = RequestMethod.POST, produces = { "application/json" })
public ResponseEntity<Map<String, Object>> getDetails(#RequestBody Details details, HttpServletRequest request) {
ResponseEntity<Map<String, Object>> response = null;
try {
/// Some code
} catch (Exception e) {
logger.error("Exception while DetailsController: " + e.getMessage());
response = new ResponseEntity<>(HttpStatus.BAD_REQUEST);
/*
* int status=response.getStatusCodeValue();
* scheduledMailTrigerService.sendErrorLogInfo(request,e,status);
*/
}
return response;
}
#ControllerAdvice
public class AppExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { Exception.class })
public String handleAnyException(Exception ex, WebRequest request) {
System.out.println("Done");
return "Done";
}
}
You can use an #Around advice to capture the result of the method execution... my suggestion is that you remove the try catch from all your endpoints, just write a try catch wrapping the ProceedingJoinPoint proceed() execution. This way you can return a ResponseEntity 400 when an exception occurs, alongside execututing email sending logic.