A Quarkus extension that implements the View Descriptor Protocol (VDP) — a standard mechanism for REST endpoints to advertise view templates and descriptors to clients through HTTP headers or inline JSON.
VDP bridges server-side data APIs with client-side view rendering. Annotate your JAX-RS endpoints with @VDP to automatically attach view metadata to responses, telling clients how to render the data they receive.
@GET
@Path("/dashboard")
@VDP(descriptor = "views/dashboard.json", transport = Transport.LINK_HEADER)
public Dashboard getDashboard() {
return new Dashboard("My Dashboard", List.of("Sales", "Traffic"));
}@VDPannotation for declarative view binding on JAX-RS endpoints- Multiple transport modes: HTTP headers, Link headers, or inline JSON
- View descriptors: JSON files describing complex, composable view layouts with slots
- Classpath descriptor loading with thread-safe caching
- Quarkus-native: build-time registration, CDI integration, Resteasy Reactive filter
- Java 25+
- Gradle 9.x (wrapper included)
- Quarkus 3.31.4+
Add the runtime dependency to your Quarkus application's build.gradle:
dependencies {
implementation 'org.sitenetsoft:quarkus-vdp:1.0.0-SNAPSHOT'
}./gradlew build./gradlew :integration-tests:testSend a View-Template header pointing clients to an HTML template:
@GET
@Path("/article")
@VDP(template = "https://example.com/templates/article.html", transport = Transport.VIEW_TEMPLATE)
public Article getArticle() {
return new Article("Hello", "World");
}Response:
HTTP/1.1 200 OK
View-Template: https://example.com/templates/article.html
Content-Type: application/json
{"title":"Hello","body":"World"}
Send a Link header referencing a view descriptor URL:
@GET
@Path("/dashboard")
@VDP(descriptor = "views/dashboard.json", transport = Transport.LINK_HEADER)
public Dashboard getDashboard() {
return new Dashboard("My Dashboard", List.of("Sales", "Traffic"));
}Response:
HTTP/1.1 200 OK
Link: <views/dashboard.json>; rel="view-descriptor"
Content-Type: application/json
{"title":"My Dashboard","widgets":["Sales","Traffic"]}
Wrap the response body with a _view key containing the template URL:
@GET
@Path("/product")
@VDP(template = "https://example.com/templates/product.html", transport = Transport.INLINE)
public Product getProduct() {
return new Product("Widget", 9.99);
}Response:
{
"_view": {
"template": "https://example.com/templates/product.html"
},
"name": "Widget",
"price": 9.99
}Load a JSON descriptor from the classpath and merge it into the response:
@GET
@Path("/inline-descriptor")
@VDP(descriptor = "views/dashboard.json", transport = Transport.INLINE)
public Dashboard getInlineDashboard() {
return new Dashboard("My Dashboard", List.of("Sales", "Traffic"));
}With src/main/resources/views/dashboard.json:
{
"template": "https://example.com/templates/dashboard.html",
"slots": {
"header": {
"template": "https://example.com/templates/header.html"
},
"widgets": [
{ "template": "https://example.com/templates/stat-card.html" },
{ "template": "https://example.com/templates/chart.html" }
]
}
}Response:
{
"_view": {
"template": "https://example.com/templates/dashboard.html",
"slots": {
"header": { "template": "https://example.com/templates/header.html" },
"widgets": [
{ "template": "https://example.com/templates/stat-card.html" },
{ "template": "https://example.com/templates/chart.html" }
]
}
},
"title": "My Dashboard",
"widgets": ["Sales", "Traffic"]
}Use Transport.AUTO (the default) to let the extension choose the transport automatically:
- If
descriptoris set →LINK_HEADER - If only
templateis set →VIEW_TEMPLATE
| Attribute | Type | Default | Description |
|---|---|---|---|
template |
String |
"" |
URL of the view template |
descriptor |
String |
"" |
Classpath path to a JSON view descriptor |
transport |
Transport |
Transport.AUTO |
How view metadata is delivered to the client |
| Mode | Mechanism | Use Case |
|---|---|---|
VIEW_TEMPLATE |
View-Template HTTP header |
Simple single-template views |
LINK_HEADER |
Link header with rel="view-descriptor" |
External descriptor references |
INLINE |
Merged into response JSON body | Self-contained responses |
AUTO |
Selects based on annotation values | Sensible defaults |
quarkus-vdp/
├── runtime/ # Extension runtime code
│ └── src/main/java/
│ └── VDP.java # @VDP annotation
│ └── VdpResponseFilter.java # Response filter (transport logic)
├── deployment/ # Build-time processor
│ └── src/main/java/
│ └── VdpProcessor.java # Registers feature & filter
├── integration-tests/ # Integration test module
│ └── src/main/java/
│ └── VdpResource.java # Example REST endpoints
│ └── src/test/java/
│ └── VdpResourceTest.java # Tests for all transport modes
└── build.gradle # Root build configuration
- Quarkus 3.31.4 — Supersonic Subatomic Java framework
- Resteasy Reactive — Non-blocking JAX-RS implementation
- Jackson — JSON serialization/deserialization
- Gradle 9.3.1 — Build tool
- Java 25 — Language target
- JUnit 5 + RestAssured — Testing
Copyright SiteNetSoft. All rights reserved.