karatelabs/karate · error · RuntimeException
karate.callSingle() requires a Suite context
Error message
karate.callSingle() requires a Suite context
What it means
executeCallSingle implements karate.callSingle(), which caches a called feature/JS result at Suite level (with optional disk persistence of JSON-like results). It needs both a FeatureRuntime and a Suite; when either is missing this RuntimeException is thrown. callSingle is inherently a suite-scope API.
Solutions
- Run features through Runner.path(...) / the JUnit runner so a Suite is created before callSingle is used.
- In embedded usage, build and pass a Suite to the FeatureRuntime you construct.
- Replace callSingle with feature-scoped karate.callonce() when only per-feature caching is needed.
- For pure JS helpers, evaluate them directly instead of routing through callSingle.
Example fix
// before: embedded engine, no suite
var engine = new ScenarioRuntime(fr, scenario); // fr has no Suite
var v = engine.evalJs("karate.callSingle('data.js')");
// after
Runner.path("classpath:my.feature").parallel(1); // Suite-backed execution Defensive patterns
Strategy: validation
Validate before calling
// embedded-usage check before using callSingle
boolean suiteReady = featureRuntime != null && featureRuntime.getSuite() != null;
if (!suiteReady) {
// fall back to plain JS or feature-scoped callonce
} Try / catch
try {
var data = karate.callSingle('classpath:data.js');
} catch (e) {
if (String(e).indexOf('requires a Suite context') >= 0) {
karate.log('no Suite; falling back to callonce');
data = karate.callonce('classpath:data.js');
} else { throw e; }
} Prevention
- Always run features via Runner/JUnit runner so a Suite exists
- In embedded setups, construct and attach a Suite to your FeatureRuntime
- Don't construct ScenarioRuntime manually in tests expecting callSingle to work
- Prefer feature-scoped karate.callonce() when suite caching isn't needed
When it happens
Trigger: Calling karate.callSingle() from a ScenarioRuntime whose FeatureRuntime has no Suite (e.g. a feature invoked programmatically outside a Suite run), or from JS evaluated with no feature runtime attached at all.
Common situations: Embedding the Karate engine in an application and invoking features without Runner's Suite; unit tests constructing ScenarioRuntime directly; parallel-runner setups where a custom harness drops the Suite reference.
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
- karate.call() requires a feature context
- karate.callonce() requires a feature context
- karate.driver can only be read 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/cac65cf4a9f13e3b.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java:693
*
* Flow:
* 1. Check in-memory cache (lock-free for fast path)
* 2. If callSingleCache is configured, check disk cache
* 3. If not cached, acquire Suite lock
* 4. Double-check cache (another thread may have cached while waiting)
* 5. Execute and cache result (in-memory and optionally to disk)
* 6. Return deep copy to prevent cross-thread mutation
*
* Disk caching (configure callSingleCache):
* - { minutes: 15 } - cache to disk for 15 minutes (default dir: karate-temp/cache)
* - { minutes: 15, dir: 'some/folder' } - custom cache directory
* - Only JSON-like results (Map/List) are persisted to disk
*
* Exceptions are cached and re-thrown on subsequent calls.
*/
public Object executeCallSingle(String path, Object arg) {
if (featureRuntime == null || featureRuntime.getSuite() == null) {
throw new RuntimeException("karate.callSingle() requires a Suite context");
}
Suite suite = featureRuntime.getSuite();
Map<String, Object> cache = suite.getCallSingleCache();
ReentrantLock lock = suite.getCallSingleLock();
// Get disk cache settings from config
int cacheMinutes = config.getCallSingleCacheMinutes();
String cacheDir = config.getCallSingleCacheDir();
if (cacheDir == null || cacheDir.isEmpty()) {
// Default to <buildDir>/karate-temp/cache (cleaned by 'karate clean')
cacheDir = io.karatelabs.common.FileUtils.getBuildDir() + "/karate-temp/cache";
}
// Fast path: check if already in memory cache (no locking needed)
if (cache.containsKey(path)) {
logger.trace("[callSingle] memory cache hit: {}", path);
return unwrapCachedResult(cache.get(path));View on GitHub (pinned to a22eb90246)