tessl install tessl/maven-io-quarkus--quarkus-qute@3.30.0Offer templating support for web, email, etc in a build time, type-safe way
Core template rendering functionality including template injection, instance creation, data binding, and rendering. Qute templates follow a three-step workflow: create template, create instance with data, and trigger rendering.
The Template interface represents an immutable parsed template definition. Templates are typically injected via CDI or obtained from the Engine.
/**
* Represents an immutable template definition.
* Templates can be injected via @Inject or obtained from Engine.
*/
interface Template {
/**
* Create a new template instance to configure the model data.
* @return a new template instance
*/
TemplateInstance instance();
/**
* Create a new template instance with root data object.
* @param data root data object accessible in template
* @return a new template instance
*/
TemplateInstance data(Object data);
/**
* Create a new template instance with named data.
* @param key the data key
* @param data the data value
* @return a new template instance
*/
TemplateInstance data(String key, Object data);
/**
* Create instance with 2 data pairs.
* @return a new template instance
*/
TemplateInstance data(String key1, Object data1, String key2, Object data2);
/**
* Create instance with 3 data pairs.
* @return a new template instance
*/
TemplateInstance data(String key1, Object data1, String key2, Object data2, String key3, Object data3);
/**
* Create instance with 4 data pairs.
* @return a new template instance
*/
TemplateInstance data(String key1, Object data1, String key2, Object data2, String key3, Object data3, String key4, Object data4);
/**
* Create instance with 5 data pairs.
* @return a new template instance
*/
TemplateInstance data(String key1, Object data1, String key2, Object data2, String key3, Object data3, String key4, Object data4, String key5, Object data5);
/**
* Convenience method to render template with data.
* @param data root data object
* @return rendered template string
*/
String render(Object data);
/**
* Convenience method to render template without data.
* @return rendered template string
*/
String render();
/**
* Get all expressions in the template.
* @return list of expressions
*/
List<Expression> getExpressions();
/**
* Find first expression matching predicate.
* @param predicate expression test
* @return matching expression or null
*/
Expression findExpression(Predicate<Expression> predicate);
/**
* Get unique generated template ID.
* @return generated ID
*/
String getGeneratedId();
/**
* Get template identifier.
* @return template ID
*/
String getId();
/**
* Get template variant (content type, locale, encoding).
* @return variant or empty
*/
Optional<Variant> getVariant();
/**
* Get parameter declarations from template.
* @return list of parameter declarations
*/
List<ParameterDeclaration> getParameterDeclarations();
/**
* Get fragment by identifier.
* @param id fragment ID
* @return fragment template
*/
Fragment getFragment(String id);
/**
* Get all fragment IDs in template.
* @return set of fragment IDs
*/
Set<String> getFragmentIds();
/**
* Check if this template is a fragment.
* @return true if fragment
*/
boolean isFragment();
/**
* Get child nodes of root node.
* @return list of template nodes
*/
List<TemplateNode> getNodes();
/**
* Find nodes matching predicate.
* @param predicate node test
* @return collection of matching nodes
*/
Collection<TemplateNode> findNodes(Predicate<TemplateNode> predicate);
/**
* Get root section node.
* @return root node
*/
SectionNode getRootNode();
/**
* Get source URI (available in development mode).
* @return source URI or empty
*/
Optional<URI> getSource();
/**
* Nested interface representing a template fragment.
*/
interface Fragment {
String ATTRIBUTE = "qute_fragment";
String getId();
Template getOriginalTemplate();
boolean isFragment();
}
}Usage Examples:
import io.quarkus.qute.Template;
import jakarta.inject.Inject;
public class TemplateService {
@Inject
Template greeting; // Injects templates/greeting.html or templates/greeting.txt
public String simpleRender() {
return greeting.render(); // Render without data
}
public String renderWithData(String name) {
return greeting.data("name", name).render();
}
public String renderWithMultipleData(String name, int age) {
return greeting.data("name", name, "age", age).render();
}
public String renderWithObject(User user) {
return greeting.data(user).render(); // User properties accessible in template
}
}TemplateInstance represents a template with associated data and attributes. It is not thread-safe and should be used for a single rendering operation.
/**
* Represents an instance of Template with associated data.
* Not thread-safe - use for single rendering operation.
*/
interface TemplateInstance {
/** Attribute key for rendering timeout in milliseconds */
String TIMEOUT = "timeout";
/** Attribute key for all template variants */
String VARIANTS = "variants";
/** Attribute key for selected variant */
String SELECTED_VARIANT = "selectedVariant";
/** Attribute key for locale */
String LOCALE = "locale";
/** Attribute key for StringBuilder initial capacity */
String CAPACITY = "capacity";
/**
* Set root data object. Removes any data set via data(String, Object).
* @param data root data object
* @return this instance
*/
TemplateInstance data(Object data);
/**
* Put data in map with key. Removes root data object.
* @param key data key
* @param data data value
* @return this instance
*/
TemplateInstance data(String key, Object data);
/**
* Associate mapping function with key. Function is applied when value is requested.
* @param key data key
* @param function mapping function
* @return this instance
*/
TemplateInstance computedData(String key, Function<String, Object> function);
/**
* Set instance attribute.
* @param key attribute key
* @param value attribute value
* @return this instance
*/
TemplateInstance setAttribute(String key, Object value);
/**
* Get instance attribute.
* @param key attribute key
* @return attribute value or null
*/
Object getAttribute(String key);
/**
* Trigger synchronous rendering. Blocks current thread until complete.
* @return rendered template string
*/
String render();
/**
* Trigger asynchronous rendering.
* @return CompletionStage with rendered result
*/
CompletionStage<String> renderAsync();
/**
* Create Mutiny Multi for consuming rendered chunks.
* @return Multi of rendered chunks
*/
Multi<String> createMulti();
/**
* Create Mutiny Uni for consuming rendered result.
* @return Uni of rendered result
*/
Uni<String> createUni();
/**
* Render and consume chunks with consumer.
* @param consumer chunk consumer
* @return CompletionStage completed when rendering finishes
*/
CompletionStage<Void> consume(Consumer<String> consumer);
/**
* Get rendering timeout in milliseconds.
* @return timeout value
*/
long getTimeout();
/**
* Get original template.
* @return template
*/
Template getTemplate();
/**
* Get fragment by identifier.
* @param id fragment ID
* @return fragment template
*/
Template getFragment(String id);
/**
* Register action to run after rendering completes.
* @param action runnable to execute
* @return this instance
*/
TemplateInstance onRendered(Runnable action);
/**
* Set locale from language tag.
* @param locale language tag string (IETF)
* @return this instance
*/
TemplateInstance setLocale(String locale);
/**
* Set locale.
* @param locale locale
* @return this instance
*/
TemplateInstance setLocale(Locale locale);
/**
* Set template variant.
* @param variant variant with content type, locale, encoding
* @return this instance
*/
TemplateInstance setVariant(Variant variant);
/**
* Set StringBuilder initial capacity for rendering.
* @param capacity initial capacity
* @return this instance
*/
TemplateInstance setCapacity(int capacity);
/**
* Component for initializing template instances.
*/
interface Initializer extends Consumer<TemplateInstance> {
}
}Usage Examples:
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import jakarta.inject.Inject;
import java.util.Locale;
import java.util.concurrent.CompletionStage;
public class AdvancedTemplateService {
@Inject
Template email;
// Synchronous rendering
public String renderSync(User user) {
return email.data("user", user).render();
}
// Asynchronous rendering
public CompletionStage<String> renderAsync(User user) {
return email.data("user", user).renderAsync();
}
// With Mutiny Uni
public Uni<String> renderUni(User user) {
return email.data("user", user).createUni();
}
// With locale and variant
public String renderLocalized(User user, Locale locale) {
return email.data("user", user)
.setLocale(locale)
.render();
}
// With computed data
public String renderWithComputed(String userId) {
return email.data("userId", userId)
.computedData("user", key -> findUser(userId))
.render();
}
// With post-rendering action
public String renderWithCallback(User user) {
return email.data("user", user)
.onRendered(() -> logRendering(user))
.render();
}
// Consume chunks
public CompletionStage<Void> streamToResponse(User user, OutputStream out) {
return email.data("user", user)
.consume(chunk -> {
try {
out.write(chunk.getBytes());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
private User findUser(String userId) {
// Implementation
return null;
}
private void logRendering(User user) {
// Implementation
}
}Templates can be injected using CDI with @Inject. The field name determines the template path unless @Location is specified.
/**
* Qualifier to specify template location explicitly.
*/
@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, FIELD, PARAMETER, METHOD })
@interface Location {
/**
* Template path relative to templates root.
*/
@Nonbinding
String value();
}Usage Examples:
import io.quarkus.qute.Template;
import io.quarkus.qute.Location;
import jakarta.inject.Inject;
public class InjectTemplateService {
// Injects templates/welcome.html or templates/welcome.txt
@Inject
Template welcome;
// Injects templates/emails/confirmation.html
@Inject
@Location("emails/confirmation")
Template emailConfirmation;
// Injects templates/reports/monthly.html
@Inject
@Location("reports/monthly.html")
Template monthlyReport;
public String renderWelcome(String name) {
return welcome.data("name", name).render();
}
public String renderConfirmation(Order order) {
return emailConfirmation.data("order", order).render();
}
public String renderMonthlyReport(ReportData data) {
return monthlyReport.data(data).render();
}
}Variants specify content type, locale, and encoding for templates. Used for content negotiation and localization.
/**
* Represents template variant with content type, locale, and encoding.
*/
class Variant {
/** Content type constant for HTML */
public static final String TEXT_HTML = "text/html";
/** Content type constant for plain text */
public static final String TEXT_PLAIN = "text/plain";
/** Content type constant for XML */
public static final String TEXT_XML = "text/xml";
/** Content type constant for JSON */
public static final String APPLICATION_JSON = "application/json";
/**
* Create variant with default locale, UTF-8 encoding, and specified content type.
* @param contentType MIME content type
* @return new variant instance
*/
public static Variant forContentType(String contentType);
/**
* Create variant with all properties using Locale and Charset.
* @param locale locale
* @param encoding character encoding as Charset
* @param contentType MIME content type
*/
public Variant(Locale locale, Charset encoding, String contentType);
/**
* Create variant with all properties using locale and encoding name.
* @param locale locale
* @param contentType MIME content type
* @param encoding character encoding name
*/
public Variant(Locale locale, String contentType, String encoding);
/**
* Get content type.
* @return MIME content type or null
*/
public String getContentType();
/**
* Get media type (alias for getContentType).
* @return MIME content type or null
*/
public String getMediaType();
/**
* Get character encoding name.
* @return encoding name or null
*/
public String getEncoding();
/**
* Get character encoding as Charset.
* @return Charset or null
*/
public Charset getCharset();
/**
* Get locale.
* @return locale or null
*/
public Locale getLocale();
}Usage Example:
import io.quarkus.qute.Template;
import io.quarkus.qute.Variant;
import jakarta.inject.Inject;
import java.util.Locale;
public class VariantTemplateService {
@Inject
Template content;
public String renderHtml(Data data) {
Variant htmlVariant = Variant.forContentType("text/html");
return content.instance()
.data(data)
.setVariant(htmlVariant)
.render();
}
public String renderJson(Data data) {
Variant jsonVariant = Variant.forContentType("application/json");
return content.instance()
.data(data)
.setVariant(jsonVariant)
.render();
}
public String renderLocalized(Data data, Locale locale) {
Variant variant = new Variant(locale, Charset.forName("UTF-8"), "text/html");
return content.instance()
.data(data)
.setVariant(variant)
.render();
}
}Fragments are reusable template parts that can be rendered independently. Defined using {#fragment} section in templates.
Usage Example:
import io.quarkus.qute.Template;
import jakarta.inject.Inject;
public class FragmentService {
@Inject
Template page;
// Render specific fragment from template
public String renderHeader(User user) {
Template.Fragment headerFragment = page.getFragment("header");
return headerFragment.data("user", user).render();
}
// Get all fragment IDs
public Set<String> getAvailableFragments() {
return page.getFragmentIds();
}
// Render fragment via instance
public String renderFooter() {
TemplateInstance instance = page.instance();
Template footer = instance.getFragment("footer");
return footer.render();
}
}Templates can declare expected parameters using {#let} or type-safe parameter declarations.
/**
* Represents a template parameter declaration.
*/
class ParameterDeclaration {
/**
* Get parameter key/name.
* @return parameter key
*/
public String getKey();
/**
* Get parameter type information.
* @return type info string or null
*/
public String getTypeInfo();
/**
* Get default value expression.
* @return default value expression or null
*/
public Expression getDefaultValue();
/**
* Get origin in template.
* @return origin
*/
public Origin getOrigin();
}Usage Example:
import io.quarkus.qute.Template;
import io.quarkus.qute.ParameterDeclaration;
import jakarta.inject.Inject;
public class ParameterService {
@Inject
Template form;
// Inspect template parameters
public void validateParameters() {
List<ParameterDeclaration> params = form.getParameterDeclarations();
for (ParameterDeclaration param : params) {
String key = param.getKey();
String type = param.getTypeInfo();
System.out.println("Parameter: " + key + " (type: " + type + ")");
}
}
}The Qute class provides convenient static methods for quick template formatting without explicit Engine setup.
/**
* Convenient static access to the Engine and formatting utilities.
*/
class Qute {
/**
* Get or create default engine.
* @return default engine instance
*/
public static Engine engine();
/**
* Set custom engine for Qute utility methods.
* @param engine custom engine
*/
public static void setEngine(Engine engine);
/**
* Format template with map data.
* @param template template string
* @param data data map
* @return rendered result
*/
public static String fmt(String template, Map<String, Object> data);
/**
* Format template with positional arguments.
* @param template template string
* @param data positional arguments
* @return rendered result
*/
public static String fmt(String template, Object... data);
/**
* Create format builder.
* @param template template string
* @return format builder
*/
public static Fmt fmt(String template);
/**
* Enable template caching.
*/
public static void enableCache();
/**
* Disable template caching.
*/
public static void disableCache();
/**
* Clear template cache.
*/
public static void clearCache();
/**
* Format builder with fluent API.
*/
interface Fmt {
Fmt cache();
Fmt noCache();
Fmt contentType(String contentType);
Fmt variant(Variant variant);
Fmt attribute(String key, Object value);
Fmt dataArray(Object... data);
Fmt dataMap(Map<String, Object> data);
Fmt data(String key, Object data);
String render();
TemplateInstance instance();
}
}Usage Examples:
import io.quarkus.qute.Qute;
// Simple formatting
String result = Qute.fmt("Hello {name}!", Map.of("name", "World"));
// With positional arguments
String result2 = Qute.fmt("Hello {0}! You are {1} years old.", "Alice", 30);
// Using format builder
String result3 = Qute.fmt("Hello {name}!")
.data("name", "Bob")
.cache()
.render();
// With attributes
String result4 = Qute.fmt("User: {user.name}")
.data("user", userObject)
.attribute("locale", Locale.FRENCH)
.render();
// Get template instance for async rendering
TemplateInstance instance = Qute.fmt("Hello {name}!")
.data("name", "Charlie")
.instance();
CompletionStage<String> async = instance.renderAsync();RawString wraps a string that should never be escaped during template rendering.
/**
* String that is never escaped during rendering.
*/
class RawString {
/**
* Create raw string wrapper.
* @param value the raw string value
*/
public RawString(String value);
/**
* Get the raw string value.
* @return raw value
*/
public String getValue();
}Usage Example:
import io.quarkus.qute.RawString;
import io.quarkus.qute.Template;
import jakarta.inject.Inject;
public class HtmlService {
@Inject
Template page;
public String renderWithHtml() {
String safeHtml = "<strong>Bold text</strong>";
// Pass as RawString to prevent escaping
return page.data("content", new RawString(safeHtml)).render();
}
public String renderEscaped() {
String userInput = "<script>alert('xss')</script>";
// Normal string will be escaped
return page.data("content", userInput).render();
// Output: <script>alert('xss')</script>
}
}Qute provides comprehensive error handling through TemplateException with fluent builder API.
/**
* Template-specific exception with origin and error code information.
*/
class TemplateException extends RuntimeException {
/**
* Serial version UID for serialization.
*/
long serialVersionUID = 1336799943548973690L;
/**
* Create exception builder.
* @return builder instance
*/
public static Builder builder();
/**
* Create exception with cause.
* @param cause the causing exception
*/
public TemplateException(Throwable cause);
/**
* Create exception with message.
* @param message the error message
*/
public TemplateException(String message);
/**
* Create exception with origin and message (backward compatibility).
* @param origin the template origin
* @param message the error message
*/
public TemplateException(Origin origin, String message);
/**
* Create exception with full details.
* @param code error code
* @param origin template origin
* @param messageTemplate message template with Qute expressions
* @param arguments arguments for message template
* @param cause causing exception
*/
public TemplateException(ErrorCode code, Origin origin, String messageTemplate,
Map<String, Object> arguments, Throwable cause);
/**
* Get origin where error occurred.
* @return origin or null
*/
public Origin getOrigin();
/**
* Get error code.
* @return error code or null
*/
public ErrorCode getCode();
/**
* Get arguments for error message template.
* @return arguments map
*/
public Map<String, Object> getArguments();
/**
* Get message template (may contain Qute expressions).
* @return message template
*/
public String getMessageTemplate();
/**
* Get error code name if available.
* @return Optional containing code name, or empty if no code
*/
public Optional<String> getCodeName();
/**
* Fluent builder for TemplateException.
*/
interface Builder {
Builder message(String message);
Builder cause(Throwable cause);
Builder origin(Origin origin);
Builder code(ErrorCode code);
Builder argument(String key, Object value);
Builder arguments(Map<String, Object> arguments);
Builder arguments(Object... arguments);
TemplateException build();
}
}ErrorCode Enumeration:
/**
* Standard error codes for template exceptions.
*/
enum ErrorCode {
TEMPLATE_NOT_FOUND,
PARSER_ERROR,
TYPE_NOT_FOUND,
PROPERTY_NOT_FOUND,
METHOD_NOT_FOUND,
RENDERING_ERROR,
TIMEOUT,
// ... and others
}Usage Examples:
import io.quarkus.qute.TemplateException;
import io.quarkus.qute.ErrorCode;
// Simple exception
throw TemplateException.builder()
.message("Template not found")
.build();
// With error code
throw TemplateException.builder()
.message("Property 'name' not found on {class}")
.argument("class", obj.getClass().getName())
.code(ErrorCode.PROPERTY_NOT_FOUND)
.build();
// With origin and cause
throw TemplateException.builder()
.message("Failed to evaluate expression: {expr}")
.argument("expr", expression.toOriginalString())
.origin(expression.getOrigin())
.cause(originalException)
.code(ErrorCode.RENDERING_ERROR)
.build();
// In value resolver
@Override
public CompletionStage<Object> resolve(EvalContext context) {
try {
return CompletedStage.of(doResolve(context));
} catch (Exception e) {
throw TemplateException.builder()
.message("Error resolving {name} on {base}")
.argument("name", context.getName())
.argument("base", context.getBase())
.cause(e)
.build();
}
}Templates handle null values gracefully with proper operators:
// Template with null-safe access
template.data("user", null).render(); // Use {user ?: 'Guest'} in template
// Optional values
template.data("value", Optional.empty()).render(); // Use {value ?: 'default'}
// Null in collections
List<String> listWithNulls = Arrays.asList("a", null, "b");
template.data("items", listWithNulls).render(); // Nulls are preserved// Empty list - use orEmpty in template
template.data("items", Collections.emptyList()).render();
// Template: {#for item in items.orEmpty}...{#else}No items{/for}
// Null list
template.data("items", null).render();
// Template: {#for item in items.orEmpty}...{#else}No items{/for}When data requires async loading, use computed data:
template.instance()
.data("userId", userId)
.computedData("user", key -> userService.findByIdAsync(userId))
.renderAsync()
.thenAccept(result -> sendResponse(result));Render specific fragments for partial updates:
Template page = engine.getTemplate("page.html");
// Render full page
String fullPage = page.data("user", user).render();
// Render just the header fragment
Template.Fragment header = page.getFragment("header");
String headerHtml = header.data("user", user).render();
// Check available fragments
Set<String> fragmentIds = page.getFragmentIds();try {
String result = template.instance()
.data("data", data)
.setAttribute(TemplateInstance.TIMEOUT, 5000L) // 5 second timeout
.render();
} catch (TemplateException e) {
if (e.getCode() != null && "TIMEOUT".equals(e.getCode().getName())) {
// Handle timeout specifically
logger.warn("Template rendering timed out");
return fallbackContent;
}
throw e;
}// Set locale for message bundles and formatting
template.instance()
.setLocale(Locale.forLanguageTag("de-DE"))
.data("user", user)
.render();
// Or via attribute
template.instance()
.setAttribute(TemplateInstance.LOCALE, Locale.FRENCH)
.data("user", user)
.render();// Pre-allocate StringBuilder capacity for large templates
template.instance()
.setCapacity(10000) // 10KB initial capacity
.data("items", largeList)
.render();// Variant with null locale (uses default)
Variant v1 = new Variant(null, Charset.forName("UTF-8"), "text/html");
// Variant with null encoding (uses default)
Variant v2 = new Variant(Locale.US, (Charset) null, "text/html");
// Variant with null content type
Variant v3 = new Variant(Locale.US, Charset.forName("UTF-8"), null);
// Variant with encoding name string
Variant v4 = new Variant(Locale.FRENCH, "text/html", "ISO-8859-1");// Empty template file
Template empty = engine.parse("");
String result = empty.render(); // Returns empty string
// Template with only whitespace
Template whitespace = engine.parse(" \n \t ");
String result2 = whitespace.render(); // Returns whitespace as-is (unless removeStandaloneLines)// Get all fragments from a template
Template page = engine.getTemplate("page.html");
Set<String> fragmentIds = page.getFragmentIds();
for (String fragmentId : fragmentIds) {
Template.Fragment fragment = page.getFragment(fragmentId);
String fragmentHtml = fragment.data("user", user).render();
// Process fragment
}
// Check if fragment exists before accessing
if (page.getFragmentIds().contains("sidebar")) {
Template.Fragment sidebar = page.getFragment("sidebar");
String sidebarHtml = sidebar.render();
}Templates are automatically cached by the Engine. Reuse Template instances:
// Good: Inject once, reuse many times
@Inject
Template emailTemplate;
public void sendEmails(List<User> users) {
for (User user : users) {
String email = emailTemplate.data("user", user).render();
send(email);
}
}
// Bad: Parse repeatedly
public void sendEmails(List<User> users) {
for (User user : users) {
Template template = engine.parse(emailContent); // Expensive!
String email = template.data("user", user).render();
send(email);
}
}Use async rendering when templates involve I/O operations:
// Async rendering with Mutiny
Uni<String> result = template.instance()
.data("userId", userId)
.computedData("user", key -> userService.loadAsync(userId))
.createUni();
// Async rendering with CompletionStage
CompletionStage<String> result = template.instance()
.data("data", data)
.renderAsync();For very large output, stream chunks instead of building complete string:
template.instance()
.data("largeDataset", dataset)
.consume(chunk -> outputStream.write(chunk.getBytes()))
.thenRun(() -> outputStream.close());// Pre-allocate capacity for known large output
TemplateInstance largeTemplate = template.instance()
.setCapacity(50000) // 50KB initial capacity
.data("bigData", largeDataset);
// Stream to avoid building full string in memory
CompletionStage<Void> result = template.instance()
.data("hugeDataset", dataset)
.consume(chunk -> {
responseWriter.write(chunk);
responseWriter.flush();
});// Good: Separate instances for concurrent rendering
@Inject
Template sharedTemplate;
public List<String> renderConcurrently(List<User> users) {
return users.parallelStream()
.map(user -> sharedTemplate.instance() // New instance per thread
.data("user", user)
.render())
.collect(Collectors.toList());
}
// Bad: Reusing same instance (not thread-safe)
public List<String> renderConcurrentlyWrong(List<User> users) {
TemplateInstance instance = sharedTemplate.instance();
return users.parallelStream()
.map(user -> instance.data("user", user).render()) // Concurrent modification!
.collect(Collectors.toList());
}