docs
reference
tessl install tessl/maven-io-quarkus--quarkus-resteasy-reactive@3.15.0A Jakarta REST implementation utilizing build time processing and Vert.x for high-performance REST endpoints with reactive capabilities in cloud-native environments.
Quarkus REST integrates with Qute, a type-safe template engine, enabling server-side HTML rendering and template-driven responses from REST endpoints.
import io.quarkus.resteasy.reactive.qute.RestTemplate;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.qute.Template;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;Utility class for creating template instances with data for REST endpoint responses.
public final class RestTemplate {
/**
* Create a template instance with a single data entry
* @param name Data key name
* @param value Data value
* @return TemplateInstance ready for rendering
*/
public static TemplateInstance data(String name, Object value);
/**
* Create a template instance with the data object as root
* @param data Data object
* @return TemplateInstance ready for rendering
*/
public static TemplateInstance data(Object data);
}@Path("/hello")
public class HelloResource {
@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance hello(@RestQuery String name) {
return RestTemplate.data("name", name != null ? name : "World");
}
}Template file (src/main/resources/templates/HelloResource/hello.html):
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>Hello {name}!</h1>
</body>
</html>@GET
@Path("/user/{id}")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance userProfile(@RestPath String id) {
User user = userService.findById(id);
List<Order> orders = orderService.findByUserId(id);
return RestTemplate.data("user", user)
.data("orders", orders)
.data("pageTitle", "User Profile");
}Template file (src/main/resources/templates/UserResource/userProfile.html):
<!DOCTYPE html>
<html>
<head>
<title>{pageTitle}</title>
</head>
<body>
<h1>{user.name}</h1>
<p>Email: {user.email}</p>
<h2>Orders</h2>
<ul>
{#for order in orders}
<li>{order.id} - {order.total}</li>
{/for}
</ul>
</body>
</html>@GET
@Path("/product/{id}")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance product(@RestPath String id) {
Product product = productService.findById(id);
return RestTemplate.data(product);
}Template file (src/main/resources/templates/ProductResource/product.html):
<!DOCTYPE html>
<html>
<head>
<title>{name}</title>
</head>
<body>
<h1>{name}</h1>
<p>{description}</p>
<p>Price: ${price}</p>
<p>In Stock: {stock}</p>
</body>
</html>Templates are resolved by convention based on the resource class and method name:
src/main/resources/templates/{ClassName}/{methodName}.htmlExamples:
HelloResource.hello() → templates/HelloResource/hello.htmlUserResource.profile() → templates/UserResource/profile.htmlProductResource.details() → templates/ProductResource/details.htmlUse @io.quarkus.qute.CheckedTemplate for type-safe templates:
@Path("/items")
public class ItemResource {
@CheckedTemplate
public static class Templates {
public static native TemplateInstance item(Item item);
public static native TemplateInstance list(List<Item> items);
}
@GET
@Path("/{id}")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance getItem(@RestPath String id) {
Item item = itemService.findById(id);
return Templates.item(item);
}
@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance listItems() {
List<Item> items = itemService.findAll();
return Templates.list(items);
}
}Templates:
templates/ItemResource/item.htmltemplates/ItemResource/list.html<body>
<h1>{user.name}</h1>
{#if user.isPremium}
<span class="badge">Premium Member</span>
{/if}
{#if user.orders.size() > 0}
<h2>Recent Orders</h2>
<ul>
{#for order in user.orders}
<li>{order.id}</li>
{/for}
</ul>
{#else}
<p>No orders yet</p>
{/if}
</body><h2>Products</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
{#for product in products}
<tr>
<td>{product.name}</td>
<td>${product.price}</td>
<td>{product.stock}</td>
</tr>
{/for}
</tbody>
</table><div class="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<h4>Address</h4>
<p>{user.address.street}</p>
<p>{user.address.city}, {user.address.state} {user.address.zip}</p>
<h4>Company</h4>
<p>{user.company.name}</p>
<p>{user.company.website}</p>
</div><p>Total: ${order.subtotal + order.tax + order.shipping}</p>
<p>Discount: {order.discount}%</p>
<p>Final: ${order.total * (1 - order.discount / 100)}</p>
{#if user.age >= 18}
<p>Adult</p>
{#else}
<p>Minor</p>
{/if}@GET
@Path("/create")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance createForm() {
return RestTemplate.data("item", new Item());
}
@POST
@Path("/create")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.TEXT_HTML)
public TemplateInstance createSubmit(
@FormParam("name") String name,
@FormParam("description") String description,
@FormParam("price") BigDecimal price
) {
Item item = new Item();
item.setName(name);
item.setDescription(description);
item.setPrice(price);
itemService.create(item);
return RestTemplate.data("item", item)
.data("message", "Item created successfully");
}Template (templates/ItemResource/createForm.html):
<!DOCTYPE html>
<html>
<head>
<title>Create Item</title>
</head>
<body>
<h1>Create New Item</h1>
{#if message}
<p class="success">{message}</p>
{/if}
<form method="post" action="/items/create">
<label>Name:
<input type="text" name="name" value="{item.name}" required>
</label>
<label>Description:
<textarea name="description">{item.description}</textarea>
</label>
<label>Price:
<input type="number" name="price" value="{item.price}" step="0.01" required>
</label>
<button type="submit">Create</button>
</form>
</body>
</html>@Path("/items")
public class ItemResource {
@GET
@Path("/{id}")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance getItem(@RestPath String id) {
Item item = itemService.findById(id);
if (item == null) {
return RestTemplate.data("itemId", id)
.data("error", "Item not found");
}
return RestTemplate.data("item", item);
}
}Template with error handling:
<!DOCTYPE html>
<html>
<head>
<title>Item Details</title>
</head>
<body>
{#if error}
<div class="error">
<h1>Error</h1>
<p>{error}</p>
<p>Item ID: {itemId}</p>
</div>
{#else}
<h1>{item.name}</h1>
<p>{item.description}</p>
<p>Price: ${item.price}</p>
{/if}
</body>
</html>@GET
@Path("/dashboard")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance dashboard() {
return RestTemplate.data("user", currentUser())
.data("stats", getStatistics())
.data("notifications", getNotifications());
}Main template (templates/DashboardResource/dashboard.html):
<!DOCTYPE html>
<html>
<head>
<title>Dashboard</title>
</head>
<body>
<div class="header">
{#include header.html /}
</div>
<div class="content">
<div class="stats">
{#include stats.html /}
</div>
<div class="notifications">
{#include notifications.html /}
</div>
</div>
<div class="footer">
{#include footer.html /}
</div>
</body>
</html>@GET
@Path("/async/{id}")
@Produces(MediaType.TEXT_HTML)
public Uni<TemplateInstance> getAsync(@RestPath String id) {
return itemService.findByIdAsync(id)
.map(item -> RestTemplate.data("item", item));
}
@GET
@Path("/combined")
@Produces(MediaType.TEXT_HTML)
public Uni<TemplateInstance> getCombined() {
Uni<User> userUni = userService.getCurrentUserAsync();
Uni<List<Order>> ordersUni = orderService.getRecentOrdersAsync();
return Uni.combine().all()
.unis(userUni, ordersUni)
.asTuple()
.map(tuple -> RestTemplate.data("user", tuple.getItem1())
.data("orders", tuple.getItem2()));
}// Good: Resource methods focus on data preparation
@GET
@Path("/{id}")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance getItem(@RestPath String id) {
Item item = itemService.findById(id);
List<Review> reviews = reviewService.findByItemId(id);
double averageRating = reviewService.calculateAverage(id);
return RestTemplate.data("item", item)
.data("reviews", reviews)
.data("averageRating", averageRating);
}@CheckedTemplate
public static class Templates {
public static native TemplateInstance item(Item item, List<Review> reviews);
}
@GET
@Path("/{id}")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance getItem(@RestPath String id) {
// Compiler checks template exists and parameter types match
return Templates.item(
itemService.findById(id),
reviewService.findByItemId(id)
);
}Templates are automatically cached by Quarkus. No manual caching needed.
@GET
@Path("/{id}")
public Response getItem(@RestPath String id, @HeaderParam("Accept") String accept) {
Item item = itemService.findById(id);
if (accept != null && accept.contains(MediaType.TEXT_HTML)) {
return Response.ok(RestTemplate.data("item", item))
.type(MediaType.TEXT_HTML)
.build();
} else {
return Response.ok(item)
.type(MediaType.APPLICATION_JSON)
.build();
}
}Or use @Produces with multiple media types:
@GET
@Path("/{id}")
@Produces({MediaType.TEXT_HTML, MediaType.APPLICATION_JSON})
public Object getItem(@RestPath String id) {
Item item = itemService.findById(id);
// Quarkus automatically selects format based on Accept header
return item; // JSON by default, or use RestTemplate for HTML
}Configure Qute templates via application.properties:
# Template suffix (default: html)
quarkus.qute.suffixes=html,txt,xml
# Template cache
quarkus.qute.dev-mode.cache=false
# Strict rendering (fail on missing properties)
quarkus.qute.strict-rendering=true
# Remove standalone lines
quarkus.qute.remove-standalone-lines=true