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 bySystem.getenv(store/deleteare unsupported).SecretStoreProvider+META-INF/services—ServiceLoaderdiscovery mirroringPropertySourceProvider; opt-in viaSecretStores.fromServiceLoader().SecretStores.resolve(beanStore)— resolves the active store from aBeanStore, defaulting toInMemorySecretStore(theBeanStore-with-drop-in-default idiom used elsewhere in the framework).- Opt-in Settings bridge —
SecretStorePropertySourceexposes a chosen store as aPropertySourceonly where a consumer deliberately wires it, and implements theSensitivePropertySourcemarker 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— aKeychainSecretStorebacked by the macOSsecuritykeychain CLI, honoring aReplayCache-styleFailMode(fail-open/fail-closed) for backend-unavailable behavior. It lives outsidejuneau-commonsbecause it shells out to an OS process.storesends the command tosecurity -ion 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.findreads the password fromsecurity 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()andSqlBeanQueryContext.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(), orcreateSession()...build()for per-request rules: narrow the columns, override the limits, add a guard, or pass a caller-owned JDBCConnection. A session offersfind,findValues,count,stream,streamValuesandresolve. - Validate before run. Every query is resolved into a
ResolvedQueryfirst. Unknown columns, disallowed operators, over-long search strings, too many clauses or sort keys, and over-deep expressions are rejected withBeanQuerySyntaxException; they are never silently dropped.defaultLimit(100) applies whenlimitis missing; alimitabovemaxLimit(1000) is clamped to it, never rejected. - Counts by policy.
CountPolicy(NONE,MATCHED,BOTH,IF_REQUESTED) decides which counts aPagecarries.Page.total()/Page.matched()are independently-optional (see below). - Safe regex.
$regexis enabled by default;allowRegex(false)disables it. In memory, each call's regex matching shares oneregexTimeoutdeadline (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
DataSourceor supplier and are released by the session.streamreads rows lazily and closes its cursor with the stream or the session. - DataTables. The
juneau-console-datatablesadapter runs on the new session API. It asks for counts withCountRequest.BOTHon the query builder and mapslength=-1tomaxLimit. BeanQuery.Builder/GroupBuilder. Replace the removedBeanQueryBuilder. Fluenteq/ne/in/contains/prefix/gt/gte/lt/lte/between/regex/op/searchconditions, promotable into cross-column$or/$and/$notgroups viaGroupBuilder. 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/FilterEvaluatorgive 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()areOptionalLong, so a caller that did not ask for a count gets an emptyOptionalLongback rather than an exception. - DataTables global search box + legacy-search parity. The DataTables adapter now maps the global search box to a synthesized
$oracross searchable columns, each with a leaf chosen by its declaredSearchType($containsfor TEXT/ID,$prefixfor VERSION,$eqicfor ENUM, and$eqconditionally for NUMERIC/BOOLEAN/TIMESTAMP only when the global term parses as that type, otherwise that column is silently skipped) instead of$containsfor every column, and reports counts through theOptionalLongpair above. RelativeDurationliterals. A signed[+-]<n>(ms|s|m|h|d)wire literal (e.g.-24hfor "24 hours ago") round-trips through the builder forTIMESTAMP/duration columns (parity with a common legacy search syntax).- Search language hardening.
$eq/$in/$neare now exact and case-sensitive, with a new case-insensitive$eqiccounterpart; a?single-character wildcard joins the existing*; a quoted token bypasses wildcard interpretation entirely;$$is the literal-dollar escape; operator names are case-insensitive;$regexgains an optionalflags=i|m|sargument;SearchOperatoris now immutable (create(...).build(), withcopy()for a pre-filled builder) with a typedextension(Class<X>)hook replacing the untypedsql(Object)hook; parse failures throwBeanQuerySyntaxExceptionwith a specificCode; a shared JSON parity corpus keeps the server (Java) and client (juneau-search.js) engines byte-for-byte identical; andSearchType.bareHelp()adds per-type search help text, including a UTC-midnight clarification for bareTIMESTAMPvalues. 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.
PostgresDialectlives in its own module and package (juneau-beanquery-postgres,org.apache.juneau.beanquery.postgres). Search literals are parsed once per query bySearchType.parse(String)(NUMERICtoBigDecimal,BOOLEANtoBoolean,TIMESTAMPtoOffsetDateTime/LocalDateTime/LocalDate) and exposed asSearchExpression.typedValue()/typedArgs(). A value that does not parse for its column type isBeanQuerySyntaxException.Code.BAD_VALUE(HTTP 400) in both the in-memory and SQL engines.SearchSqlRendererandSqlDialecttake typedObjectvalues, and a custom operator opts in withSearchOperator.Builder.typedArgs(true). Relative-duration literals[+-]<n>(ms|s|m|h|d)work forNUMERICandTIMESTAMPcolumns and resolve against a single per-query request time. - ISO-8601 durations in BeanQuery. Wherever a
NUMERICorTIMESTAMPcolumn 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 injuneau-search.js; invalid ISO text isBAD_VALUE. Existing literals and bare epoch milliseconds are unchanged. - Built-in SQL row mappers.
SqlRowMapper.map()andSqlRowMapper.bean(Class)(records or setter beans), chosen automatically bySqlBeanQueryContext.Builder.build()when norowMapper(...)is set. - In-memory and SQL parity. A SQL dialect that does not support an operator (such as
$regex), or that does not declaresupportsVersionOrdering()for theVERSIONrange operators, rejects that query withBAD_VALUEinstead of silently returning different rows.PostgresDialectcomparesENUMcolumns as text (wildcards included), comparesVERSIONcolumns as dotted-integer tuples, renders$regexwith the POSIX~/~*operators (mirroringflags=i|m|s; Postgres and Java regex syntax differ), and makes$notmatchNULLcells as the in-memory engine does. - BREAKING:
PostgresDialectmoved toorg.apache.juneau.beanquery.postgres, andSearchSqlRenderer/SqlDialect.renderBarenow receive typedObjectvalues. 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(packagingpom) — 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 plusjuneau-rest-client:juneau-microservice-jetty-bundlejuneau-microservice-tomcat-bundlejuneau-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(plusSpecies/PetStatus/OrderStatus/UserStatusenums) underorg.apache.juneau.petstore.dto. Plain Juneau beans, no JPA/Hibernate/Derby. - In-memory
PetStoreservice —ConcurrentHashMap-backed, lazy-loads classpath seed JSON atpetstore/init/{Pets,Orders,Users}.jsonviaJson5Parser. Auto-IDs viaAtomicLong. PetStoreResource— full CRUD@Restservlet (GET/POST/PUT/DELETEover/pets,/orders,/users). Usesorg.apache.juneau.http.response.NotFound(next-gen) and@org.apache.juneau.http.Path/@Content. Spring-agnostic — extendsBasicRestServletso 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-Viewreturn path through thejuneau-rest-server-view-{mustache,freemarker}bridges. Pure-classpath engines so both deployments inherit view rendering at parity. (They replace the retiredPetMustacheViewResource/PetFreemarkerViewResource.) - No-build React SPA demo —
PetstoreUiResourcemountsStaticFilesMixinto servehtdocs/petstore-ui.html, a single-page React UI that calls/petstore/petsoverfetch(). 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 underhtdocs/vendor/, MIT-licensed, acknowledged in the rootLICENSE; the app uses plainReact.createElement, so there is no in-browser JSX compiler). - Secure demo +
StubBearerTokenValidator— the console's bearer-token demo page (/console/dev/secure, withSecureApiRest) replaces the retiredPetSecureResource(/petstore-secure/*); the auth-gated CRUD subset is gated by aBearerTokenGuard(fail-closed, op-level) backed by the in-memory stub validator. Demonstrates the bearer-token +@Auth Principalpattern; resource-Javadoc cross-referencesAuthFilterChainfor multi-mechanism deployments.
juneau-petstore-jetty
Jetty/Microservice deployment glue:
App.maincallsMicroservice.create().configurations(JettyConfiguration.class, AppConfig.class)....RootResources extends BasicRestServletGroup— mounts every core child resource plus the Jetty-onlyMicroserviceadmin trio (ConfigResource/LogsResource/ShutdownResource), sourced from the newjuneau-microservice-examplesmodule (see Breaking Changes below).- Bundled
jetty.xmlandjuneau-petstore-jetty.cfg. - Starter-zip assembly — sources + cfg +
jetty.xmlfor 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:
Appis a@SpringBootApplicationwith@Bean RootResources+@Bean ServletRegistrationBean<Servlet>mounted at/*.RootResources extends BasicSpringRestServletGroupso child resources resolve as Spring beans (required forHelloResource's@Autowired HelloMessageProviderinjection demo).- Inherits
${spring.version}from the repo parent. application.propertiesconfigures port 5000.spring-boot-maven-plugin repackageproduces 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:
kpiandinventory-gaugecustom cards on/console/dev/cardsdemonstrate 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,PetMustacheViewResourceandPetFreemarkerViewResource(and their/petstore-info,/petstore-html,/petstore-secureand/pet-viewsURLs) 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 retiredPetInfoResourceat/petstore-info) — utility-beans demo:BeanDescription/Hyperlink/SeeOtherRootrendered against the petstore's ownPetdomain bean.HtmlFlavorRest(/console/dev/flavors/html, replacing the retiredPetHtmlResourceat/petstore-html) — HTML5 bean-builder demo: a hand-built HTML5Tablefragment of all pets rendered from live petstore data.GET/PUT /petstore/pets/{id}/photo(added toPetStoreResource) — image upload/serve demo, backed by new petstore-localPetPhotoSerializer/PetPhotoParserclasses (org.apache.juneau.petstore.marshall) and wired to the previously-unusedPet.photofield.
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/jpegaccepted: the exampleImageParserand the petstorePetPhotoParsernow consumeimage/jpegas well asimage/pngandimage/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()returnsURI.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 whennull.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 privateMap<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—RuntimeExceptioncarrying aProblem. Lets handlers throw a custom problem without manually building aBasicHttpException. The bean module owns this type so thejuneau-rest-serverprocessor can unwrap it without a reverse dependency.ProblemMapper<T extends Throwable>— pluggable interface for translating arbitrary throwables intoProblemshapes; 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 ofProblem.title/Problem.detail. Defaults toProblemLocalizationStrategy.IDENTITY(pass-through). A referenceMessages(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_linksmap (Map<String,Object>whose values are singleHalLinkorHalLinkArray), the reserved_embeddedmap (Map<String,Object>whose values are singleHalResourceorHalResourceArray), 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_linksmap. Mirrors the top-level-array pattern used byJsonSchemaArray.HalResourceArray—LinkedList<HalResource>used as the multi-resource branch inside an_embeddedmap.HalLinkOrArraySwap/HalResourceOrArraySwap— per-propertyObjectSwaps applied to_links/_embeddedso 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 withdata,errors,meta,jsonapi,links,included. Includes avalidate()helper that enforces the document-level invariants: at least one ofdata/errors/metamust be present, anddataanderrorsMUST NOT coexist.JsonApiResource— resource object withtype,id,attributes,relationships,links,meta.typeis modeled as a plainString(and not as a@Marshalled(typePropertyName="type", dictionary=...)discriminator) because JSON:APItypeis 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— slimtype/id/metaform used inside relationship linkage.JsonApiRelationship—datalinkage (null, singleJsonApiResourceIdentifier, or list of them),links,meta.JsonApiError+JsonApiErrorSource— error object withid,links,status,code,title,detail,source,meta; thesourcesub-object carriespointer,parameter,header.JsonApiLink— link object withhref,rel,describedby,title,type,hreflang,meta.JsonApiVersion— thejsonapimember (version,meta).JsonApiLinkOrStringSwap— per-propertyObjectSwapapplied to everylinksmap (onJsonApiDocument,JsonApiResource,JsonApiRelationship,JsonApiError) so that each map entry round-trips as either a JSON string URL or aJsonApiLinkJSON 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 sharedpath(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 theopmember. Kept open (notsealed) so downstream callers can extend the dictionary with custom ops.AddOp,RemoveOp,ReplaceOp,MoveOp,CopyOp,TestOp— the six standard operations from §4.add/replace/testcarry avaluepayload (Object);move/copycarry afromJSON Pointer;removehas no extra members.JsonPatch—LinkedList<JsonPatchOperation>representing a JSON Patch document, which serializes as a top-level JSON array per the RFC. Mirrors theJsonSchemaArraytop-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.