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

  1. Use karate.callonce() only within scenario/Background JS of a running feature.
  2. For suite-wide one-time calls use karate.callSingle() instead, which has Suite context.
  3. Ensure the calling feature is executed through the Karate Runner so a FeatureRuntime exists.
  4. 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

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


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)