eclipse-vertx/vert.x · error · IllegalStateException

Listen already called

Error message

Listen already called

What it means

Each NetServer instance can bind exactly once. The internal 'listening' flag is set in bind(), and any subsequent bind attempt on the same instance throws IllegalStateException. To listen again you must create a new server instance.

Source

Thrown at vertx-core/src/main/java/io/vertx/core/net/impl/tcp/NetServerImpl.java:387

      long checkIntervalForStatsInMillis = options.getCheckIntervalForStatsTimeUnit().toMillis(options.getCheckIntervalForStats());
      trafficShapingHandler.configure(options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInMillis);
      if (options.getPeakOutboundGlobalBandwidth() != 0) {
        trafficShapingHandler.setMaxGlobalWriteSize(options.getPeakOutboundGlobalBandwidth());
      }
      if (options.getMaxDelayToWait() != 0) {
        long maxDelayToWaitInMillis = options.getMaxDelayToWaitTimeUnit().toMillis(options.getMaxDelayToWait());
        trafficShapingHandler.setMaxWriteDelay(maxDelayToWaitInMillis);
      }
      promise.complete(true);
    } else {
      log.info("Not updating traffic shaping options as they have not changed");
      promise.complete(false);
    }
  }

  private synchronized Future<Channel> bind(ContextInternal context, SocketAddress localAddress) {
    if (listening) {
      throw new IllegalStateException("Listen already called");
    }

    this.listening = true;
    this.eventLoop = context.nettyEventLoop();

    SocketAddress bindAddress;
    Map<ServerID, NetServerInternal> sharedNetServers = vertx.sharedTcpServers();
    synchronized (sharedNetServers) {
      actualPort = localAddress.port();
      String hostOrPath = localAddress.isInetSocket() ? localAddress.host() : localAddress.path();
      NetServerImpl main;
      boolean shared;
      ServerID id;
      if (actualPort > 0 || localAddress.isDomainSocket()) {
        id = new ServerID(actualPort, hostOrPath);
        main = (NetServerImpl) sharedNetServers.get(id);
        shared = true;
        bindAddress = localAddress;

View on GitHub (pinned to fb308bd8c3)

Solutions

  1. Call listen() only once per NetServer; create a new instance via vertx.createNetServer() for re-listen or retry
  2. Close the existing server (server.close()) and create a fresh instance before listening again
  3. Move listen() out of retry loops and retry only the surrounding business logic

Example fix

// before
server.listen(1234);
server.listen(1234); // IllegalStateException: Listen already called
// after
server.listen(1234);
NetServer second = vertx.createNetServer();
second.connectHandler(server.connectHandler()); // or reconfigure
second.listen(1234);
Defensive patterns

Strategy: validation

Validate before calling

AtomicBoolean started = new AtomicBoolean();
if (started.compareAndSet(false, true)) {
  server.listen(port);
}

Try / catch

try {
  server.listen(port);
} catch (IllegalStateException e) {
  if (!e.getMessage().contains("Listen already called")) throw e;
  // ignore: already listening
}

Prevention

When it happens

Trigger: Calling listen() twice on the same NetServer; retrying listen after a failure without recreating the server; code paths that can invoke listen more than once (e.g. restart logic).

Common situations: Application restart/redeploy logic reusing the server object; retry wrappers around listen(); tests that call listen in setup and per test method on a shared instance.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of eclipse-vertx/vert.x@fb308bd8c3 (2026-09-06). Data as JSON: /api/errors/c96ed9b993549d9b. Report an issue: GitHub.