karatelabs/karate · error · RuntimeException
Dynamic expression must return a list or function
Error message
Dynamic expression must return a list or function: ${expression}, got: ${result != null ? result.getClass().getName() : "null"} What it means
When a Scenario uses a dynamic expression (e.g. Examples with a 'karate.setup...' style expression), Karate evaluates it and expects the result to be a List of Maps or a JavaCallable generator function. If the evaluated result is any other type (or the expression itself is malformed), this RuntimeException is thrown listing the expression and the actual Java class of the result.
Solutions
- Make the dynamic expression return a java.util.List of Maps (or a JS array) of scenario parameters
- If using a function, ensure it is callable and returns Maps until done (null/non-Map terminates)
- Log or print the expression result type in a scratch scenario to see what is actually returned
- Wrap single-object results in a list, e.g. return [row] instead of return row
Example fix
// before
* def data = { a: 1, b: 2 }
// after
* def data = [{ a: 1, b: 2 }] Defensive patterns
Strategy: type-guard
Validate before calling
// before using as dynamic expression
var result = <expression>;
if (!Array.isArray(result)) { karate.abort('dynamic expression must return a list, got: ' + typeof result); } Type guard
function isRowList(v) { return v instanceof Array && v.every(function (x) { return x instanceof Map || typeof x === 'object'; }); } Try / catch
try { evaluate(expression) } catch (RuntimeException e) { if (e.getMessage().startsWith('Dynamic expression must return a list')) { log.error('Return a List<Map> or generator function from: ' + expression); } throw e; } Prevention
- Always return arrays (even single-element) from dynamic expressions
- Return null (not {}) from generator functions to terminate iteration
- Print the expression result once during development to confirm its type
When it happens
Trigger: A dynamic scenario expression returns a single Map instead of a list, a String, a number, or null-typed object that is neither List nor JavaCallable — e.g. 'Examples: <expression>' where the expression evaluates to one row object rather than a collection of rows.
Common situations: A helper JS function accidentally returns one object instead of an array; a Java helper method returns a Map; the expression is wrapped in quotes so it evaluates as a string; forgetting that the expression must produce multiple scenarios.
Related errors
- Failed to evaluate dynamic expression
- set() with a single argument expects a Map / JSON object
- prettyXml() argument must be XML node or string
- setTimeout: callback is not a function
- Unable to resolve global `this`
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/3514cf8dda749425.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/core/FeatureRuntime.java:750
// Evaluate the dynamic expression in this runtime's engine. This runtime never
// goes through call(), so its client is released here or not at all — one leak
// per dynamic-outline feature, every run.
String expression = templateScenario.getDynamicExpression();
Object result;
try {
result = sr.eval(expression);
} finally {
sr.releaseHttpClient();
}
if (result instanceof List) {
return (List<?>) result;
} else if (result instanceof JavaCallable) {
// Generator function - call repeatedly until null/non-map
return evaluateGeneratorFunction(result);
} else {
// Expression didn't return a list or function - error
throw new RuntimeException("Dynamic expression must return a list or function: " + expression + ", got: " + (result != null ? result.getClass().getName() : "null"));
}
} catch (Exception e) {
throw new RuntimeException("Failed to evaluate dynamic expression: " + templateScenario.getDynamicExpression(), e);
}
}
/**
* Evaluates a generator function by calling it repeatedly with incrementing index
* until it returns null or a non-Map value.
*/
@SuppressWarnings("unchecked")
private List<Map<String, Object>> evaluateGeneratorFunction(Object function) {
List<Map<String, Object>> results = new ArrayList<>();
JavaCallable callable = (JavaCallable) function;
int index = 0;
while (true) {
Object rowValue;View on GitHub (pinned to a22eb90246)