or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.quarkus/quarkus-qute@3.30.x

docs

checked-templates.mdconfiguration.mdengine-advanced.mdindex.mdmessage-bundles.mdtemplate-basics.mdtemplate-extensions.mdtemplate-syntax.mdutilities.mdvalue-resolvers.md
tile.json

tessl/maven-io-quarkus--quarkus-qute

tessl install tessl/maven-io-quarkus--quarkus-qute@3.30.0

Offer templating support for web, email, etc in a build time, type-safe way

template-basics.mddocs/

Template Basics

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.

Capabilities

Template Interface

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 Interface

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
    }
}

Template Injection

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();
    }
}

Template Variants

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();
    }
}

Template Fragments

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();
    }
}

Parameter Declarations

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 + ")");
        }
    }
}

Qute Utility Class

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 Class

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: &lt;script&gt;alert('xss')&lt;/script&gt;
    }
}

TemplateException and Error Handling

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();
    }
}

Edge Cases and Special Scenarios

Handling Null Values

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 Collections

// 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}

Async Data Resolution

When data requires async loading, use computed data:

template.instance()
    .data("userId", userId)
    .computedData("user", key -> userService.findByIdAsync(userId))
    .renderAsync()
    .thenAccept(result -> sendResponse(result));

Fragment Rendering

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();

Timeout Handling

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;
}

Locale-Specific Rendering

// 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();

Custom Capacity for Large Output

// Pre-allocate StringBuilder capacity for large templates
template.instance()
    .setCapacity(10000)  // 10KB initial capacity
    .data("items", largeList)
    .render();

Variant Constructor Edge Cases

// 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 Rendering

// 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)

Multiple Fragment Access

// 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();
}

Performance Considerations

Template Caching

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);
    }
}

Async Rendering for I/O

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();

Streaming Large Output

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());

Memory Optimization

// 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();
    });

Rendering Concurrency

// 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());
}