spring-projects/spring-security · error · IllegalStateException
Unused placeholders in template
Error message
Unused placeholders in template: [%s]
What it means
HtmlTemplates.render validates that every {{placeholder}} in the template string is actually supplied by the caller's context. If any placeholder remains unreplaced after rendering (because no value was passed for it), rendering aborts with IllegalStateException listing the unused placeholders. This catches template/context mismatches early instead of shipping a page with literal {{...}} text.
Solutions
- Add the missing key/value to the context map passed to render
- Remove the {{placeholder}} from the template if it is no longer needed
- Fix key-name typos/casing so the placeholder matches a supplied key
- Ensure every code path rendering the template supplies all placeholders
Example fix
// before
HtmlTemplates.render("<h1>{{title}}</h1>{{subtitle}}", Map.of("title", "Hi"))
// after
HtmlTemplates.render("<h1>{{title}}</h1>{{subtitle}}", Map.of("title", "Hi", "subtitle", "Welcome")) Defensive patterns
Strategy: validation
Validate before calling
// verify placeholders are covered before render
Set<String> placeholders = extractPlaceholders(template); // regex \{\{([a-zA-Z0-9]+)}}
if (!context.keySet().containsAll(placeholders)) {
throw new IllegalArgumentException("Missing values: " + placeholders.removeAll(context.keySet()));
} Try / catch
try {
return HtmlTemplates.render(template, context);
} catch (IllegalStateException ex) {
logger.error("Template mismatch: " + ex.getMessage());
throw ex;
} Prevention
- Add a unit test per template asserting every placeholder has a supplied value
- Never edit templates without updating all render call sites
- Use constants for context keys instead of inline strings
When it happens
Trigger: Calling the public render(template, model/context) method where the template contains a {{name}} placeholder that has no corresponding key in the provided values map — e.g. a typo in the key name or a template updated without updating the caller.
Common situations: Editing an HTML template to add a new placeholder but forgetting to pass its value; passing context keys with wrong casing; reusing one template across code paths where one path omits a value.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- authorizationManagerFactory must be an instance of…
- authorizationManagerFactory must be an instance of…
- Cannot apply to already built object
- Cannot configure both a CorsConfigurationSource and a…
- Headers security is enabled, but no headers will be added…
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/324ecc68e64bd1ca.
Report an issue: GitHub.
Appendix: source
Thrown at web/src/main/java/org/springframework/security/web/server/ui/HtmlTemplates.java:100
* Render the template. All placeholders MUST have a corresponding value. If a
* placeholder does not have a corresponding value, throws
* {@link IllegalStateException}.
* @return the rendered template
*/
String render() {
String template = this.template;
for (String key : this.values.keySet()) {
String pattern = "{{" + key + "}}";
template = template.replace(pattern, this.values.get(key));
}
String unusedPlaceholders = Pattern.compile("\\{\\{([a-zA-Z0-9]+)}}")
.matcher(template)
.results()
.map((result) -> result.group(1))
.collect(Collectors.joining(", "));
if (StringUtils.hasLength(unusedPlaceholders)) {
throw new IllegalStateException("Unused placeholders in template: [%s]".formatted(unusedPlaceholders));
}
return template;
}
}
}
View on GitHub (pinned to 96852e8860)