Skip to main content

Release 10.0.0: Other modules

This page is part of the Release 10.0.0 release notes. The overview page lists the major changes, the breaking changes and the API changes for the whole release.

juneau-commons / juneau-secret-macos-keychain​

SecretStore SPI — the secure, mutable sibling of PropertySource​

Juneau 10.0 adds a small, JDK-only SecretStore SPI (org.apache.juneau.commons.secret, @since 10.0.0) for storing and retrieving secrets by key — store / find / exists / delete. It is the secure, mutable sibling of PropertySource: values are held as char[] (never String), and are never toString()'d, logged, or dumped.

  • InMemorySecretStore — the zero-config, ConcurrentHashMap-backed, process-local default (honest about being neither persistent nor cross-node), with defensive-copy in/out so callers can zero their own arrays.
  • EnvVarSecretStore — a read-only store backed by System.getenv (store/delete are unsupported).
  • SecretStoreProvider + META-INF/services — ServiceLoader discovery mirroring PropertySourceProvider; opt-in via SecretStores.fromServiceLoader().
  • SecretStores.resolve(beanStore) — resolves the active store from a BeanStore, defaulting to InMemorySecretStore (the BeanStore-with-drop-in-default idiom used elsewhere in the framework).
  • Opt-in Settings bridge — SecretStorePropertySource exposes a chosen store as a PropertySource only where a consumer deliberately wires it, and implements the SensitivePropertySource marker so dump/log/toString() paths redact bridged values. Secrets stay out of the general config/$P{...}/SVL namespace by default.
  • New opt-in module juneau-secret-macos-keychain — a KeychainSecretStore backed by the macOS security keychain CLI, honoring a ReplayCache-style FailMode (fail-open/fail-closed) for backend-unavailable behavior. It lives outside juneau-commons because it shells out to an OS process. store sends the command to security -i on stdin, so the secret never appears on a command line and a JVM started from a terminal is never left waiting on a password prompt. find reads the password from security find-generic-password -g, so secrets containing tabs, other control characters, non-ASCII text, quotes or backslashes come back exactly as stored.

See the Secrets section of the Settings topic page for details.

juneau-commons / juneau-beanquery-sql / juneau-beanquery-postgres​

BeanQuery contexts and sessions​

org.apache.juneau.commons.beanquery is a protocol-agnostic API for searching, sorting, projecting and paging rows of beans, in memory or over JDBC. Engines follow a context/session split:

  • Immutable, builder-built contexts. InMemoryBeanQueryContext.create(Person.class)...build() and SqlBeanQueryContext.create(Person.class)...build() fix the columns, operators, limits and a server-side guard once. copy() derives a variant.
  • Per-request sessions. getSession(rows) / getSession(), or createSession()...build() for per-request rules: narrow the columns, override the limits, add a guard, or pass a caller-owned JDBC Connection. A session offers find, findValues, count, stream, streamValues and resolve.
  • Validate before run. Every query is resolved into a ResolvedQuery first. Unknown columns, disallowed operators, over-long search strings, too many clauses or sort keys, and over-deep expressions are rejected with BeanQuerySyntaxException; they are never silently dropped. defaultLimit (100) applies when limit is missing; a limit above maxLimit (1000) is clamped to it, never rejected.
  • Counts by policy. CountPolicy (NONE, MATCHED, BOTH, IF_REQUESTED) decides which counts a Page carries. Page.total()/Page.matched() are independently-optional (see below).
  • Safe regex. $regex is enabled by default; allowRegex(false) disables it. In memory, each call's regex matching shares one regexTimeout deadline (default 50 ms).
  • Engine errors. Failures raise BeanQueryExecutionException (a server error), whose message names no SQL or bind values.
  • SQL engine. Guards are parenthesized and AND-ed into every statement. There are fetch-size and query-timeout settings, and connections come from a DataSource or supplier and are released by the session. stream reads rows lazily and closes its cursor with the stream or the session.
  • DataTables. The juneau-console-datatables adapter runs on the new session API. It asks for counts with CountRequest.BOTH on the query builder and maps length=-1 to maxLimit.
  • BeanQuery.Builder/GroupBuilder. Replace the removed BeanQueryBuilder. Fluent eq/ne/in/contains/prefix/gt/gte/lt/lte/between/regex/op/search conditions, promotable into cross-column $or/$and/$not groups via GroupBuilder. Repeated conditions on the same column built through the fluent API no longer silently overwrite each other — they merge into an implicit $and(...).
  • One resolved search tree. Filter/FilterResolver/FilterEvaluator give the in-memory and SQL engines a single, already-validated search tree to walk instead of each parsing search strings independently.
  • Independently-optional counts. Page.hasTotal() is gone; Page.total()/Page.matched() are OptionalLong, so a caller that did not ask for a count gets an empty OptionalLong back rather than an exception.
  • DataTables global search box + legacy-search parity. The DataTables adapter now maps the global search box to a synthesized $or across searchable columns, each with a leaf chosen by its declared SearchType ($contains for TEXT/ID, $prefix for VERSION, $eqic for ENUM, and $eq conditionally for NUMERIC/BOOLEAN/TIMESTAMP only when the global term parses as that type, otherwise that column is silently skipped) instead of $contains for every column, and reports counts through the OptionalLong pair above.
  • RelativeDuration literals. A signed [+-]<n>(ms|s|m|h|d) wire literal (e.g. -24h for "24 hours ago") round-trips through the builder for TIMESTAMP/duration columns (parity with a common legacy search syntax).
  • Search language hardening. $eq/$in/$ne are now exact and case-sensitive, with a new case-insensitive $eqic counterpart; a ? single-character wildcard joins the existing *; a quoted token bypasses wildcard interpretation entirely; $$ is the literal-dollar escape; operator names are case-insensitive; $regex gains an optional flags=i|m|s argument; SearchOperator is now immutable (create(...).build(), with copy() for a pre-filled builder) with a typed extension(Class<X>) hook replacing the untyped sql(Object) hook; parse failures throw BeanQuerySyntaxException with a specific Code; a shared JSON parity corpus keeps the server (Java) and client (juneau-search.js) engines byte-for-byte identical; and SearchType.bareHelp() adds per-type search help text, including a UTC-midnight clarification for bare TIMESTAMP values. See the Porting searches from another $-operator syntax note on the topic page below for migrating a case-insensitive $eq(...) search.
  • Postgres dialect and typed binds. PostgresDialect lives in its own module and package (juneau-beanquery-postgres, org.apache.juneau.beanquery.postgres). Search literals are parsed once per query by SearchType.parse(String) (NUMERIC to BigDecimal, BOOLEAN to Boolean, TIMESTAMP to OffsetDateTime/LocalDateTime/LocalDate) and exposed as SearchExpression.typedValue()/typedArgs(). A value that does not parse for its column type is BeanQuerySyntaxException.Code.BAD_VALUE (HTTP 400) in both the in-memory and SQL engines. SearchSqlRenderer and SqlDialect take typed Object values, and a custom operator opts in with SearchOperator.Builder.typedArgs(true). Relative-duration literals [+-]<n>(ms|s|m|h|d) work for NUMERIC and TIMESTAMP columns and resolve against a single per-query request time.
  • ISO-8601 durations in BeanQuery. Wherever a NUMERIC or TIMESTAMP column accepts a relative literal such as -24h, it now also accepts a signed ISO-8601 duration (PT24H, -PT24H, +PT30M, P1D) with identical semantics, in the in-memory and SQL engines and in juneau-search.js; invalid ISO text is BAD_VALUE. Existing literals and bare epoch milliseconds are unchanged.
  • Built-in SQL row mappers. SqlRowMapper.map() and SqlRowMapper.bean(Class) (records or setter beans), chosen automatically by SqlBeanQueryContext.Builder.build() when no rowMapper(...) is set.
  • In-memory and SQL parity. A SQL dialect that does not support an operator (such as $regex), or that does not declare supportsVersionOrdering() for the VERSION range operators, rejects that query with BAD_VALUE instead of silently returning different rows. PostgresDialect compares ENUM columns as text (wildcards included), compares VERSION columns as dotted-integer tuples, renders $regex with the POSIX ~/~* operators (mirroring flags=i|m|s; Postgres and Java regex syntax differ), and makes $not match NULL cells as the in-memory engine does.
  • BREAKING: PostgresDialect moved to org.apache.juneau.beanquery.postgres, and SearchSqlRenderer/SqlDialect.renderBare now receive typed Object values. See the Migration Guide.

See the new BeanQuery Package topic page.

Build & packaging (BOM + curated bundles)​

Published BOM + curated dependency bundles​

Juneau now publishes a Bill of Materials and a small set of curated dependency bundles so consumers can depend on a coherent, version-aligned slice of the framework without hand-maintaining <version> tags — while keeping Juneau's explicit-over-magic configuration model (no Spring-style classpath auto-configuration).

  • org.apache.juneau:juneau-bom (packaging pom) — import it (scope=import, type=pom) and drop explicit versions from your Juneau dependencies. Enumerates the consumer-facing Juneau modules plus the small set of starter-relevant third-party versions the bundles need (micrometer-core, opentelemetry-api, jakarta.servlet-api, jakarta.validation-api). It is not a whole-world third-party BOM, so it won't collide with a third-party BOM you import yourself.
  • Three deployment-shape bundles (packaging pom) — depend on one and transitively pull a coherent module set plus juneau-rest-client:
    • juneau-microservice-jetty-bundle
    • juneau-microservice-tomcat-bundle
    • juneau-springboot-bundle
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-bom</artifactId>
<version>10.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

The bundles are dependency aggregators only — they register no configurations and perform no classpath auto-discovery. Configuration registration stays explicit via Microservice.Builder.configurations(Class<?>...). This is a deliberate design choice: per the maintainer, "I actually see auto-configuration as a weakness in Spring Boot that leads to applications with long startup times and impossible-to-understand architectures." These bundles are also distinct from the clone-and-go juneau-microservice-*-starter templates, which live as true-orphan branches of apache/juneau (microservice-jetty-starter, microservice-tomcat-starter, microservice-springboot-starter). Clone with git clone -b microservice-jetty-starter --single-branch https://github.com/apache/juneau.git my-app (replace the branch name for the other two). See Starter Projects.

A reactor-consistency guard (scripts/check-bom-completeness.py, wired into the push gate) fails the build if a published module is missing from the BOM, so the enumeration can't silently drift. The binary distribution now also bundles juneau-microservice-tomcat (previously omitted).

See the new Dependency Management (BOM & Bundles) topic page for usage and the full explicit-over-magic rationale.

GraalVM native-image feasibility verdict + Docker layering guidance​

A feasibility assessment of GraalVM native-image support for Juneau's reflection-heavy marshalling/bean core landed a CONDITIONAL GO verdict: all reflection is funnelled through one wrapper layer (org.apache.juneau.commons.reflect.*, single setAccessible() choke point) with no Unsafe/MethodHandle/bytecode generation, and the BeanContext/ClassMeta model already computes the exact member set reachability metadata needs — making a Juneau-authored metadata emitter realistic. The maintainer-approved follow-on (a BeanContext-driven reachability-metadata.json emitter targeting the juneau-marshall core first, REST server deferred) is tracked as a dedicated committed work item; native-image builds of Juneau are not yet supported. The new topic page also documents Docker image-layering guidance for Juneau apps. See GraalVM Native Image & Docker Layering.

juneau-petstore (new module family)​

Three new modules under a top-level juneau-petstore/ aggregator together form the canonical Juneau petstore showcase application. The legacy juneau-examples-rest{,-jetty,-springboot,-jetty-ftest} family of modules has been retired in this release in favor of the petstore family — see the Breaking Changes section below.

juneau-petstore-core​

Shared, deployment-agnostic surface consumed verbatim by both the Jetty and Spring Boot deployments. Owns:

  • Domain beans — Pet, Order, User (plus Species/PetStatus/OrderStatus/UserStatus enums) under org.apache.juneau.petstore.dto. Plain Juneau beans, no JPA/Hibernate/Derby.
  • In-memory PetStore service — ConcurrentHashMap-backed, lazy-loads classpath seed JSON at petstore/init/{Pets,Orders,Users}.json via Json5Parser. Auto-IDs via AtomicLong.
  • PetStoreResource — full CRUD @Rest servlet (GET/POST/PUT/DELETE over /pets, /orders, /users). Uses org.apache.juneau.http.response.NotFound (next-gen) and @org.apache.juneau.http.Path/@Content. Spring-agnostic — extends BasicRestServlet so both deployments mount it identically.
  • View-engine demos — the Mustache and Freemarker subtabs of the console's rendering-flavors page (/console/dev/flavors/mustache, /console/dev/flavors/freemarker) demonstrate the typed-View return path through the juneau-rest-server-view-{mustache,freemarker} bridges. Pure-classpath engines so both deployments inherit view rendering at parity. (They replace the retired PetMustacheViewResource / PetFreemarkerViewResource.)
  • No-build React SPA demo — PetstoreUiResource mounts StaticFilesMixin to serve htdocs/petstore-ui.html, a single-page React UI that calls /petstore/pets over fetch(). Demonstrates the headless-JSON + decoupled-SPA pattern alongside the server-rendered HTML-doc UI; no Node/npm in the build and no network access needed (React and ReactDOM 18.3.1 are vendored under htdocs/vendor/, MIT-licensed, acknowledged in the root LICENSE; the app uses plain React.createElement, so there is no in-browser JSX compiler).
  • Secure demo + StubBearerTokenValidator — the console's bearer-token demo page (/console/dev/secure, with SecureApiRest) replaces the retired PetSecureResource (/petstore-secure/*); the auth-gated CRUD subset is gated by a BearerTokenGuard (fail-closed, op-level) backed by the in-memory stub validator. Demonstrates the bearer-token + @Auth Principal pattern; resource-Javadoc cross-references AuthFilterChain for multi-mechanism deployments.

juneau-petstore-jetty​

Jetty/Microservice deployment glue:

  • App.main calls Microservice.create().configurations(JettyConfiguration.class, AppConfig.class)....
  • RootResources extends BasicRestServletGroup — mounts every core child resource plus the Jetty-only Microservice admin trio (ConfigResource/LogsResource/ShutdownResource), sourced from the new juneau-microservice-examples module (see Breaking Changes below).
  • Bundled jetty.xml and juneau-petstore-jetty.cfg.
  • Starter-zip assembly — sources + cfg + jetty.xml for IDE-import use.
  • In-module integration tests (@JettyMicroserviceTest, ephemeral port).
  • Default URL: http://localhost:10000.

Launcher: python3 scripts/start-petstore-jetty.py.

juneau-petstore-springboot​

Spring Boot deployment glue:

  • App is a @SpringBootApplication with @Bean RootResources + @Bean ServletRegistrationBean<Servlet> mounted at /*.
  • RootResources extends BasicSpringRestServletGroup so child resources resolve as Spring beans (required for HelloResource's @Autowired HelloMessageProvider injection demo).
  • Inherits ${spring.version} from the repo parent.
  • application.properties configures port 5000.
  • spring-boot-maven-plugin repackage produces a runnable fat jar.
  • @SpringBootTest(webEnvironment=RANDOM_PORT) integration tests.
  • Default URL: http://localhost:5000.

Launcher: python3 scripts/start-petstore-springboot.py.

Parity story​

The core-hosted surface (CRUD, console, view demos, React SPA demo, auth-gated subset) is identical across both deployments by construction — it's shared code, not parallel copies. The deployment-specific surface (Jetty's microservice console + admin resources vs Spring Boot's @Autowired injection demo) is intentionally non-parity and documented as such.

Petstore admin console (/console)​

The petstore now ships a complete admin console at /console, built on console-ui and offered as the worked example for the framework pages. See juneau-petstore.

  • Fourteen pages (P1-P14). Each page has an FTL template and a Java builder twin (?builder), so the same page is shown both ways.
  • Server-mode tables driven by BeanQuery: filter, sort and page the pets, orders and users on the server.
  • Staged pet edits with the pending-changes badge (the photo field is excluded from staging).
  • Restock async job with a job-log card that follows the job as it runs.
  • Audit trail of console writes.
  • Custom cards. Petstore: kpi and inventory-gauge custom cards on /console/dev/cards demonstrate adopter card types.
  • Four rendering flavors (Builder, Html, Mustache and Freemarker) of the same pet list under /console/dev/flavors.
  • Bearer-token demo at /console/dev/secure, next to a CSRF boundary on /console/* in both deployments.
  • Tests. A Playwright browser harness exercises the pages, and FailLoud negative fixtures pin the error messages for mis-built pages.
  • Retired legacy resources. PetInfoResource, PetHtmlResource, PetSecureResource, PetMustacheViewResource and PetFreemarkerViewResource (and their /petstore-info, /petstore-html, /petstore-secure and /pet-views URLs) are gone, replaced by console pages. This is a breaking change for users of the sample only; the petstore is a sample, so it is not listed under Breaking Changes below.

Petstore legacy-surface completion​

The retired juneau-examples-rest family (see Breaking Changes below) carried six REST-servlet demonstrations that hadn't yet been ported into petstore or consciously dropped. That gap is now closed — three demos got petstore-flavored equivalents, and three were consciously dropped:

Ported to juneau-petstore-core, wired into both the Jetty and Spring Boot deployments:

  • AboutRest (/console/about, replacing the retired PetInfoResource at /petstore-info) — utility-beans demo: BeanDescription/Hyperlink/SeeOtherRoot rendered against the petstore's own Pet domain bean.
  • HtmlFlavorRest (/console/dev/flavors/html, replacing the retired PetHtmlResource at /petstore-html) — HTML5 bean-builder demo: a hand-built HTML5 Table fragment of all pets rendered from live petstore data.
  • GET/PUT /petstore/pets/{id}/photo (added to PetStoreResource) — image upload/serve demo, backed by new petstore-local PetPhotoSerializer/PetPhotoParser classes (org.apache.juneau.petstore.marshall) and wired to the previously-unused Pet.photo field.

Consciously dropped (not ported):

  • ATOM-feed DTO serialization demo — generic marshalling showcase with no petstore-domain tie-in; Atom (de)serialization itself remains exhaustively covered by juneau-bean-atom's own test suite and docs.
  • JSON-Schema DTO serialization demo — same shape as the Atom case; JSON-Schema generation is already exhaustively covered by juneau-bean-jsonschema's test suite and docs.
  • Server-Sent-Events demo — SSE transport mechanics are already integration-tested independent of any demo resource; a petstore-flavored SSE feature (e.g. live status updates) would be net-new feature work, out of scope for a port-or-drop cleanup.

The plan's remaining stretch scope — a @Remote typed-client demo and a Config API + SVL var-resolver showcase — was split out to a separate follow-on work item rather than folded in here.

juneau-examples-core / juneau-petstore-core​

  • image/jpeg accepted: the example ImageParser and the petstore PetPhotoParser now consume image/jpeg as well as image/png and image/jpg.

juneau-bean-rfc7807 (new module)​

A new bean module, juneau-bean-rfc7807, provides typed beans for RFC 7807 — Problem Details for HTTP APIs (application/problem+json). RFC 7807 was obsoleted by RFC 9457 in July 2023, but the data model and the IANA media-type registration are unchanged.

The module ships a single Problem bean covering the five canonical members defined in §3.1 (type, title, status, detail, instance) plus an open-ended extension-member fan-out per §3.2. Only depends on juneau-marshall.

Bean shape​

  • URI type — nullable. getType() returns the raw value; getTypeOrDefault() returns URI.create("about:blank") per §3.1 when unset (without mutating the field).
  • String title — short human-readable summary of the problem type.
  • Integer status — OPTIONAL HTTP status code; serialized as a JSON number when set, omitted when null.
  • String detail — human-readable explanation specific to this occurrence.
  • URI instance — URI identifying the specific occurrence.
  • Extension members (§3.2) flatten into the top-level JSON object via a @BeanProp("*") triplet (extraKeys() / get(String) / set(String, Object)) over a private Map<String,Object>.

A single static factory, Problem.fromStatus(int status, String title, String detail), covers the common "build a problem from status code + title + detail" call site. Callers wire from BasicHttpException themselves with one line:

Problem.fromStatus(e.getStatusCode(), e.getStatusLine().getReasonPhrase(), e.getMessage());

The bean does not eagerly default the type field, so a fresh new Problem() round-trips without a synthetic "type":"about:blank" on the wire.

Example​

import org.apache.juneau.bean.rfc7807.*;
import org.apache.juneau.marshall.json.*;

Problem problem = new Problem()
.setType(new URI("https://example.com/probs/out-of-credit"))
.setTitle("You do not have enough credit.")
.setStatus(403)
.setDetail("Your current balance is 30, but that costs 50.")
.setInstance(new URI("/account/12345/msgs/abc"))
.set("balance", 30)
.set("accounts", List.of("/account/12345", "/account/67890"));

String json = JsonSerializer.DEFAULT.serialize(problem);
Problem back = JsonParser.DEFAULT.parse(json, Problem.class);

The matching Content-Type constants (ContentType.APPLICATION_PROBLEM_JSON and ContentType.APPLICATION_PROBLEM_XML) have shipped in juneau-rest-common since 9.2.x; this module closes the loop by adding the typed wire bean.

Throw + map SPIs​

The module also ships the seams used by the new juneau-rest-server problem-details wiring (see RFC 7807 / 9457 Problem-Details server-side wiring above):

  • ProblemException — RuntimeException carrying a Problem. Lets handlers throw a custom problem without manually building a BasicHttpException. The bean module owns this type so the juneau-rest-server processor can unwrap it without a reverse dependency.
  • ProblemMapper<T extends Throwable> — pluggable interface for translating arbitrary throwables into Problem shapes; the server-side processor discovers it via the resource bean store and dispatches in most-specific-first order across the exception hierarchy.
  • ProblemMapperList — aggregator bean for registering more than one mapper on the same resource (works around the single-slot collapse of multiple @Bean public ProblemMapper foo() factories that share a return type).
  • ProblemLocalizationStrategy — functional-interface SPI seam for future locale-aware translation of Problem.title / Problem.detail. Defaults to ProblemLocalizationStrategy.IDENTITY (pass-through). A reference Messages (resource-bundle) implementation is intentionally deferred to a future release.

See juneau-bean-rfc7807 for the full topic.

juneau-bean-hal (new module)​

A new bean module, juneau-bean-hal, models the HAL (Hypertext Application Language) hypermedia format (draft-kelly-json-hal-08) as Juneau @Marshalled POJOs. The beans round-trip cleanly through the default JsonSerializer / JsonParser for application/hal+json payloads — no custom marshaller required. Only depends on juneau-marshall. The matching Content-Type constant (ContentType.APPLICATION_HAL_JSON) has shipped in juneau-rest-common since 9.2.x; this module closes the loop by adding the typed wire beans.

Bean shape​

  • HalResource — top-level HAL resource carrying the reserved _links map (Map<String,Object> whose values are single HalLink or HalLinkArray), the reserved _embedded map (Map<String,Object> whose values are single HalResource or HalResourceArray), and an open-ended payload via the @BeanProp("*") extra-properties triplet (extraKeys() / get(String) / set(String, Object)).
  • HalLink — all eight Link Object fields per draft-kelly-json-hal-08: href (required URI/URI-template), templated, type, deprecation, name, profile, title, hreflang.
  • HalLinkArray — LinkedList<HalLink> used as the multi-link branch inside an _links map. Mirrors the top-level-array pattern used by JsonSchemaArray.
  • HalResourceArray — LinkedList<HalResource> used as the multi-resource branch inside an _embedded map.
  • HalLinkOrArraySwap / HalResourceOrArraySwap — per-property ObjectSwaps applied to _links / _embedded so each map entry round-trips as either a single object or a JSON array per the spec.

Convenience methods on HalResource make the spec's single-or-array shapes ergonomic: addLink(rel, link) sets a single-link value; addLinks(rel, links...) promotes the entry to a HalLinkArray (creating one or appending to an existing array as needed). The same pattern is mirrored for _embedded via addEmbedded(rel, res) / addEmbedded(rel, res...).

Example​

import org.apache.juneau.bean.hal.*;
import org.apache.juneau.marshall.json.*;

HalResource order = new HalResource()
.addLink("self", new HalLink("/orders/123"))
.addLinks("curies",
new HalLink("https://acme.example/{rel}").setName("acme").setTemplated(true))
.set("currency", "USD")
.set("total", 99.50)
.addEmbedded("customer",
new HalResource()
.addLink("self", new HalLink("/customers/7"))
.set("name", "Kelsey"));

String json = JsonSerializer.DEFAULT.serialize(order);
HalResource back = JsonParser.DEFAULT.parse(json, HalResource.class);

See juneau-bean-hal for the full topic.

juneau-bean-jsonapi (new module)​

A new bean module, juneau-bean-jsonapi, models the JSON:API v1.1 wire format (application/vnd.api+json) as Juneau @Marshalled POJOs. The beans round-trip cleanly through the default JsonSerializer / JsonParser — no custom marshaller required. Only depends on juneau-marshall. The matching Content-Type constant (ContentType.APPLICATION_VND_API_JSON) has shipped in juneau-rest-common since 9.2.x.

Bean shape​

  • JsonApiDocument — top-level container with data, errors, meta, jsonapi, links, included. Includes a validate() helper that enforces the document-level invariants: at least one of data / errors / meta must be present, and data and errors MUST NOT coexist.
  • JsonApiResource — resource object with type, id, attributes, relationships, links, meta. type is modeled as a plain String (and not as a @Marshalled(typePropertyName="type", dictionary=...) discriminator) because JSON:API type is an open-ended entity-type name chosen by the API; turning it into a Juneau dispatch discriminator would break round-tripping of unknown values and force every API to enumerate its resource types in a Java dictionary up front.
  • JsonApiResourceIdentifier — slim type/id/meta form used inside relationship linkage.
  • JsonApiRelationship — data linkage (null, single JsonApiResourceIdentifier, or list of them), links, meta.
  • JsonApiError + JsonApiErrorSource — error object with id, links, status, code, title, detail, source, meta; the source sub-object carries pointer, parameter, header.
  • JsonApiLink — link object with href, rel, describedby, title, type, hreflang, meta.
  • JsonApiVersion — the jsonapi member (version, meta).
  • JsonApiLinkOrStringSwap — per-property ObjectSwap applied to every links map (on JsonApiDocument, JsonApiResource, JsonApiRelationship, JsonApiError) so that each map entry round-trips as either a JSON string URL or a JsonApiLink JSON object per the spec.

Example​

import org.apache.juneau.bean.jsonapi.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.marshall.json.*;

JsonApiResource article = new JsonApiResource("articles", "1")
.setAttributes(JsonMap.of("title", "JSON:API paints my bikeshed!"))
.putRelationship("author",
new JsonApiRelationship().setData(new JsonApiResourceIdentifier("people", "9")));
JsonApiDocument doc = new JsonApiDocument()
.setJsonapi(new JsonApiVersion("1.1"))
.setData(article);

String json = JsonSerializer.DEFAULT.serialize(doc);
JsonApiDocument back = JsonParser.DEFAULT.parse(json, JsonApiDocument.class);

See juneau-bean-jsonapi for the full topic.

juneau-bean-jsonpatch (new module)​

A new bean module, juneau-bean-jsonpatch, models JSON Patch (RFC 6902) operation documents (application/json-patch+json) as Juneau @Marshalled POJOs with polymorphic dispatch on the op discriminator. Only depends on juneau-marshall. The matching Content-Type constant (ContentType.APPLICATION_JSON_PATCH) has shipped in juneau-rest-common since 9.2.x.

Bean shape​

  • JsonPatchOperation — abstract base carrying the shared path (JSON Pointer / RFC 6901) field. Annotated @Marshalled(typePropertyName="op", dictionary={AddOp, RemoveOp, ReplaceOp, MoveOp, CopyOp, TestOp}) so the parser materializes the correct concrete subclass keyed off the op member. Kept open (not sealed) so downstream callers can extend the dictionary with custom ops.
  • AddOp, RemoveOp, ReplaceOp, MoveOp, CopyOp, TestOp — the six standard operations from §4. add/replace/test carry a value payload (Object); move/copy carry a from JSON Pointer; remove has no extra members.
  • JsonPatch — LinkedList<JsonPatchOperation> representing a JSON Patch document, which serializes as a top-level JSON array per the RFC. Mirrors the JsonSchemaArray top-level-array pattern (rather than @Json(wrapperAttr=...)).

Polymorphic Serialization​

JsonPatchOperation uses @Marshalled(typePropertyName="op", dictionary={...}) to discriminate at the wire level. Configure your serializer with addBeanTypes() to emit the discriminator on output:

JsonSerializer ser = JsonSerializer.create()
.addBeanTypes()
.typePropertyName(JsonPatchOperation.class, "op")
.build();

JsonParser par = JsonParser.create()
.typePropertyName(JsonPatchOperation.class, "op")
.build();

Example​

import org.apache.juneau.bean.jsonpatch.*;

JsonPatch patch = new JsonPatch()
.append(new TestOp("/a/b/c", "foo"))
.append(new RemoveOp("/a/b/c"))
.append(new AddOp("/a/b/c", java.util.List.of("foo", "bar")))
.append(new ReplaceOp("/a/b/c", 42))
.append(new MoveOp("/a/b/d", "/a/b/c"))
.append(new CopyOp("/a/b/e", "/a/b/d"));

String wire = ser.serialize(patch);
JsonPatch back = par.parse(wire, JsonPatch.class);

See juneau-bean-jsonpatch for the full topic.