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

checked-templates.mddocs/

Type-Safe Templates

Type-safe template declaration and validation using @CheckedTemplate annotation. Checked templates enable compile-time validation of template parameters and expressions, providing enhanced reliability and IDE support for template development.

Capabilities

CheckedTemplate Annotation

@CheckedTemplate configures type-safe templates on static nested classes or template record types. All native static methods in the annotated class declare templates with compile-time parameter validation.

/**
 * Configures type-safe templates.
 * Place on static nested class with native static methods, or on template records.
 * Each native static method declares a template with validated parameters.
 *
 * IMPORTANT: This annotation only works in fully integrated environments like Quarkus applications.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface CheckedTemplate {
    /** Constant for defaulted base path */
    String DEFAULTED = "<<defaulted>>";

    /** Constant for element name strategy */
    String ELEMENT_NAME = "<<element name>>";

    /** Constant for hyphenated element name strategy */
    String HYPHENATED_ELEMENT_NAME = "<<hyphenated element name>>";

    /** Constant for underscored element name strategy */
    String UNDERSCORED_ELEMENT_NAME = "<<underscored element name>>";

    /**
     * Base path relative to templates root.
     * Default: simple name of enclosing class for nested class, empty for top-level class.
     * @return base path
     */
    String basePath() default DEFAULTED;

    /**
     * If true, templates can only contain type-safe expressions.
     * Default: true
     * @return whether type-safe expressions are required
     */
    boolean requireTypeSafeExpressions() default true;

    /**
     * Default naming strategy for template methods/records.
     * Values: ELEMENT_NAME, HYPHENATED_ELEMENT_NAME, UNDERSCORED_ELEMENT_NAME
     * Default: ELEMENT_NAME
     * @return naming strategy
     */
    String defaultName() default ELEMENT_NAME;

    /**
     * If true, methods with $ in name are not treated as fragments.
     * Default: false
     * @return whether to ignore fragments
     */
    boolean ignoreFragments() default false;
}

Usage Examples:

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import java.util.List;

// Example 1: Nested static class with defaulted base path
public class ItemResource {

    @CheckedTemplate
    public static class Templates {
        // Template at: templates/ItemResource/item.html
        public static native TemplateInstance item(Item item);

        // Template at: templates/ItemResource/items.html
        public static native TemplateInstance items(List<Item> items);

        // Template at: templates/ItemResource/details.html
        public static native TemplateInstance details(Item item, User user);
    }

    public TemplateInstance showItem(Long id) {
        Item item = findItem(id);
        return Templates.item(item);  // Type-safe: parameter validated at build time
    }

    public TemplateInstance listItems() {
        List<Item> items = findAllItems();
        return Templates.items(items);
    }
}

// Example 2: Custom base path
public class ReportController {

    @CheckedTemplate(basePath = "reports/v2")
    public static class Templates {
        // Template at: templates/reports/v2/monthly.html
        public static native TemplateInstance monthly(ReportData data);

        // Template at: templates/reports/v2/annual.html
        public static native TemplateInstance annual(ReportData data);
    }

    public TemplateInstance generateMonthly(int year, int month) {
        ReportData data = prepareMonthlyData(year, month);
        return Templates.monthly(data);
    }
}

// Example 3: Top-level class (templates at root)
@CheckedTemplate
public class GlobalTemplates {
    // Template at: templates/error.html
    public static native TemplateInstance error(String message);

    // Template at: templates/success.html
    public static native TemplateInstance success(String message);
}

// Example 4: Hyphenated naming
public class DashboardResource {

    @CheckedTemplate(defaultName = CheckedTemplate.HYPHENATED_ELEMENT_NAME)
    public static class Templates {
        // Template at: templates/DashboardResource/user-profile.html
        public static native TemplateInstance userProfile(User user);

        // Template at: templates/DashboardResource/admin-panel.html
        public static native TemplateInstance adminPanel(Admin admin);
    }
}

// Example 5: Underscored naming
public class AdminResource {

    @CheckedTemplate(defaultName = CheckedTemplate.UNDERSCORED_ELEMENT_NAME)
    public static class Templates {
        // Template at: templates/AdminResource/user_profile.html
        public static native TemplateInstance userProfile(User user);

        // Template at: templates/AdminResource/admin_panel.html
        public static native TemplateInstance adminPanel(Admin admin);
    }
}

// Example 6: Type-safe expressions disabled (legacy templates)
public class LegacyResource {

    @CheckedTemplate(requireTypeSafeExpressions = false)
    public static class Templates {
        // Type checking relaxed for legacy template compatibility
        public static native TemplateInstance legacy(Object data);
    }
}

Type-Safe Fragments

Fragments within checked templates are declared using methods with $ in the name. The part before $ is the template method name, and the part after is the fragment identifier.

Usage Examples:

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import java.util.List;

public class ProductResource {

    @CheckedTemplate
    public static class Templates {
        // Main template at: templates/ProductResource/products.html
        public static native TemplateInstance products(List<Product> products);

        // Fragment "item" in products template
        // Fragment identifier derived from part after $
        public static native TemplateInstance products$item(Product product);

        // Fragment "header" in products template
        public static native TemplateInstance products$header(int total);
    }

    // Render full template
    public TemplateInstance showProducts() {
        List<Product> products = findAllProducts();
        return Templates.products(products);
    }

    // Render just the item fragment (e.g., for AJAX partial updates)
    public TemplateInstance renderSingleProduct(Long id) {
        Product product = findProduct(id);
        return Templates.products$item(product);
    }

    // Render just the header fragment
    public TemplateInstance renderHeader() {
        int total = countProducts();
        return Templates.products$header(total);
    }
}

// Disable fragment interpretation
public class SimpleResource {

    @CheckedTemplate(ignoreFragments = true)
    public static class Templates {
        // Method name with $ NOT treated as fragment
        public static native TemplateInstance data$export(Data data);
    }
}

Template Records

Java records implementing TemplateInstance can be annotated with @CheckedTemplate for concise type-safe template declarations.

Usage Example:

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;

// Template record at: templates/UserPage.html
@CheckedTemplate
public record UserPage(User user, String title) implements TemplateInstance {
}

// Template record with custom base path
@CheckedTemplate(basePath = "emails")
public record WelcomeEmail(User user, String verificationLink) implements TemplateInstance {
    // Template at: templates/emails/WelcomeEmail.html
}

// Usage
public class UserController {

    public TemplateInstance showUser(Long id) {
        User user = findUser(id);
        return new UserPage(user, "User Profile");
    }

    public TemplateInstance sendWelcome(User user) {
        String link = generateVerificationLink(user);
        return new WelcomeEmail(user, link);
    }
}

Build-Time Validation

Checked templates provide comprehensive build-time validation:

  • Parameter validation: Method parameters must match template parameter declarations
  • Type checking: Template expressions validated against parameter types
  • Template existence: Build fails if referenced template file doesn't exist
  • Expression validation: Type-safe expressions checked for correctness

Example Template with Parameter Declaration:

{! Template: templates/ItemResource/item.html !}
{@org.acme.Item item}

<h1>{item.name}</h1>
<p>Price: ${item.price}</p>
<p>Stock: {item.stock}</p>

Corresponding Java Code:

@CheckedTemplate
public static class Templates {
    // Build validates that:
    // 1. templates/ItemResource/item.html exists
    // 2. Template declares parameter: {@org.acme.Item item}
    // 3. All expressions (item.name, item.price, item.stock) are valid for Item type
    public static native TemplateInstance item(Item item);
}

Integration with REST Resources

Checked templates integrate seamlessly with Quarkus REST (formerly RESTEasy Reactive) for automatic rendering.

Usage Example:

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.List;

@Path("/items")
public class ItemResource {

    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance item(Item item);
        public static native TemplateInstance items(List<Item> items);
    }

    @GET
    @Path("/{id}")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance get(@PathParam("id") Long id) {
        Item item = findItem(id);
        // Return TemplateInstance directly - Quarkus REST renders automatically
        return Templates.item(item);
    }

    @GET
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance list() {
        List<Item> items = findAllItems();
        return Templates.items(items);
    }
}

Advanced Type Safety

Checked templates support complex parameter types including generics, collections, and custom types.

Usage Example:

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class AdvancedResource {

    @CheckedTemplate
    public static class Templates {
        // Generic types
        public static native TemplateInstance genericList(List<String> items);

        // Map types
        public static native TemplateInstance mapData(Map<String, Object> data);

        // Optional types
        public static native TemplateInstance optionalUser(Optional<User> user);

        // Nested generics
        public static native TemplateInstance nestedData(Map<String, List<Item>> categories);

        // Multiple parameters with different types
        public static native TemplateInstance complex(
            User user,
            List<Order> orders,
            Map<String, String> metadata,
            int pageNumber
        );

        // Varargs
        public static native TemplateInstance tags(String... tags);
    }
}

TemplateData Annotation

@TemplateData marks a target type for automatic value resolver generation. This annotation is repeatable, allowing multiple configurations for the same class or targeting different classes.

IMPORTANT: This annotation only works in fully integrated environments like Quarkus applications.

/**
 * Configures value resolver generation for a type.
 * Controls property and method accessibility in templates.
 * Non-public members, constructors, static initializers, static, synthetic and void methods are always ignored.
 */
@Retention(RUNTIME)
@Target(TYPE)
@Repeatable(Container.class)
@interface TemplateData {
    /**
     * Constant for namespace indicating underscored FQCN should be used.
     */
    String UNDERSCORED_FQCN = "<<undescored fqcn>>";

    /**
     * Constant for namespace indicating simple name should be used.
     */
    String SIMPLENAME = "<<simplename>>";

    /**
     * Target class for which to generate value resolver.
     * Default: TemplateData.class (meaning the annotated class itself)
     */
    Class<?> target() default TemplateData.class;

    /**
     * Regular expressions to match members that should be ignored.
     * Use patterns to exclude specific fields or methods from template access.
     */
    String[] ignore() default {};

    /**
     * If true, do not automatically analyze superclasses.
     * Default: false (superclass members are included)
     */
    boolean ignoreSuperclasses() default false;

    /**
     * If true, include only properties: instance fields and methods without params.
     * Excludes methods with parameters from template access.
     * Default: false (all public methods accessible)
     */
    boolean properties() default false;

    /**
     * Namespace for accessing static fields and methods in templates.
     * If set to non-empty value, a namespace resolver is automatically generated.
     * Default: UNDERSCORED_FQCN (e.g., org.acme.Foo becomes org_acme_Foo)
     * Special values: UNDERSCORED_FQCN, SIMPLENAME
     * Note: Namespace can only contain alphanumeric characters and underscores.
     */
    String namespace() default UNDERSCORED_FQCN;

    /**
     * Container annotation for repeatable @TemplateData.
     */
    @Retention(RUNTIME)
    @Target(TYPE)
    @interface Container {
        TemplateData[] value();
    }
}

Usage Examples:

import io.quarkus.qute.TemplateData;

// Example 1: Basic usage - expose all public members
@TemplateData
public class Product {
    private String name;
    private BigDecimal price;
    private int stock;

    public String getName() { return name; }
    public BigDecimal getPrice() { return price; }
    public int getStock() { return stock; }
    public boolean isAvailable() { return stock > 0; }
}
// In template: {product.name}, {product.price}, {product.available}

// Example 2: Ignore specific members using regex
@TemplateData(ignore = {"password", "internal.*"})
public class User {
    private String name;
    private String email;
    private String password;        // Ignored - exact match
    private Long internalId;        // Ignored - pattern match
    private String internalCode;    // Ignored - pattern match

    // Getters...
}
// In template: {user.name}, {user.email} accessible
// {user.password}, {user.internalId} not accessible

// Example 3: Properties-only mode (no methods with parameters)
@TemplateData(properties = true)
public class Item {
    private String name;

    public String getName() {
        return name;  // Accessible - no params
    }

    public String format(String pattern) {
        return String.format(pattern, name);  // NOT accessible - has param
    }
}
// In template: {item.name} works, {item.format('...')} fails

// Example 4: Namespace resolver for static methods
@TemplateData(namespace = "math")
public class MathUtils {
    public static int abs(int value) {
        return Math.abs(value);
    }

    public static double round(double value) {
        return Math.round(value);
    }

    public static final double PI = Math.PI;
}
// In template: {math:abs(-5)}, {math:round(3.7)}, {math:PI}

// Example 5: Namespace with simple name
@TemplateData(namespace = TemplateData.SIMPLENAME)
public class StringHelper {
    public static String upper(String s) {
        return s.toUpperCase();
    }
}
// In template: {StringHelper:upper('hello')}

// Example 6: Ignore superclass members
@TemplateData(ignoreSuperclasses = true)
public class SpecialUser extends User {
    private String specialField;
    // Only SpecialUser members accessible, User members ignored
}

// Example 7: Multiple @TemplateData annotations
@TemplateData  // Instance resolver for the class itself
@TemplateData(target = OtherClass.class)  // Generate resolver for OtherClass too
public class MyClass {
    // Members
}

// Example 8: Target a different class
@TemplateData(target = ThirdPartyClass.class, ignore = {"sensitiveMethod"})
public class Adapters {
    // This annotation generates resolver for ThirdPartyClass
}

Namespace Resolver Behavior:

When namespace is set to a non-empty value:

  • A namespace resolver is generated for static fields and methods
  • Access pattern in templates: {namespace:memberName} or {namespace:methodName(args)}
  • Default namespace is FQCN with dots/dollar signs replaced by underscores
  • Use SIMPLENAME constant for shorter namespace (just class name)
  • Namespace must contain only alphanumeric characters and underscores

Build-Time Processing:

@TemplateData triggers build-time value resolver generation:

  1. Scans all public instance fields and methods (unless properties = true)
  2. Respects ignore patterns to exclude members
  3. Optionally generates namespace resolver for static members
  4. Validates all generated resolvers at build time

TemplateGlobal Annotation

@TemplateGlobal defines global variables accessible in all templates without explicitly passing data. Can be applied to individual static fields/methods or to entire classes.

IMPORTANT: This annotation only works in fully integrated environments like Quarkus applications.

/**
 * Denotes static fields and methods that supply global variables accessible in every template.
 *
 * When applied to a class: Every non-void non-private static method with no parameters
 * and every non-private static field becomes a global variable.
 *
 * Global variable method requirements:
 * - Must not be private
 * - Must be static
 * - Must not accept any parameters
 * - Must not return void
 *
 * Global variable field requirements:
 * - Must not be private
 * - Must be static
 */
@Retention(RUNTIME)
@Target({ TYPE, METHOD, FIELD })
@interface TemplateGlobal {
    /**
     * Constant indicating element name should be used.
     */
    String ELEMENT_NAME = "<<element name>>";

    /**
     * Variable name in templates.
     * Default: ELEMENT_NAME (use field/method name)
     */
    String name() default ELEMENT_NAME;
}

Usage Examples:

import io.quarkus.qute.TemplateGlobal;
import java.time.LocalDate;
import java.time.LocalDateTime;

// Example 1: Individual field annotation
public class AppConfig {

    @TemplateGlobal
    public static final String APP_NAME = "MyApp";

    @TemplateGlobal
    public static final String VERSION = "1.0.0";

    private static final String SECRET = "xyz";  // Not annotated, not global
}

// Example 2: Individual method annotation
public class GlobalMethods {

    @TemplateGlobal
    public static String currentYear() {
        return String.valueOf(LocalDate.now().getYear());
    }

    @TemplateGlobal(name = "now")
    public static LocalDateTime currentDateTime() {
        return LocalDateTime.now();
    }

    // Not annotated - not a global variable
    public static String helper(String input) {
        return input.toUpperCase();
    }
}

// Example 3: Class-level annotation (all qualifying members become globals)
@TemplateGlobal
public class Globals {

    // Field - becomes global variable "APP_NAME"
    public static final String APP_NAME = "MyApp";

    // Field - becomes global variable "VERSION"
    public static final String VERSION = "2.0.0";

    // Method with no params - becomes global variable "currentYear"
    public static int currentYear() {
        return LocalDate.now().getYear();
    }

    // Method with no params - becomes global variable "serverTime"
    public static LocalDateTime serverTime() {
        return LocalDateTime.now();
    }

    // Private field - NOT a global variable (excluded)
    private static final String SECRET = "xyz";

    // Method with parameters - NOT a global variable (excluded)
    public static String format(String pattern) {
        return pattern;
    }

    // Void method - NOT a global variable (excluded)
    public static void doSomething() {
        // ...
    }
}

// Example 4: Custom names for global variables
public class NamedGlobals {

    @TemplateGlobal(name = "version")
    public static String appVersion() {
        return "1.0.0";
    }

    @TemplateGlobal(name = "env")
    public static String environment() {
        return System.getenv("APP_ENV");
    }

    @TemplateGlobal(name = "debug")
    public static final boolean DEBUG_MODE = true;
}

Template Usage:

<!-- Access global variables without data() call -->
<footer>
    &copy; {currentYear} {APP_NAME} - Version {version}
    <span>Environment: {env}</span>
    {#if debug}
        <div class="debug-info">Debug mode enabled</div>
    {/if}
</footer>

Best Practices:

  • Use for truly global data: application name, version, environment, build info
  • Keep methods simple and fast (avoid heavy computation)
  • Consider using class-level annotation when you have many related globals

TemplateEnum Annotation

@TemplateEnum generates a namespace resolver for enum types, making all enum constants accessible in templates.

/**
 * Generate namespace resolver for enum type.
 * Makes enum constants accessible via namespace.
 */
@Retention(RUNTIME)
@Target(TYPE)
@interface TemplateEnum {
    // No attributes - uses simple name as namespace
}

Usage Examples:

import io.quarkus.qute.TemplateEnum;

@TemplateEnum
public enum Status {
    ACTIVE,
    INACTIVE,
    PENDING
}

@TemplateEnum
public enum Priority {
    LOW,
    MEDIUM,
    HIGH
}

Template usage:

{#if user.status == Status:ACTIVE}
    User is active
{/if}

{#when task.priority}
    {#is Priority:HIGH}
        <span class="high">High Priority</span>
    {#is Priority:MEDIUM}
        <span class="medium">Medium Priority</span>
    {#is Priority:LOW}
        <span class="low">Low Priority</span>
{/when}

Java usage:

// Enum constants accessible as namespace
template.data("expectedStatus", Status.ACTIVE).render();

TemplateContents Annotation

@TemplateContents allows you to specify template contents inline instead of in separate template files. This is useful for small templates or when you want to keep template code with the Java code that uses it.

IMPORTANT: This annotation only works in fully integrated environments like Quarkus applications.

/**
 * Specifies inline contents for a type-safe template.
 * Can be applied to @CheckedTemplate methods or template records.
 */
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
@interface TemplateContents {
    /**
     * The template content string.
     */
    String value();

    /**
     * File suffix to determine content type for template variants.
     * Default: "txt" (text/plain)
     * Use "html" for text/html content type.
     */
    String suffix() default "txt";
}

Usage Examples:

import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateContents;
import io.quarkus.qute.TemplateInstance;

// Example 1: Inline template for method
public class MessageResource {

    @CheckedTemplate
    public static class Templates {
        @TemplateContents(value = "<h1>Hello {name}!</h1>", suffix = "html")
        public static native TemplateInstance greeting(String name);

        @TemplateContents("Welcome, {user.name}. You have {count} messages.")
        public static native TemplateInstance welcome(User user, int count);
    }

    public TemplateInstance getGreeting(String name) {
        return Templates.greeting(name);
    }
}

// Example 2: Inline template for record
@CheckedTemplate
@TemplateContents(value = "<div class=\"alert\">{message}</div>", suffix = "html")
public record AlertMessage(String message) implements TemplateInstance {
}

// Usage
public TemplateInstance showAlert() {
    return new AlertMessage("Operation completed successfully");
}

// Example 3: Plain text template (default)
@CheckedTemplate
public static class Templates {
    @TemplateContents("Hello {name}, your order #{orderId} is ready.")
    public static native TemplateInstance orderReady(String name, String orderId);
}

Benefits:

  • Keep small templates close to the code that uses them
  • No need for separate template files
  • Type-safe parameters still validated at build time
  • Supports both text and HTML content types

When to use:

  • Short, simple templates (1-2 lines)
  • Email subject lines
  • Alert messages
  • API response templates
  • Situations where template is tightly coupled to specific Java code

Best Practices and Common Patterns

Organizing Checked Templates

// Pattern 1: One Templates class per resource
@Path("/products")
public class ProductResource {
    
    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance list(List<Product> products);
        public static native TemplateInstance detail(Product product);
        public static native TemplateInstance edit(Product product);
    }
}

// Pattern 2: Shared templates class
@CheckedTemplate(basePath = "shared")
public class SharedTemplates {
    public static native TemplateInstance header(User user);
    public static native TemplateInstance footer();
    public static native TemplateInstance error(String message);
}

// Pattern 3: Feature-based organization
@CheckedTemplate(basePath = "orders")
public class OrderTemplates {
    public static native TemplateInstance confirmation(Order order);
    public static native TemplateInstance invoice(Order order);
    public static native TemplateInstance shipping(Order order, Address address);
}

Type-Safe Template Parameters

// Ensure template parameter declarations match method signatures
@CheckedTemplate
public static class Templates {
    // Method signature
    public static native TemplateInstance user(User user, boolean showEmail);
}

// Corresponding template (templates/MyClass/user.html)
// Must have matching parameter declarations:
// {@org.acme.User user}
// {@boolean showEmail}

Handling Optional Parameters

@CheckedTemplate
public static class Templates {
    // Use Optional for truly optional parameters
    public static native TemplateInstance profile(User user, Optional<String> message);
}

// Template: templates/profile.html
// {@org.acme.User user}
// {@java.util.Optional<java.lang.String> message}
// {#if message.isPresent}{message.get}{/if}

Fragment Naming Conventions

@CheckedTemplate
public static class Templates {
    // Main template
    public static native TemplateInstance products(List<Product> products);
    
    // Fragment: "item" in products template
    public static native TemplateInstance products$item(Product product);
    
    // Fragment: "header" in products template
    public static native TemplateInstance products$header(String title);
    
    // Fragment: "footer" in products template
    public static native TemplateInstance products$footer(int total);
}

// Template file: templates/products.html
// {#fragment item}...{/fragment}
// {#fragment header}...{/fragment}
// {#fragment footer}...{/fragment}

Error Handling

@CheckedTemplate
public static class Templates {
    public static native TemplateInstance user(User user);
}

public TemplateInstance getUser(Long id) {
    User user = userRepository.findById(id)
        .orElseThrow(() -> new NotFoundException("User not found"));
    
    try {
        return Templates.user(user);
    } catch (TemplateException e) {
        logger.error("Failed to render user template", e);
        return Templates.error(e.getMessage());
    }
}

Testing Checked Templates

@QuarkusTest
public class TemplateTest {
    
    @Test
    public void testUserTemplate() {
        User testUser = new User("John", "john@example.com");
        
        TemplateInstance instance = Templates.user(testUser);
        String result = instance.render();
        
        assertNotNull(result);
        assertTrue(result.contains("John"));
        assertTrue(result.contains("john@example.com"));
    }
    
    @Test
    public void testFragmentRendering() {
        Product product = new Product("Widget", 19.99);
        
        TemplateInstance fragment = Templates.products$item(product);
        String result = fragment.render();
        
        assertTrue(result.contains("Widget"));
        assertTrue(result.contains("19.99"));
    }
}

Troubleshooting

Build Fails: Template Not Found

Error: Template not found: templates/MyClass/myMethod.html

Solutions:

  • Create the template file at the expected location
  • Check basePath() configuration
  • Verify file name matches method name (or use defaultName strategy)
  • Ensure template file is in src/main/resources/templates/

Build Fails: Parameter Mismatch

Error: Parameter mismatch in template

Solutions:

  • Add parameter declarations to template: {@org.acme.User user}
  • Ensure parameter types match method signature exactly
  • Check generic types are fully qualified
  • Verify parameter names match between Java and template

Build Fails: Expression Not Valid

Error: Expression validation failed: property not found

Solutions:

  • Add @TemplateData to the class
  • Ensure getter methods exist for properties
  • Check property names match (case-sensitive)
  • Add to quarkus.qute.type-check-excludes if dynamic
  • Set requireTypeSafeExpressions = false for legacy templates

Runtime: Fragment Not Found

Error: Fragment "xyz" not found in template

Solutions:

  • Verify fragment is defined: {#fragment xyz}...{/fragment}
  • Check fragment ID matches method name after $
  • Ensure ignoreFragments = false (default)
  • Verify template file contains the fragment definition

Edge Cases

Template File Naming with Special Characters

@CheckedTemplate(defaultName = CheckedTemplate.HYPHENATED_ELEMENT_NAME)
public static class Templates {
    // Method: userProfile
    // File: templates/MyClass/user-profile.html
    public static native TemplateInstance userProfile(User user);
    
    // Method: adminDashBoard (capital B)
    // File: templates/MyClass/admin-dash-board.html
    public static native TemplateInstance adminDashBoard();
}

Generic Type Erasure

@CheckedTemplate
public static class Templates {
    // Generic type information preserved at compile time
    public static native TemplateInstance process(List<String> strings);
    
    // Multiple generics
    public static native TemplateInstance map(Map<String, List<Item>> data);
}

// Template must declare full generic types:
// {@java.util.List<java.lang.String> strings}
// {@java.util.Map<java.lang.String, java.util.List<org.acme.Item>> data}

Fragment Method Naming Edge Cases

@CheckedTemplate
public static class Templates {
    // Normal fragment
    public static native TemplateInstance page$header(String title);
    
    // Multiple dollar signs - only first is fragment separator
    public static native TemplateInstance page$fragment$with$dollars(String data);
    // Template: templates/page.html
    // Fragment ID: "fragment$with$dollars"
}

@CheckedTemplate(ignoreFragments = true)
public static class Templates {
    // Dollar sign in method name, but NOT a fragment
    public static native TemplateInstance export$csv(Data data);
    // Template: templates/export$csv.html
}

Empty Parameter Lists

@CheckedTemplate
public static class Templates {
    // No parameters - template has no declared parameters
    public static native TemplateInstance empty();
    
    // Template can still use global variables
}

// Template: templates/empty.html
// No {@...} declarations needed
// Can still access: {APP_NAME}, {currentYear}, etc.

Varargs Parameter Handling

@CheckedTemplate
public static class Templates {
    // Varargs parameter
    public static native TemplateInstance tags(String... tags);
}

// Template declaration:
// {@java.lang.String[] tags}  // Note: varargs become array

// Usage in Java:
Templates.tags("java", "quarkus", "qute");
Templates.tags(new String[]{"a", "b", "c"});
Templates.tags();  // Empty array

Primitive Types in Templates

@CheckedTemplate
public static class Templates {
    public static native TemplateInstance numbers(int count, long total, boolean flag);
}

// Template declarations (primitives, not wrappers):
// {@int count}
// {@long total}
// {@boolean flag}

// In template, autoboxing applies:
// {count.compareTo(10)}  // Works, int autoboxed to Integer

Null Handling in Checked Templates

@CheckedTemplate
public static class Templates {
    public static native TemplateInstance display(User user);
}

// Passing null is allowed, but template must handle it:
Templates.display(null).render();

// Template should use null-safe operators:
// {user ?: 'No user'}
// {#if user}{user.name}{/if}
// {user.name ?: 'Anonymous'}

Record Template with Validation

@CheckedTemplate
public record ValidatedEmail(
    @NotNull @Email String recipient,
    @NotNull String subject,
    @NotNull String body
) implements TemplateInstance {
    
    public ValidatedEmail {
        // Compact constructor for validation
        Objects.requireNonNull(recipient, "recipient cannot be null");
        Objects.requireNonNull(subject, "subject cannot be null");
        Objects.requireNonNull(body, "body cannot be null");
    }
}

// Template: templates/ValidatedEmail.html
// {@java.lang.String recipient}
// {@java.lang.String subject}
// {@java.lang.String body}

Template Path Resolution Rules

// Nested class template path calculation:
package com.example;

public class MyController {
    @CheckedTemplate
    public static class Templates {
        public static native TemplateInstance index();
    }
}
// Template path: templates/MyController/index.html

// Top-level class:
@CheckedTemplate
public class TopTemplates {
    public static native TemplateInstance index();
}
// Template path: templates/index.html

// With custom base path:
@CheckedTemplate(basePath = "admin/reports")
public static class Templates {
    public static native TemplateInstance summary();
}
// Template path: templates/admin/reports/summary.html

Build-Time vs Runtime Validation

@CheckedTemplate(requireTypeSafeExpressions = true)  // Default
public static class StrictTemplates {
    // Build fails if template has unvalidated expressions
    public static native TemplateInstance strict(User user);
}

@CheckedTemplate(requireTypeSafeExpressions = false)
public static class LenientTemplates {
    // Build succeeds even with unvalidated expressions
    // Runtime validation only
    public static native TemplateInstance lenient(Object data);
}