Edit this Page

Native Transport Reference

Quarkus supports Netty’s native transports to improve I/O performance on specific platforms. This guide explains how to configure and use native transports in your Quarkus application.

1. What are native transports?

By default, Quarkus uses Java NIO for network I/O, which works across all platforms but relies on the JVM’s abstraction layer. Netty provides native transports that bypass this abstraction and directly use platform-specific system calls:

  • epoll — Linux-specific transport using the epoll API

  • kqueue — macOS/BSD transport using the kqueue API

  • io_uring — Linux-specific transport using the io_uring API

Native transports can provide:

  • Lower latency for I/O operations

  • Reduced CPU overhead

  • Better scalability under high connection counts

  • Access to platform-specific socket options

The performance benefit varies by workload. Applications with high I/O volume or many concurrent connections typically see the most improvement.

2. Supported transports

Transport Platform Dependency Classifiers Native executable

epoll

Linux

io.netty:netty-transport-native-epoll

linux-x86_64, linux-aarch_64

Supported

kqueue

macOS, BSD

io.netty:netty-transport-native-kqueue

osx-x86_64, osx-aarch_64

Not yet supported

io_uring

Linux 5.1+

io.netty:netty-transport-native-io_uring

linux-x86_64, linux-aarch_64

Supported

3. Quick start

To use native transports, add the appropriate dependencies and enable native transport in your configuration.

3.1. Add dependencies

Add the native transport dependencies matching your deployment platform. You can include multiple transports to support different platforms from the same build.

pom.xml
<!-- Linux epoll (x86) -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-epoll</artifactId>
    <classifier>linux-x86_64</classifier>
</dependency>

<!-- Linux epoll (ARM) -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-epoll</artifactId>
    <classifier>linux-aarch_64</classifier>
</dependency>

<!-- Linux io_uring (x86) -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-io_uring</artifactId>
    <classifier>linux-x86_64</classifier>
</dependency>

<!-- Linux io_uring (ARM) -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-io_uring</artifactId>
    <classifier>linux-aarch_64</classifier>
</dependency>

<!-- OSX KQueue (x86) -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-kqueue</artifactId>
    <classifier>osx-x86_64</classifier>
</dependency>

<!-- OSX KQueue (Apple Silicon) -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-kqueue</artifactId>
    <classifier>osx-aarch_64</classifier>
</dependency>
build.gradle
// Linux epoll
implementation("io.netty:netty-transport-native-epoll::linux-x86_64")
implementation("io.netty:netty-transport-native-epoll::linux-aarch_64")

// Linux io_uring
implementation("io.netty:netty-transport-native-io_uring::linux-x86_64")
implementation("io.netty:netty-transport-native-io_uring::linux-aarch_64")

// macOS kqueue
implementation("io.netty:netty-transport-native-kqueue::osx-x86_64")
implementation("io.netty:netty-transport-native-kqueue::osx-aarch_64")
The version is managed by the Quarkus BOM, so you do not need to specify it explicitly.

3.2. Enable native transport

Add the following to your application.properties:

quarkus.vertx.native-transport=if-available

Or in application.yml:

quarkus:
    vertx:
        native-transport: if-available

3.3. Verify native transport is active

When native transport is successfully loaded, you will see a log message at startup:

[io.qua.ver.cor.run.VertxCoreRecorder] (main) Vertx has Native Transport Enabled: true

If native transport dependencies are missing, you will see:

[io.qua.ver.cor.run.VertxCoreProcessor] (build) No native transport detected. Consider adding io.netty:netty-transport-native-epoll or io.netty:netty-transport-native-kqueue dependency.

4. Configuration reference

Quarkus provides two configuration properties to control native transport behavior. Both are build-time properties (fixed at build time), meaning they are resolved during the Quarkus augmentation phase. They can be set in application.properties and overridden using Quarkus profiles (e.g., %dev, %prod), but cannot be changed at runtime via environment variables.

Property Default Description

quarkus.vertx.native-transport

disabled

Controls whether and how strictly native transport should be used. Accepted values: disabled (do not use native transport), if-available (use native transport if available, fall back to Java NIO otherwise), required (require native transport, the build fails if the dependency is missing, the application fails to start if the native library cannot load).

quarkus.vertx.native-transport-type

auto

Select a specific transport: auto (let Vert.x choose), epoll, kqueue, or io-uring. Setting this to a value other than auto implicitly enables native transport (treated as at least if-available).

4.1. Property relationships

  • Setting native-transport=disabled disables native transport entirely.

  • Setting native-transport-type to a specific value (e.g., epoll) implicitly enables native transport, even if native-transport is disabled.

  • When native-transport=required, missing dependencies cause a build failure, and a transport that cannot load at runtime causes a startup failure.

4.2. Example configurations

Use any available native transport
quarkus.vertx.native-transport=if-available
Require epoll on Linux (fail if unavailable)
quarkus.vertx.native-transport=required
quarkus.vertx.native-transport-type=epoll
Prefer io_uring but fall back to NIO if unavailable
quarkus.vertx.native-transport=if-available
quarkus.vertx.native-transport-type=io-uring

4.3. Profile-based configuration

Since native transport properties are fixed at build time, you can use Quarkus profiles to configure different transports per environment. This is useful when developing on macOS and deploying on Linux:

# Dev mode: no native transport needed (works on macOS or Linux)
%dev.quarkus.vertx.native-transport=disabled

# Production: require io_uring on Linux
%prod.quarkus.vertx.native-transport=required
%prod.quarkus.vertx.native-transport-type=io-uring

The %dev profile applies during quarkus dev, while %prod applies when building production artifacts. This lets you develop locally without native transport dependencies and enforce them in production.

5. Build-time and runtime checks

Quarkus performs two-stage validation of native transport configuration:

5.1. Build-time validation

During the build, Quarkus scans the classpath for native transport dependencies and validates them against the configuration:

  • If native-transport-type is set to a specific transport (e.g., epoll) but the corresponding dependency is missing:

    • When native-transport=required: the build fails with a ConfigurationException.

    • When native-transport=if-available: the build logs a warning and continues. The application will fall back to Java NIO at runtime.

  • If native-transport=if-available but no native transport dependency is found, the build logs a warning.

5.2. Runtime behavior

Even when a transport dependency is on the classpath, the native library may fail to load at runtime (e.g., running on a different platform, or the kernel does not support io_uring). Quarkus checks the actual transport status at startup:

5.2.1. When native-transport=if-available

If the transport fails to load, Quarkus logs a warning and falls back to Java NIO.

5.2.2. When native-transport=required

If the transport fails to load, the application fails to start with an IllegalStateException.

5.2.3. Transport type mismatch

If you request a specific transport type (e.g., epoll) but a different transport loaded (e.g., io_uring), the application logs a warning or fails depending on the native-transport setting.

6. Platform-specific socket options

Native transports provide access to platform-specific socket options not available with Java NIO.

6.1. Linux (epoll and io_uring)

6.1.1. SO_REUSEPORT

Allows multiple sockets to bind to the same address and port. The kernel distributes incoming connections across the sockets, improving scalability.

quarkus.http.so-reuse-port=true

6.1.2. TCP_QUICKACK

Disables delayed ACKs, reducing latency for request-response protocols.

quarkus.http.tcp-quick-ack=true

6.1.3. TCP_CORK

Buffers small packets until the buffer is full or the cork is removed, reducing the number of packets sent.

quarkus.http.tcp-cork=true

6.1.4. TCP_FASTOPEN

Enables TCP Fast Open, allowing data to be sent in the SYN packet during connection establishment.

quarkus.http.tcp-fast-open=true

6.1.5. TCP_USER_TIMEOUT

Sets the maximum time (in milliseconds) that transmitted data may remain unacknowledged before the connection is forcibly closed.

quarkus.http.tcp-user-timeout=30000

6.2. OSX (kqueue)

6.2.1. SO_REUSEPORT

Same functionality as Linux (see above).

quarkus.http.so-reuse-port=true

7. epoll vs io_uring

Both epoll and io_uring are available on Linux. Which should you use?

7.1. Use epoll if

  • You need a stable, battle-tested transport

  • Your application primarily handles network I/O

  • You are running on an older kernel (pre-5.1)

  • You need the widest compatibility

7.2. Use io_uring if

  • You have a mixed workload with file and network I/O

  • You are running a modern kernel (5.6+)

  • You want to experiment with cutting-edge I/O performance

  • You can tolerate experimental features

This article, from Nicolas J. Altmann, gives a gentle introduction to the differences between epoll and io_uring.

7.3. Default behavior

When both epoll and io_uring are available on the classpath, Quarkus prefers epoll in auto mode. This default prioritizes stability over experimental features.

To explicitly select io_uring, set:

quarkus.vertx.native-transport-type=io-uring

8. io_uring details

io_uring is a modern Linux I/O interface that provides unified semantics for file and network I/O.

8.1. Kernel requirements

  • Minimum: Linux kernel 5.1

  • Recommended: Linux kernel 5.6 or later

Earlier kernels either do not support io_uring or have significant bugs and performance issues.

8.2. Check your kernel version

uname -r

Example output:

6.5.0-28-generic

To verify io_uring support:

grep io_uring_setup /proc/kallsyms

If the command returns output, io_uring is available.

8.3. Limitations

  • Domain sockets are not yet supported with io_uring

  • The Vert.x asynchronous file system API does not yet use io_uring

9. Native executable support

Native transports can be used in Quarkus native executables on Linux. epoll and io_uring are supported in native mode. kqueue is not yet supported in native executables.

When building a native executable with native transport dependencies on the classpath, the transport is available at runtime just as in JVM mode. No additional configuration is needed beyond the standard setup described in this guide.

10. Troubleshooting

10.1. Wrong classifier

If you see a warning about native transport being unavailable, verify that your dependency uses the correct classifier for your platform.

Common classifiers:

  • Linux x86_64: linux-x86_64

  • Linux aarch64: linux-aarch_64

  • macOS x86_64: osx-x86_64

  • macOS aarch64: osx-aarch_64

Example error:

WARN [io.qua.ver.cor.run.VertxCoreRecorder] (main) Native transport was requested but is not available.

Solution: Add the dependency with the correct classifier:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-transport-native-epoll</artifactId>
    <classifier>linux-x86_64</classifier>
</dependency>

10.2. Missing dependencies

If native transport is not detected at build time, ensure you have added the dependency to your pom.xml or build.gradle.

Enable debug logging to see more details:

quarkus.log.category."io.quarkus.vertx.core.runtime".level=DEBUG

10.3. Kernel too old for io_uring

If you request io_uring on a kernel older than 5.1, the transport will fail to load.

Check your kernel version:

uname -r

If your kernel is too old, either:

  1. Upgrade your kernel to 5.6 or later

  2. Use epoll instead

  3. Set native-transport=if-available to fall back to NIO

Related content