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!
My requirement is to access the custom exception thrown from first service along with it's body content in the second service
I have tried 2 things so far, FallbackFactory and ErrorDecoder, out of which only Fallback factory worked for me. Error decoder did not have the message of the exception which was thrown from other service. Here is the sample code that I found in another question:
There will be 2 services: inventory-service and product-service
inventory-service
InventoryController.java
#RestController
#RequestMapping("/inventories")
public class InventoryController {
private final ProductServiceClient productService;
public InventoryController(ProductServiceClient productService) {
super();
this.productService = productService;
}
#GetMapping
public ResponseEntity<?> companyInfo() {
return productService.hello();
}
}
ProductServiceClient.java
#FeignClient(name = "product-service", url = "http://localhost:9100", fallbackFactory = ProductServiceClientFallback.class)
public interface ProductServiceClient {
#GetMapping("/products")
ResponseEntity<?> hello();
}
#Component
class ProductServiceClientFallback implements FallbackFactory<ProductServiceClient> {
#Override
public ProductServiceClient create(Throwable cause) {
return new ProductServiceClient() {
#Override
public ResponseEntity<?> hello() {
System.out.println("hello!! fallback reason was " + cause.getMessage());
return ResponseEntity.ok().build();
}
};
}
}
product-service
ProductController.java
#RestController
#RequestMapping(value = "/products")
public class ProductController {
#GetMapping
public String hello() throws Exception {
if (true) {
throw new Exception("Service B Exception...");
}
return "Hello World";
}
}
ProductControllerAdvice.java
#RestControllerAdvice
public class ProductControllerAdvice {
#ExceptionHandler
public ResponseEntity<?> handleException(Exception exception) {
return new ResponseEntity<>("Caused due to : " + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
So, when /inventories api is triggered in Inventory controller, it triggers a call to product-service via Feign Client and on product-service side, I throw a custom exception with a message, I have to access that message in my inventory-service.
To get that I have implemented fallback factory and it worked in a test-workspace since I got an output like this in console of inventory-service
hello!! fallback reason was status 500 reading ProductServiceClient#hello(); content:
Caused due to : Service B Exception...
But, my problem is when I try the similar approach with the applications that I'm working on, I did not get the message of exception, instead I got an out put like this:
reached fallback on workflow side, reason: status 400 reading ProvisioningServiceProxy#executeOrderAction(Long,Long,String)
Service-A
TestServiceA.java
#FeignClient( url = "/executeOrder", fallbackFactory = TestServiceAFallback.class )
public interface TestServiceA extends Serializable{
#PostMapping( value = "order/{requestId}/order/{orderId}/{command}" )
public ResponseEntity<ProcessInstanceVariable> executeOrderAction( #PathVariable( name = "command" ) String command );
}
Service-B from where the custom exception is thrown
TestServiceBController.java
#PostMapping( value = /executeOrder )
public ResponseEntity<ProcessInstanceVariable> executeOrderAction( #PathVariable( value = "command" ) String command )
{ //switch code to check the command value and throw exception for one particular command
throw new ValidationException("validation exception from service B");
}
I have an advice also, which handles Validation Exceptions and there is a method like this in that class
TestServiceBControllerAdvice.java
#ExceptionHandler( ValidationException.class )
public ResponseEntity<Object> handleValidationException( ValidationException ve )
{
return new ResponseEntity<>( ve.getMessage(), HttpStatus.BAD_REQUEST );
}
So, I was expecting to receive the message on TestServiceA side which I sent from TestServiceB, but I received a generic message showing that BAD REQUEST while reading the API.
I'm not sure if any extra configuration is required on TestServiceA side apart from below configuration:
testServiceA.properties
feign.hystrix.enabled=true
Let me know if anything is missing from my end, I have gone through this documentation and seems to me I have done the implementation the way it should happen to get the message and body of exception thrown from other service.
For anyone who comes to this question looking for some answers, I did end up implementing ErrorDecoder, which helped me in capturing the errors. The details are a little fade to me, how the message was caught.
But I used the below code:
public class CustomExceptionDecoder implements feign.codec.ErrorDecoder
{
#Override
public Exception decode( String methodKey,
Response response )
{
final ErrorDecoder defaultErrorDecoder = new Default();
try
{
if( response.body() != null )
{
byte[] bodyData = Util.toByteArray( response.body().asInputStream() );
String responseBody = new String( bodyData );
LOGGER.error( "Error captured in Custom Exception Decoder: ", responseBody );
return new CustomValidationException( responseBody );
}
}
catch( IOException e )
{
LOGGER.error( "Throwing IOException :: {}", e.getCause() );
}
return defaultErrorDecoder.decode( methodKey, response );
}
}
I have an issue when trying to test a class that represents a Rest Client. I'm using RestTemplate in Spring Boot.
This is the abstract RestClient class:
public abstract class RestClient {
...
public RestResponse sendPostRequest(URI baseUri, String resource, IRestRequest restRequest, ClassresponseClass)
throws ServerException, ClientException {
...
try {
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(baseUri, HttpMethod.POST, getEntity(restRequest), responseClass);
result = response.getBody();
getLogger().debug("[{}] received", result);
return result;
} catch (HttpClientErrorException e) {
throw new ClientException(e.getCause());
} catch (HttpServerErrorException e) {
throw new ServerException(e.getCause());
} catch (Exception e) {
getLogger().error("Error with cause: {}.", e.getMessage());
}
...
}
}
This is the actual implementation:
public class ActualRestClient extends RestClient {
public RestResponse sendFetchFileRequest(URI baseUri, FetchFileRequest request) throws ServerException, ClientException {
return sendPostRequest(baseUri, "FETCH_FILE", request, RestResponse.class);
}
}
An this is the test:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ActualRestClient.class, RestClient.class})
public class ActualResRestClientTest {
private static final String REQUEST_URI = "something";
#InjectMocks
public ActualRestClient testee;
#Mock
private RestTemplate restTemplate;
#Test(expected = ServerException.class)
public void sendPostRequestWithResponseBody_throwsServerException() throws Exception {
HttpServerErrorException httpServerErrorException = new HttpServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR);
when(restTemplate.exchange(Mockito.any(URI.class), eq(HttpMethod.POST), Mockito.any(), eq(FetchFileRequest.class))).thenThrow(httpServerErrorException);
testee.sendFetchFileRequest(new URI(REQUEST_URI), new FetchFileRequest());
}
}
ClientException and ServerException are exceptions created by me by extending Exception class.
My problem is that in the RestClient class another Exception is catched(message:"URI is not absolute") instead of HttpServerErrorException and I can't understand why. Thank you!
As the commenter already expressed: doing new URI("something") already throws at you. But even when you pass a "valid" URI, your code will not work, as there is a misconception on your end. You see:
RestTemplate restTemplate = new RestTemplate();
response = restTemplate.exchange(baseUri, HttpMethod.POST, getEntity(restRequest), responseClass);
That code lives within a method of your class under test. But #InjectMocks works only for fields of classes.
In other words: when your production code gets executed, a new (completely different** ResponseTemplate instance is created. And therefore your mocking spec is irrelevant, because the method isn't invoked on your mock in the first place.
Two choices:
turn that local variable into a field of your class under test (then injecting should work)
or, as you are already using PowerMock(ito), you could use that mocking framework to intercept that call to new().
I suggest you rather use option one, and avoid to use the PowerMock(ito) extension altogether!
I am trying to catch WebApplicationException with my javax.ws.rs.ext.ExceptionMapper class but I get a strange behavior.
This is my simple rest method:
#GET
#Path("/saySomething")
public List<String> saySomething() {
String response = EchoRestClient.ping();
List<String> list = new ArrayList<>();
list.add(response);
list.add("okay");
return list;
}
(1st) This is the client class which calls another rest api:
public class EchoRestClient {
private static Client client = ClientBuilder.newClient();
public static String ping() {
String serviceUrl = PropertyReader.getProperty(ServiceUrl.ECHO_SERVICE);
Response response = client
.target(serviceUrl)
.path("saySomething")
.request(ExtendedMediaType.APPLICATION_UTF8)
.get();
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
return response.getEntity(String.class);
}
throw new WebApplicationException(response);
}
}
And my custom Exception handler, which does NOT catch the above thrown exception:
#Provider
public class WebservletExceptionMapper implements ExceptionMapper<Exception> {
#Override
public Response toResponse(Exception exception) {
System.out.println("caught exception");
Response response;
if (exception instanceof WebApplicationException) {
response = ((WebApplicationException) exception).getResponse();
} else {
response = Response....build();
}
return response;
}
}
(2nd) BUT if I do this the exception is caught (EchoRestClient.java):
public static String ping() {
// same code then before
WebApplicationException e = new WebApplicationException(response);
throw new RuntimeException("xxxxxx", e);
}
My code above works fine and I get a proper response when I call the saySomething rest method from my web browser.
BUT if I undeploy the EchoService rest (contains the called ping rest method) the HTTP 404 is not caught in the 1st case. I need to throw a RuntimeException because WebApplicationException is not caught (2nd case).
According to the documentation the exception hierarchy is WebApplicationException extends RuntimeException.
What is wrong here?
-- EDIT --
If I throw this exception then it is caught fine: throw new WebApplicationException(response.getStatus())
But this one does not work: throw new WebApplicationException(response)
Is something wrong in the response object?
This is caused by an issue in Jersey. If your code throws a WebApplicationException that contains a Response object, ExceptionMappers are not called.
See also:
https://github.com/eclipse-ee4j/jersey/issues/3716
ExceptionMapper not working as expected
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
}
}
}
}
}