{"record":{"id":"cac65cf4a9f13e3b","repo":"karatelabs/karate","slug":"karate-callsingle-requires-a-suite-context","errorCode":null,"errorMessage":"karate.callSingle() requires a Suite context","messagePattern":"karate\\.callSingle\\(\\) requires a Suite context","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java","lineNumber":693,"sourceCode":"     *\n     * Flow:\n     * 1. Check in-memory cache (lock-free for fast path)\n     * 2. If callSingleCache is configured, check disk cache\n     * 3. If not cached, acquire Suite lock\n     * 4. Double-check cache (another thread may have cached while waiting)\n     * 5. Execute and cache result (in-memory and optionally to disk)\n     * 6. Return deep copy to prevent cross-thread mutation\n     *\n     * Disk caching (configure callSingleCache):\n     * - { minutes: 15 } - cache to disk for 15 minutes (default dir: karate-temp/cache)\n     * - { minutes: 15, dir: 'some/folder' } - custom cache directory\n     * - Only JSON-like results (Map/List) are persisted to disk\n     *\n     * Exceptions are cached and re-thrown on subsequent calls.\n     */\n    public Object executeCallSingle(String path, Object arg) {\n        if (featureRuntime == null || featureRuntime.getSuite() == null) {\n            throw new RuntimeException(\"karate.callSingle() requires a Suite context\");\n        }\n\n        Suite suite = featureRuntime.getSuite();\n        Map<String, Object> cache = suite.getCallSingleCache();\n        ReentrantLock lock = suite.getCallSingleLock();\n\n        // Get disk cache settings from config\n        int cacheMinutes = config.getCallSingleCacheMinutes();\n        String cacheDir = config.getCallSingleCacheDir();\n        if (cacheDir == null || cacheDir.isEmpty()) {\n            // Default to <buildDir>/karate-temp/cache (cleaned by 'karate clean')\n            cacheDir = io.karatelabs.common.FileUtils.getBuildDir() + \"/karate-temp/cache\";\n        }\n\n        // Fast path: check if already in memory cache (no locking needed)\n        if (cache.containsKey(path)) {\n            logger.trace(\"[callSingle] memory cache hit: {}\", path);\n            return unwrapCachedResult(cache.get(path));","sourceCodeStart":675,"sourceCodeEnd":711,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-core/src/main/java/io/karatelabs/core/ScenarioRuntime.java#L675-L711","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: embedded engine, no suite\nvar engine = new ScenarioRuntime(fr, scenario); // fr has no Suite\nvar v = engine.evalJs(\"karate.callSingle('data.js')\");\n\n// after\nRunner.path(\"classpath:my.feature\").parallel(1); // Suite-backed execution","handlingStrategy":"validation","validationCode":"// embedded-usage check before using callSingle\nboolean suiteReady = featureRuntime != null && featureRuntime.getSuite() != null;\nif (!suiteReady) {\n    // fall back to plain JS or feature-scoped callonce\n}","typeGuard":null,"tryCatchPattern":"try {\n    var data = karate.callSingle('classpath:data.js');\n} catch (e) {\n    if (String(e).indexOf('requires a Suite context') >= 0) {\n        karate.log('no Suite; falling back to callonce');\n        data = karate.callonce('classpath:data.js');\n    } else { throw e; }\n}","preventionTips":["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"],"tags":["callsingle","suite","lifecycle","karate-js"],"backgroundTag":"invalid-state-transition","analyzedSha":"a22eb90246d958d15a47bf436693d0121ad2812d","analyzedAt":"2026-09-12T09:01:00.220Z","contentChangedAt":"2026-09-12T09:01:00.220Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}