or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.quarkus/quarkus-resteasy-reactive@3.15.x

docs

index.md
tile.json

tessl/maven-io-quarkus--quarkus-resteasy-reactive

tessl install tessl/maven-io-quarkus--quarkus-resteasy-reactive@3.15.0

A Jakarta REST implementation utilizing build time processing and Vert.x for high-performance REST endpoints with reactive capabilities in cloud-native environments.

rest-links.mddocs/reference/

REST Links (HATEOAS)

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

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;

Annotations

@RestLink

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

@RestLinkId

Marks a field or method parameter as the ID used for link generation.

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RestLinkId {
}

@InjectRestLinks

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

Enums

RestLinkType

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
}

Interfaces

RestLinksProvider

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

Basic Usage

Simple Entity with Links

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

Collection with Links

@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" }
//     ]
//   }
// ]

Link Types

Type-Level Links

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

Instance-Level Links

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

Custom Link Relations

Standard Link Relations

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

Custom Relations

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

Entity Type Specification

Explicit Entity Type

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

Custom Links Provider

Implementing RestLinksProvider

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

Complex Scenarios

Nested Entities

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

Conditional Links

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

Pagination Links

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

Best Practices

Use Standard Link Relations

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

Always Include Self Link

@GET
@Path("/{id}")
@RestLink(rel = "self")
@InjectRestLinks
public Entity getEntity(@RestPath String id) {
    return service.findById(id);
}

Use Entity Type for Related Resources

@GET
@Path("/{userId}/orders")
@RestLink(rel = "orders", entityType = User.class)
public List<Order> getUserOrders(@RestPath String userId) {
    return orderService.findByUserId(userId);
}

Keep Links Collections Immutable

public class Entity {
    private List<Link> links = new ArrayList<>();

    public List<Link> getLinks() {
        return Collections.unmodifiableList(links);
    }
}

Configuration

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
}