tessl install tessl/maven-io-quarkus--quarkus-qute@3.30.0Offer templating support for web, email, etc in a build time, type-safe way
Qute is a templating engine designed specifically for Quarkus applications that enables type-safe, build-time validated templates for web pages, emails, and other content. It minimizes reflection usage for optimal native image sizes, combines imperative and reactive programming styles, and provides comprehensive development-time features including hot reload for templates with instant change visibility.
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Engine;
import io.quarkus.qute.CheckedTemplate;For CDI injection:
import jakarta.inject.Inject;
import io.quarkus.qute.Location;For message bundles:
import io.quarkus.qute.i18n.MessageBundle;
import io.quarkus.qute.i18n.Message;
import io.quarkus.qute.i18n.Localized;
import io.quarkus.qute.i18n.MessageBundles;import jakarta.inject.Inject;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
public class MyService {
@Inject
Template hello; // Injects templates/hello.txt or templates/hello.html
public String renderGreeting(String name) {
return hello.data("name", name).render();
}
}import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
public class ItemResource {
@CheckedTemplate
public static class Templates {
// Defines template at templates/ItemResource/item.html
public static native TemplateInstance item(Item item);
// Defines template at templates/ItemResource/items.html
public static native TemplateInstance items(List<Item> items);
}
public TemplateInstance getItem(Long id) {
Item item = findItem(id);
return Templates.item(item); // Type-safe template rendering
}
}import io.quarkus.qute.Engine;
import jakarta.inject.Inject;
public class DynamicTemplateService {
@Inject
Engine engine;
public String renderDynamic() {
Template template = engine.parse("Hello {name}!");
return template.data("name", "World").render();
}
}Qute's architecture consists of several key components:
Templates are located in src/main/resources/templates/ by default. In development mode, changes to template files are detected automatically and reflected immediately without restart.
Core template rendering functionality including template injection, instance creation, data binding, and synchronous/asynchronous rendering. Supports multiple data formats and template variants.
interface Template {
TemplateInstance instance();
TemplateInstance data(Object data);
TemplateInstance data(String key, Object data);
TemplateInstance data(String key1, Object data1, String key2, Object data2);
TemplateInstance data(String key1, Object data1, String key2, Object data2, String key3, Object data3);
TemplateInstance data(String key1, Object data1, String key2, Object data2, String key3, Object data3, String key4, Object data4);
TemplateInstance data(String key1, Object data1, String key2, Object data2, String key3, Object data3, String key4, Object data4, String key5, Object data5);
String render(Object data);
String render();
List<Expression> getExpressions();
Expression findExpression(Predicate<Expression> predicate);
String getGeneratedId();
String getId();
Optional<Variant> getVariant();
List<ParameterDeclaration> getParameterDeclarations();
Fragment getFragment(String id);
Set<String> getFragmentIds();
boolean isFragment();
List<TemplateNode> getNodes();
Collection<TemplateNode> findNodes(Predicate<TemplateNode> predicate);
SectionNode getRootNode();
Optional<URI> getSource();
}
interface TemplateInstance {
TemplateInstance data(Object data);
TemplateInstance data(String key, Object data);
TemplateInstance computedData(String key, Function<String, Object> function);
TemplateInstance setAttribute(String key, Object value);
Object getAttribute(String key);
String render();
CompletionStage<String> renderAsync();
Uni<String> createUni();
Multi<String> createMulti();
CompletionStage<Void> consume(Consumer<String> consumer);
long getTimeout();
Template getTemplate();
Template getFragment(String id);
TemplateInstance onRendered(Runnable action);
TemplateInstance setLocale(String locale);
TemplateInstance setLocale(Locale locale);
TemplateInstance setVariant(Variant variant);
TemplateInstance setCapacity(int capacity);
}Type-safe template declaration and validation using @CheckedTemplate annotation. Enables compile-time validation of template parameters and expressions for enhanced reliability.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface CheckedTemplate {
String basePath() default DEFAULTED;
boolean requireTypeSafeExpressions() default true;
String defaultName() default ELEMENT_NAME;
boolean ignoreFragments() default false;
}Complete template expression language including value expressions, conditional sections, loops, includes, fragments, and operators. Supports nested expressions and method calls.
Built-in template extension methods for strings, collections, numbers, dates, and maps. Includes custom extension method creation using @TemplateExtension annotation.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD })
@interface TemplateExtension {
String namespace() default ANY;
String matchName() default EMPTY;
String[] matchNames() default {};
int priority() default DEFAULT_PRIORITY;
}Custom value resolution for accessing and transforming data in templates. Supports property access, method invocation, and custom resolution strategies.
interface ValueResolver extends Resolver, WithPriority {
int getPriority();
boolean appliesTo(EvalContext context);
CompletionStage<Object> resolve(EvalContext context);
ValueResolver getCachedResolver(EvalContext context);
Set<String> getSupportedProperties();
Set<String> getSupportedMethods();
}
interface NamespaceResolver extends Resolver {
CompletionStage<Object> resolve(EvalContext context);
String getNamespace();
int getPriority();
}Internationalization system with type-safe message bundles using @MessageBundle and @Message annotations. Supports localized message templates with parameter substitution.
@Retention(RUNTIME)
@Target(TYPE)
@interface MessageBundle {
String value() default DEFAULTED_NAME;
String defaultKey() default Message.ELEMENT_NAME;
String locale() default DEFAULT_LOCALE;
}
@Retention(RUNTIME)
@Target(METHOD)
@interface Message {
String key() default DEFAULT_NAME;
String value() default DEFAULT_VALUE;
String defaultValue() default DEFAULT_VALUE;
}
@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD, PARAMETER })
@interface Localized {
String value();
}
class MessageBundles {
public static <T> T get(Class<T> bundleInterface);
public static <T> T get(Class<T> bundleInterface, Localized localized);
}Advanced engine configuration, template caching, custom section helpers, parser hooks, and tracing. Provides full control over template parsing and rendering behavior.
interface Engine extends ErrorInitializer {
static EngineBuilder builder();
Template parse(String content);
Template parse(String content, Variant variant);
Template parse(String content, Variant variant, String id);
Template getTemplate(String id);
Template putTemplate(String id, Template template);
boolean isTemplateLoaded(String id);
void clearTemplates();
void removeTemplates(Predicate<String> test);
List<ResultMapper> getResultMappers();
String mapResult(Object result, Expression expression);
SectionHelperFactory<?> getSectionHelperFactory(String name);
Map<String, SectionHelperFactory<?>> getSectionHelperFactories();
List<ValueResolver> getValueResolvers();
List<NamespaceResolver> getNamespaceResolvers();
Evaluator getEvaluator();
Optional<TemplateLocation> locate(String id);
List<TemplateInstance.Initializer> getTemplateInstanceInitializers();
long getTimeout();
boolean useAsyncTimeout();
boolean removeStandaloneLines();
TraceManager getTraceManager();
void addTraceListener(TraceListener listener);
void removeTraceListener(TraceListener listener);
EngineBuilder newBuilder();
}
interface EngineBuilder {
EngineBuilder addValueResolver(ValueResolver valueResolver);
EngineBuilder addNamespaceResolver(NamespaceResolver namespaceResolver);
EngineBuilder addSectionHelper(SectionHelperFactory<?> factory);
EngineBuilder addResultMapper(ResultMapper mapper);
EngineBuilder addLocator(TemplateLocator locator);
EngineBuilder addParserHook(ParserHook hook);
EngineBuilder addTemplateInstanceInitializer(TemplateInstance.Initializer initializer);
EngineBuilder addDefaultSectionHelpers();
EngineBuilder addDefaultValueResolvers();
EngineBuilder addDefaults();
EngineBuilder removeStandaloneLines(boolean value);
EngineBuilder strictRendering(boolean value);
EngineBuilder iterationMetadataPrefix(String prefix);
EngineBuilder timeout(long value);
EngineBuilder useAsyncTimeout(boolean value);
EngineBuilder enableTracing(boolean value);
Engine build();
}Declares global variables and functions accessible from all templates using @TemplateGlobal annotation. Globals can be constants or computed values available without passing them as template data.
@Retention(RUNTIME)
@Target({FIELD, METHOD})
@interface TemplateGlobal {
String ELEMENT_NAME = "<<element name>>";
String name() default ELEMENT_NAME;
}Usage example:
public class Globals {
@TemplateGlobal
public static final String APP_NAME = "MyApp";
@TemplateGlobal(name = "currentYear")
public static int getCurrentYear() {
return LocalDate.now().getYear();
}
}Template usage: {APP_NAME} © {currentYear}
Convenience classes and helpers for template operations including the Qute static accessor, escaping utilities, raw strings, and template instance decorators.
class Qute {
public static Engine engine();
public static void setEngine(Engine engine);
public static String fmt(String template, Map<String, Object> data);
public static String fmt(String template, Object... data);
public static Fmt fmt(String template);
public static void enableCache();
public static void disableCache();
public static void clearCache();
}
class Escaper {
public String escape(CharSequence value);
public static Builder builder();
}
class RawString {
public RawString(String value);
public String getValue();
}
class HtmlEscaper extends CharReplacementResultMapper { }
class JsonEscaper implements ResultMapper { }
abstract class ForwardingTemplateInstance implements TemplateInstance {
protected abstract TemplateInstance delegate();
}
class ResultsCollectingTemplateInstance extends ForwardingTemplateInstance { }
interface TemplateGlobalProvider extends TemplateInstance.Initializer, NamespaceResolver { }Qute configuration options including template suffixes, content types, escaping, charset, and type checking. Configured via application.properties using quarkus.qute.* prefix.
@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
@ConfigMapping(prefix = "quarkus.qute")
interface QuteConfig {
List<String> suffixes();
Map<String, String> contentTypes();
Optional<List<String>> typeCheckExcludes();
Pattern templatePathExclude();
String iterationMetadataPrefix();
List<String> escapeContentTypes();
Charset defaultCharset();
DuplicitTemplatesStrategy duplicitTemplatesStrategy();
}
@ConfigRoot(phase = ConfigPhase.RUN_TIME)
@ConfigMapping(prefix = "quarkus.qute")
interface QuteRuntimeConfig {
Optional<PropertyNotFoundStrategy> propertyNotFoundStrategy();
boolean removeStandaloneLines();
boolean strictRendering();
long timeout();
boolean useAsyncTimeout();
}class Variant {
// Constants for common content types
public static final String TEXT_HTML = "text/html";
public static final String TEXT_PLAIN = "text/plain";
public static final String TEXT_XML = "text/xml";
public static final String APPLICATION_JSON = "application/json";
// Factory method
public static Variant forContentType(String contentType);
// Constructors
public Variant(Locale locale, Charset encoding, String contentType);
public Variant(Locale locale, String contentType, String encoding);
// Accessors
public Locale getLocale();
public String getMediaType();
public String getContentType();
public String getEncoding();
public Charset getCharset();
}
interface Expression {
// Namespace access
String getNamespace();
boolean hasNamespace();
// Parts access
List<Part> getParts();
// Literal handling
boolean isLiteral();
Object getLiteral();
CompletableFuture<Object> getLiteralValue();
CompletionStage<Object> asLiteral();
// Origin and metadata
Origin getOrigin();
String toOriginalString();
int getGeneratedId();
// Type information
boolean hasTypeInfo();
String collectTypeInfo();
/**
* Part of an expression (property or virtual method).
*/
interface Part {
String getName();
String getTypeInfo();
boolean isVirtualMethod();
VirtualMethodPart asVirtualMethod();
}
/**
* Part that represents a virtual method with parameters.
*/
interface VirtualMethodPart extends Part {
List<Expression> getParameters();
}
}
interface ErrorCode {
String getName();
}
class TemplateException extends RuntimeException {
// Factory method
public static Builder builder();
// Constructors
public TemplateException(String message);
public TemplateException(Throwable cause);
public TemplateException(Origin origin, String message);
public TemplateException(ErrorCode code, Origin origin, String messageTemplate,
Map<String, Object> arguments, Throwable cause);
// Accessors
public Origin getOrigin();
public ErrorCode getCode();
public Map<String, Object> getArguments();
public String getMessageTemplate();
public Optional<String> getCodeName();
/**
* Builder for TemplateException with template-based messages.
*/
public static class Builder {
public Builder message(String message);
public Builder cause(Throwable cause);
public Builder origin(Origin origin);
public Builder code(ErrorCode code);
public Builder argument(String key, Object value);
public Builder arguments(Map<String, Object> arguments);
public Builder arguments(Object... arguments);
public TemplateException build();
}
}class ParameterDeclaration {
public String getKey();
public String getTypeInfo();
public Expression getDefaultValue();
public Origin getOrigin();
}interface TemplateLocator extends WithPriority {
Optional<TemplateLocation> locate(String id);
int getPriority();
interface TemplateLocation {
Reader read();
Optional<Variant> getVariant();
Optional<URI> getSource();
}
}When working with templates, always handle potential errors:
try {
String result = template.data("user", user).render();
return result;
} catch (TemplateException e) {
// Log error with origin information
logger.error("Template error at {}:{} - {}",
e.getOrigin().getTemplateId(),
e.getOrigin().getLine(),
e.getMessage());
// Return fallback or rethrow
return fallbackContent;
}Use elvis operator and orEmpty for null-safe templates:
// In template: {user.name ?: 'Guest'}
// In template: {#for item in items.orEmpty}{item}{/for}
// In Java: provide defaults
template.data("user", user != null ? user : new GuestUser())
.data("items", items != null ? items : Collections.emptyList())
.render();renderAsync() or createUni()// Good: Reuse template instance
@Inject
Template emailTemplate;
public String sendEmail(User user) {
return emailTemplate.data("user", user).render();
}
// Bad: Parse every time
public String sendEmail(User user) {
Template template = engine.parse(emailContent); // Expensive!
return template.data("user", user).render();
}// Good: Share Template, create new TemplateInstance
@Inject
Template shared; // Thread-safe
public String render(String name) {
return shared.instance() // New instance per call
.data("name", name)
.render();
}Error: TemplateException: Template not found: mytemplate
Solutions:
src/main/resources/templates/Error: TemplateException: Property "xyz" not found on base object
Solutions:
orEmpty or elvis operator for optional properties@TemplateData annotation to expose propertiesquarkus.qute.type-check-excludes configurationError: Build fails with type checking errors
Solutions:
{@org.acme.User user}@CheckedTemplate for type-safe templatesError: TemplateException: Rendering timeout
Solutions:
quarkus.qute.timeout=30000Problem: HTML tags appear as text instead of rendering
Solutions:
RawString for trusted HTML: new RawString("<b>Bold</b>").raw virtual method in template: {content.raw}quarkus.qute.escape-content-types configurationTemplates 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();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());