netty-handler is the Netty module that contains SslHandler, the class that terminates and originates TLS for a huge share of the JVM's network traffic, and that concentration of crypto duty is exactly why io.netty:netty-handler shows up in vulnerability reports more than any other Netty artifact. gRPC, Reactor Netty (and therefore Spring WebFlux), Lettuce, Cassandra drivers, Elasticsearch clients: they all speak TLS through this one module, usually three layers of transitive dependency away from your pom. This post explains what the module does, walks the two advisories worth understanding rather than just patching, and ends with the upgrade policy that keeps the findings queue quiet.
What netty-handler actually contains
Netty is deliberately modular: netty-buffer, netty-transport, netty-codec, and friends each do one layer. The netty handler maven artifact is the "useful ChannelHandlers" module, and its headline residents are:
SslHandler— TLS/SSL for any Netty pipeline, wrapping either the JDK'sSSLEngineor the OpenSSL-basednetty-tcnativeIdleStateHandlerandReadTimeoutHandler/WriteTimeoutHandler— connection liveness and timeout enforcement- Traffic shaping handlers — per-channel and global bandwidth limits
LoggingHandler,FlowControlHandler, IP filtering, and similar pipeline utilities
Declared directly, it looks like this:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>4.1.118.Final</version>
</dependency>
In practice you almost never declare it: it rides in behind whatever framework you use, and every Netty module must share one version. Version skew across io.netty:* artifacts produces genuinely weird runtime failures, so alignment via the Netty BOM (io.netty:netty-bom) is the correct mechanism when you need to override what a framework pinned:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.118.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
The advisory everyone argues about: hostname verification
CVE-2023-4586 says something that surprises most Java developers: when you build a TLS client with Netty and create the engine via SslContext.newEngine, hostname verification is not enabled by default in the 4.1.x line. The certificate's signature chain gets validated, but nothing checks that the certificate was issued for the host you think you are talking to, which is the property that stops an active man-in-the-middle holding any valid certificate.
The status of this advisory is messy in databases, it has been withdrawn and disputed in various trackers, and Netty's position is that it is documented behavior slated to change only in the next major version, but the underlying fact is simply true and has bitten real products. The fix belongs in your code (or your framework's), not in a version bump:
SSLEngine engine = sslContext.newEngine(channel.alloc(), host, port);
SSLParameters params = engine.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS");
engine.setSSLParameters(params);
Before filing this under "surely my framework handles it": mature ones do (Reactor Netty and gRPC configure endpoint identification for their clients), but bespoke Netty bootstraps, homegrown service-mesh shims, and older libraries frequently do not. If your codebase constructs SslContext for outbound connections anywhere, grep for setEndpointIdentificationAlgorithm; absence is a finding. This is a class of bug dependency scanners cannot see, the jar is "clean", the call site is not, which is why configuration review sits alongside SCA scanning in any serious pipeline, and why a tool such as Safeguard pairing dependency data with reachability context beats a bare version diff.
The advisory you just patch: the native crash
CVE-2025-24970 is the conventional kind. From 4.1.91.Final through 4.1.117.Final, a specially crafted packet arriving at SslHandler could fail validation in a way that crashes the process, when running the native, OpenSSL-based SSLEngine (netty-tcnative), rather than the JDK engine. CVSS 7.5, remotely triggerable, denial of service via native crash. The patch shipped in 4.1.118.Final on February 10, 2025.
Notes for triage:
- Exposure requires the native TLS path. Plenty of deployments use netty-tcnative deliberately, it is faster than the JDK engine, so "are we on the native engine?" is the first question, answerable by checking for
netty-tcnativeon the classpath andOpenSsl.isAvailable()in logs. - A native crash takes the whole JVM, not a request thread. For a gateway or database proxy, that is an outage lever an unauthenticated peer can pull.
- The workaround (falling back to the JDK engine) trades performance for safety; the upgrade is small and binary-compatible, so patching is almost always the cheaper move.
Why netty-handler findings deserve a fast lane
Netty's team runs a disciplined security process: advisories with clear affected ranges and single-version fixes on a stable branch that has stayed binary-compatible for a decade. That makes remediation unusually cheap, and it changes the economics of your policy. When the fix is "bump one BOM property and redeploy", batching Netty patches quarterly is a choice to hold risk you could release for pennies.
Three habits worth institutionalizing:
- Track the 4.1.x patch stream. Netty releases frequently; treat
netty-bomlike a security feed, not a framework detail. (The 4.2 line began rolling out in 2025; move when your frameworks do, not before.) - Watch the transitive spread. One repo's
mvn dependency:tree -Dincludes=io.nettyoutput rarely matches another's; gRPC and Reactor may pin different Netty patches in sibling services. Fleet-wide visibility is the point of continuous dependency scanning, a Netty advisory should produce a list of affected services in minutes, not a spreadsheet exercise. - Confirm the tcnative pairing.
netty-tcnativehas its own versioning and its own OpenSSL statically linked inside. Upgrading netty-handler while leaving a year-old tcnative is half a patch; the BOM keeps the pair aligned.
A hardening checklist for Netty TLS
Beyond CVE response, the SslHandler configuration itself deserves review:
- Build contexts with
SslContextBuilder, pin.protocols("TLSv1.3", "TLSv1.2"), and leave legacy protocol support off. - Enable endpoint identification on every client engine (see above); servers requesting client certificates should set
ClientAuth.REQUIREexplicitly rather than relying on surrounding code. - Put
IdleStateHandlerand read timeouts in the same pipeline, slowloris-style connection hoarding against a TLS endpoint is cheap to attempt and cheap to prevent, and the handlers are already in this module. - Log negotiated protocol and cipher at connection setup in staging; "we thought we were on TLS 1.3" is a finding better discovered before the pentest.
FAQ
What is netty-handler used for?
It is the Netty module containing pipeline handlers for TLS (SslHandler), idle detection and timeouts, traffic shaping, logging, and IP filtering. Any Netty-based stack doing TLS, which includes gRPC, Reactor Netty, and most modern JVM database drivers, depends on it.
Does netty-handler verify TLS hostnames by default?
Not in the 4.1.x line: engines created via SslContext.newEngine need setEndpointIdentificationAlgorithm("HTTPS") set on their SSLParameters to check that the certificate matches the host (the issue tracked as CVE-2023-4586). Major frameworks generally configure this for you; hand-rolled Netty clients must do it themselves.
What is CVE-2025-24970 and am I affected?
A crafted packet could crash SslHandler when using the native OpenSSL-based SSLEngine, affecting 4.1.91.Final through 4.1.117.Final and fixed in 4.1.118.Final (February 2025). You are exposed only if netty-tcnative provides your TLS engine; the JDK engine path is unaffected.
How do I upgrade netty-handler when it is a transitive dependency?
Import io.netty:netty-bom in dependencyManagement (or a Gradle platform) at the patched version. That aligns every io.netty module at once, which matters because mixed Netty versions cause runtime failures, and it overrides whatever your framework pinned without forking it.