juneau-bean-hal
The juneau-bean-hal module provides Java beans for the HAL (Hypertext Application Language) hypermedia format, also described in draft-kelly-json-hal-08. HAL is a JSON-based wire format that lets a REST API describe its resources alongside the links and embedded sub-resources that surround them.
Overview
HAL standardises two reserved members on every resource: _links (a map of relation name to one or more HalLink objects) and _embedded (a map of relation name to one or more sub-resources). Everything else in the object is the resource's own payload. This module brings that wire format to Juneau as typed @Marshalled POJOs that round-trip through the default JsonSerializer / JsonParser — no custom marshaller required.
Key Features
- Typed beans for the full HAL spec —
HalResource,HalLink,HalLinkArray,HalResourceArray, with all eight Link Object fields per draft-kelly-json-hal-08. - Spec-correct single-or-array handling —
_linksand_embeddedmap values can be either a single object or a JSON array per the spec. Per-propertyObjectSwaps (HalLinkOrArraySwap/HalResourceOrArraySwap) handle the union both directions. - Open-ended payload via
@BeanProp("*")— Arbitrary resource fields are added viaset(String, Object)and read back viaget(String)/extraKeys(). They serialise flat at the top level alongside_linksand_embedded. - Fluent ergonomics for the single-or-array shapes —
addLink(rel, link)sets a single-link entry;addLinks(rel, links...)promotes the entry to aHalLinkArray(creating one or extending an existing array as needed). Same pattern is mirrored for_embedded. - No external dependencies — Only depends on
juneau-marshall.
Bean Classes
| Class | Notes |
|---|---|
HalResource | Top-level HAL resource. Reserved _links and _embedded maps + open payload. |
HalLink | Link Object with href, templated, type, deprecation, name, profile, title, hreflang. |
HalLinkArray | LinkedList<HalLink> — multi-link branch inside an _links map. |
HalResourceArray | LinkedList<HalResource> — multi-resource branch inside an _embedded map. |
HalLinkOrArraySwap | Per-property ObjectSwap resolving each _links value to single HalLink or HalLinkArray. |
HalResourceOrArraySwap | Per-property ObjectSwap resolving each _embedded value to single HalResource or HalResourceArray. |
Basic Usage
Building a HAL resource
import org.apache.juneau.bean.hal.*;
import org.apache.juneau.marshall.json.*;
HalResource order = new HalResource()
.addLink("self", new HalLink("/orders/123"))
.addLink("warehouse", new HalLink("/warehouse/56"))
.addLink("invoice", new HalLink("/invoices/873"))
.addLinks("curies",
new HalLink("https://acme.example/{rel}").setName("acme").setTemplated(true))
.set("currency", "USD")
.set("status", "shipped")
.set("total", 30.00)
.addEmbedded("customer",
new HalResource()
.addLink("self", new HalLink("/customers/7"))
.set("name", "Kelsey"));
String wire = JsonSerializer.DEFAULT.write(order);
HalResource back = JsonParser.DEFAULT.read(wire, HalResource.class);
Single-link vs. link-array
HAL allows a relation in _links to map to either a single Link Object or an array of them. The convenience methods make the choice explicit at the call site:
addLink(rel, link)— sets a single-link value atrel, replacing any existing value.addLinks(rel, links...)— promotes the entry underrelto aHalLinkArray. If the existing value is a singleHalLink, it is wrapped into a new array; if it is already an array, the new links are appended; if absent, a fresh array is created.
HalResource r = new HalResource()
.addLink("self", new HalLink("/orders/123")) // _links.self ⇒ object
.addLinks("curies",
new HalLink("https://acme.example/{rel}").setName("acme").setTemplated(true),
new HalLink("https://acme.example/v2/{rel}").setName("acme2").setTemplated(true)
); // _links.curies ⇒ array
The same pattern is mirrored on _embedded via addEmbedded(rel, res) and addEmbedded(rel, res...). The HalLinkOrArraySwap / HalResourceOrArraySwap swaps make both wire shapes round-trip cleanly through the default JSON parser.
Open payload via @BeanProp("*")
Arbitrary resource fields are added via set(String, Object) and read back via get(String) / extraKeys(). They serialise flat into the top-level object, alongside (and not nested under) _links and _embedded.
HalResource r = new HalResource()
.addLink("self", new HalLink("/orders/123"))
.set("total", 99.50)
.set("currency", "USD");
// {"_links":{"self":{"href":"/orders/123"}},"total":99.5,"currency":"USD"}
_embedded is a tree, not a graph
Per the spec, _embedded represents tree-shaped containment, not arbitrary graphs. Juneau's BeanTraverseContext throws on cyclic object references by default — which is the right behaviour here, so leave it on.
Content-Type
The matching Content-Type constant lives in juneau-rest-common and has been available since 9.2.x:
import org.apache.juneau.http.header.ContentType;
ContentType.APPLICATION_HAL_JSON; // "application/hal+json"