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 provides HATEOAS (Hypermedia as the Engine of Application State) support through REST Links, allowing automatic generation of hypermedia links in API responses.
import io.quarkus.resteasy.reactive.links.RestLink;
import io.quarkus.resteasy.reactive.links.RestLinkId;
import io.quarkus.resteasy.reactive.links.InjectRestLinks;
import io.quarkus.resteasy.reactive.links.RestLinkType;
import io.quarkus.resteasy.reactive.links.RestLinksProvider;
import jakarta.ws.rs.core.Link;Declares a web link for a resource method.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RestLink {
/**
* Link relation type (rel attribute)
*/
String rel() default "";
/**
* Entity type for link resolution
*/
Class<?> entityType() default Object.class;
}Marks a field or method parameter as the ID used for link generation.
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RestLinkId {
}Injects REST links into the response entity.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectRestLinks {
/**
* Type of links to inject
*/
RestLinkType value() default RestLinkType.TYPE;
}Specifies the type of REST links to inject.
public enum RestLinkType {
/**
* Type-level links (same for all instances)
*/
TYPE,
/**
* Instance-level links (specific to each entity)
*/
INSTANCE
}Provider interface for custom link generation.
public interface RestLinksProvider {
/**
* Get type-level links for an entity type
*/
Collection<Link> getTypeLinks(Class<?> elementType);
/**
* Get instance-level links for a specific entity
*/
<T> Collection<Link> getInstanceLinks(T instance);
}public class Book {
@RestLinkId
private String id;
private String title;
private String author;
private List<Link> links; // Links will be injected here
// Getters and setters
}
@Path("/books")
public class BookResource {
@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks
public Book getBook(@RestPath String id) {
return bookService.findById(id);
}
@PUT
@Path("/{id}")
@RestLink(rel = "update")
public Book updateBook(@RestPath String id, Book book) {
return bookService.update(id, book);
}
@DELETE
@Path("/{id}")
@RestLink(rel = "delete")
public void deleteBook(@RestPath String id) {
bookService.delete(id);
}
}
// Response:
// {
// "id": "123",
// "title": "Effective Java",
// "author": "Joshua Bloch",
// "links": [
// { "rel": "self", "href": "/books/123" },
// { "rel": "update", "href": "/books/123" },
// { "rel": "delete", "href": "/books/123" }
// ]
// }@GET
@InjectRestLinks(RestLinkType.INSTANCE)
public List<Book> listBooks() {
// Each book will have its own instance-specific links
return bookService.findAll();
}
// Response:
// [
// {
// "id": "123",
// "title": "Book 1",
// "links": [
// { "rel": "self", "href": "/books/123" },
// { "rel": "update", "href": "/books/123" }
// ]
// },
// {
// "id": "456",
// "title": "Book 2",
// "links": [
// { "rel": "self", "href": "/books/456" },
// { "rel": "update", "href": "/books/456" }
// ]
// }
// ]Same links for all instances of an entity type.
@Path("/books")
public class BookResource {
@GET
@RestLink(rel = "list")
@InjectRestLinks(RestLinkType.TYPE)
public List<Book> listBooks() {
// All books share the same type-level links
return bookService.findAll();
}
@POST
@RestLink(rel = "create")
public Book createBook(Book book) {
return bookService.create(book);
}
}
// Response includes type-level links:
// [
// {
// "id": "123",
// "title": "Book 1",
// "links": [
// { "rel": "list", "href": "/books" },
// { "rel": "create", "href": "/books" }
// ]
// }
// ]Links specific to each entity instance.
@Path("/books")
public class BookResource {
@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks(RestLinkType.INSTANCE)
public Book getBook(@RestPath String id) {
return bookService.findById(id);
}
@GET
@Path("/{id}/reviews")
@RestLink(rel = "reviews", entityType = Book.class)
public List<Review> getReviews(@RestPath String id) {
return reviewService.findByBookId(id);
}
}
// Response for /books/123:
// {
// "id": "123",
// "title": "Book 1",
// "links": [
// { "rel": "self", "href": "/books/123" },
// { "rel": "reviews", "href": "/books/123/reviews" }
// ]
// }@Path("/books")
public class BookResource {
@GET
@Path("/{id}")
@RestLink(rel = "self")
public Book getBook(@RestPath String id) {
return bookService.findById(id);
}
@GET
@RestLink(rel = "collection")
public List<Book> listBooks() {
return bookService.findAll();
}
@POST
@RestLink(rel = "create")
public Book createBook(Book book) {
return bookService.create(book);
}
@PUT
@Path("/{id}")
@RestLink(rel = "edit")
public Book updateBook(@RestPath String id, Book book) {
return bookService.update(id, book);
}
@DELETE
@Path("/{id}")
@RestLink(rel = "delete")
public void deleteBook(@RestPath String id) {
bookService.delete(id);
}
}@Path("/users")
public class UserResource {
@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks
public User getUser(@RestPath String id) {
return userService.findById(id);
}
@GET
@Path("/{id}/orders")
@RestLink(rel = "orders", entityType = User.class)
public List<Order> getUserOrders(@RestPath String id) {
return orderService.findByUserId(id);
}
@GET
@Path("/{id}/profile")
@RestLink(rel = "profile", entityType = User.class)
public Profile getUserProfile(@RestPath String id) {
return profileService.findByUserId(id);
}
@POST
@Path("/{id}/activate")
@RestLink(rel = "activate", entityType = User.class)
public void activateUser(@RestPath String id) {
userService.activate(id);
}
}@Path("/orders")
public class OrderResource {
@GET
@Path("/{orderId}/items")
@RestLink(rel = "items", entityType = Order.class)
public List<Item> getOrderItems(@RestPath String orderId) {
return itemService.findByOrderId(orderId);
}
@POST
@Path("/{orderId}/cancel")
@RestLink(rel = "cancel", entityType = Order.class)
public Order cancelOrder(@RestPath String orderId) {
return orderService.cancel(orderId);
}
}
@Path("/orders")
public class OrderResource2 {
@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks
public Order getOrder(@RestPath String id) {
// Links from OrderResource will be included
return orderService.findById(id);
}
}@ApplicationScoped
public class CustomLinksProvider implements RestLinksProvider {
@Override
public Collection<Link> getTypeLinks(Class<?> elementType) {
List<Link> links = new ArrayList<>();
if (elementType == Book.class) {
links.add(Link.fromUri("/books")
.rel("collection")
.build());
links.add(Link.fromUri("/books/search")
.rel("search")
.build());
}
return links;
}
@Override
public <T> Collection<Link> getInstanceLinks(T instance) {
List<Link> links = new ArrayList<>();
if (instance instanceof Book book) {
links.add(Link.fromUri("/books/" + book.getId())
.rel("self")
.build());
links.add(Link.fromUri("/books/" + book.getId() + "/reviews")
.rel("reviews")
.build());
if (book.getAuthorId() != null) {
links.add(Link.fromUri("/authors/" + book.getAuthorId())
.rel("author")
.build());
}
}
return links;
}
}public class Order {
@RestLinkId
private String orderId;
private User user;
private List<Item> items;
private List<Link> links;
}
public class User {
@RestLinkId
private String userId;
private String name;
private List<Link> links;
}
@Path("/orders")
public class OrderResource {
@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks(RestLinkType.INSTANCE)
public Order getOrder(@RestPath String id) {
// Both Order and nested User will have links injected
return orderService.findById(id);
}
}@ApplicationScoped
public class ConditionalLinksProvider implements RestLinksProvider {
@Inject
SecurityContext securityContext;
@Override
public <T> Collection<Link> getInstanceLinks(T instance) {
List<Link> links = new ArrayList<>();
if (instance instanceof Book book) {
// Always include self link
links.add(Link.fromUri("/books/" + book.getId())
.rel("self")
.build());
// Only include edit/delete for admins
if (securityContext.isUserInRole("ADMIN")) {
links.add(Link.fromUri("/books/" + book.getId())
.rel("edit")
.build());
links.add(Link.fromUri("/books/" + book.getId())
.rel("delete")
.build());
}
// Only include purchase link if book is available
if (book.isAvailable()) {
links.add(Link.fromUri("/books/" + book.getId() + "/purchase")
.rel("purchase")
.build());
}
}
return links;
}
@Override
public Collection<Link> getTypeLinks(Class<?> elementType) {
return Collections.emptyList();
}
}public class PagedResult<T> {
private List<T> items;
private int page;
private int pageSize;
private long totalItems;
private List<Link> links;
}
@Path("/books")
public class BookResource {
@GET
@InjectRestLinks
public PagedResult<Book> listBooks(
@RestQuery @DefaultValue("0") int page,
@RestQuery @DefaultValue("20") int pageSize
) {
PagedResult<Book> result = bookService.findPaginated(page, pageSize);
// Links are automatically added:
// - self: current page
// - first: first page
// - last: last page
// - next: next page (if available)
// - prev: previous page (if available)
return result;
}
}Follow standard link relations from IANA Link Relations:
@RestLink(rel = "self") // Current resource
@RestLink(rel = "edit") // Edit link
@RestLink(rel = "collection") // Collection link
@RestLink(rel = "item") // Item in collection
@RestLink(rel = "next") // Next page
@RestLink(rel = "prev") // Previous page
@RestLink(rel = "first") // First page
@RestLink(rel = "last") // Last page@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks
public Entity getEntity(@RestPath String id) {
return service.findById(id);
}@GET
@Path("/{userId}/orders")
@RestLink(rel = "orders", entityType = User.class)
public List<Order> getUserOrders(@RestPath String userId) {
return orderService.findByUserId(userId);
}public class Entity {
private List<Link> links = new ArrayList<>();
public List<Link> getLinks() {
return Collections.unmodifiableList(links);
}
}No specific configuration required for basic REST Links usage. The feature is automatically enabled when annotations are present.
For custom providers, register them as CDI beans:
@ApplicationScoped
public class MyLinksProvider implements RestLinksProvider {
// Implementation
}