skip to content

Integration Java DSL

IntegrationFlow builds a flow fluently as a @Bean instead of XML, chaining transform, filter, route and handle steps. Interviewers ask about it as the readable, testable modern face of Spring Integration.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

5

What is the Spring Integration Java DSL and how do you define a basic flow with it?

level: juniorimportance: must knowfreq 60%

answer

  1. IntegrationFlow @Bean
  2. IntegrationFlow.from(...) entry point
  3. fluent .transform().filter().route().handle()
  4. auto-created DirectChannel between steps
  5. replaces XML namespaces

basics

~10 s

It's a fluent Java API for building Spring Integration message pipelines instead of XML. You define an IntegrationFlow bean starting with IntegrationFlow.from(a source), then chain steps like .transform() and .handle().

solid answer

~40 s

The Java DSL is a fluent, builder-style Java API for wiring Spring Integration message flows, replacing the older XML configuration. You declare a flow as a Spring @Bean of type IntegrationFlow, typically built with the IntegrationFlow.from(...) factory to define where messages enter (a channel, a message source, or a gateway), then chain endpoints such as .transform(), .filter(), .route(), and .handle(). Each chained method adds a message-handling endpoint; Spring auto-creates the channels between them. Because the flow is a normal bean, it participates in the application context lifecycle and can use dependency injection. It reads top-to-bottom like the pipeline it describes, which makes it far more readable and refactor-friendly than the equivalent XML, while producing the same underlying MessageChannel and MessageHandler infrastructure.

code

java · 16 lines
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.dsl.IntegrationFlow;

@Configuration
public class GreetingFlowConfig {

    @Bean
    public IntegrationFlow greetingFlow() {
        return IntegrationFlow.from("inputChannel")   // entry point: a named channel
                .filter((String s) -> !s.isBlank())    // drop empty payloads
                .transform(String::toUpperCase)        // change the payload
                .handle(m -> System.out.println("Got: " + m.getPayload())) // terminal handler
                .get();                                // finish -> IntegrationFlow
    }
}

go deeper

for a junior

Should know it's a fluent Java alternative to XML: an IntegrationFlow @Bean built with from(...) then chained steps.

for a middle

Should explain the roles of transform/filter/route/handle and that channels between steps are auto-created (DirectChannel, same thread).

for a senior

Should discuss threading implications of DirectChannel, when to insert explicit channels/pollers, and DI/testability advantages over XML.

for a principal

Should reason about flow composition, lifecycle registration via IntegrationFlowBeanPostProcessor, and when Spring Integration DSL is the right tool vs plain services or a broker.

**Spring Integration** implements Enterprise Integration Patterns (EIP): messages flow through channels and are processed by endpoints (transformers, filters, routers, service activators). Historically you wired these with XML `<int:...>` namespaces. The **Java DSL** (package `org.springframework.integration.dsl`) is a fluent, builder-based alternative introduced to configure the same infrastructure in type-safe Java. **Core type — `IntegrationFlow`:** A flow is expressed as a bean of the functional interface `IntegrationFlow`. You almost never implement it by hand; instead you use the builder returned by the static factory `IntegrationFlow.from(...)` and finish with `.get()`, which returns an `IntegrationFlow` instance. Registering it as a `@Bean` tells Spring Integration's `IntegrationFlowBeanPostProcessor` to unpack the flow and register all its channels and endpoints in the application context. **Starting a flow — `IntegrationFlow.from(...)`:** Every flow needs an entry point. `from(...)` accepts several kinds of source: - A **channel** — by bean name `from("inputChannel")` or a `MessageChannel` instance. Messages sent to that channel enter the flow. - A **message source** — e.g. `from(() -> new GenericMessage<>("data"))` or a `MessageSource` such as a file/JDBC poller, usually paired with a poller: `from(source, e -> e.poller(Pollers.fixedRate(1000)))`. - A **gateway interface** — `from(MyGateway.class)` binds a Java interface so calling its method injects a message into the flow. **Chaining endpoints (the fluent builder):** After `from(...)` you get an `IntegrationFlowBuilder` exposing EIP operations: - `.transform(payload -> ...)` — a **transformer**, changes the payload/headers (maps input message to output). - `.filter(payload -> booleanCondition)` — a **filter**, drops messages that don't match (silently, or throws if configured). - `.route(m -> key)` — a **router**, sends messages to different channels/sub-flows based on a value. - `.handle(...)` — a **service activator / message handler**, the terminal-style consumer that does work (call a bean method, an outbound adapter, etc.). - `.split()`, `.aggregate()`, `.enrichHeaders()`, `.channel(...)` and more. Each call adds an endpoint; Spring **auto-creates a `DirectChannel` between consecutive endpoints** unless you insert an explicit `.channel(...)`. A `DirectChannel` runs the next handler on the **same thread** synchronously, so unless you introduce an executor/queue channel or a poller, the whole flow executes on the caller's thread. **Why `@Bean`-registered flows over XML:** compile-time checking, IDE navigation/refactoring, lambdas for inline handlers, easy DI of collaborators, and top-to-bottom readability. XML is still supported but the DSL is the modern default. **Gotchas:** - The flow bean must be returned as type `IntegrationFlow` — don't forget `.get()` at the end of the builder chain. - A flow that only has an implicit input channel gets an auto-generated channel name; to send into it explicitly, start with a named channel (`from("myChannel")`) or use a messaging gateway. - `.handle()` typically ends a linear flow; adding steps after a one-way terminal handler that produces no output won't do anything. - Don't confuse `.transform()` (must return a value, message is passed on) with `.handle()` (may be terminal / one-way). **When to use:** any in-JVM pipeline orchestration — file/FTP/JDBC/HTTP/Kafka adapters, message routing, EIP-style decoupling — where you'd otherwise write glue code or XML.

  • Why must the method return type be IntegrationFlow and what does .get() do?
    The builder chain produces an IntegrationFlowBuilder; .get() finalizes it into an IntegrationFlow instance. Returning it as an IntegrationFlow @Bean lets the IntegrationFlowBeanPostProcessor unpack and register all the channels/endpoints in the context.
  • What thread does a simple from().transform().handle() flow run on?
    The caller's thread. Consecutive endpoints are joined by auto-created DirectChannels, which invoke the next handler synchronously on the same thread — unless you insert an ExecutorChannel, a QueueChannel with a poller, or start from a polling source.

saying these in an interview costs you the question

  • Thinking the Java DSL is a separate messaging system rather than a fluent config for the same Spring Integration infrastructure as XML
  • Believing each .transform()/.handle() step automatically runs on its own thread asynchronously
  • Forgetting .get() / returning the builder instead of an IntegrationFlow

context

open as a page

Explain the difference between .transform(), .filter(), .route(), and .handle() in the Integration Java DSL.

level: middleimportance: must knowfreq 55%

basics

~20 s

transform changes the payload and passes it on; filter drops messages that fail a boolean test; route sends messages to different channels/sub-flows based on a value; handle consumes the message to do work, often ending the flow.

open as a page

How does a @Bean-returned IntegrationFlow get turned into running channels and endpoints, and what advantages does it have over XML configuration?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Spring Integration's IntegrationFlowBeanPostProcessor detects the IntegrationFlow bean, unpacks its endpoints and channels, and registers each as its own bean in the context. Versus XML you get compile-time safety, DI, lambdas, and top-to-bottom readability.

open as a page

In a DSL flow, when does processing switch threads or become asynchronous, and how do you control that?

level: seniorimportance: should knowfreq 35%

basics

~20 s

By default consecutive steps are joined by DirectChannels that run synchronously on the caller's thread. To go async or switch threads you insert an explicit .channel() that is an ExecutorChannel (thread pool) or a QueueChannel (with a poller).

open as a page

As an architect, how do you decide whether to use Spring Integration Java DSL versus plain service classes or a dedicated message broker, and how do you keep flows maintainable?

level: principalimportance: nice to knowfreq 20%

basics

~20 s

Use the DSL for in-JVM EIP-style orchestration across adapters (files, HTTP, JDBC, Kafka) where routing/transforming/filtering adds value. For simple call chains, plain services are clearer; for cross-service, durable, scalable messaging, use a real broker. Keep flows small and composed from reusable sub-flows.

open as a page