TooTallNate/Java-WebSocket · error · IllegalStateException

can only be started once.

Error message

 can only be started once.

What it means

WebSocketServer.start() throws IllegalStateException if selectorthread is already set, i.e. the server instance was already started (or run() already executed). A server instance is single-use: once its selector thread exists it cannot be started again; create a new instance to restart.

Solutions

  1. Guard with a started flag or selectorthread check before calling start(), or catch IllegalStateException and treat it as already-running.
  2. To restart, stop() the server, create a NEW WebSocketServer instance, then call start() on it.
  3. In tests, build the server fresh in @BeforeEach instead of a shared @BeforeAll field.

Example fix

// before
server.start(); // second call after earlier start -> IllegalStateException
server.start();
// after
if (server.selectorthread == null) { // or track your own started flag
  server.start();
}
// or for restarts:
server.stop();
server = new MyServer(address);
server.start();
Defensive patterns

Strategy: try-catch

Validate before calling

if (server.selectorthread != null) {
  // already started — skip or restart with a new instance
}

Type guard

static boolean notYetStarted(WebSocketServer s) { return s.selectorthread == null; }

Try / catch

try {
  server.start();
} catch (IllegalStateException e) {
  log.info("Server already started, ignoring duplicate start");
}

Prevention

When it happens

Trigger: Calling start() twice on the same WebSocketServer instance; calling start() after run() was invoked directly; test harnesses that start the server in setup and then start it again per test; restart logic that reuses the old object instead of constructing a fresh one.

Common situations: Application restart logic (config reload, watchdog) re-calling start(); Spring/CDI re-initialization invoking a @PostConstruct twice; JUnit tests sharing a server field across tests.

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 TooTallNate/Java-WebSocket@afeacbf8c0 (2026-09-09). Data as JSON: /api/errors/0ca2376291865262. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/org/java_websocket/server/WebSocketServer.java:270

    for (int i = 0; i < decodercount; i++) {
      WebSocketWorker ex = new WebSocketWorker();
      decoders.add(ex);
    }
  }


  /**
   * Starts the server selectorthread that binds to the currently set port number and listeners for
   * WebSocket connection requests. Creates a fixed thread pool with the size {@link
   * WebSocketServer#AVAILABLE_PROCESSORS}<br> May only be called once.
   * <p>
   * Alternatively you can call {@link WebSocketServer#run()} directly.
   *
   * @throws IllegalStateException Starting an instance again
   */
  public void start() {
    if (selectorthread != null) {
      throw new IllegalStateException(getClass().getName() + " can only be started once.");
    }
    Thread t = new Thread(this);
    t.setDaemon(isDaemon());
    t.start();
  }

  public void stop(int timeout) throws InterruptedException {
    stop(timeout, "");
  }

  /**
   * Closes all connected clients sockets, then closes the underlying ServerSocketChannel,
   * effectively killing the server socket selectorthread, freeing the port the server was bound to
   * and stops all internal workerthreads.
   * <p>
   * If this method is called before the server is started it will never start.
   *
   * @param timeout Specifies how many milliseconds the overall close handshaking may take

View on GitHub (pinned to afeacbf8c0)