skip to content

A Tomcat <Connector> element's protocol attribute can be set to "HTTP/1.1", to org.apache.coyote.http11.Http11Nio2Protocol, or to the APR/native implementation. What do those choose between, and which would you deploy today?

level: middleimportance: nice to knowfreq 38%

answer

  1. it picks I/O, not HTTP version
  2. BIO tied one thread per connection
  3. poller threads decouple sockets from exec threads
  4. the C library survived, the connector did not
  5. an upgrade trap in server.xml

basics

~20 s

They select Tomcat's socket-handling implementation. NIO uses Java non-blocking sockets with poller threads and is the default; NIO2 uses the JDK's asynchronous channel API; APR/native used the C Apache Portable Runtime and is gone in current Tomcat. Deploy NIO.

solid answer

~50 s

The `protocol` attribute picks the connector's I/O implementation, not the HTTP version. `protocol="HTTP/1.1"` gives you the NIO implementation, which uses Java non-blocking sockets: a small number of acceptor and poller threads watch the sockets, and an exec thread is only borrowed while a request is actually being processed. `Http11Nio2Protocol` does the same job through the JDK's asynchronous channel API with completion handlers instead of a selector loop; in practice its throughput is close enough to NIO that it is rarely worth choosing. The APR/native connector delegated socket and TLS work to the C Apache Portable Runtime library and was the fast option in the old blocking-I/O era; it was deprecated in Tomcat 10.1 and removed in Tomcat 11. The purely blocking BIO connector disappeared back in Tomcat 8.5. So the honest answer today is: use the default NIO, and if you want OpenSSL rather than JSSE for TLS, that is still available to the NIO connectors through the tomcat-native library.

go deeper

for a junior

Know that the protocol attribute selects Tomcat's socket implementation rather than an HTTP version, and that the default today is the Java NIO connector.

for a middle

Explain how NIO's acceptor and poller threads decouple open connections from request threads, and what NIO2 and the removed APR connector each did differently.

for a senior

Bring the upgrade angle: an APR connector left in server.xml fails on Tomcat 11, OpenSSL now rides on the NIO connector via tomcat-native, and implementation choice is rarely the cause of a latency problem.

for a principal

Be the person who refuses unmeasured tuning: state what evidence would justify moving off the default implementation, and weigh the maintenance cost of a native dependency against the crypto throughput it buys.

## What the attribute actually selects The name is misleading. `protocol="HTTP/1.1"` looks like it declares an HTTP version, but what it really selects is which **Coyote connector implementation** — that is, which socket-handling strategy — Tomcat uses for this port. HTTP/2 is added separately, as an upgrade protocol nested inside the connector, not by changing this attribute. Historically there were four implementations: - **BIO** — one thread blocked per connection, for the connection's entire life. Removed in Tomcat 8.5. - **NIO** (`Http11NioProtocol`) — non-blocking Java sockets driven by a selector. This is what `HTTP/1.1` gives you on current Tomcat. - **NIO2** (`Http11Nio2Protocol`) — the same non-blocking idea expressed with the JDK's asynchronous channel API and completion handlers rather than an explicit selector loop. - **APR/native** (`Http11AprProtocol`) — socket and TLS handling pushed down into the C Apache Portable Runtime plus OpenSSL, via the tomcat-native library. Deprecated in Tomcat 10.1 and removed in Tomcat 11. ```xml <!-- default: NIO --> <Connector port="8080" protocol="HTTP/1.1"/> <!-- explicit NIO2 --> <Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"/> ``` ## Why non-blocking mattered so much Under BIO, a connection and a thread were the same resource. A browser holding a keep-alive connection open for 60 seconds of thinking time owned a thread for those 60 seconds, so `maxThreads` was effectively a limit on *connected clients*, and a few thousand mostly-idle clients could park a server that was doing no work at all. NIO breaks that coupling. A small pool of acceptor and poller threads watches all sockets; a request-processing thread from the exec pool is taken only once a complete request is readable, and returned as soon as the response is written. That is the structural reason `maxConnections` defaults to 8192 while `maxThreads` defaults to 200 — the two are now measuring different things. It is also what makes long-polling and asynchronous servlets viable: a request suspended with `AsyncContext` holds no exec thread while it waits. ## NIO versus NIO2 in practice NIO2's promise is that the JDK, and beneath it the OS, drives completion rather than Tomcat polling readiness. On paper this can reduce syscalls; in practice benchmarks over the years have not shown a consistent, meaningful win for typical HTTP workloads, and NIO is the more heavily exercised path in the wild. Choosing NIO2 is defensible if you have measured your own workload and it wins; choosing it because it sounds newer is exactly the kind of unmeasured tuning an interviewer is probing for. ## The APR story, and what replaced it APR mattered for two reasons. First, in the BIO era it offered a genuinely different, event-driven socket model. Second, it did TLS with OpenSSL, which for a long time was substantially faster than the JVM's JSSE implementation and supported features JSSE lagged on. Both advantages eroded: NIO removed the I/O gap, and the JVM's TLS implementation improved considerably. What survived is the useful half. You can still run OpenSSL-backed TLS on the NIO or NIO2 connectors by installing tomcat-native and pointing the connector's SSL implementation at OpenSSL — the C library does the crypto, while the socket handling stays pure Java. So "we need APR for OpenSSL performance" has not been a reason to use the APR connector for several releases. ## What this means when you are asked to tune The practical upshot is that the connector implementation is almost never the lever worth pulling. If someone proposes switching implementations to fix a latency or throughput problem, the questions to ask first are what the exec threads are actually doing (a thread dump usually shows them blocked on a downstream dependency, not on I/O), whether `maxThreads` and `maxConnections` are sized for the traffic shape, and whether TLS handshake cost is being paid repeatedly because connections are not being reused. Changing `protocol` moves none of those numbers. One genuinely version-sensitive point is worth stating explicitly in an interview: because the APR connector was removed in Tomcat 11, an upgrade from an older release will fail to start if `server.xml` still names `Http11AprProtocol`. That is a real migration trap, and noticing it is more valuable than reciting the implementations.

  • If a service on Tomcat 11 was upgraded from Tomcat 9, what could an APR-era server.xml break?
    Startup. The APR/native HTTP connector was removed in Tomcat 11, so a `<Connector>` still declaring `Http11AprProtocol` has no implementation to load and the connector fails to start. The fix is to move to the default NIO connector, and to reconfigure OpenSSL-backed TLS through tomcat-native on that connector if it was relied on.
  • Does changing the protocol attribute enable HTTP/2?
    No. HTTP/2 is added as an upgrade protocol nested inside a connector, alongside the I/O implementation the protocol attribute selects. The two are orthogonal: you keep the NIO connector and configure the HTTP/2 upgrade on it, rather than putting an HTTP/2 value into the protocol attribute.

saying these in an interview costs you the question

  • Thinks protocol="HTTP/1.1" means the connector cannot do HTTP/2
  • Claims APR is still the fast choice for TLS today
  • Believes NIO2 is reliably faster than NIO
  • Says NIO uses one thread per connection like the old BIO connector
  • Proposes switching implementations to fix a latency problem before profiling

context