chimiz13
chimiz13

Reputation: 85

ClassCastException in Spring IntegrationFlow Implementation

I currently have an IntegrationFlow implementation that utilizes a Service class to implement all desired functionality to be performed by the flow. Something like this...

@Service
public class FlowService {

    public Message<String> removeLineFeeds(Message<String> message) {
        return MessageBuilder
                .withPayload(StringUtils.remove(message.getPayload(), StringUtils.LF))
                .copyHeadersIfAbsent(message.getHeaders())
                .build();
    }

}

@Configuration
@EnableIntegration
public class FlowConfiguration {

    @Autowired
    private FlowService flowService;

    @Bean
    public IntegrationFlow flow() {
        return IntegrationFlows
                .from("inputChannel")
                .transform(flowService, "removeLineFeeds")
                .get();
    }
}

The above implementation works exactly as desired but I was hoping to improve/modify the implementation to utilize the power of Java 8/Lambdas so that it looked something like this...

@Bean
public IntegrationFlow flow() {
    return IntegrationFlows
            .from("inputChannel")
            .transform(flowService::removeLineFeeds)
            .get();
}

Unfortunately, when implemented this way, the flow will throw a ClassCastException whenever it processes a message. I have tried a few of the different proposed solutions that exist online currently but none of them seem to do the trick. I am encountering a similar issue regardless of the IntegrationFlow method used (transform, filter, etc.).

What needs to be changed with the current implementation to allow the use of flowService::removeLineFeeds within the IntegrationFlow methods?

EDIT: PER ARTEM'S RESPONSE

It appears a simple converter in the IntegrationFlow did the trick. My current implementation seemed to be passing the message as a Message<byte[]> instead of the Message<String> I was expecting. See Artem's full response below for more details.

@Bean
public IntegrationFlow flow() {
    return IntegrationFlows
            .from("inputChannel")
            .convert(String.class)
            .transform(flowService::removeLineFeeds)
            .get();
}

Upvotes: 0

Views: 1321

Answers (1)

Artem Bilan
Artem Bilan

Reputation: 121337

The point is that lambda must correspond to some functional interface. In case of transform() it is a GenericTransformer<S, T>. Indeed your Message<String> removeLineFeeds(Message<String> message) satisfies such a contract. And it would work well if you deal with only payload:

public String removeLineFeeds(String message) {
    return StringUtils.remove(message.getPayload(), StringUtils.LF);
}

Just because when all the generic information from the target implementation is erased at runtime we can't guess you would like to deal with the whole Message<?>, so the framework only propagates to your lambda only a payload. That how your String cannot be cast to Message, therefore a ClassCastException.

To fix the problem and mock Java generics system we suggest an overloaded method with an explicit expected type:

/**
 * Populate the {@link MessageTransformingHandler} instance for the provided
 * {@link GenericTransformer} for the specific {@code payloadType} to convert at
 * runtime.
 * @param payloadType the {@link Class} for expected payload type. It can also be
 * {@code Message.class} if you wish to access the entire message in the transformer.
 * Conversion to this type will be attempted, if necessary.
 * @param genericTransformer the {@link GenericTransformer} to populate.
 * @param <P> the payload type - 'transform from' or {@code Message.class}.
 * @param <T> the target type - 'transform to'.
 * @return the current {@link BaseIntegrationFlowDefinition}.
 * @see MethodInvokingTransformer
 * @see LambdaMessageProcessor
 */
public <P, T> B transform(Class<P> payloadType, GenericTransformer<P, T> genericTransformer) {

So, your configuration should look like this:

.transform(Message.class, flowService::removeLineFeeds)

This way we say the framework that we would like to get a whole message for our function to process.

Anyway I'd prefer the first variant just with a payload: the framework will take care for you about coping request headers into a reply message.

See more info in Docs: https://docs.spring.io/spring-integration/reference/html/dsl.html#java-dsl-class-cast

Upvotes: 1

Related Questions