karatelabs/karate · error · JsErrorException
Maximum call stack size exceeded
Error message
Maximum call stack size exceeded
What it means
During synchronous function execution, a Java StackOverflowError escaping executeBody is translated into this JS RangeError so scripts get a normal, catchable JS error instead of crashing the host. It fires when JS recursion (direct or mutual) exceeds the thread stack.
Solutions
- Add or fix the recursion base case so it terminates.
- Convert recursion to iteration (explicit stack/loop) for deep data.
- Catch the RangeError at the boundary and fail gracefully with a diagnostic.
- Increase the host thread stack size (-Xss) only as a last resort.
Example fix
// before
function sum(arr, i) { return arr[i] + sum(arr, i + 1); } // no base case
// after
function sum(arr, i) { return i >= arr.length ? 0 : arr[i] + sum(arr, i + 1); } Defensive patterns
Strategy: try-catch
Validate before calling
let depth = 0; function checkDepth() { if (++depth > 5000) throw new Error('recursion too deep'); } Try / catch
try { return fn(); } catch (e) { if (e instanceof RangeError && /Maximum call stack/.test(e.message)) return { error: 'stack-overflow' }; throw e; } Prevention
- Guarantee every recursive function has a reachable base case.
- Cap recursion depth explicitly with a depth counter.
- Use iterative traversals (explicit stack) for unbounded-depth data.
- Bound input nesting depth when accepting external JSON payloads.
When it happens
Trigger: Unbounded recursion like function f(){ return f(); } f(), deep recursion on large nested data without a base case, mutually recursive functions with no termination, extremely deep data structures walked recursively.
Common situations: Recursive JSON/tree walkers over deeply nested API payloads, recursive template or formula evaluators, accidental infinite recursion after a refactor removed a base case.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- too much recursion
- array index too large for dense storage:
- Invalid array length
- Invalid array length
- Cannot convert non-finite number to BigInt
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/ae6e3c5fde462a88.
Report an issue: GitHub.
Appendix: source
Thrown at karate-js/src/main/java/io/karatelabs/js/JsFunctionNode.java:302
Object bindArgsAndExecute(CoreContext functionContext, CoreContext parentContext, Object[] args) {
functionContext.privateEnv = privateEnv;
if (generator) {
// A generator call runs no body code — it returns the generator
// object; the body executes on the generator's vthread one driver
// step at a time. Parameter binding is deferred to the first
// next(), the same documented deviation async has.
Engine engine = functionContext.getEngine();
return new JsGenerator(engine, this, functionContext, args);
}
if (async) {
// Argument binding is part of the activation's startup, so it runs on
// the activation thread under the startup-outcome protocol — not here.
return AsyncSupport.callAsync(this, functionContext, args);
}
try {
return executeBody(functionContext, parentContext, args);
} catch (StackOverflowError e) {
throw JsErrorException.rangeError("Maximum call stack size exceeded");
}
}
/** The synchronous body run. For an async function this is what the
* activation thread executes; the caller has already been handed a promise. */
Object executeBody(CoreContext functionContext, CoreContext parentContext, Object[] args) {
// Attach the slot frame here — the single choke point every call path
// shares before params bind and defaults evaluate. For an async function
// this runs on the activation thread, so the frame exists before the
// body's first statement there too; the frame lives on the context and
// follows it across await suspensions.
SlotTable table = slotTable;
if (table == null && SlotTable.ENABLED && ++callCount == 2) {
table = SlotTable.forNodeForced(node, argNodes, body);
slotTable = table;
}
if (table != null) {
functionContext.frame = table.newFrame();View on GitHub (pinned to a22eb90246)