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.
answer
- Connection factory owns sockets; Net vs Nio; server vs client
- Serializer/Deserializer = framing (CRLF, length-header, STX/ETX)
- Gateways auto-correlate; adapter-pair shares one factory
- Reply routing via IpHeaders.CONNECTION_ID — don't drop it
- singleUse / caching factory / TCP connection events
basics
~20 sTCP 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 sTCP 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// 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
Aware that Spring Integration can send/receive over TCP sockets via dedicated endpoints.
Knows a connection factory plus a serializer are needed and that gateways handle request-reply.
Distinguishes gateway vs adapter-pair, Net vs Nio factories, and framing choices; aware replies need connection correlation.
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)