karatelabs/karate · error · RuntimeException
th:each requires `name : expression` form
Error message
th:each requires `name : expression` form (got `${av}`). What it means
The `th:each` iteration processor in karate-markup requires the attribute value to be in the `name : expression` form, splitting on the first colon to get the iteration variable name. If the value contains no colon at all, the processor cannot determine the loop variable and throws this RuntimeException.
Solutions
- Rewrite the attribute as `th:each="item : ${collection}"` with a variable name, a colon, and the iterable expression.
- Check for a missing colon or a full-width/odd character used in place of `:`.
- If iterating a map, use the documented entry form, e.g. `th:each="entry : ${map}"`.
- Validate templates at build/test time by rendering each one in a unit test to catch the typo before runtime.
Example fix
// before
<li th:each="${items}">...</li>
// after
<li th:each="item : ${items}">...</li> Defensive patterns
Strategy: validation
Validate before calling
String av = tag.getAttributeValue("th:each");
if (av == null || !av.contains(":")) {
throw new IllegalArgumentException("th:each needs `name : expression`, got: " + av);
} Try / catch
try {
render(template);
} catch (RuntimeException e) {
if (e.getMessage() != null && e.getMessage().contains("th:each requires")) {
log.error("Malformed th:each: {}", e.getMessage());
}
throw e;
} Prevention
- Always write th:each as `item : ${collection}`
- Check the colon survived editing — it is required
- Render all templates in CI to catch syntax slips
- Migrating from other engines? Convert loop syntax to th:each form
When it happens
Trigger: Any template with `th:each` whose value lacks a colon, e.g. `th:each="items"` instead of `th:each="item : ${items}"`; also an empty `th:each=""` value.
Common situations: Typos where `:` was dropped or replaced (e.g. `th:each="item ${items}"`); forgetting the variable name entirely; converting from other template engines that use a different loop syntax; whitespace-only values from dynamic attributes.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- karate-markup does not support param lists in th:fragment…
- ka:dispatch requires an event name; got
- ka:data requires format 'varName:expression', got
- karate-markup does not support param lists in th:fragment…
- read() requires a path argument
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/8709fc68d575ee45.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/markup/KaEachProcessor.java:59
private static final Logger logger = LoggerFactory.getLogger(KaEachProcessor.class);
private static final int PRECEDENCE = 200;
private static final String ATTR_NAME = "each";
KaEachProcessor(final TemplateMode templateMode, final String dialectPrefix) {
super(templateMode, dialectPrefix, null, false, ATTR_NAME, true, PRECEDENCE, true);
}
@Override
protected void doProcess(
final ITemplateContext ctx,
final IProcessableElementTag tag,
final AttributeName attributeName, String av,
final IElementTagStructureHandler structureHandler) {
int pos = av.indexOf(':');
if (pos == -1) {
throw new RuntimeException(
"th:each requires `name : expression` form (got `" + av + "`).");
}
String varPart = av.substring(0, pos).trim();
av = av.substring(pos + 1).trim();
String iterVarName;
String statusVarName = null;
// Check for status variable: "item, iter" or just "item"
int commaPos = varPart.indexOf(',');
if (commaPos != -1) {
iterVarName = varPart.substring(0, commaPos).trim();
statusVarName = varPart.substring(commaPos + 1).trim();
} else {
iterVarName = varPart;
}
MarkupTemplateContext kec = (MarkupTemplateContext) ctx;
Object value = kec.evalLocal(av);
// Convert Map to list of entry objects with 'key' and 'value' properties
// This enables Thymeleaf-style iteration: th:each="entry : someMap"View on GitHub (pinned to a22eb90246)