{"record":{"id":"8709fc68d575ee45","repo":"karatelabs/karate","slug":"th-each-requires-name-expression-form-got-av","errorCode":null,"errorMessage":"th:each requires `name : expression` form (got `${av}`).","messagePattern":"th:each requires `name : expression` form \\(got `(.+?)`\\)\\.","errorType":"validation","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"karate-core/src/main/java/io/karatelabs/markup/KaEachProcessor.java","lineNumber":59,"sourceCode":"\n    private static final Logger logger = LoggerFactory.getLogger(KaEachProcessor.class);\n\n    private static final int PRECEDENCE = 200;\n    private static final String ATTR_NAME = \"each\";\n\n    KaEachProcessor(final TemplateMode templateMode, final String dialectPrefix) {\n        super(templateMode, dialectPrefix, null, false, ATTR_NAME, true, PRECEDENCE, true);\n    }\n\n    @Override\n    protected void doProcess(\n            final ITemplateContext ctx,\n            final IProcessableElementTag tag,\n            final AttributeName attributeName, String av,\n            final IElementTagStructureHandler structureHandler) {\n        int pos = av.indexOf(':');\n        if (pos == -1) {\n            throw new RuntimeException(\n                    \"th:each requires `name : expression` form (got `\" + av + \"`).\");\n        }\n        String varPart = av.substring(0, pos).trim();\n        av = av.substring(pos + 1).trim();\n        String iterVarName;\n        String statusVarName = null;\n        // Check for status variable: \"item, iter\" or just \"item\"\n        int commaPos = varPart.indexOf(',');\n        if (commaPos != -1) {\n            iterVarName = varPart.substring(0, commaPos).trim();\n            statusVarName = varPart.substring(commaPos + 1).trim();\n        } else {\n            iterVarName = varPart;\n        }\n        MarkupTemplateContext kec = (MarkupTemplateContext) ctx;\n        Object value = kec.evalLocal(av);\n        // Convert Map to list of entry objects with 'key' and 'value' properties\n        // This enables Thymeleaf-style iteration: th:each=\"entry : someMap\"","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/karatelabs/karate/blob/a22eb90246d958d15a47bf436693d0121ad2812d/karate-core/src/main/java/io/karatelabs/markup/KaEachProcessor.java#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n<li th:each=\"${items}\">...</li>\n\n// after\n<li th:each=\"item : ${items}\">...</li>","handlingStrategy":"validation","validationCode":"String av = tag.getAttributeValue(\"th:each\");\nif (av == null || !av.contains(\":\")) {\n    throw new IllegalArgumentException(\"th:each needs `name : expression`, got: \" + av);\n}","typeGuard":null,"tryCatchPattern":"try {\n    render(template);\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"th:each requires\")) {\n        log.error(\"Malformed th:each: {}\", e.getMessage());\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["markup","thymeleaf","template","syntax"],"backgroundTag":"invalid-argument-format","analyzedSha":"a22eb90246d958d15a47bf436693d0121ad2812d","analyzedAt":"2026-09-12T09:01:00.220Z","contentChangedAt":"2026-09-12T09:01:00.220Z","schemaVersion":2},"datasetVersion":"2026-09-16T19:17:19.609Z"}