spring-integrationspring-integration-http

Error handling - no output-channel or replyChannel header available


I am trying to handle exceptions using ExpressionEvaluatingRequestHandlerAdvice, have a transformer for a fail channel,

<int:transformer input-channel="afterFailureChannel" output-channel="validateOutputChannel" ref="testExceptionTransformer" method="handleLockServiceResponse"/>

In testExceptionTransformer, I am forming user defined exception and sending it in http response entity which I want to send as a rest api response, Even though transformer has outputChannel, application throws

org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
    at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutput(AbstractMessageProducingHandler.java:452) ~[spring-integration-core-5.5.13.jar:5.5.13]

Could you please help?

Edit:

Transformer looks like this,

public ResponseEntity<Object> handleLockServiceResponse(Message<MessagingException> message) throws Exception {
        ResponseEntity<Object> response = null;
    LOGGER.error(message.getPayload().getFailedMessage().toString());
        LOGGER.error(message.getPayload().getCause().toString());

        try {
            Throwable exception = message.getPayload().getCause();
            if (exception.getCause() instanceof HttpClientErrorException) {
                throw new handleValidationException(exception.getCause().getMessage());
            }
        }catch(handleValidationException ex){
            return adapterErrorHandler.handleCustomValidationException(ex);
        }
        return response;
    }

Solution

  • It indeed doesn't fail in your transformer since you have that output-channel it fails in the initial gateway when it tries to correlate the reply message into a TemporaryReplyChannel from headers. We need to see what your transformer does, but the rule of thumb is if you return a Message from the transformer, you have to coyp headers from request message. However with an ExpressionEvaluatingRequestHandlerAdvice and its failureChannel it is a bit tricky.

    The logic there is like this:

        if (evalResult != null && this.failureChannel != null) {
            MessagingException messagingException =
                    new MessageHandlingExpressionEvaluatingAdviceException(message, "Handler Failed",
                            unwrapThrowableIfNecessary(exception), evalResult);
            ErrorMessage errorMessage = new ErrorMessage(messagingException);
            this.messagingTemplate.send(this.failureChannel, errorMessage);
        }
    

    It becomes obvious that ErrorMessage doesn't have a request message headers. So, you need to extract them from that exception via getFailedMessage() and that's the one is sent to your service instrumented with that ExpressionEvaluatingRequestHandlerAdvice.

    We probably need to improve the doc on the matter: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#expression-advice

    UPDATE

    So, now you return a ResponseEntity from your transformer method and headers for the reply message is copied from that ErrorMessage we send from the ExpressionEvaluatingRequestHandlerAdvice. To preserve original message headers in the reply message you must do something like this:

    public Message<ResponseEntity<Object>> handleLockServiceResponse(Message<MessagingException> message) throws Exception {
        ResponseEntity<Object> response = null;
    LOGGER.error(message.getPayload().getFailedMessage().toString());
        LOGGER.error(message.getPayload().getCause().toString());
    
        try {
            Throwable exception = message.getPayload().getCause();
            if (exception.getCause() instanceof HttpClientErrorException) {
                throw new handleValidationException(exception.getCause().getMessage());
            }
        }catch(handleValidationException ex){
            response = adapterErrorHandler.handleCustomValidationException(ex);
        }
        return MessageBuilder.withPayload(response).copyHeaders(message.getPayload().getFailedMessage().getHeaders()).build();
    }