grpc/grpc-java · error · RuntimeException

ChannelOption(${optionName}) is not available

Error message

ChannelOption(${optionName}) is not available

What it means

Utils.getEpollChannelOption reflectively reads a static ChannelOption field (e.g. EpollChannelOption.TCP_USER_TIMEOUT) from io.netty.channel.epoll.EpollChannelOption. If the class exists but the field lookup or access fails (missing in the Netty version, or access error), the exception is wrapped and rethrown with this message. It signals an incompatibility between the requested epoll channel option and the Netty version on the classpath.

Source

Thrown at netty/src/main/java/io/grpc/netty/Utils.java:482

   * Returns TCP_USER_TIMEOUT channel option for Epoll channel if Epoll is available, otherwise
   * null.
   */
  @Nullable
  static ChannelOption<Integer> maybeGetTcpUserTimeoutOption() {
    return getEpollChannelOption("TCP_USER_TIMEOUT");
  }

  @Nullable
  @SuppressWarnings("unchecked")
  private static <T> ChannelOption<T> getEpollChannelOption(String optionName) {
    if (isEpollAvailable()) {
      try {
        return
            (ChannelOption<T>) Class.forName("io.netty.channel.epoll.EpollChannelOption")
                .getField(optionName)
                .get(null);
      } catch (Exception e) {
        throw new RuntimeException("ChannelOption(" + optionName + ") is not available", e);
      }
    }
    return null;
  }

  private static final class DefaultEventLoopGroupResource implements Resource<EventLoopGroup> {
    private final String name;
    private final int numEventLoops;
    private final EventLoopGroupType eventLoopGroupType;

    DefaultEventLoopGroupResource(
        int numEventLoops, String name, EventLoopGroupType eventLoopGroupType) {
      this.name = name;
      // See the implementation of MultithreadEventLoopGroup.  DEFAULT_EVENT_LOOP_THREADS there
      // defaults to NettyRuntime.availableProcessors() * 2.  We don't think we need that many
      // threads.  The overhead of a thread includes file descriptors and at least one chunk
      // allocation from PooledByteBufAllocator.  Here we reduce the default number of threads by
      // half.

View on GitHub (pinned to 64daddc1f3)

Solutions

  1. Upgrade Netty (netty-transport-native-epoll) to a version that defines the requested EpollChannelOption field (e.g. TCP_USER_TIMEOUT, Netty 4.1.x recent)
  2. Align grpc-netty's transitive Netty version explicitly with your Netty dependencies to avoid shading/mixing versions
  3. Avoid enabling the option that requires the lookup (e.g. don't set tcp user timeout) if your Netty version lacks it
  4. Use NIO channel types so the epoll option lookup path is skipped

Example fix

// before (pom.xml)
<dependency><groupId>io.netty</groupId><artifactId>netty-transport-native-epoll</artifactId><version>4.1.30.Final</version></dependency>
// after
<dependency><groupId>io.netty</groupId><artifactId>netty-transport-native-epoll</artifactId><version>4.1.100.Final</version><classifier>linux-x86_64</classifier></dependency>
Defensive patterns

Strategy: validation

Validate before calling

boolean tcpUserTimeoutSupported;
try {
  Class.forName("io.netty.channel.epoll.EpollChannelOption").getField("TCP_USER_TIMEOUT");
  tcpUserTimeoutSupported = true;
} catch (ReflectiveOperationException e) { tcpUserTimeoutSupported = false; }

Try / catch

try {
  channel = builder.build();
} catch (RuntimeException e) {
  if (e.getMessage() != null && e.getMessage().startsWith("ChannelOption(")) {
    // downgrade: drop the option / use NIO and retry
    channel = builder.channelType(NioSocketChannel.class).build();
  } else { throw e; }
}

Prevention

When it happens

Trigger: grpc-netty calls maybeGetTcpUserTimeoutOption (e.g. when enableCheckEffectiveTcpUserTimeout / user timeout config is used) on a Netty version where EpollChannelOption lacks the requested field, or field access is blocked; only happens on epoll path on Linux.

Common situations: Netty version too old to define TCP_USER_TIMEOUT in EpollChannelOption; shaded/relocated Netty classes so the reflection target isn't the expected class; security manager or module restrictions blocking getField().get(null).

Related errors


AI-assisted analysis of grpc/grpc-java@64daddc1f3 (2026-09-08). Data as JSON: /api/errors/4fb4673794331ccc. Report an issue: GitHub.