skip to content

Explain how Spring Integration bridges raw TCP sockets — connection factories, the adapter-pair vs gateway choice, and how replies are correlated on a shared server connection.

level: principalimportance: nice to knowfreq 18%

answer

  1. Connection factory owns sockets; Net vs Nio; server vs client
  2. Serializer/Deserializer = framing (CRLF, length-header, STX/ETX)
  3. Gateways auto-correlate; adapter-pair shares one factory
  4. Reply routing via IpHeaders.CONNECTION_ID — don't drop it
  5. singleUse / caching factory / TCP connection events

basics

~20 s

TCP endpoints share a connection factory (server or client) that owns sockets plus a serializer/deserializer defining message framing. Use TcpInboundGateway/TcpOutboundGateway for request-reply, or a TcpReceivingChannelAdapter + TcpSendingMessageHandler pair sharing one factory for asynchronous flows, correlating replies by the connection id header.

solid answer

~40 s

TCP is a raw byte stream, so Spring Integration adds two things: an AbstractConnectionFactory (TcpNetServerConnectionFactory/TcpNioServerConnectionFactory or the client variants) that manages sockets and threads, and a Serializer/Deserializer that frames the stream into discrete messages (length-header, CRLF, STX/ETX, custom). For synchronous request-reply you use TcpInboundGateway (server) and TcpOutboundGateway (client), which handle correlation for you. For fully asynchronous or multiplexed traffic you instead wire a collaborating pair — TcpReceivingChannelAdapter and TcpSendingMessageHandler — that share the same connection factory; because a server factory serves many clients over long-lived connections, the framework stamps each inbound message with IpHeaders.CONNECTION_ID, and your reply must carry that header back so the sending handler writes to the correct socket. Single-use vs shared connections, NIO vs net threading, and the framing contract are the main design levers.

code

java · 34 lines
java
// Shared SERVER connection factory with length-header framing
@Bean
public AbstractServerConnectionFactory serverFactory() {
    TcpNioServerConnectionFactory f = new TcpNioServerConnectionFactory(9000);
    f.setSerializer(new ByteArrayLengthHeaderSerializer());
    f.setDeserializer(new ByteArrayLengthHeaderSerializer());
    return f;
}

// Asynchronous adapter PAIR sharing the SAME factory
@Bean
public TcpReceivingChannelAdapter inbound(AbstractServerConnectionFactory f) {
    TcpReceivingChannelAdapter a = new TcpReceivingChannelAdapter();
    a.setConnectionFactory(f);
    a.setOutputChannelName("tcpIn");   // message-driven: no poller
    return a;
}

@Bean
@ServiceActivator(inputChannel = "tcpIn")
public TcpSendingMessageHandler outbound(AbstractServerConnectionFactory f) {
    TcpSendingMessageHandler h = new TcpSendingMessageHandler();
    h.setConnectionFactory(f); // same factory => can reply on the originating socket
    return h;
}

// The flow MUST preserve IpHeaders.CONNECTION_ID so the reply hits the right client
@Transformer(inputChannel = "tcpIn", outputChannel = "tcpOut")
public Message<byte[]> handle(Message<byte[]> in) {
    byte[] reply = process(in.getPayload());
    return MessageBuilder.withPayload(reply)
            .copyHeaders(in.getHeaders())   // keeps IpHeaders.CONNECTION_ID
            .build();
}

go deeper

for a junior

Aware that Spring Integration can send/receive over TCP sockets via dedicated endpoints.

for a middle

Knows a connection factory plus a serializer are needed and that gateways handle request-reply.

for a senior

Distinguishes gateway vs adapter-pair, Net vs Nio factories, and framing choices; aware replies need connection correlation.

for a principal

Designs the full TCP integration: connection lifecycle (singleUse/caching), NIO scaling, framing contract, CONNECTION_ID propagation for multiplexed replies, connection-event monitoring, and error/desync recovery.

**Why TCP needs extra machinery.** Unlike JMS or HTTP, TCP is an unframed byte stream — there is no built-in notion of 'a message.' Spring Integration's IP module supplies two collaborating concerns: 1. **Connection factory** — `AbstractConnectionFactory` subclasses own the sockets, threads, and lifecycle. Server side: `TcpNetServerConnectionFactory` (thread-per-connection, blocking IO) or `TcpNioServerConnectionFactory` (NIO, fewer threads, better for many idle connections). Client side: `TcpNetClientConnectionFactory` / `TcpNioClientConnectionFactory`. Endpoints don't open sockets themselves — they attach to a factory. **Multiple endpoints share one factory** (that's how a receiving adapter and a sending handler talk over the *same* connections). 2. **Serializer/Deserializer (framing)** — a `Serializer<?>`/`Deserializer<?>` on the factory defines message boundaries. Built-ins: `ByteArrayCrlfSerializer` (default, `\r\n` delimited), `ByteArrayLengthHeaderSerializer` (4-byte length prefix — robust for binary), `ByteArrayStxEtxSerializer`, `ByteArraySingleTerminatorSerializer`, `ByteArrayRawSerializer`. Both ends must agree on the framing or the stream desynchronizes. **Two ways to model an exchange.** - **Gateways (request-reply):** `TcpInboundGateway` (server) receives a framed message, sends it into the flow, and writes the reply back on the *same* connection — correlation is automatic. `TcpOutboundGateway` (client) sends a request and blocks for the correlated reply. Use these when the protocol is strictly one-request-one-reply per connection turn. - **Collaborating adapter pair (asynchronous / multiplexed):** `TcpReceivingChannelAdapter` (inbound, message-driven — no poller) plus `TcpSendingMessageHandler` (outbound), **both configured with the same connection factory**. This decouples receive from send, enabling arbitrary async patterns (server pushes, interleaved replies, fan-out). This is the classic pattern for protocols where a reply may come much later or out of order. **Correlation on a shared server connection.** A server factory typically holds **many long-lived client connections** simultaneously. When the receiving adapter emits a message, the framework stamps it with **`IpHeaders.CONNECTION_ID`** (and `IpHeaders.IP_ADDRESS`, etc.) identifying which socket it came from. When you later send a reply through `TcpSendingMessageHandler`, that handler inspects `IpHeaders.CONNECTION_ID` to pick the correct open connection to write to. **If you lose that header** (e.g. an aggregator or transformer that rebuilds the message without copying headers), the reply either fails or goes to the wrong client. Preserving/propagating `CONNECTION_ID` end-to-end is the central gotcha of the adapter-pair pattern. (Gateways hide this because request and reply are bound to the same in-flight exchange.) **Client-side connection lifecycle.** `singleUse=false` (default for many setups) keeps a shared long-lived connection; `singleUse=true` opens a fresh connection per request (simpler correlation, more overhead). For clients needing concurrency over shared connections you may need a `CachingClientConnectionFactory` (a pool) or a `ThreadAffinityClientConnectionFactory`, because a single shared socket can't safely interleave unrelated request-replies without protocol-level correlation. **Threading / NIO.** `Net` factories dedicate a thread per connection — simple, fine for a bounded number of connections. `Nio` factories multiplex many connections on a small thread pool — better at scale but require care (assembling partial reads, `soTimeout`, backlog). NIO fragments are reassembled by the deserializer; a broken framing contract shows up as corrupted or truncated payloads. **Error handling & robustness.** Configure `errorChannel` on the inbound side so deserialization/handler errors don't silently drop connections. Watch for: half-open connections, `soTimeout` closing idle sockets, deserializer exceptions on malformed frames (which can desync a stream), and the need for a `TcpConnectionEventListener`/`ApplicationListener` to observe open/close/exception events (`TcpConnectionOpenEvent`, `TcpConnectionCloseEvent`, `TcpConnectionExceptionEvent`). **When to choose which.** - Strict synchronous request-reply, one exchange at a time per connection → **gateways** (least code, automatic correlation). - Asynchronous, server-push, or interleaved/multiplexed messaging → **receiving adapter + sending handler sharing a factory**, correlating via `IpHeaders.CONNECTION_ID`. - Many idle connections / high fan-in → **NIO** factories; modest bounded connections → **Net** factories. - Binary protocols → prefer `ByteArrayLengthHeaderSerializer` over CRLF to avoid delimiter collisions.

  • On a server serving many clients over one factory, how does the outbound handler know which socket to write a reply to?
    The inbound adapter stamps each message with IpHeaders.CONNECTION_ID identifying the originating connection. TcpSendingMessageHandler reads that header to select the open connection. If a transformer/aggregator drops the header, the reply fails or goes to the wrong client — you must copy headers through.
  • When would you use the gateway pair instead of the receiving-adapter/sending-handler pair?
    Use TcpInboundGateway/TcpOutboundGateway when the protocol is strict synchronous request-reply per exchange — they correlate request and reply automatically. Use the collaborating adapter pair for asynchronous, server-push, or interleaved/multiplexed traffic where replies may be out of order or delayed.
  • Why does the choice of Serializer/Deserializer matter so much on both ends?
    TCP is an unframed byte stream; the (de)serializer defines message boundaries. If the two ends disagree (e.g. CRLF vs length-header), frames are split or merged and the stream desynchronizes, producing corrupt or truncated payloads. Length-header framing is safest for binary protocols.

saying these in an interview costs you the question

  • Thinking TCP endpoints frame messages automatically without a serializer
  • Dropping IpHeaders.CONNECTION_ID in a transformer and expecting replies to still route
  • Assuming one shared client connection can safely interleave unrelated request-replies without correlation
  • Using CRLF framing for binary payloads
  • Believing a receiving channel adapter needs a poller (it is message-driven)

context