karatelabs/karate · error · RuntimeException
karate.callonce() requires a feature context
Error message
karate.callonce() requires a feature context
What it means
executeJsCallOnce implements karate.callonce(), running a feature once per FeatureRuntime with a shared cache (double-check locking for parallel safety). It requires a feature context because the cache and the child call are feature-scoped; if featureRuntime is null this RuntimeException is thrown.
Solutions
- Use karate.callonce() only within scenario/Background JS of a running feature.
- For suite-wide one-time calls use karate.callSingle() instead, which has Suite context.
- Ensure the calling feature is executed through the Karate Runner so a FeatureRuntime exists.
- If shared data is needed before features run, compute it in callSingle JS and pass it as a system property/config variable.
Example fix
// before: inside callSingle JS
var token = karate.callonce('classpath:auth.feature').authToken;
// after: inside the feature
* def token = karate.callonce('classpath:auth.feature').authToken Defensive patterns
Strategy: type-guard
Type guard
function callOnceSafe(path, arg) {
try { return karate.callonce(path, arg); }
catch (e) {
if (String(e).indexOf('requires a feature context') >= 0) return null;
throw e;
}
} Try / catch
try {
var token = karate.callonce('classpath:auth.feature').authToken;
} catch (e) {
if (String(e).indexOf('requires a feature context') >= 0) {
throw new Error('karate.callonce used outside a feature — move it into a scenario');
}
throw e;
} Prevention
- Use callonce only in scenario/Background JS
- Use karate.callSingle() when suite-level caching is needed
- Don't nest callonce inside callSingle utilities
- Ensure features are launched via Runner so FeatureRuntime exists
When it happens
Trigger: Calling karate.callonce('path.feature', arg) from JS outside any feature execution — e.g. callSingle-cached code, karate-config.js, or standalone JS evaluation without a feature run.
Common situations: Nesting callonce inside callSingle utilities; using callonce from a karate-config.js bootstrap script; running Karate JS via embedded engine without Runner.
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.setupOnce() 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
- karate.call() requires a feature context
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/3f6d141da7c28641.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java:634
if (arg instanceof Map) {
callArg = (Map<String, Object>) arg;
} else {
throw new RuntimeException("karate.call() arg must be a map or list, got: " + arg.getClass());
}
}
Map<String, Object> resultVars = executor.callFeatureSingle(calledFeature, callArg, tagSelector, lineFilters);
return resultVars != null ? resultVars : new HashMap<>();
}
/**
* Execute karate.callonce() - runs a feature once per FeatureRuntime and caches the result.
* Uses the same cache as the callonce keyword.
* Uses double-check locking to ensure thread-safe execution in parallel scenarios.
*/
public Object executeJsCallOnce(String path, Object arg) {
if (featureRuntime == null) {
throw new RuntimeException("karate.callonce() requires a feature context");
}
// Use the same cache key format as the keyword: "callonce:call read('path')"
String cacheKey = "callonce:call read('" + path + "')";
// Use feature-level cache (not suite-level) - callOnce is scoped per feature
Map<String, Object> cache = featureRuntime.getCallOnceCache();
java.util.concurrent.locks.ReentrantLock lock = featureRuntime.getCallOnceLock();
// Fast path - check cache without lock
Object cached = cache.get(cacheKey);
if (cached != null) {
// Deep copy to prevent cross-scenario mutation
return StepUtils.deepCopy(cached);
}
// Slow path - acquire lock for execution
lock.lock();View on GitHub (pinned to a22eb90246)