Qute Templating Engine

Qute is a templating engine designed specifically to meet the Quarkus needs. The usage of reflection is minimized to reduce the size of native images. The API combines both the imperative and the non-blocking reactive style of coding. In the development mode, all files located in src/main/resources/templates are watched for changes and modifications are immediately visible. Furthermore, we try to detect most of the template problems at build time. In this guide, you will learn how to easily render templates in your application.

This technology is considered experimental.

In experimental mode, early feedback is requested to mature the idea. There is no guarantee of stability nor long term presence in the platform until the solution matures. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker.

For a full list of possible extension statuses, check our FAQ entry.

Hello World with JAX-RS

If you want to use Qute in your JAX-RS application, you need to add the quarkus-qute-resteasy extension first. In your pom.xml file, add:

  1. <dependency>
  2. <groupId>io.quarkus</groupId>
  3. <artifactId>quarkus-resteasy-qute</artifactId>
  4. </dependency>

We’ll start with a very simple template:

hello.txt

  1. Hello {name}! (1)
1{name} is a value expression that is evaluated when the template is rendered.
By default, all files located in the src/main/resources/templates directory and its subdirectories are registered as templates. Templates are validated during startup and watched for changes in the development mode.

Now let’s inject the “compiled” template in the resource class.

HelloResource.java

  1. package org.acme.quarkus.sample;
  2. import javax.inject.Inject;
  3. import javax.ws.rs.GET;
  4. import javax.ws.rs.Path;
  5. import javax.ws.rs.QueryParam;
  6. import io.quarkus.qute.TemplateInstance;
  7. import io.quarkus.qute.Template;
  8. @Path("hello")
  9. public class HelloResource {
  10. @Inject
  11. Template hello; (1)
  12. @GET
  13. @Produces(MediaType.TEXT_PLAIN)
  14. public TemplateInstance get(@QueryParam("name") String name) {
  15. return hello.data("name", name); (2) (3)
  16. }
  17. }
1If there is no @ResourcePath qualifier provided, the field name is used to locate the template. In this particular case, we’re injecting a template with path templates/hello.txt.
2Template.data() returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key name. The data map is accessible during rendering.
3Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.

If your application is running, you can request the endpoint:

  1. $ curl -w "\n" http://localhost:8080/hello?name=Martin
  2. Hello Martin!

Type-safe templates

There’s an alternate way to declare your templates in your Java code, which relies on the following convention:

  • Organise your template files in the /src/main/resources/templates directory, by grouping them into one directory per resource class. So, if your ItemResource class references two templates hello and goodbye, place them at /src/main/resources/templates/ItemResource/hello.txt and /src/main/resources/templates/ItemResource/goodbye.txt. Grouping templates per resource class makes it easier to navigate to them.

  • In each of your resource class, declare a @CheckedTemplate static class Template {} class within your resource class.

  • Declare one public static native TemplateInstance method(); per template file for your resource.

  • Use those static methods to build your template instances.

Here’s the previous example, rewritten using this style:

We’ll start with a very simple template:

HelloResource/hello.txt

  1. Hello {name}! (1)
1{name} is a value expression that is evaluated when the template is rendered.

Now let’s declare and use those templates in the resource class.

HelloResource.java

  1. package org.acme.quarkus.sample;
  2. import javax.inject.Inject;
  3. import javax.ws.rs.GET;
  4. import javax.ws.rs.Path;
  5. import javax.ws.rs.QueryParam;
  6. import io.quarkus.qute.TemplateInstance;
  7. import io.quarkus.qute.api.CheckedTemplate;
  8. @Path("hello")
  9. public class HelloResource {
  10. @CheckedTemplate
  11. public static class Templates {
  12. public static native TemplateInstance hello(); (1)
  13. }
  14. @GET
  15. @Produces(MediaType.TEXT_PLAIN)
  16. public TemplateInstance get(@QueryParam("name") String name) {
  17. return Templates.hello().data("name", name); (2) (3)
  18. }
  19. }
1This declares a template with path templates/HelloResource/hello.txt.
2Templates.hello() returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key name. The data map is accessible during rendering.
3Note that we don’t trigger the rendering - this is done automatically by a special ContainerResponseFilter implementation.
Once you have declared a @CheckedTemplate class, we will check that all its methods point to existing templates, so if you try to use a template from your Java code and you forgot to add it, we will let you know at build time :)

Keep in mind this style of declaration allows you to reference templates declared in other resources too:

HelloResource.java

  1. package org.acme.quarkus.sample;
  2. import javax.inject.Inject;
  3. import javax.ws.rs.GET;
  4. import javax.ws.rs.Path;
  5. import javax.ws.rs.QueryParam;
  6. import io.quarkus.qute.TemplateInstance;
  7. @Path("goodbye")
  8. public class GoodbyeResource {
  9. @GET
  10. @Produces(MediaType.TEXT_PLAIN)
  11. public TemplateInstance get(@QueryParam("name") String name) {
  12. return HelloResource.Templates.hello().data("name", name);
  13. }
  14. }

Toplevel type-safe templates

Naturally, if you want to declare templates at the toplevel, directly in /src/main/resources/templates/hello.txt, for example, you can declare them in a toplevel (non-nested) Templates class:

HelloResource.java

  1. package org.acme.quarkus.sample;
  2. import io.quarkus.qute.TemplateInstance;
  3. import io.quarkus.qute.Template;
  4. import io.quarkus.qute.api.CheckedTemplate;
  5. @CheckedTemplate
  6. public class Templates {
  7. public static native TemplateInstance hello(); (1)
  8. }
1This declares a template with path templates/hello.txt.

Template Parameter Declarations

If you declare a parameter declaration in a template then Qute attempts to validate all expressions that reference this parameter and if an incorrect expression is found the build fails.

Let’s suppose we have a simple class like this:

Item.java

  1. public class Item {
  2. public String name;
  3. public BigDecimal price;
  4. }

And we’d like to render a simple HTML page that contains the item name and price.

Let’s start again with the template:

ItemResource/item.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>{item.name}</title> (1)
  6. </head>
  7. <body>
  8. <h1>{item.name}</h1>
  9. <div>Price: {item.price}</div> (2)
  10. </body>
  11. </html>
1This expression is validated. Try to change the expression to {item.nonSense} and the build should fail.
2This is also validated.

Finally, let’s create a resource class with type-safe templates:

ItemResource.java

  1. package org.acme.quarkus.sample;
  2. import javax.inject.Inject;
  3. import javax.ws.rs.GET;
  4. import javax.ws.rs.Path;
  5. import javax.ws.rs.PathParam;
  6. import javax.ws.rs.Produces;
  7. import javax.ws.rs.core.MediaType;
  8. import io.quarkus.qute.TemplateInstance;
  9. import io.quarkus.qute.Template;
  10. import io.quarkus.qute.api.CheckedTemplate;
  11. @Path("item")
  12. public class ItemResource {
  13. @CheckedTemplate
  14. public static class Templates {
  15. public static native TemplateInstance item(Item item); (1)
  16. }
  17. @GET
  18. @Path("{id}")
  19. @Produces(MediaType.TEXT_HTML)
  20. public TemplateInstance get(@PathParam("id") Integer id) {
  21. return Templates.item(service.findItem(id)); (2)
  22. }
  23. }
1Declare a method that gives us a TemplateInstance for templates/ItemResource/item.html and declare its Item item parameter so we can validate the template.
2Make the Item object accessible in the template.

Template parameter declaration inside the template itself

Alternatively, you can declare your template parameters in the template file itself.

Let’s start again with the template:

item.html

  1. {@org.acme.Item item} (1)
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <meta charset="UTF-8">
  6. <title>{item.name}</title> (2)
  7. </head>
  8. <body>
  9. <h1>{item.name}</h1>
  10. <div>Price: {item.price}</div>
  11. </body>
  12. </html>
1Optional parameter declaration. Qute attempts to validate all expressions that reference the parameter item.
2This expression is validated. Try to change the expression to {item.nonSense} and the build should fail.

Finally, let’s create a resource class.

ItemResource.java

  1. package org.acme.quarkus.sample;
  2. import javax.inject.Inject;
  3. import javax.ws.rs.GET;
  4. import javax.ws.rs.Path;
  5. import javax.ws.rs.PathParam;
  6. import javax.ws.rs.Produces;
  7. import javax.ws.rs.core.MediaType;
  8. import io.quarkus.qute.TemplateInstance;
  9. import io.quarkus.qute.Template;
  10. @Path("item")
  11. public class ItemResource {
  12. @Inject
  13. ItemService service;
  14. @Inject
  15. Template item; (1)
  16. @GET
  17. @Path("{id}")
  18. @Produces(MediaType.TEXT_HTML)
  19. public TemplateInstance get(@PathParam("id") Integer id) {
  20. return item.data("item", service.findItem(id)); (2)
  21. }
  22. }
1Inject the template with path templates/item.html.
2Make the Item object accessible in the template.

Template Extension Methods

Template extension methods are used to extend the set of accessible properties of data objects.

Sometimes, you’re not in control of the classes that you want to use in your template, and you cannot add methods to them. Template extension methods allows you to declare new method for those classes that will be available from your templates just as if they belonged to the target class.

Let’s keep extending on our simple HTML page that contains the item name, price and add a discounted price. The discounted price is sometimes called a “computed property”. We will implement a template extension method to render this property easily. Let’s update our template:

HelloResource/item.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>{item.name}</title>
  6. </head>
  7. <body>
  8. <h1>{item.name}</h1>
  9. <div>Price: {item.price}</div>
  10. {#if item.price > 100} (1)
  11. <div>Discounted Price: {item.discountedPrice}</div> (2)
  12. {/if}
  13. </body>
  14. </html>
1if is a basic control flow section.
2This expression is also validated against the Item class and obviously there is no such property declared. However, there is a template extension method declared on the TemplateExtensions class - see below.

Finally, let’s create a class where we put all our extension methods:

TemplateExtensions.java

  1. package org.acme.quarkus.sample;
  2. import io.quarkus.qute.TemplateExtension;
  3. @TemplateExtension
  4. public class TemplateExtensions {
  5. public static BigDecimal discountedPrice(Item item) { (1)
  6. return item.price.multiply(new BigDecimal("0.9"));
  7. }
  8. }
1A static template extension method can be used to add “computed properties” to a data class. The class of the first parameter is used to match the base object and the method name is used to match the property name.
you can place template extension methods in every class if you annotate them with @TemplateExtension but we advise to keep them either grouped by target type, or in a single TemplateExtensions class by convention.

Rendering Periodic Reports

Templating engine could be also very useful when rendering periodic reports. You’ll need to add the quarkus-scheduler and quarkus-qute extensions first. In your pom.xml file, add:

  1. <dependency>
  2. <groupId>io.quarkus</groupId>
  3. <artifactId>quarkus-qute</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>io.quarkus</groupId>
  7. <artifactId>quarkus-scheduler</artifactId>
  8. </dependency>

Let’s suppose the have a SampleService bean whose get() method returns a list of samples.

Sample.java

  1. public class Sample {
  2. public boolean valid;
  3. public String name;
  4. public String data;
  5. }

The template is simple:

report.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Report {now}</title>
  6. </head>
  7. <body>
  8. <h1>Report {now}</h1>
  9. {#for sample in samples} (1)
  10. <h2>{sample.name ?: 'Unknown'}</h2> (2)
  11. <p>
  12. {#if sample.valid}
  13. {sample.data}
  14. {#else}
  15. <strong>Invalid sample found</strong>.
  16. {/if}
  17. </p>
  18. {/for}
  19. </body>
  20. </html>
1The loop section makes it possible to iterate over iterables, maps and streams.
2This value expression is using the elvis operator - if the name is null the default value is used.

ReportGenerator.java

  1. package org.acme.quarkus.sample;
  2. import javax.inject.Inject;
  3. import io.quarkus.qute.Template;
  4. import io.quarkus.qute.api.ResourcePath;
  5. import io.quarkus.scheduler.Scheduled;
  6. public class ReportGenerator {
  7. @Inject
  8. SampleService service;
  9. @ResourcePath("reports/v1/report_01") (1)
  10. Template report;
  11. @Scheduled(cron="0 30 * * * ?") (2)
  12. void generate() {
  13. String result = report
  14. .data("samples", service.get())
  15. .data("now", java.time.LocalDateTime.now())
  16. .render(); (3)
  17. // Write the result somewhere...
  18. }
  19. }
1In this case, we use the @ResourcePath qualifier to specify the template path: templates/reports/v1/report_01.html.
2Use the @Scheduled annotation to instruct Quarkus to execute this method on the half hour. For more information see the Scheduler guide.
3The TemplateInstance.render() method triggers rendering. Note that this method blocks the current thread.

Reactive and Asynchronous APIs

Templates can be rendered as a CompletionStage<String> (completed with the rendered output asynchronously) or as Publisher<String> containing the rendered chunks:

  1. CompletionStage<String> async = template.data("name", "neo").renderAsync();
  2. Publisher<String> publisher = template.data("name", "neo").publisher();

In the case of a Publisher, the template is rendered chunk by chunk following the requests from the subscriber. The rendering is not started until a subscriber requests it. The returned Publisher is an instance of io.smallrye.mutiny.Multi.

It is possible to create an instance of io.smallrye.mutiny.Uni as follows:

  1. Uni<String> uni = Uni.createFrom().completionStage(() -> template.data("name", "neo").renderAsync());

In this case, the rendering only starts once the subscriber requests it.

Qute Reference Guide

To learn more about Qute, please refer to the Qute reference guide.