What does @MessagingGateway do, and how does it let application code call an integration flow through a plain Java interface?
answer
- Interface + @MessagingGateway → GatewayProxyFactoryBean proxy
- Args → Message (@Payload / @Header)
- Non-void return = request-reply; void = one-way
- Temporary reply channel, no manual correlation
- Needs @IntegrationComponentScan; Future/Mono for async
basics
~20 s@MessagingGateway on an interface makes Spring create a proxy. Calling an interface method turns the arguments into a Message sent to a request channel; the reply Message's payload is returned. Your code never touches MessageChannel or Message APIs.
solid answer
~40 s@MessagingGateway hides the messaging infrastructure behind an ordinary interface. You annotate an interface; Spring's GatewayProxyFactoryBean builds a proxy bean implementing it. When you call a method, the proxy maps the arguments into a Message (payload plus headers from @Payload/@Header), sends it to the configured request channel, and — if the method has a non-void return type — blocks for a reply on a temporary reply channel and returns the reply payload (converted to the return type). Void methods are one-way sends. You can override channels per method with @Gateway, set a defaultRequestChannel/defaultReplyChannel and defaultReplyTimeout at the type level, and return Future, CompletableFuture, or Mono for asynchronous replies. This keeps business code decoupled from Spring Integration — the interface is the entire messaging API the caller sees.
code
java · 26 lines@MessagingGateway(defaultRequestChannel = "orders.input",
defaultReplyTimeout = "2000")
public interface OrderGateway {
// request-reply: blocks for a reply, returns its payload
@Gateway(requestChannel = "orders.priced")
Price price(@Payload Order order, @Header("region") String region);
// one-way: void => fire-and-forget send
void submit(Order order);
// asynchronous request-reply
CompletableFuture<Receipt> submitAsync(Order order);
}
@Configuration
@IntegrationComponentScan // REQUIRED so the @MessagingGateway interface is proxied
public class GatewayConfig {
@Bean
public IntegrationFlow pricingFlow() {
return IntegrationFlow.from("orders.priced")
.handle((Order o, MessageHeaders h) -> new Price(o.total()))
.get(); // final handler's return becomes the reply
}
}go deeper
Knows @MessagingGateway lets you call an integration flow via a plain interface method.
Must know: args→Message mapping (@Payload/@Header), non-void=request-reply vs void=one-way, and @IntegrationComponentScan requirement.
Explains temporary reply channel correlation, reply/request timeouts, errorChannel fallback, and Future/Mono async returns.
Weighs gateway as the coupling seam for testability and layering; reasons about timeout/back-pressure/thread-blocking tradeoffs and error-channel contracts across teams.
**The problem it solves.** Without a gateway, code that wants to start an integration flow must obtain a `MessageChannel`, build a `Message<?>` with `MessageBuilder`, send it, and (for request-reply) correlate and wait for the reply. That couples business code to the messaging API. The **Messaging Gateway** pattern (EIP) hides all of that behind a normal interface. `@MessagingGateway` is the annotation-driven way to declare one. **How it works.** Put `@MessagingGateway` on an *interface*. Spring registers a `GatewayProxyFactoryBean` that produces a JDK dynamic proxy implementing the interface. Each method invocation is intercepted: 1. Method arguments are mapped to a `Message`. A single argument becomes the payload by default. With multiple arguments you annotate them: `@Payload` marks the payload (or a SpEL expression builds it), `@Header("name")` maps an argument to a header. 2. The message is sent to the method's **request channel** (`@Gateway(requestChannel=...)`, else the type-level `defaultRequestChannel`). 3. If the return type is **non-void**, the proxy waits for a **reply** and returns its payload, converted to the declared return type. If **void**, it is a one-way send and returns immediately. **Reply mechanics.** By default the gateway does *not* need an explicit reply channel: it creates a per-request **temporary reply channel** (a `TemporaryReplyChannel`), puts it in the message's `replyChannel` header, and the downstream flow's final component replies to that header automatically. This avoids manual correlation. You *may* set a shared `defaultReplyChannel`/`@Gateway(replyChannel=...)`, in which case the gateway bridges from that channel back to the caller. **Timeouts.** `defaultRequestTimeout` bounds how long the send can block (relevant on full bounded channels); `defaultReplyTimeout` bounds how long it waits for a reply. On reply timeout the method returns `null` (or throws, depending on `errorOnTimeout`). A common bug is a flow that never replies, causing the caller to block until the reply timeout. **Asynchronous returns.** Declare the method to return `java.util.concurrent.Future<T>` or `CompletableFuture<T>` and the gateway runs the request-reply on an executor, returning immediately. Returning `reactor.core.publisher.Mono<T>` gives a reactive handle. `void` = fire-and-forget. **Error handling.** Set `errorChannel` at the type level so exceptions thrown downstream are wrapped as an `ErrorMessage` and routed there; the gateway can then return a fallback reply instead of propagating the raw exception to the caller. **Declaration details.** The interface must be picked up — either the config class is annotated with `@IntegrationComponentScan` (which detects `@MessagingGateway` interfaces) or you register it explicitly. This is separate from `@ComponentScan`, which does not detect gateway interfaces. **When to use.** Use `@MessagingGateway` at the *entry* to an integration flow whenever application/service code needs to invoke the flow synchronously (request-reply) or fire-and-forget without depending on `MessageChannel`. It is the idiomatic seam between plain Spring beans and a Spring Integration flow. **Gotchas.** Forgetting `@IntegrationComponentScan` → no proxy bean, injection fails. A non-void method against a one-way flow → blocks and times out. Multiple unannotated arguments → the framework can't decide the payload and fails; annotate them. Expecting exceptions to surface transparently when an `errorChannel` swallows them.
- How does the gateway correlate the reply to the right caller without a correlation ID?For a non-void call it creates a per-request TemporaryReplyChannel and sets it as the message's replyChannel header. The flow's terminating handler replies to that header, so each thread's reply comes back on its own private channel — no explicit correlation ID needed.
- Why might a call to a gateway method hang until the reply timeout?The downstream flow never produced a reply — e.g. it ended in a one-way outbound adapter, dropped the replyChannel header, or an exception was swallowed. A non-void gateway method expects a reply, so it blocks until defaultReplyTimeout, then returns null (or throws).
- Which scan detects @MessagingGateway interfaces?@IntegrationComponentScan. Plain @ComponentScan does not create the gateway proxy, so the interface bean would be missing.
saying these in an interview costs you the question
- Annotating a class instead of an interface
- Forgetting @IntegrationComponentScan and expecting the proxy to exist
- Believing every gateway method blocks even when void
- Thinking you must hand-write correlation IDs for the reply
- Confusing @MessagingGateway (client-side proxy) with an inbound HTTP/TCP gateway (server-side endpoint)