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

  1. Compare the reported unused-placeholder names with your values map keys and supply/fix the missing (or misspelled) entries.
  2. If a placeholder should render literally (e.g. example text in docs), pass its value or remove the token from the template.
  3. 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

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


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)