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
- Create a new PooledDriverProvider for each suite/run instead of reusing a shut-down one
- Ensure shutdown() is only called once, in a final afterSuite hook, and not in afterScenario/afterFeature hooks
- Do not share a single provider instance across parallel runner threads; give each parallel branch its own pool
- 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
- Call shutdown() only once, in a final afterSuite/daemon-stop hook
- Never share one provider across independent runs or parallel branches
- Encapsulate provider lifecycle in a manager that recreates it after shutdown
- Log shutdown events to trace who closed the pool
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
- cannot reset while shutting down
- karate.driver can only be read within a scenario
- channel() can only be called within a scenario
- karate.setup() is not available in this context
- karate.setup() requires a feature context
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)