quarkusio/quarkus · error · RuntimeException
Invalid template link
Error message
Invalid template link [${templateLink}] - Expeting a link that ends with .html What it means
QuteDataPageBuilder.templateLink(String) validates that the Qute template link is a non-empty path ending in '.html' because the Dev UI resolves and serves the underlying Qute template file by that path. Anything else cannot be located or rendered.
Solutions
- Append the extension: templateLink("templates/my-page.html")
- Reference templates that exist under META-INF/resources of the extension and end with .html
- Normalize the input: if (link != null && !link.endsWith(".html")) link = link + ".html"; before calling
- Verify the file is present in the built artifact (check target/classes / the extension jar)
Example fix
// before
quteDataPageBuilder().templateLink("templates/status"); // throws
// after
quteDataPageBuilder().templateLink("templates/status.html"); Defensive patterns
Strategy: validation
Validate before calling
if (link == null || link.isBlank() || !link.endsWith(".html")) {
throw new IllegalStateException("templateLink must be a non-empty path ending in .html: " + link);
} Type guard
boolean isValidTemplateLink(String l) {
return l != null && !l.isBlank() && l.toLowerCase().endsWith(".html");
} Try / catch
try {
quteDataPageBuilder().templateLink(link);
} catch (RuntimeException e) {
if (e.getMessage().contains("Expeting a link that ends with .html")) {
quteDataPageBuilder().templateLink(link + ".html");
} else throw e;
} Prevention
- Always pass paths ending in .html, never source .md/.qute files
- Normalize suffixes before calling: if (!link.endsWith(".html")) link += ".html"
- Verify the template ships under META-INF/resources in the built jar
- Keep template path constants in one place to avoid drift
When it happens
Trigger: Calling quteDataPageBuilder().templateLink("templates/foo") or templateLink("") / templateLink(null) — i.e. missing the .html extension or passing an empty value.
Common situations: Storing the template path in config without the extension; users pointing at Markdown/asciidoc source files instead of the compiled HTML; path built by string concatenation dropping the suffix; classpath resource actually missing so the suffix was 'fixed' by removing it.
Related errors
- Cannot coerce to a BigDecimal
- CardAction requires either jsonRpcMethodName() or url()
- CardAction title is required
- CardText requires either staticText(), dynamicText(), or…
- className parameter is required
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/e4bd61ace7c5a48a.
Report an issue: GitHub.
Appendix: source
Thrown at extensions/devui/deployment-spi/src/main/java/io/quarkus/devui/spi/page/QuteDataPageBuilder.java:17
package io.quarkus.devui.spi.page;
public class QuteDataPageBuilder extends PageBuilder<QuteDataPageBuilder> {
private static final String DOT_HTML = ".html";
private static final String QWC_DATA_QUTE_PAGE_JS = "qwc-data-qute-page.js";
private String templateLink;
protected QuteDataPageBuilder(String title) {
super();
super.title = title;
super.internalComponent = true;// As external page runs on "internal" namespace
super.componentLink = QWC_DATA_QUTE_PAGE_JS;
}
public QuteDataPageBuilder templateLink(String templateLink) {
if (templateLink == null || templateLink.isEmpty() || !templateLink.endsWith(DOT_HTML)) {
throw new RuntimeException(
"Invalid template link [" + templateLink + "] - Expeting a link that ends with .html");
}
this.templateLink = templateLink;
return this;
}
@Override
public Page build() {
super.metadata("templatePath", getTemplatePath());
return super.build();
}
public String getTemplatePath() {
return "/dev-ui/" + this.templateLink;
}
View on GitHub (pinned to e1c734241f)