quarkusio/quarkus · error · IllegalArgumentException

ExecutorService must not be null

Error message

ExecutorService must not be null

What it means

Thrown by QuarkusRestClientBuilder.executorService when a null ExecutorService is passed. The builder wraps the executor with ContextualExecutors so async calls run with proper context propagation, and a null executor has no valid meaning.

Source

Thrown at extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusRestClientBuilder.java:246

    }

    @Override
    public RestClientBuilder keyStore(KeyStore keyStore, String keystorePassword) {
        this.keyStore = keyStore;
        this.keystorePassword = keystorePassword;
        return this;
    }

    @Override
    public RestClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) {
        this.hostnameVerifier = hostnameVerifier;
        return this;
    }

    @Override
    public RestClientBuilder executorService(ExecutorService executor) {
        if (executor == null) {
            throw new IllegalArgumentException("ExecutorService must not be null");
        }
        executorService = ContextualExecutors.wrap(executor);
        return this;
    }

    public <T> T build(Class<T> aClass, ClientHttpEngine httpEngine)
            throws IllegalStateException, RestClientDefinitionException {

        RestClientListeners.get().forEach(listener -> listener.onNewClient(aClass, this));

        // Interface validity
        verifyInterface(aClass);

        if (baseURI == null) {
            throw new IllegalStateException("Neither baseUri nor baseUrl was specified");
        }

        // Provider annotations

View on GitHub (pinned to e1c734241f)

Solutions

  1. Pass a non-null ExecutorService, e.g. a ManagedExecutor or Executors.newFixedThreadPool(...)
  2. Use Quarkus's ManagedExecutor injection (io.quarkus.threadpool.ThreadPoolConfig managed executor) instead of building one by hand
  3. Omit the executorService call entirely and let the builder use its default executor

Example fix

// before
ExecutorService exec = lookupExecutor(); // may return null
builder.executorService(exec); // IllegalArgumentException
// after
ExecutorService exec = lookupExecutor();
if (exec != null) {
    builder.executorService(exec);
}
Defensive patterns

Strategy: validation

Validate before calling

Objects.requireNonNull(executor, "ExecutorService must not be null");

Try / catch

try {
    builder.executorService(exec);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("ExecutorService must not be null")) {
        builder.executorService(Executors.newFixedThreadPool(4));
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling builder.executorService(null); passing an executor field/lookup result that resolved to null (e.g. container-managed executor not available, failed JNDI/CDI lookup).

Common situations: Programmatic client building in tests where an executor mock wasn't wired; relying on a ManagedExecutorService that isn't injected; refactor leaving the executor uninitialized.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/b5b9fb51b23b5f67. Report an issue: GitHub.