spring-projects/spring-security · error · IllegalStateException
Unused placeholders in template
Error message
Unused placeholders in template: [%s]
What it means
HtmlTemplates' render() validates that all {{placeholder}} tokens in the template were supplied values; after substitution it scans for remaining {{name}} patterns and throws this IllegalStateException listing the leftover placeholder names. It is a developer-time consistency check between the template and the values map.
Solutions
- Compare the reported unused-placeholder names with your values map keys and supply/fix the missing (or misspelled) entries.
- If a placeholder should render literally (e.g. example text in docs), pass its value or remove the token from the template.
- Note the message is emitted with .formatted() while the check requires [a-zA-Z0-9]+ only — hyphenated or underscored placeholder names will never match the leftover scan, so use the supported name format.
Example fix
// before
String html = WebAuthnHtmlTemplates.rpRegistration()
.render(Map.of("relyingPartyId", rpId)); // missing 'username'
// after
String html = WebAuthnHtmlTemplates.rpRegistration()
.render(Map.of("relyingPartyId", rpId, "username", username)); Defensive patterns
Strategy: validation
Validate before calling
Set<String> required = new HashSet<>();
Matcher m = Pattern.compile("\\{\\{([a-zA-Z0-9]+)}}").matcher(template);
while (m.find()) required.add(m.group(1));
if (!values.keySet().containsAll(required)) {
throw new IllegalStateException("Missing values: " + required.removeAll(values.keySet()));
}
Try / catch
try {
String html = template.render(values);
} catch (IllegalStateException e) {
if (!e.getMessage().startsWith("Unused placeholders")) throw e;
log.error("Template/values mismatch: {} — check placeholder keys vs map keys", e.getMessage());
throw e;
}
Prevention
- Extract placeholder names from the template and diff them against your values map keys before render
- Rename placeholders in both template and callers atomically
- Use only [a-zA-Z0-9] characters in placeholder names — the leftover scan does not detect other formats
- Write a unit test per template asserting render succeeds with the canonical example map
When it happens
Trigger: Calling render(values) with a values map that lacks entries for one or more placeholders present in the template — e.g. a typo in the map key ("relyingPartyid" vs "relyingPartyId"), or using a template with placeholders not intended for that render call.
Common situations: Hand-editing a template and forgetting to pass the new placeholder; renaming a placeholder in the template but not in the caller; reusing a partially populated values map across renders.
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
- Credential with id already exists
- <ex.getMessage()>
- Unable to authenticate the PublicKeyCredential
- Unable to authenticate the PublicKeyCredential. No…
- Amount of performance parameters invalid
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/eea72af40fbe2a04.
Report an issue: GitHub.
Appendix: source
Thrown at webauthn/src/main/java/org/springframework/security/web/webauthn/registration/HtmlTemplates.java:102
* 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)