tessl install tessl/maven-io-quarkus--quarkus-qute@3.30.0Offer templating support for web, email, etc in a build time, type-safe way
This document covers utility classes and helper types that simplify template usage, provide convenience methods, and enable advanced customization of Qute's behavior.
The Qute class provides quick and convenient access to a static engine instance and formatting methods for simple template rendering without explicit engine management.
package io.quarkus.qute;
/**
* Provides quick access to a static engine instance and convenient formatting methods.
* If no specific engine is set, a default engine is created lazily.
*/
public final class Qute {
/**
* Get or create the static engine instance.
* Default engine includes:
* - All default value resolvers and section helpers
* - ReflectionValueResolver
* - IndexedArgumentsParserHook
* - HtmlEscaper for text/html and text/xml
*/
public static Engine engine();
/**
* Set a specific engine instance for static access.
* Clears the template cache when called.
*/
public static void setEngine(Engine engine);
/**
* Format template with named data map.
*/
public static String fmt(String template, Map<String, Object> data);
/**
* Format template with indexed data array.
* Empty placeholders {} are replaced with {data[0]}, {data[1]}, etc.
*/
public static String fmt(String template, Object... data);
/**
* Create a fluent Fmt builder for advanced formatting.
*/
public static Fmt fmt(String template);
/**
* Enable template caching by default.
*/
public static void enableCache();
/**
* Disable template caching by default.
*/
public static void disableCache();
/**
* Clear the template cache.
*/
public static void clearCache();
}The simplest way to render templates is using Qute.fmt():
import io.quarkus.qute.Qute;
import java.util.Map;
// Indexed arguments (simple placeholders)
String result1 = Qute.fmt("Hello {}!", "World");
// => "Hello World!"
String result2 = Qute.fmt("Hello {} {}!", "John", "Doe");
// => "Hello John Doe!"
// Named arguments
String result3 = Qute.fmt("Hello {name}!", Map.of("name", "Alice"));
// => "Hello Alice!"
String result4 = Qute.fmt(
"User: {username}, Email: {email}",
Map.of("username", "bob", "email", "bob@example.com")
);
// => "User: bob, Email: bob@example.com"Empty placeholders {} are automatically replaced with indexed array accessors:
// Template: "Hello {}!"
// Becomes: "Hello {data[0]}!"
Qute.fmt("Hello {}!", "World"); // Hello World!
// Template: "{} + {} = {}"
// Becomes: "{data[0]} + {data[1]} = {data[2]}"
Qute.fmt("{} + {} = {}", 2, 3, 5); // 2 + 3 = 5For advanced formatting with caching, content types, and attributes:
public static final class Fmt {
/**
* Enable template caching for this format operation.
*/
public Fmt cache();
/**
* Disable template caching for this format operation.
*/
public Fmt noCache();
/**
* Set content type (affects escaping behavior).
*/
public Fmt contentType(String contentType);
/**
* Set template variant.
*/
public Fmt variant(Variant variant);
/**
* Add template instance attribute.
*/
public Fmt attribute(String key, Object value);
/**
* Set data as named map.
*/
public Fmt dataMap(Map<String, Object> data);
/**
* Set data as indexed array.
*/
public Fmt dataArray(Object... data);
/**
* Add single data item.
*/
public Fmt data(String key, Object value);
/**
* Render the template.
*/
public String render();
}Fmt Builder Examples:
// HTML escaping
String html = Qute.fmt("<html>{header}</html>")
.contentType("text/html")
.data("header", "<h1>Title</h1>")
.render();
// => "<html><h1>Title</h1></html>"
// With caching enabled
String cached = Qute.fmt("Hello {name}!")
.cache()
.data("name", "World")
.render();
// With attributes
String withAttrs = Qute.fmt("{msg:hello}")
.attribute("locale", Locale.FRENCH)
.render();
// Complex example
String complex = Qute.fmt("User: {user.name}, Status: {status}")
.contentType("text/plain")
.cache()
.data("user", userObject)
.data("status", "active")
.attribute("timestamp", System.currentTimeMillis())
.render();The IndexedArgumentsParserHook enables the {} placeholder syntax. It's automatically included in the default Qute engine:
/**
* Parser hook that replaces empty placeholders {} with indexed data accessors.
*/
public class IndexedArgumentsParserHook implements ParserHook {
// Automatically transforms:
// "Hello {}!" => "Hello {data[0]}!"
// "{} + {} = {}" => "{data[0]} + {data[1]} = {data[2]}"
}Control template caching globally or per-operation:
// Enable caching globally
Qute.enableCache();
String result = Qute.fmt("Hello {name}!", Map.of("name", "World"));
// Disable caching globally
Qute.disableCache();
// Clear the cache
Qute.clearCache();
// Per-operation caching control
String cached = Qute.fmt("Template")
.cache() // Enable for this operation only
.render();
String uncached = Qute.fmt("Template")
.noCache() // Disable for this operation only
.render();Replace the default static engine with a custom configuration:
Engine customEngine = Engine.builder()
.addDefaults()
.addValueResolver(new MyCustomResolver())
.timeout(5000)
.build();
Qute.setEngine(customEngine);
// Now all Qute.fmt() calls use the custom engine
String result = Qute.fmt("Hello {name}!", Map.of("name", "World"));The Escaper class provides character-based escaping using configurable replacement maps.
package io.quarkus.qute;
/**
* Escapes character sequences using a map of character replacements.
*/
public final class Escaper {
/**
* Escape a character sequence.
*/
public String escape(CharSequence value);
/**
* Create a new escaper builder.
*/
public static Builder builder();
public static class Builder {
/**
* Add character replacement.
*/
public Builder add(char c, String replacement);
/**
* Build the escaper.
*/
public Escaper build();
}
}Example:
import io.quarkus.qute.Escaper;
// Create custom escaper
Escaper xmlEscaper = Escaper.builder()
.add('<', "<")
.add('>', ">")
.add('&', "&")
.add('"', """)
.add('\'', "'")
.build();
String escaped = xmlEscaper.escape("<tag attr='value'>content</tag>");
// => "<tag attr='value'>content</tag>"
// CSV escaper example
Escaper csvEscaper = Escaper.builder()
.add('"', "\"\"")
.add(',', "\\,")
.build();
String csvValue = csvEscaper.escape("Hello, \"World\"");
// => "Hello\\, \"\"World\"\""The HtmlEscaper automatically escapes HTML entities for text/html and text/xml content types.
package io.quarkus.qute;
/**
* Escapes HTML entities for HTML and XML templates.
* Replaces: ", ', &, <, >
*/
public class HtmlEscaper extends CharReplacementResultMapper {
/**
* Create HTML escaper for specified content types.
*/
public HtmlEscaper(List<String> escapedContentTypes);
}Replacements:
| Character | Replacement |
|---|---|
" | " |
' | ' |
& | & |
< | < |
> | > |
Example:
Template template = engine.parse("<div>{content}</div>",
Variant.forContentType("text/html"));
String result = template.data("content", "<script>alert('XSS')</script>").render();
// => "<div><script>alert('XSS')</script></div>"Registration:
Engine engine = Engine.builder()
.addDefaults()
.addResultMapper(new HtmlEscaper(List.of("text/html", "text/xml")))
.build();The JsonEscaper escapes strings for JSON content according to RFC 8259.
package io.quarkus.qute;
/**
* Escapes strings for application/json content type.
* Handles: ", \, control characters (U+0000 to U+001F), and special chars
*/
public final class JsonEscaper implements ResultMapper {
/**
* Escape a string for JSON output.
*/
static String escape(String toEscape);
}Replacements:
| Character | Replacement |
|---|---|
" | \" |
\ | \\ |
/ | \/ |
\b (backspace) | \b |
\f (form feed) | \f |
\n (newline) | \n |
\r (return) | \r |
\t (tab) | \t |
| Control chars | \uXXXX |
Example:
Template jsonTemplate = engine.parse("{\"message\": \"{msg}\"}",
Variant.forContentType("application/json"));
String result = jsonTemplate.data("msg", "Hello\n\"World\"").render();
// => "{\"message\": \"Hello\\n\\\"World\\\"\"}"Registration:
Engine engine = Engine.builder()
.addDefaults()
.addResultMapper(new JsonEscaper())
.build();The RawString class wraps a string to prevent automatic escaping.
package io.quarkus.qute;
/**
* Wrapper that prevents escaping during template rendering.
* Useful for pre-escaped HTML or trusted content.
*/
public final class RawString {
/**
* Create a raw string wrapper.
*/
public RawString(String value);
/**
* Get the wrapped value.
*/
public String getValue();
}Example:
import io.quarkus.qute.RawString;
Template htmlTemplate = engine.parse("<div>{content}</div>",
Variant.forContentType("text/html"));
// Normal escaping
String escaped = htmlTemplate.data("content", "<b>Bold</b>").render();
// => "<div><b>Bold</b></div>"
// Prevent escaping with RawString
String raw = htmlTemplate.data("content", new RawString("<b>Bold</b>")).render();
// => "<div><b>Bold</b></div>"Use Cases:
Security Warning:
Only use RawString with trusted content. Using it with user input can lead to XSS vulnerabilities:
// UNSAFE - Do not do this with user input!
String userInput = getUserInput();
template.data("content", new RawString(userInput)); // XSS risk!
// SAFE - Let Qute escape user input
template.data("content", userInput); // Automatically escapedThe ForwardingTemplateInstance is an abstract base class for creating template instance decorators using the forwarding pattern.
package io.quarkus.qute;
/**
* Abstract base class for template instance decorators.
* Forwards all calls to a delegate instance.
*/
public abstract class ForwardingTemplateInstance implements TemplateInstance {
/**
* Return the delegate instance to forward calls to.
*/
protected abstract TemplateInstance delegate();
// All TemplateInstance methods forward to delegate()
}Example: Logging Decorator
public class LoggingTemplateInstance extends ForwardingTemplateInstance {
private final TemplateInstance delegate;
public LoggingTemplateInstance(TemplateInstance delegate) {
this.delegate = delegate;
}
@Override
protected TemplateInstance delegate() {
return delegate;
}
@Override
public String render() {
long start = System.currentTimeMillis();
String result = super.render();
long duration = System.currentTimeMillis() - start;
System.out.println("Template rendered in " + duration + "ms");
return result;
}
}
// Usage
Template template = engine.getTemplate("mytemplate");
TemplateInstance logged = new LoggingTemplateInstance(template.instance());
String result = logged.data("name", "World").render();Example: Caching Decorator
public class CachingTemplateInstance extends ForwardingTemplateInstance {
private final TemplateInstance delegate;
private final Map<String, String> cache = new ConcurrentHashMap<>();
public CachingTemplateInstance(TemplateInstance delegate) {
this.delegate = delegate;
}
@Override
protected TemplateInstance delegate() {
return delegate;
}
@Override
public String render() {
String cacheKey = computeCacheKey();
return cache.computeIfAbsent(cacheKey, k -> super.render());
}
private String computeCacheKey() {
// Compute cache key from template ID and data
return delegate.getTemplate().getId() + ":" + dataHashCode();
}
}The ResultsCollectingTemplateInstance collects rendering results for analysis, testing, or caching.
package io.quarkus.qute;
/**
* Collects all rendering results for the specified template instance.
* Useful for testing, debugging, and result caching.
*/
public class ResultsCollectingTemplateInstance extends ForwardingTemplateInstance {
/**
* Create a results collector.
*
* @param delegate the template instance to wrap
* @param resultConsumer consumer called with (instance, result) after rendering
*/
public ResultsCollectingTemplateInstance(
TemplateInstance delegate,
BiConsumer<TemplateInstance, String> resultConsumer
);
}Example: Collect Rendering Results
import io.quarkus.qute.ResultsCollectingTemplateInstance;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// Create result collector
Map<String, String> results = new ConcurrentHashMap<>();
Template template = engine.getTemplate("user-profile");
TemplateInstance collector = new ResultsCollectingTemplateInstance(
template.instance(),
(instance, result) -> {
String templateId = instance.getTemplate().getId();
results.put(templateId, result);
}
);
// Render template - result is collected
String output = collector.data("user", user).render();
// Access collected result
String collected = results.get("user-profile");Example: Testing Template Output
public class TemplateTest {
@Test
public void testTemplateRendering() {
List<String> renderedResults = new ArrayList<>();
Template template = engine.getTemplate("test-template");
TemplateInstance collecting = new ResultsCollectingTemplateInstance(
template.instance(),
(inst, result) -> renderedResults.add(result)
);
collecting.data("name", "Test").render();
// Assert on collected results
assertEquals(1, renderedResults.size());
assertTrue(renderedResults.get(0).contains("Test"));
}
}Example: Async Result Collection
Map<String, String> asyncResults = new ConcurrentHashMap<>();
Template asyncTemplate = engine.getTemplate("async-template");
TemplateInstance asyncCollector = new ResultsCollectingTemplateInstance(
asyncTemplate.instance(),
(instance, result) -> {
asyncResults.put(instance.getTemplate().getId(), result);
}
);
// Works with async rendering
asyncCollector.data("data", data)
.renderAsync()
.thenAccept(result -> {
// Result also collected via consumer
String collected = asyncResults.get("async-template");
});Use Cases:
The TemplateGlobalProvider interface is implemented by generated classes that provide global variables to all templates.
package io.quarkus.qute;
/**
* An implementation is generated for each class declaring template globals.
* Combines TemplateInstance.Initializer and NamespaceResolver.
*/
public interface TemplateGlobalProvider
extends TemplateInstance.Initializer, NamespaceResolver {
}Purpose:
This interface is used internally by Quarkus to implement the @TemplateGlobal annotation. When you annotate a class or members with @TemplateGlobal, Quarkus generates an implementation of TemplateGlobalProvider that:
Generated Implementation:
// User code
@TemplateGlobal
public class AppGlobals {
public static final String APP_NAME = "MyApp";
public static int currentYear() {
return LocalDate.now().getYear();
}
}
// Quarkus generates something like:
public class AppGlobals_GlobalProvider implements TemplateGlobalProvider {
@Override
public void accept(TemplateInstance instance) {
// Initialize template with globals
instance.data("APP_NAME", "MyApp");
instance.data("currentYear", currentYear());
}
@Override
public CompletionStage<Object> resolve(EvalContext context) {
// Resolve global variable references
return switch(context.getName()) {
case "APP_NAME" -> CompletedStage.of("MyApp");
case "currentYear" -> CompletedStage.of(currentYear());
default -> Results.notFound(context);
};
}
private static int currentYear() {
return LocalDate.now().getYear();
}
}Manual Registration:
While typically handled by Quarkus, you can manually register global providers:
Engine engine = Engine.builder()
.addDefaults()
.addTemplateInstanceInitializer(new MyGlobalProvider())
.addNamespaceResolver(new MyGlobalProvider())
.build();Template Usage:
<!DOCTYPE html>
<html>
<head>
<title>{APP_NAME}</title>
</head>
<body>
<footer>© {currentYear} {APP_NAME}</footer>
</body>
</html>See Also:
The ContentTypes class is a CDI singleton that determines MIME content types for template paths based on file extensions and configuration.
package io.quarkus.qute.runtime;
import jakarta.inject.Singleton;
/**
* Utility for determining content types from template paths.
* Considers file extensions, configuration mappings, and standard MIME types.
*/
@Singleton
public class ContentTypes {
/**
* Determine content type from a template path.
*
* @param templatePath path relative to template root, using '/' separator
* @return MIME content type string
*/
public String getContentType(String templatePath);
}Content Type Resolution:
The getContentType() method resolves content types using this priority:
quarkus.qute.content-types configuration.json files return application/jsonURLConnection.getFileNameMap()application/octet-stream if undetectableCDI Injection:
import jakarta.inject.Inject;
import io.quarkus.qute.runtime.ContentTypes;
public class TemplateService {
@Inject
ContentTypes contentTypes;
public String getTemplateContentType(String path) {
return contentTypes.getContentType(path);
}
}Example Usage:
// Built-in mappings
contentTypes.getContentType("templates/page.html"); // → "text/html"
contentTypes.getContentType("templates/data.json"); // → "application/json"
contentTypes.getContentType("templates/page.xml"); // → "text/xml"
contentTypes.getContentType("templates/doc.txt"); // → "text/plain"
// Custom mapping via configuration
// application.properties: quarkus.qute.content-types.qute=text/html
contentTypes.getContentType("templates/page.qute"); // → "text/html"Configuration:
Custom content type mappings can be defined in application.properties:
# Map custom suffixes to MIME types
quarkus.qute.content-types.qute=text/html
quarkus.qute.content-types.email=text/plain
quarkus.qute.content-types.svg=image/svg+xmlUse Cases:
See Also:
// Good: Simple one-liners
String msg = Qute.fmt("Hello {}!", name);
// Good: With caching for repeated use
String template = "User: {name}, Email: {email}";
Qute.enableCache();
for (User user : users) {
String result = Qute.fmt(template, Map.of(
"name", user.getName(),
"email", user.getEmail()
));
}
// Bad: Complex templates (use proper Template instead)
String complex = Qute.fmt("""
{#for item in items}
{item.name}
{/for}
""", Map.of("items", items)); // Use Template for this!// SAFE: Trusted content from your code
template.data("header", new RawString("<h1>Welcome</h1>"));
// SAFE: Content from sanitized source
String sanitized = sanitizer.sanitize(userInput);
template.data("content", new RawString(sanitized));
// UNSAFE: Direct user input
String userInput = request.getParameter("content");
template.data("content", new RawString(userInput)); // XSS vulnerability!
// SAFE: Let Qute escape user input
template.data("content", userInput); // Automatically escaped// CSV escaper
Escaper csvEscaper = Escaper.builder()
.add('"', "\"\"") // Double quotes
.add('\n', "\\n") // Newlines
.add('\r', "\\r") // Carriage returns
.build();
// SQL escaper (for identifiers, not values!)
Escaper sqlEscaper = Escaper.builder()
.add('\'', "''") // Single quotes
.build();
// XML attribute escaper
Escaper xmlAttrEscaper = Escaper.builder()
.add('<', "<")
.add('>', ">")
.add('&', "&")
.add('"', """)
.add('\'', "'")
.build();// Use case 1: Add default data
public class DefaultDataInstance extends ForwardingTemplateInstance {
private final TemplateInstance delegate;
public DefaultDataInstance(TemplateInstance delegate) {
this.delegate = delegate;
// Add default data
delegate.data("timestamp", System.currentTimeMillis());
delegate.data("version", "1.0");
}
@Override
protected TemplateInstance delegate() {
return delegate;
}
}
// Use case 2: Add security checks
public class SecureTemplateInstance extends ForwardingTemplateInstance {
private final TemplateInstance delegate;
private final SecurityContext securityContext;
@Override
public String render() {
if (!securityContext.canRenderTemplate(delegate.getTemplate().getId())) {
throw new SecurityException("Access denied");
}
return super.render();
}
@Override
protected TemplateInstance delegate() {
return delegate;
}
}
// Use case 3: Add metrics
public class MetricsTemplateInstance extends ForwardingTemplateInstance {
private final TemplateInstance delegate;
private final MeterRegistry metrics;
@Override
public String render() {
Timer.Sample sample = Timer.start(metrics);
try {
return super.render();
} finally {
sample.stop(metrics.timer("template.render",
"template", delegate.getTemplate().getId()));
}
}
@Override
protected TemplateInstance delegate() {
return delegate;
}
}Qute provides several utility classes for common operations:
| Class | Purpose |
|---|---|
Qute | Static engine access and convenient formatting |
Qute.Fmt | Fluent template formatting with caching |
IndexedArgumentsParserHook | Enables {} placeholder syntax |
Escaper | Configurable character escaping |
HtmlEscaper | HTML entity escaping for HTML/XML |
JsonEscaper | JSON string escaping (RFC 8259) |
RawString | Prevent automatic escaping |
ForwardingTemplateInstance | Base class for instance decorators |
ResultsCollectingTemplateInstance | Collect rendering results |
TemplateGlobalProvider | Provide global variables (generated) |
ContentTypes | Determine MIME types from template paths |
These utilities cover the most common template usage patterns and provide building blocks for custom extensions.