How to catch FeignClient exception - spring-mvc

I try to catch exception wich I received from another microservice connected by FeignClient. I've made custom ErrorDecoder, and
public class CustomErrorDecoder implements ErrorDecoder {
private final Logger log = LoggerFactory.getLogger(getClass());
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
log.info("----------------- "+methodKey+" , "+response.status()+" , "+ response.reason());
return new RestApiException(99,99,"");
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
Where RestApiException extends Exception.
#ControllerAdvice
public class GlobalControllerAdvice {
private final Logger log = LoggerFactory.getLogger(getClass());
#ExceptionHandler(RestApiException.class)
public ResponseEntity<RestApiException> handleException(RestApiException exception, HttpServletRequest req) {
log.error("Sending error to client ( "+req.getUserPrincipal().getName()+" ) \"{}\"", exception.getErrMsg());
return new ResponseEntity<RestApiException>(exception, exception.getStatus());
}
#ExceptionHandler(Throwable.class)
public ResponseEntity<RestApiException> handleException(Throwable throwable, HttpServletRequest req) {
RestApiException exception=new RestApiException(HttpStatus.INTERNAL_SERVER_ERROR, 100, 100,
throwable.getMessage());
return handleException(exception, req);
}
As a result, when I get <--- HTTP/1.1 400 Bad Request (5380ms)
I've got default error messages
HttpStatus.INTERNAL_SERVER_ERROR, 100, 100,
throwable.getMessage());
but not expexted custom exception, which I try to set in CustomErrorDecoder.
What I'm doing wrong, why I can't call RetAppiException and return error answer to rest client.
Thanks.

You can't catch the exception of the FeignClient with a #ControllerAdvice. The exception handler will not catch the exceptions generated by the feign client, error decoder..
A simple solution would be catch your feign calls, and then throw the exception you want.
try{
feignClient.method();
} catch(Exception ex){
//throw exceptions you want
throw new YourException();
}
Then you'll be able to handle it:
#ControllerAdvice
public class GlobalControllerAdvice {
private final Logger log = LoggerFactory.getLogger(getClass());
#ExceptionHandler(YourException.class)
public ResponseEntity<RestApiException> handleException(RestApiException exception, HttpServletRequest req) {
//impl
}
}

You can catch feign client exceptions by catching HystrixRuntimeException and casting the getCause() to FeignClientException.
Example:
#ExceptionHandler
public ResponseEntity<Problem> handleFeignException(HystrixRuntimeException ex, NativeWebRequest request) {
final FeignException.FeignClientException feignClientException = (FeignException.FeignClientException) ex.getCause();
return handleException(feignClientException, request);
}

Related

Getting org.springframework.retry.TerminatedRetryException with RetryListener

I'm using spring-kafka 2.2.8.RELEASE and using #KafkaListener annotation to create a consumer and here is my consumer configuration code.
#Bean
public <K,V> ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(primaryConsumerFactory());
factory.setRetryTemplate(retryTemplate());
return factory;
}
#Bean
public DefaultKafkaConsumerFactory<Object, Object> primaryConsumerFactory() {
return new DefaultKafkaConsumerFactory<>(MyConsumerConfig.getConfigs());
}
public RetryTemplate retryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setListeners(new RetryListener[]{myKafkaRetryListener});
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(Integer.parseInt(3));
retryTemplate.setRetryPolicy(retryPolicy);
ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
exponentialBackOffPolicy.setInitialInterval(500);
//As per the spring-kafka documentation, maxInterval (60000 ms) should be set less than max.poll.interval.ms (600000 ms)
exponentialBackOffPolicy.setMaxInterval(60000);
retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
return retryTemplate;
}
Here is my custom retry listener code:
#Component
public class MyRetryListener implements RetryListener {
#Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
System.out.println("##### IN open method");
return false;
}
#Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("##### IN close method");
}
#Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
System.out.println("##### Got an error and will retry");
}
}
Now, when I'm sending a message to a test topic, and in the consumer I'm throwing a TimeoutException so that the retry will trigger and here is my consumer code.
#KafkaListener(topics = "CONSUMER_RETRY_TEST_TOPIC")
public void listen(ConsumerRecord message) throws RetriableException {
System.out.println("CONSUMER_RETRY testing - Received message with key "+message.key()+" on topic " + CONSUMER_RETRY_TEST_TOPIC + " \n \n ");
throw new TimeoutException();
}
With the above code configuration, the retry is not triggered and 'onError' method of my custom retry listener is never invoked and I'm getting the below error. Please suggest what am i missing here?
org.springframework.retry.TerminatedRetryException: Retry terminated abnormally by interceptor before first attempt
See the JavaDocs for RetryListener.open().
<T,E extends Throwable> boolean open(RetryContext context,
RetryCallback<T,E> callback)
Called before the first attempt in a retry. For instance, implementers can set up state that is needed by the policies in the RetryOperations. The whole retry can be vetoed by returning false from this method, in which case a TerminatedRetryException will be thrown.
Type Parameters:
T - the type of object returned by the callback
E - the type of exception it declares may be thrown
Parameters:
context - the current RetryContext.
callback - the current RetryCallback.
Returns:
true if the retry should proceed.
You need to return true not false.

SoapFault handling with Spring WS client - WebServiceGatewaySupport and WebServiceTemplate

I am trying to write a Spring WS client using WebServiceGatewaySupport. I managed to test the client for a successful request and response. Now I wanted to write test cases for soap faults.
public class MyClient extends WebServiceGatewaySupport {
public ServiceResponse method(ServiceRequest serviceRequest) {
return (ServiceResponse) getWebServiceTemplate().marshalSendAndReceive(serviceRequest);
}
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SpringTestConfig.class)
#DirtiesContext
public class MyClientTest {
#Autowired
private MyClient myClient;
private MockWebServiceServer mockServer;
#Before
public void createServer() throws Exception {
mockServer = MockWebServiceServer.createServer(myClient);
}
}
My question is how do i stub the soap fault response in the mock server, so that my custom FaultMessageResolver will be able to unmarshall soap fault?
I tried couple of things below, but nothing worked.
// responsePayload being SoapFault wrapped in SoapEnvelope
mockServer.expect(payload(requestPayload))
.andRespond(withSoapEnvelope(responsePayload));
// tried to build error message
mockServer.expect(payload(requestPayload))
.andRespond(withError("soap fault string"));
// tried with Exception
mockServer.expect(payload(requestPayload))
.andRespond(withException(new RuntimeException));
Any help is appreciated. Thanks!
Follow Up:
Ok so, withSoapEnvelope(payload) I managed to get the controller to go to my custom MySoapFaultMessageResolver.
public class MyCustomSoapFaultMessageResolver implements FaultMessageResolver {
private Jaxb2Marshaller jaxb2Marshaller;
#Override
public void resolveFault(WebServiceMessage message) throws IOException {
if (message instanceof SoapMessage) {
SoapMessage soapMessage = (SoapMessage) message;
SoapFaultDetailElement soapFaultDetailElement = (SoapFaultDetailElement) soapMessage.getSoapBody()
.getFault()
.getFaultDetail()
.getDetailEntries()
.next();
Source source = soapFaultDetailElement.getSource();
jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setContextPath("com.company.project.schema");
Object object = jaxb2Marshaller.unmarshal(source);
if (object instanceof CustomerAlreadyExistsFault) {
throw new CustomerAlreadyExistsException(soapMessage);
}
}
}
}
But seriously!!! I had to unmarshall every message and check the instance of it. Being a client I should be thorough with all possible exceptions of the service here, and create custom runtime exceptions and throw it from the resolver. Still at the end, its been caught in WebServiceTemplate and re thrown as just a runtime exception.
You could try with something like this:
#Test
public void yourTestMethod() // with no throw here
{
Source requestPayload = new StringSource("<your request>");
String errorMessage = "Your error message from WS";
mockWebServiceServer
.expect(payload(requestPayload))
.andRespond(withError(errorMessage));
YourRequestClass request = new YourRequestClass();
// TODO: set request properties...
try {
yourClient.callMethod(request);
}
catch (Exception e) {
assertThat(e.getMessage()).isEqualTo(errorMessage);
}
mockWebServiceServer.verify();
}
In this part of code mockWebServiceServer represents the instance of MockWebServiceServer class.

How to test common error ErrorController in spring-boot with junit

In a spring boot project, I'd like to test my ErrorController with Junit.
The code is as the following snippet.
#RestController
public class ApiErrorController implements ErrorController {
private static final Logger LOGGER = LoggerFactory.getLogger(ApiErrorController.class);
#Value("${server.error.path}")
private String errorPath;
#Override
public String getErrorPath() {
return this.errorPath;
}
#RequestMapping("/error")
public ResponseEntity<ErrorResult> error(HttpServletRequest request, HttpServletResponse response) {
String requestURI = (String) request.getAttribute("javax.servlet.forward.request_uri");
LOGGER.info("error handling start url = {}", requestURI);
String servletMessage = (String) request.getAttribute("javax.servlet.error.message");
Integer servletStatus = (Integer) request.getAttribute("javax.servlet.error.status_code");
String[] messages = new String[0];
if (!StringUtils.isNullOrEmpty(servletMessage)) {
messages = new String[] { servletMessage };
}
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
try {
if (servletStatus != null && servletStatus instanceof Integer) {
status = HttpStatus.valueOf(servletStatus);
}
} catch (Exception ex) { // test this exception
LOGGER.warn("http status not converted.{}", request.getAttribute("javax.servlet.error.status_code"), ex);
}
ErrorResult body = new ErrorResult();
body.setMessages(messages);
ResponseEntity<ErrorResult> responseResult = new ResponseEntity<>(body, status);
return responseResult;
}
}
When a business exception happened in my Controller(for example AbcController), then the program goes into the ExceptionControllerAdvice class.
If an exception happened in ExceptionControllerAdvice, then the program goes into the above ApiErrorController class.
Could someone tell me how to test the case that HttpStatus.valueOf(servletStatus) fail?
In addition, I want request.getAttribute("javax.servlet.error.message") return a non-empty string.
How to achieve what I'd like to test?
By the way, I don't want to only test the logic of error method. I'd like to use AbcController I mentioned to make the test. What I want is when a error happens in AbcController, then the error method in ApiErrorController can handle it successfully.
APPEND:
For example, ExceptionControllerAdvice will handle the business exception.
#ControllerAdvice(annotations = RestController.class)
public class ExceptionControllerAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
#ExceptionHandler({ BusinessCloudException.class })
public ResponseEntity<ErrorResult> handleBlCloudException(HttpServletRequest request, HttpServletResponse response,
BlCloudException ex) {
HttpStatus status = ErrorUtils.toHttpStatus(ex.getType());
ErrorResult body = new ErrorResult();
body.setMessages(ex.getMessageArray());
ResponseEntity<ErrorResult> responseResult = new ResponseEntity<>(body, status);
return responseResult;
}
}
If there's an error happened in the handleBlCloudException method, then the program goes into ApiErrorController to handle this error.
How do the program produce the a specific servletStatus and javax.servlet.error.message? How to mock to do this?
First of all there is quite a lot going on in that error method. You might consider moving some of the logic to a specialized class / public methods.
Apart from that i would suggest using Mockito.
Fist of all create a method to encapsulate the HttpStatus retrieval:
HttpStatus getHttpStatusByServletStatus(Integer servletStatus){
return HttpStatus.valueOf(servletStatus);
}
and change your code to :
if (servletStatus != null && servletStatus instanceof Integer) {
status = getHttpStatusByServletStatus(servletStatus);
}
Then the test class:
public ApiErrorControllerTest{
#Spy
private ApiErrorController apiErrorController;
#Mock
HttpServletRequest requestMock;
#Mock
HttpServletResponse responseMock;
#Befire
public void init(){
MockitoAnnotations.initMocks(this);
}
#Test
public void test(){
// Arrange
HttpStatus expectedStatus = // expected status
String expectedErrorMessage = // ..
doReturn(expectedStatus).when(apiErrorController)
.getHttpStatusByServletStatus(Mockito.anyString());
when(requestMock.getAttribute("javax.servlet.error.message"))
.thenReturn(expectedErrorMessage);
// other setup..
// Act
apiErrorController.error(requestMock, responseMock);
// Assertions
}

Spring MVC exception handling with HandlerExceptionResolver

I am currently trying to use HandlerExceptionResolver for exception handling in a Spring MVC project.
I want to handle normal exceptions via resolveException as well as 404's via
handleNoSuchRequestHandlingMethod.
Depending on the request type JSON or text/html the exception response should be returned appropriately.
resolveException works now.
But handleNoSuchRequestHandlingMethod is giving me a headache. It's never called!
According to the docu the method should be called on 404 errors
http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.html
What am I doing wrong...
This is what I have so far.
public class JsonExceptionResolver implements HandlerExceptionResolver {
protected final Log logger = LogFactory.getLog(getClass());
public ModelAndView resolveException(HttpServletRequest request,
if (exception instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) exception, request, response, handler);
}
...
}
public ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler){
logger.info("Handle my exception!!!");
ModelAndView mav = new ModelAndView();
boolean isJSON = request.getHeader("Accept").equals("application/json");
if(isJSON){
...
}else{
..
}
return mav;
}
}
EDIT with DefaultHandlerExceptionResolver:
public class MyExceptionResolver extends DefaultHandlerExceptionResolver {
protected final Log logger = LogFactory.getLog(getClass());
#Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) {
logger.warn("An Exception has occured in the application", exception);
logger.info("exception thrown " + exception.getMessage() );
if (exception instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) exception, request, response, handler);
}
...
return mav;
}
public ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
HttpServletRequest request,
HttpServletResponse response,
Object handler){
logger.info("Handle my exception!!!");
ModelAndView mav = new ModelAndView();
boolean isJSON = request.getHeader("Accept").equals("application/json");
if(isJSON){
...
}else{
...
}
return mav;
}
}
The above code still has no effect.
Any other ideas?
According to Juergen Hoeller from Spring, it isn't possible with the HandlerExceptionResolver because it only works for sub-mapping e.g.
you have a controller mapped to /account/** and accesss a method from acount where no mapping exists like /acount/notExists than it should work.
I will open a JIRA improvement ticket for this functionality
EDIT:
JIRA ticket about this issue
https://jira.springsource.org/browse/SPR-8837?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=72648#comment-72648
handleNoSuchRequestHandlingMethod isn't part of the HandlerExceptionResolver interface, so just declaring a method of that name will do nothing. It's a protected method specific to DefaultHandlerExceptionResolver, and is called from its resolveException method (which is part of the interface):
if (ex instanceof NoSuchRequestHandlingMethodException) {
return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, request, response, handler);
}
To reproduce the same functionality, you can either subclass DefaultHandlerExceptionResolver and override the methods you need to, or you need to add a case in your resolveException method that handles NoSuchRequestHandlingMethodException.

Using Spring 3 #ExceptionHandler with commons FileUpload and SizeLimitExceededException/MaxUploadSizeExceededException

I am having trouble with catching and gracefully handling commons fileupload's FileUploadBase.SizeLimitExceededException or spring's MaxUploadSizeExceededException when uploading large files.
From what I can tell these exceptions are thrown during data binding, before the controller is actually reached, therefore resulting in a 500 and no calling of the exception handler method. Has anyone come across this before, and what is the best way for handling these exceptions properly?
thanks to thetoolman for this simple solution. I extended it a bit. I wanted to leave the file handling untouched and transport the Exception to the Controller.
package myCompany;
public class DropOversizeFilesMultipartResolver extends CommonsMultipartResolver {
/**
* Parse the given servlet request, resolving its multipart elements.
*
* Thanks Alexander Semenov # http://forum.springsource.org/showthread.php?62586
*
* #param request
* the request to parse
* #return the parsing result
*/
#Override
protected MultipartParsingResult parseRequest(final HttpServletRequest request) {
String encoding = determineEncoding(request);
FileUpload fileUpload = prepareFileUpload(encoding);
List fileItems;
try {
fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
} catch (FileUploadBase.SizeLimitExceededException ex) {
request.setAttribute(EXCEPTION_KEY, ex);
fileItems = Collections.EMPTY_LIST;
} catch (FileUploadException ex) {
throw new MultipartException("Could not parse multipart servlet request", ex);
}
return parseFileItems(fileItems, encoding);
}
}
and in the controller
#InitBinder("fileForm")
protected void initBinderDesignForm(WebDataBinder binder) {
binder.setValidator(new FileFormValidator());
}
#RequestMapping(value = "/my/mapping", method = RequestMethod.POST)
public ModelAndView acceptFile(HttpServletRequest request, Model model, FormData formData,
BindingResult result) {
Object exception = request.getAttribute(DropOversizeFilesMultipartResolver.EXCEPTION_KEY);
if (exception != null && FileUploadBase.SizeLimitExceededException.class.equals(exception.getClass())) {
result.rejectValue("file", "<your.message.key>");
LOGGER.error(exception);
}
the spring config remains the same. It would be really nice to have the exception transported to the validator, but I haven't figured out how to do this yet.
I know this is old, but I was looking for a solution to this as well and could not find anything. We are providing RESTful services using Spring and we are doing file upload and were not sure how to handle this. I came up with the following and hopefully it will be useful to someone:
All our exceptions are handled with annotations, so we have our error handler resolver set-up like this:
#Configuration
public class MyConfig{
#Bean
public AnnotationMethodHandlerExceptionResolver exceptionResolver(){
final AnnotationMethodHandlerExceptionResolver resolver = new AnnotationMethodHandlerExceptionResolver();
resolver.setMessageConverters(messageConverters());
resolver;
}
}
Then a common class that can handle the exception
public class MultipartExceptionHandler
{
#ExceptionHandler(MaxUploadSizeExceededException.class)
#ResponseStatus(value = HttpStatus.PRECONDITION_FAILED)
#ResponseBody
protected CustomError handleMaxUploadSizeExceededException(final HttpServletRequest request,
final HttpServletResponse response, final Throwable e)
throws IOException
{
logger.error(e);
CustomError c = new CustomErrorMaxFileSize("Max file size exceeded", MAX_FILE_SIZE);
return c;
}
#ExceptionHandler(MultipartException.class)
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
protected CustomError handleGenericMultipartException(final HttpServletRequest request,
final HttpServletResponse response, final Throwable e)
throws IOException
{
logger.error(e);
CustomError c = new CustomErrorGeneric("There was a problem with the upload");
return c;
}
}
Then we subclass the commons multipart resolver and implement the HandlerExceptionResolver interface
#Component(value="multipartResolver") // Spring expects this name
public class MyMultipartResolver extends CommonsMultipartResolver implements HandlerExceptionResolver
{
// This is the Spring bean that handles exceptions
// We defined this in the Java configuration file
#Resource(name = "exceptionResolver")
private AnnotationMethodHandlerExceptionResolver exceptionResolver;
// The multipart exception handler with the #ExceptionHandler annotation
private final MultipartExceptionHandler multipartExceptionHandler = new MultipartExceptionHandler();
// Spring will call this when there is an exception thrown from this
// multipart resolver
#Override
public ModelAndView resolveException(
final HttpServletRequest request,
final HttpServletResponse response,
final Object handlerParam,
final Exception ex)
{
// Notice that we pass this.multipartExceptionHandler
// and not the method parameter 'handlerParam' into the
// exceptionResolver. We do this because the DispatcherServlet
// doDispatch() method calls checkMultipart() before determining
// the handler for the request. If doing the multipart check fails
// with a MultipartException, Spring will never have a reference
// to the handler and so 'handlerParam' will be null at this point.
return exceptionResolver.resolveException(request, response, this.multipartExceptionHandler, ex);
}
}
This seems to be a quite common problem. I've had similar problems and similar questions have been asked, see for example this question. I have yet to see a nice solution to the problem. You could use a vanilla servlet filter to handle these exceptions, but that will duplicate your error handling since you already have an ExceptionHandler.

Resources