quarkusio/quarkus · error · IllegalStateException
No template variant found
Error message
No template variant found
What it means
MailTemplateInstanceImpl.send() renders a Qute template and mails the result. When the underlying Qute TemplateInstance exposes no TemplateInstance.VARIANTS attribute, send() cannot know which variants (text/html, text/plain) to render, so it throws this IllegalStateException. This happens when the injected MailTemplate does not come from a Qute template with multiple declared variants.
Source
Thrown at extensions/mailer/runtime/src/main/java/io/quarkus/mailer/runtime/MailTemplateInstanceImpl.java:159
.setAttribute(TemplateInstance.SELECTED_VARIANT, variant).renderAsync();
}
})));
}
}
if (results.isEmpty()) {
throw new IllegalStateException("No suitable template variant found");
}
List<Uni<String>> unis = results.stream().map(Result::resolve).collect(Collectors.toList());
return Uni.combine().all().unis(unis)
.combinedWith(combine(results))
.chain(new Function<Mail, Uni<? extends Void>>() {
@Override
public Uni<? extends Void> apply(Mail m) {
return mailer.send(m);
}
});
} else {
throw new IllegalStateException("No template variant found");
}
}
private Function<List<?>, Mail> combine(List<Result> results) {
return new Function<List<?>, Mail>() {
@Override
public Mail apply(List<?> resolved) {
for (int i = 0; i < resolved.size(); i++) {
Result result = results.get(i);
// We can safely cast, as we know that the results are Strings.
String content = (String) resolved.get(i);
if (result.variant.getContentType().equals(Variant.TEXT_HTML)) {
mail.setHtml(content);
} else if (result.variant.getContentType().equals(Variant.TEXT_PLAIN)) {
mail.setText(content);
}
}
return mail;View on GitHub (pinned to e1c734241f)
Solutions
- Ensure the template directory contains variant files with proper suffixes, e.g. templates/welcome.qute.html and templates/welcome.qute.txt, matching the @Location value.
- If building a TemplateInstance programmatically, set instance.setAttribute(TemplateInstance.VARIANTS, List.of(variant...)) before calling send().
- If you only need a fixed body, skip the mail template and use ReactiveMailer directly: mailer.send(Mail.withText(to, subject, body)).
- Verify @Location("...") path matches the actual resource under src/main/resources/templates.
Example fix
// before (single non-variant template)
@Inject @Location("welcome") MailTemplate welcome; // templates/welcome.qute only
welcome.to(to).subject(sub).send();
// after (variant files)
// src/main/resources/templates/welcome.qute.html
// src/main/resources/templates/welcome.qute.txt
welcome.to(to).subject(sub).send(); Defensive patterns
Strategy: validation
Validate before calling
Object variants = templateInstance.templateInstance().getAttribute(TemplateInstance.VARIANTS);
if (variants == null) {
throw new IllegalStateException("Template has no variants; add .qute.html/.qute.txt files or set the VARIANTS attribute");
} Try / catch
try {
template.send().await().indefinitely();
} catch (IllegalStateException e) {
if (e.getMessage().contains("No template variant")) {
LOG.error("Mail template lacks text/html or text/plain variants", e);
}
throw e;
} Prevention
- Always ship both welcome.qute.html and welcome.qute.txt variant files next to each other
- Never build TemplateInstance objects manually unless you set TemplateInstance.VARIANTS
- Add an application-startup smoke test that renders each mail template
When it happens
Trigger: Calling send() on a MailTemplateInstance whose Qute template was not loaded with template variants (e.g. the template defines no .qute.html/.qute.txt variant files, or the TemplateInstance was created programmatically without setting the VARIANTS attribute).
Common situations: Injecting a MailTemplate via @Location pointing to a plain template that only has a single non-variant file; using a custom TemplateInstance built by hand (e.g. via MailTemplateInstanceAdaptor) without variants; renaming template files so the html/txt variant suffixes no longer match.
Related errors
- No suitable template variant found
- No template resource path specified
- Template extension method declared on must be static:
- Dev services for ${request.getName()} requires a startable s
- This cache is not an instance of
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/fb7437ff956e1a02.
Report an issue: GitHub.