karatelabs/karate · error · IllegalStateException

Provider has been shut down

Error message

Provider has been shut down

What it means

PooledDriverProvider refuses to hand out drivers once the provider has been shut down. After shutdown() is called the pool is permanently closed; any later acquire() call throws this IllegalStateException instead of silently creating or reusing a driver. It guards against using a torn-down pool in a new lifecycle phase.

Solutions

  1. Create a new PooledDriverProvider for each suite/run instead of reusing a shut-down one
  2. Ensure shutdown() is only called once, in a final afterSuite hook, and not in afterScenario/afterFeature hooks
  3. Do not share a single provider instance across parallel runner threads; give each parallel branch its own pool
  4. If embedding Karate, re-initialize the provider (or restart the Runner) after any shutdown

Example fix

// before
PooledDriverProvider provider = new PooledDriverProvider();
runSuite(provider);
provider.shutdown();
runSecondSuite(provider); // IllegalStateException
// after
PooledDriverProvider provider = new PooledDriverProvider();
runSuite(provider);
provider.shutdown();
PooledDriverProvider provider2 = new PooledDriverProvider(); // fresh pool
runSecondSuite(provider2);
Defensive patterns

Strategy: validation

Validate before calling

// before acquiring
if (provider.isShutdown()) { // expose or track shutdown state yourself
    provider = new PooledDriverProvider();
}
Driver driver = provider.acquire(runtime, config);

Type guard

boolean poolUsable(PooledDriverProvider p) { return p != null && !p.isShutdown(); }

Try / catch

try { Driver d = provider.acquire(runtime, config); }
catch (IllegalStateException e) {
    if (e.getMessage().contains("shut down")) { provider = new PooledDriverProvider(); provider.acquire(runtime, config); }
    else throw e;
}

Prevention

When it happens

Trigger: Calling Driver acquire(ScenarioRuntime, Map) after PooledDriverProvider.shutdown() has run — typically re-using a karate configuration/provider object across two suites, or calling acquire manually after the runner closed the pool.

Common situations: Reusing a single karate Configuration or custom provider across parallel runners where one finishes and shuts down the shared pool; embedding Karate in a long-lived application that shuts down the pool then attempts another scenario; test frameworks re-invoking karate.output methods after teardown.

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 karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/e1ff24ca3aaeb3ec. Report an issue: GitHub.

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/driver/PooledDriverProvider.java:102

    /**
     * Create a pooled driver provider with explicit pool size.
     * Use this when you need to override the auto-detected size.
     *
     * @param poolSize maximum number of drivers to create
     */
    public PooledDriverProvider(int poolSize) {
        if (poolSize < 1) {
            throw new IllegalArgumentException("Pool size must be at least 1");
        }
        this.poolSize = poolSize;
        this.availableDrivers = new ArrayBlockingQueue<>(poolSize);
    }

    @Override
    public Driver acquire(ScenarioRuntime runtime, Map<String, Object> config) {
        if (shutdown) {
            throw new IllegalStateException("Provider has been shut down");
        }

        // Initialize pool lazily with auto-detected size
        ensurePoolInitialized(runtime);

        // Check if this scenario already has a driver assigned (shouldn't happen normally)
        Driver existing = assignedDrivers.get(runtime);
        if (existing != null && !existing.isTerminated()) {
            logger.debug("Returning existing driver for scenario: {}", runtime.getScenario().getName());
            return existing;
        }

        // Try to get a driver from the pool
        Driver driver = takeHealthyFromPool(runtime.getScenario().getName());

        if (driver == null) {
            // Need to create a new driver or wait for one
            synchronized (initLock) {

View on GitHub (pinned to a22eb90246)