Skip to main content

v10.0 Migration Guide

The following guide can be used to help migrate your code to v10.0. Note that you can also refer to the Release Notes for changes as well.

This guide covers all breaking changes introduced since v9.1, including changes that were originally developed under the v9.5.0 label (a release that was never shipped — it was renamed to v10.0.0). Teams upgrading from 8.x, 9.1, or any earlier version can use this as a single authoritative reference.

Package Renames​

REST Server Package Rename: org.apache.juneau.rest.* → org.apache.juneau.rest.server.*​

All server-side REST classes have been moved from org.apache.juneau.rest to org.apache.juneau.rest.server. This affects the core module as well as all server add-on modules (-auth-jwt, -mcp, -metrics-micrometer, -auth-oauth, -auth-oidc-rp, -tracing-otel, -rdf, -reactive, -reactive-reactor, -auth-saml, -springboot, -view-*). Client-side classes (org.apache.juneau.rest.client.*) and mock testing classes (org.apache.juneau.rest.mock.*) are not affected. Additionally, the juneau-rest-server-reactive-reactor module had its package renamed from org.apache.juneau.rest.server.reactor → org.apache.juneau.rest.server.reactive.reactor to match the new module name.

Migration: Update all import org.apache.juneau.rest.X → import org.apache.juneau.rest.server.X in user code. IDE global find-replace on the import prefix is sufficient.

Marshall Package Rename: org.apache.juneau.* (marshall classes) → org.apache.juneau.marshall.*​

All classes from the juneau-marshall and juneau-marshall-rdf modules have been moved from the root org.apache.juneau namespace into the org.apache.juneau.marshall namespace. This includes all format-specific subpackages (json, xml, html, yaml, csv, uon, urlencoding, msgpack, cbor, bson, json5, jsonl, jsonschema, markdown, plaintext, oapi, proto, soap, sse, parquet, toml, hocon, hjson, ini, jcs, serializer, parser, marshaller) and the RDF/Jena subpackage (org.apache.juneau.jena → org.apache.juneau.marshall.jena). Classes from other modules at the org.apache.juneau.* level (commons, bean, microservice, rest) are not affected.

Migration: Update all import org.apache.juneau.X → import org.apache.juneau.marshall.X where X is a class or subpackage from the marshall module. IDE global find-replace on the org.apache.juneau. prefix filtered to affected subpackages is the safest approach.

Annotation Sub-Package Collapse​

All *.annotation sub-packages have been merged into their parent packages. Classes that were in org.apache.juneau.foo.annotation are now in org.apache.juneau.foo.

Old packageNew package
org.apache.juneau.annotation.*org.apache.juneau.*
org.apache.juneau.bson.annotation.*org.apache.juneau.bson.*
org.apache.juneau.cbor.annotation.*org.apache.juneau.cbor.*
org.apache.juneau.csv.annotation.*org.apache.juneau.csv.*
org.apache.juneau.hjson.annotation.*org.apache.juneau.hjson.*
org.apache.juneau.hocon.annotation.*org.apache.juneau.hocon.*
org.apache.juneau.html.annotation.*org.apache.juneau.html.*
org.apache.juneau.ini.annotation.*org.apache.juneau.ini.*
org.apache.juneau.jcs.annotation.*org.apache.juneau.jcs.*
org.apache.juneau.json.annotation.*org.apache.juneau.json.*
org.apache.juneau.jsonl.annotation.*org.apache.juneau.jsonl.*
org.apache.juneau.jsonschema.annotation.*org.apache.juneau.jsonschema.*
org.apache.juneau.markdown.annotation.*org.apache.juneau.markdown.*
org.apache.juneau.msgpack.annotation.*org.apache.juneau.msgpack.*
org.apache.juneau.oapi.annotation.*org.apache.juneau.oapi.*
org.apache.juneau.parquet.annotation.*org.apache.juneau.parquet.*
org.apache.juneau.parser.annotation.*org.apache.juneau.parser.*
org.apache.juneau.plaintext.annotation.*org.apache.juneau.plaintext.*
org.apache.juneau.proto.annotation.*org.apache.juneau.proto.*
org.apache.juneau.serializer.annotation.*org.apache.juneau.serializer.*
org.apache.juneau.soap.annotation.*org.apache.juneau.soap.*
org.apache.juneau.toml.annotation.*org.apache.juneau.toml.*
org.apache.juneau.uon.annotation.*org.apache.juneau.uon.*
org.apache.juneau.urlencoding.annotation.*org.apache.juneau.urlencoding.*
org.apache.juneau.xml.annotation.*org.apache.juneau.xml.*
org.apache.juneau.yaml.annotation.*org.apache.juneau.yaml.*
org.apache.juneau.commons.annotation.*org.apache.juneau.commons.*
org.apache.juneau.jena.annotation.*org.apache.juneau.jena.*
org.apache.juneau.http.annotation.*org.apache.juneau.http.*
org.apache.juneau.rest.annotation.*org.apache.juneau.rest.*

Migration action required: Update all import statements that reference an *.annotation sub-package. Wildcard imports can simply be collapsed to the parent package — for example:

-import org.apache.juneau.json.annotation.*;
+import org.apache.juneau.json.*;

If the parent package is already imported, the *.annotation import can be dropped entirely. Single-type imports follow the same rename, e.g.:

-import org.apache.juneau.json.annotation.Json;
+import org.apache.juneau.json.Json;

REST Context Builder and Configuration Refactoring​

OldNew
@Rest(allowedHeaderParams="NONE") / @Rest(allowedMethodHeaders="NONE") / @Rest(allowedMethodParams="NONE") — the literal string "NONE" was used to suppress inheriting the attribute from a parent class.The "NONE" sentinel has been removed. Use the standard Rest.noInherit array to suppress inheritance, e.g. @Rest(noInherit={"allowedHeaderParams"}). The same substitution applies for allowedMethodHeaders and allowedMethodParams.
RestContext.Builder — large stateful builder with dozens of fluent setters (allowedHeaderParams(String), encoders(Class<?>...), callLogger(Class<?>), etc.).Removed. All configuration now flows through @Rest(...) annotation attributes or @Bean-annotated methods/fields supplying named beans to the REST bean store. See the per-setting migration table in the 10.0.0 release notes.
RestOpContext.Builder — large stateful builder with dozens of fluent setters.Removed. Same replacement model as RestContext.Builder — @RestOp(...) / @RestGet(...) / @RestPost(...) annotation attributes and @Bean beans.
Sub-builder chaining via RestContext.Builder.encoders(), .parsers(), .serializers(), etc. (returned mutable child builders that user code chained fluent calls on).Removed. Compose by supplying a fully-built EncoderSet / ParserSet / SerializerSet bean via @Bean(name="encoders" / "parsers" / "serializers" / ...), or rely on the @Rest(encoders=..., parsers=..., serializers=...) class array attributes.
static Optional<X> createXxx(BeanStore, Resource, Logger, ...) magic-named static factory methods on the resource class — used to supply EncoderSet, ParserSet, SerializerSet, MarshallingContext, StaticFiles, SwaggerProvider, HeaderList, NamedAttributeMap, etc. The framework discovered these by name via BeanCreateMethodFinder reflection.Annotate the same method with @Bean and the matching name attribute, e.g. @Bean(name="encoders") static EncoderSet myEncoders(BeanStore bs, Resource r, Logger l) { ... }. The method signature is unchanged; only the method name becomes free and the annotation makes the wiring explicit. The BeanCreateMethodFinder scan has been removed.
RestContext.Builder.defaultAccept(String) / defaultContentType(String) convenience setters.Use the new @Rest(defaultAccept="...", defaultContentType="...") annotation attributes (also available on @RestOp / @RestGet / etc.). They expand internally into Accept / Content-Type entries on defaultRequestHeaders, preserving the semantic.
RestOpContext.Builder.dotAll() flag.Removed. The flag is now inferred from the URL pattern itself — a path containing ** or /.* implies dotAll=true. Delete the call; the URL pattern dictates behavior.
RestOp(defaultCharset) / RestOp(maxInput) resolved by falling back to RestContext-level builder values.Both fall back to the resource class's @Rest(defaultCharset) / @Rest(maxInput) annotation values directly (walking the class hierarchy, governed by @Rest(noInherit={...})). The RestContext-level builder fields/getters have been removed; they were only ever populated by the now-deleted builder.
Lifecycle method getter return types — RestContext.getStartCallMethods(), getEndCallMethods(), getPostCallMethods() (and similar for pre-call etc.) returned MethodInvoker[] for some lists and MethodList for others.All seven lifecycle method lists now return MethodList for consistency. Callers that iterated MethodInvoker[] should migrate to MethodList's iteration API (or call .toArray(new MethodInvoker[0]) if they truly need an array).
Reusable bean definitions had to be declared inline on each REST resource class via @Bean-annotated methods/fields, with no first-class way to share them across multiple resources without using a full DI container like Spring.New @Rest(beans={MyConfig.class, ...}) attribute. The named classes are scanned for @Bean members and their beans are contributed to the REST bean store, Spring-@Configuration-style. @Bean on the resource class still works as before; beans= is purely additive. Bean classes are resolved as: (1) all-static methods → no instance, (2) public static INSTANCE field → used as-is, (3) otherwise → instantiated by the bean store (no-arg or DI-style constructor). When the same @Bean name appears in multiple sources, the resource class wins over beans=, child classes win over parent classes, and earlier beans= entries win over later ones.
RestContext.create(resourceClass, parentContext, servletConfig).init(supplier).path(p).children(c).build() static-factory chain — used by mock REST clients, child-resource bootstrap, and RestServlet.init(ServletConfig).Removed. Use the new new RestContext(RestContext.Args args) constructor with a RestContext.Args record carrying the same six bootstrap fields (resource class, parent context, servlet config, resource supplier, path, children) plus an optional Consumer<BasicBeanStore> configurer hook for tests / mock clients that need to register beans before any findXxx() memoizer fires. Convenience two-arg ctor new RestContext.Args(MyResource.class, () -> new MyResource()) covers the common top-level case.
Resource classes could expose a public MyResource(RestContext.Builder builder) throws Exception { builder.path(...); builder.children(...); } constructor and have the framework inject the in-flight builder so the resource could imperatively configure itself.Removed. The Builder-injection protocol is gone. Resource classes must declare configuration declaratively via @Rest(...) annotation attributes and @Bean members, or pass values through RestContextInit when constructing the context directly. There is no longer a way to mutate the in-flight bootstrap state from inside the resource constructor.
Per-operation @RestInit public void init(RestOpContext.Builder b) { ... } hook — the framework discovered every @RestInit method whose parameter list contained RestOpContext.Builder and invoked it once per @RestOp-annotated method, threading the in-flight per-op builder so the hook could imperatively customize a single operation's context.Removed. The per-op @RestInit(RestOpContext.Builder) injection protocol is gone. All operation-level configuration is now expressed declaratively via @RestOp(...) / @RestGet(...) / etc. attributes or via @Bean(name=, methodScope=)-named beans.
RestOpContext.create(java.lang.reflect.Method, RestContext) static factory + the fluent .beanStore(...).type(...).build() chain — used internally by RestContext and (rarely) by user code building one-off RestOpContext instances.Removed. The two internal callers in RestContext.Builder.createRestOperations migrated to direct constructor invocation: new RestOpContext(method, context) for the standard path and new RrpcRestOpContext(method, context) for the RRPC special case (the latter takes a new public 2-arg ctor that internally uses the root bean store). External callers should follow the same pattern; RestOpContext.Builder is reachable via its now-public Builder(Method, RestContext) ctor + public beanStore(BasicBeanStore) setter for the (rare) cases that still need it, but the Builder type itself is slated for removal in a future release.
Class-level @RestInit public void init(RestContext.Builder b) { ... } hook — the framework added the in-flight RestContext.Builder to the resource's bean store so any @RestInit method that declared a RestContext.Builder parameter received it and could imperatively configure the resource-level context (builder.path(...), builder.children(...), builder.encoders(...), etc.).Removed. The class-level Builder-injection protocol is gone — RestContext.Builder is no longer published as a bean, so any @RestInit(RestContext.Builder b) method now fails at startup with a "missing prerequisites" error. Audit before upgrading: the codebase had zero non-test callers prior to deletion, but doc examples used the pattern heavily. Migrate by moving each builder.xxx(...) call to the equivalent @Rest(xxx=...) annotation attribute, a @Bean(name="xxx") bean-supplier method, or (for one-time setup work that doesn't touch the builder) a no-arg @RestInit method. The remaining supported @RestInit parameter shapes are ServletConfig, ServletContext, the resource instance, @Bean-supplied beans, and zero-arg.
Custom annotation appliers — user code that subclassed the internal AnnotationApplier<Rest, RestContext.Builder> (or AnnotationApplier<RestOp, RestOpContext.Builder>) to extend the annotation-processing pass (the apply(AnnotationInfo<A>, B builder) hook invoked once per annotation during context construction).Removed. The builder-based apply-pass is gone; RestAnnotation.Apply (RestContextApply) is now a package-private nested class inside RestContext and is not extensible. Contribute configuration by (a) adding annotation attributes via @Rest(xxx=...) / @RestOp(xxx=...), (b) declaring a @Bean(name="xxx") bean-supplier method or field on the resource class, or (c) using @Rest(beans={MyConfig.class}) to register a configuration class whose @Bean members are contributed to the bean store.
Custom RestAnnotation.create(...) / RestOpAnnotation.create(...) builder-of-builders patterns — programmatic construction of @Rest / @RestOp annotation proxies used to feed synthetic annotations into the builder apply-pass (common in test fixtures and extension libraries).The annotation proxy builders still exist for test use (RestAnnotation.create() / RestOpAnnotation.create() are still available via annotation-test helpers), but they no longer feed into a builder apply-pass because the pass is gone. For test fixtures that previously used this pattern to inject configuration, switch to RestContextInit's beanStoreConfigurer hook: new RestContextInit(..., bs -> bs.add("myBean", myImpl)).
All prior REST debug/logging mechanisms, in order of vintage: @Rest(debug) / @RestOp(debug) plain-string attributes; the nested @Debug annotation (value()/on()/format()/level()/config()) and its debugDefault/debugEnablement/debugOn siblings; DebugEnablement/BasicDebugEnablement/DebugConfig/DebugRule; and the CallLogger/CallLoggerRule/BasicCallLogger logging SPI (including the juneau.restLogger.* config keys).Removed — replaced by a single JUL logger-level signal. Set the resource's own java.util.logging.Logger level: INFO = basic request/response logging, FINE = adds headers, FINEST = adds bodies (cumulative detail tiers). No annotation, attribute, or config key controls this anymore — see Logging / Debugging.

REST Debug/Logging Redesign (JUL-Level-Driven RestDebugFormatter)​

Juneau 10.0 replaces every prior REST debug/logging mechanism with a single signal: the resource's own java.util.logging.Logger level. This is a hard break with no deprecation shim — see Logging / Debugging for the full new model (level resolution, tier table, the RestDebugFormatter SPI, and secure-by-default redaction + body cap).

Enablement was public 9.x API in the juneau-marshall core artifact — this is not just a rest-server-internal removal

org.apache.juneau.Enablement (in 9.x; it would have been org.apache.juneau.marshall.Enablement under the 10.0 "Marshall Package Rename" described earlier in this guide, had it survived) was a released public enum — ALWAYS / CONDITIONAL / NEVER, with fromString(String), isEnabled(boolean), and isOneOf(Enablement...) — shipped in the juneau-marshall core artifact, not a rest-server-only type. It has been deleted with no replacement in 10.0.

  • If you used Enablement only for @Rest(debug=...) / @RestOp(debug=...) (its sole in-framework consumer), no action is needed beyond the debug/logging migration below — the JUL logger level replaces it entirely, and there is no CONDITIONAL-equivalent (capture is derived from the logger's level, not evaluated per-request).
  • If your own code imported Enablement as a general-purpose tri-state enum unrelated to REST debug, define your own replacement enum — Juneau 10.0 does not ship one. This is easy to miss because the class lived in a core marshalling artifact most projects already depend on, not an opt-in rest-server module.

For Spring Boot users: Logback propagation to JUL is default-on for real Logback backends (juneau.rest.logging.propagate-levels=true), additive by default (...reset-jul=false), and silently no-ops for other backends. See juneau-rest-server-springboot for fallback behavior and the process-global side effects of opting into reset-jul=true.

RemovedReplacement
@Rest(debug=...) / @RestOp(debug=...) and the nested @Debug annotation (value(), on(), format(), level(), config())No annotation replacement. Set your resource's JUL logger level (INFO/FINE/FINEST) instead.
The debug/ package types — DebugEnablement, BasicDebugEnablement, DebugEnablementMap, DebugConfig, DebugRule, DebugResult, DebugFormatContext, DebugFormat (and the 4 built-in debug/format/* formats)RestDebugFormatter / BasicRestDebugFormatter (new package org.apache.juneau.rest.server.logging).
The CallLogger logging SPI — CallLogger, CallLoggerRule, CallLoggingDetail, BasicCallLogger, BasicDisabledCallLogger, BasicTestCallLogger, BasicTestCaptureCallLogger, plus Rest#callLogger() and Child#callLogger()/Mixin#callLogger()Same replacement — RestDebugFormatter / BasicRestDebugFormatter — resolved via resource-implements-the-interface or a BeanStore-registered bean. There's no annotation slot; resolution is purely bean-based.
Config keys juneau.restLogger.level / .enabled / .requestDetail / .responseDetail / .logger, and RestContext.debugDefaultRemoved, no replacement. The resource's own logger level is the only configuration surface.
RestContextArgs-injectable DebugEnablement parameter type on @RestOp methodsRemoved, not replaced. DebugEnablement is gone; there is no injectable equivalent.

Per-operation logger naming (mixins and bean-overridden loggers)​

The per-operation logger name is <hostLoggerName>.<methodName>, where hostLoggerName is the host resource's resolved logger name (see Logging / Debugging) — not necessarily the raw resource class name:

  • Mixins. An operation contributed by a composed @Rest(mixins=...) mixin resolves its logger name against the host / top-level resource that composed the mixin, not the mixin class itself. Raising the host resource's own JUL level cascades to every mixin-served operation as a result, and the same mixin class composed into two different hosts resolves to two independent, host-isolated operation loggers — one per host.
  • Bean-overridden resource loggers. If a resource replaces its resolved logger via a @Bean-annotated RichLogger factory method, the per-operation logger is a JUL child of that overridden name, not the raw resource class name — so raising the overridden logger's level still cascades to its operations.
  • 404 / unrouted requests. When no operation is resolved, only the basic status-line summary is ever logged; headers and bodies are never rendered on this path, regardless of how high the resolved logger's level is set.
Footgun: raising a logger's level for ordinary app logging also turns on REST capture

Because the signal is the resource's own JUL logger, raising it to FINE/FINEST for reasons that have nothing to do with REST debugging — e.g. turning up verbose application logging on the resource class — also turns on header (FINE) and body (FINEST) capture for every request that class handles.

Mitigation: default header redaction (Authorization, Proxy-Authorization, Cookie, Set-Cookie) and the default 8 KB body cap are always active, regardless of how the level got raised. If you don't want REST body capture bundled with your application-level FINE logging, log application messages through a different logger name than the resource class (e.g. a dedicated com.example.MyResource.app logger) rather than the resource-class-named logger that REST debug capture reads.

See Logging / Debugging for the full model, and its own "Migrating from pre-10.0" section, which links back here.

Log level no longer controls EchoMixin reachability or marshalling debug​

Two behaviors that previously rode the JUL logger level are now decoupled from it — raising a logger changes verbosity only. This is an unreleased-10.0 hard break (no compatibility shim, no new echo annotation) and a deliberate security behavior change.

OldNewNotes
Raising a resource/op logger to FINE-or-finer un-hid EchoMixin's /echo/* endpoint (it returned 404 below FINE).EchoMixin is disabled by default and returns 404 at every log level. Enable explicitly: EchoMixin.create().enabled().build().Enablement is tri-state: explicit enabled(true)/enabled(false) wins; unset resolves the ${juneau.echo.enabled:false} SVL fallback (system property / relaxed env var). The fallback is JVM-global — prefer per-host builder enabled() and pair with the host's inherited @Rest(guards=...). Closes an accidental attack-surface expansion where a logging change un-hid a request-reflecting endpoint. The default mount is now /echo/* only (/debug/echo/* dropped).
Raising a logger to FINE-or-finer engaged serializer/parser debug behavior (richer/stack-prefixed serialization exceptions, retained/quoted parse input).Controlled by the dedicated cascading debugMarshalling setting, OFF by default at every level.String attribute on all eight REST annotation types (@Rest, @RestOp, six HTTP-method annotations); blank "" means inherit (op inherits resource, which resolves ${juneau.debugMarshalling:false}). Query at runtime via RestRequest.isDebugMarshalling(). A serializer/parser with its own marshall-layer Context.debug remains independent — the REST layer never clobbers it. RestRequest.isDebug() stays a derived/read-only logger query and the framework no longer keys behavior off it.

Migration. If you relied on raising a logger to reach /echo/*, add a @Bean EchoMixin factory that calls .enabled() (or set -Djuneau.echo.enabled=true for a JVM-global default). If you relied on a raised logger for richer marshalling errors, set @Rest(debugMarshalling="true") / @RestOp(debugMarshalling="true") (or the SVL default) on the relevant resource/op. See Ops / Introspection Mixin Pack and Logging / Debugging.

Async/reactive debug records now emit on the completion path​

For true asynchronous responses (CompletableFuture/CompletionStage) and reactive SSE/NDJSON streams, the single debug/access record is now emitted on the response-completion path — after the async body and any headers set during completion have been written — rather than during the synchronous request finish. Previously an async record could be emitted before its late headers/body existed, producing an incomplete capture. Synchronous responses and the MockRestClient/non-async-container fallback are unchanged (still emitted during synchronous finish).

Migration. None for typical usage. Two behavioral notes if you assert on async debug output: (1) an async record's headers/body/Exec time now reflect the completed response, so previously-empty async captures will now be populated; and (2) @RestEndCall still runs on the request thread at lifecycle time (before the async body completes) — it was not moved to the completion thread. Exactly one INFO-level record is still emitted per request on every path. See Logging / Debugging for the full model.

Health Probe Routing (mixins + paths)​

OldNewNotes
HealthServlet standalone auto-mount used @Rest(path="/"), which collided with root resources mounted at /* in Jetty microservices.HealthServlet now uses @Rest(paths={"/healthz","/readyz","/livez"}), and probes can also be composed into an existing root resource via @Rest(mixins=HealthMixin.class).Prefer mixins (HealthMixin) when you already have a root @Rest(path="/") resource. Keep standalone HealthServlet, wired in via HealthProbeConfiguration, only when you explicitly want a separate servlet mount.

SVL and Runtime Input Types Moved to juneau-commons​

OldNewNotes
org.apache.juneau.svl.*org.apache.juneau.commons.svl.*Update imports for VarResolver, VarResolverSession, Var, and related SVL types.
org.apache.juneau.svl.vars.*org.apache.juneau.commons.svl.vars.*Update imports for ArgsVar, ManifestFileVar, SystemPropertiesVar, etc.
org.apache.juneau.svl.ResolvingJsonMaporg.apache.juneau.collections.ResolvingMarshalledMapResolvingMarshalledMap (renamed from ResolvingJsonMap) remains in juneau-marshall and now lives in the collections package. See the MarshalledMap / MarshalledList section below for the rename details.
org.apache.juneau.collections.Argsorg.apache.juneau.commons.runtime.ArgsLegacy JsonMap-based type removed. New type is immutable and Optional-based.
org.apache.juneau.utils.ManifestFileorg.apache.juneau.commons.runtime.ManifestFileLegacy JsonMap-based type removed. New type is immutable and Optional-based.
Args#getArg(int) / getArg(String) / getArgs(String) / hasArg(String) / size()Args#get(int) / get(String) / getAll(String) / has(String) / argCount()+optionCount()get(...) now returns Optional<String>.
ManifestFile#getString(...) / getWithDefault(...) / containsKey(...) (and other JsonMap methods)ManifestFile#get(String).orElse(...), get(String section, String key)Convert callers to explicit Optional handling and parsing.
Microservice#getArgs() / Builder.args(Args) / console command signatures using old ArgsSame methods/signatures but now typed to org.apache.juneau.commons.runtime.ArgsMost call sites only need import + method rename updates (getArg -> get(...).orElse(...)).
Microservice#getManifest() / Builder.manifest(Object) consumers using old ManifestFile APISame methods/signatures but now typed to org.apache.juneau.commons.runtime.ManifestFileReplace getString()/containsKey() patterns with Optional-based get(...).
SettingSource / SettingStore / FunctionalSource / FunctionalStorePropertySource / PropertyStore / FunctionalPropertySource / FunctionalPropertyStoreSPI rename for settings/property source composition. The old types have been removed (no compatibility shims); existing Settings facade remains, migrate custom source/store implementations to the renamed interfaces.
Source-specific SVL lookups ($S, $E, $A, $MF, $C) as the only option for property discoveryNew unified resolver $P{key[,default]}$P walks the configured Settings source hierarchy. Keep source-specific vars when strict source targeting is required.

MarshalledMap / MarshalledList + Json5Map / Json5List​

The generic-model collection hierarchy has been refactored. JsonMap / JsonList are no longer the one-size-fits-all generic-model types: they are now strict-JSON-flavored subclasses of a new neutral, marshaller-agnostic base (MarshalledMap / MarshalledList), with JSON5-flavored siblings (Json5Map / Json5List) carrying the historical JSON5 behavior. Parser sessions now produce flavored maps / lists based on the parser's language, which changes the runtime type returned by parser.parse(text, Object.class) for every parser other than JsonParser.

OldNewNotes
JsonMap#toString() returned JSON5 (via Json5.of(this)).Returns strict RFC 8259 JSON (via Json.of(this)).Callers that want JSON5 output should switch to Json5Map (or call Json5.of(map) explicitly). On JsonMap itself, toJson5() still returns JSON5 — only toString() changed.
JsonList#toString() returned JSON5.Returns strict JSON.Same advice — switch to Json5List, or call Json5.of(list) / list.toJson5().
new JsonMap(CharSequence) / new JsonMap(Reader) / JsonMap.ofJson(CharSequence) defaulted to Json5Parser.DEFAULT.Default to JsonParser.DEFAULT. The single-arg parse factory is now ofString(...) (ofText(...) remains as a deprecated alias).Callers feeding JSON5-with-unquoted-keys text into the bare constructor will now throw ParseException. Either: (1) switch to new Json5Map(CharSequence) / Json5Map.ofString(...), or (2) pass an explicit Json5Parser.DEFAULT to MarshalledMap.ofString(text, Json5Parser.DEFAULT). Call sites of JsonMap.ofJson(...) should be renamed to JsonMap.ofString(...).
new JsonList(CharSequence) / new JsonList(Reader) / JsonList.ofJson(CharSequence) defaulted to Json5Parser.DEFAULT.Default to JsonParser.DEFAULT. The single-arg parse factory is now ofString(...) (ofText(...) remains as a deprecated alias).Same change, same advice — switch to Json5List / Json5List.ofString(...), or pass an explicit Json5Parser.DEFAULT to MarshalledList.ofString(...). Call sites of JsonList.ofJson(...) should be renamed to JsonList.ofString(...).
Json5Map.ofJson5(CharSequence) / Json5Map.ofJson5(Reader)Json5Map.ofString(CharSequence) / Json5Map.ofString(Reader) (ofText(...) deprecated alias)Method naming now aligns with string-based parse factories across MarshalledMap/JsonMap/Json5Map. No behavioral change — the single-arg form still uses Json5Parser.DEFAULT.
Json5List.ofJson5(CharSequence) / Json5List.ofJson5(Reader)Json5List.ofString(CharSequence) / Json5List.ofString(Reader) (ofText(...) deprecated alias)Method naming now aligns with string-based parse factories across MarshalledList/JsonList/Json5List. No behavioral change — the single-arg form still uses Json5Parser.DEFAULT. (Note: Json5List.ofJson5OrCdl(String) was NOT renamed.)
Json5Parser.DEFAULT.parse(s, Object.class) returned a JsonMap (and nested objects were JsonMap / JsonList).Returns a Json5Map (and nested objects are Json5Map / Json5List).Same change applies to parse(s, Map.class) / parse(s, Collection.class) targets, and to nested maps / lists inside the parsed tree. Code that casts the result to JsonMap will throw ClassCastException. Fix options: (1) cast to Json5Map / Json5List instead, (2) cast to the neutral MarshalledMap / MarshalledList when flavor doesn't matter, or (3) pass an explicit target class — parser.parse(text, JsonMap.class) — to force the old runtime type. The hook is only consulted for Object / Map<String,Object> / Collection<Object> targets, so explicit targets always win.
Every parser other than JsonParser / Json5Parser (XmlParser, YamlParser, UonParser, UrlEncodingParser, HoconParser, MsgPackParser, CborParser, BsonParser, HtmlParser, JsonlParser, HjsonParser, MarkdownParser, CsvParser, RdfXmlParser, etc.) returned JsonMap / JsonList from parse(s, Object.class).Returns the neutral MarshalledMap / MarshalledList.Same fix options as the Json5Parser row above. Per-language flavored XMap / XList (e.g. XmlMap, YamlMap, UonMap) are planned as a follow-on; they are not delivered in v10.0.
org.apache.juneau.collections.ResolvingJsonMaporg.apache.juneau.collections.ResolvingMarshalledMapRenamed AND re-parented from JsonMap to the new neutral MarshalledMap base — SVL resolution is language-agnostic. Hard rename, no deprecation shim. Update the import and any new ResolvingJsonMap(...) constructor calls.

Serializer / Parser Builder CRTP Self-Type​

Serializer / Parser builder subclasses no longer need to override fluent setters for covariant return types — the CRTP SELF self-type on SerializerBuilder<SELF> / ParserBuilder<SELF> handles covariant returns automatically. Remove any override boilerplate that existed solely to narrow the return type; the base setters already return the correct concrete builder type.

Serializer.serialize(...) → write(...) / Parser.parse(...) → read(...) Rename​

The two core marshalling entry-point methods were renamed for symmetry with the read / write verbs the Marshaller facade already exposed (Marshaller.read(...) / Marshaller.write(...)). After the rename the facade and the underlying types finally use the same verbs — Marshaller.read delegates to Parser.read, and Marshaller.write delegates to Serializer.write.

This is a hard rename — the old serialize / parse names are gone with no @Deprecated delegating aliases — and it cascades through the session classes, the abstract format hooks, the parseInto* / parseArgs family, the HTTP-part marshalling family, and the record / token streaming interfaces.

Core serializer / parser methods:

WasNowNotes
serializer.serialize(o)serializer.write(o)Returns String (WriterSerializer) or byte[] (OutputStreamSerializer).
serializer.serialize(o, output)serializer.write(o, output)Writer / stream output overload.
serializer.serializeToString(o)serializer.writeToString(o)The differently-named convenience method was renamed too.
parser.parse(input, MyBean.class)parser.read(input, MyBean.class)All parse(...) overloads (Class / ClassMeta / String inputs) renamed.
parser.parse(input, Type, Type...)parser.read(input, Type, Type...)Parameterized map / collection overload.

Session classes and abstract format hooks:

WasNowNotes
SerializerSession.serialize(o) / serialize(o, out) / serializeToString(o)write(o) / write(o, out) / writeToString(o)
ParserSession.parse(input, ...)ParserSession.read(input, ...)All overloads.
Serializer.doSerialize(session, pipe, o) / SerializerSession.doSerialize(pipe, o)doWrite(...)Abstract hook overridden by every concrete format serializer (JsonSerializer, HtmlSerializer, MsgPackSerializerSession, the RDF/Jena serializers, …). Custom serializers must rename their override.
Parser.doParse(session, pipe, cm) / ParserSession.doParse(pipe, cm)doRead(...)Abstract hook overridden by every concrete format parser. Custom parsers must rename their override.

Parser parseInto* / parseArgs family:

WasNowNotes
parseArgs(input, argTypes)readArgs(input, argTypes)On Parser / ParserSession.
parseIntoCollection(...)readIntoCollection(...)
parseIntoMap(...)readIntoMap(...)
doParseIntoCollection(...) / doParseIntoMap(...)doReadIntoCollection(...) / doReadIntoMap(...)Session hooks. Format-specific private helpers (parseIntoBeanMap → readIntoBeanMap) in the XML / HTML / UON / URL-encoding / RDF / YAML parser sessions follow the same convention.

HTTP-part marshalling family:

WasNowNotes
HttpPartSerializerSession.serialize(type, schema, value)write(type, schema, value)Also the Base* / SimplePart* / OpenApi* / Uon* session implementations.
HttpPartParserSession.parse(type, schema, in, toType)read(type, schema, in, toType)
HttpPartSerializer.serialize(type, schema, value) / HttpPartParser.parse(...)write(...) / read(...)The part-serializer / part-parser convenience methods (BaseHttpPartSerializer, OpenApiSerializer, UonSerializer, and their parser counterparts) follow the same rename.

Record / token streaming interfaces:

WasNowNotes
RecordReadable.parseRecords(input)readRecords(input)
RecordWritable.serializeRecords(output)writeRecords(output)
TokenReadable.parseTokens(input)readTokens(input)
TokenWritable.serializeTokens(output)writeTokens(output)
ArrayRecordReadable.parseArrayRecords(input) (+ (input, rootElementName))readArrayRecords(...)
ArrayRecordWritable.serializeArrayRecords(output) (+ (output, expectedCount))writeArrayRecords(...)

Interface type names (RecordReadable, ArrayRecordWritable, …), the isRecordStreaming() / isArrayRecordStreaming() capability probes, and the cursor / support types (RecordReader, TokenWriter, RecordAdapter, …) are unchanged — only the parse* / serialize* factory methods were renamed.

Serializer / Parser I/O Narrowed to Streams (File / StringBuilder Dropped)​

The core serialize/parse I/O surface now accepts only Reader / Writer / InputStream / OutputStream (plus the scalar String / byte[] / CharSequence shortcut forms). The File and StringBuilder instanceof branches were removed from the internal SerializerPipe / ParserPipe plumbing, so passing a File or StringBuilder to Serializer.write(Object, Object), Parser.read(Object, ...), the token/record streaming methods (writeTokens / readTokens / writeRecords / writeArrayRecords), or any session createPipe(...) now throws at runtime. Because these methods are typed Object, callers passing the dropped forms still compile — the change surfaces at runtime as "Cannot convert object of type ... to a Writer/OutputStream/InputStream".

Wrap it yourself:

WasNow
serializer.serialize(bean, file) (char)try (Writer w = Files.newBufferedWriter(file.toPath())) { serializer.write(bean, w); }
serializer.serialize(bean, file) (binary)try (OutputStream os = Files.newOutputStream(file.toPath())) { serializer.write(bean, os); }
parser.parse(file, MyBean.class) (char)try (Reader r = Files.newBufferedReader(file.toPath())) { parser.read(r, MyBean.class); }
serializer.serialize(bean, sb) (StringBuilder)var sw = new StringWriter(); serializer.write(bean, sw); String out = sw.toString(); — or just use the String-returning shortcut (Json.of(bean)).

SerializerPipe / ParserPipe are retained (they still own the stream close/flush lifecycle, charset decoding, debug capture, and position tracking) — only their File / StringBuilder handling and the now-dead fileCharset pipe-constructor argument were removed. The fileCharset(...) builder setting (and its getFileCharset() getter and @SerializerConfig/@ParserConfig fileCharset attributes) — a dead no-op after this stream-only change — has since been removed entirely. Remove any lingering .fileCharset(...) calls; no behavioral change results.

Bean→Marshalled Renames​

Annotation Renames​

OldNewNotes
import org.apache.juneau.annotation.Bean;import org.apache.juneau.annotation.Marshalled;Marshall-level annotation renamed.
@Bean(properties=...)@Marshalled(properties=...)All @Bean attributes are available on @Marshalled.
@Bean(interceptor=...)@Marshalled(interceptor=...)Same attribute, new annotation name.
@Bean(typeName=...) etc.@Marshalled(typeName=...)All other @Bean attributes likewise.
@BeanApply@MarshalledApplyConfig-class applier for @Marshalled.
@Beanp / @Beanp(name=...)@MarshalledProp / @MarshalledProp(name=...)Bean property annotation renamed.
@BeanpApply@MarshalledPropApplyConfig-class applier for @MarshalledProp.
@Beanc / @Beanc(properties=...)@MarshalledCtor / @MarshalledCtor(properties=...)Constructor mapping annotation renamed.
@BeancApply@MarshalledCtorApplyConfig-class applier for @MarshalledCtor.
@BeanIgnore on a field or method@MarshalledIgnore on the same targetField/method-level semantics unchanged.
@BeanIgnore on a class (→ toString)@Marshalled(as=STRING)Old behavior was toString(); new @MarshalledIgnore on a class outputs null. Migrate to @Marshalled(as=STRING) to preserve toString behavior.
@BeanIgnoreApply@MarshalledIgnoreApplyConfig-class applier for @MarshalledIgnore.
@BeanConfig@MarshalledConfigConfig-class annotation renamed.
import org.apache.juneau.rest.annotation.RestInject;import org.apache.juneau.commons.inject.Bean;@RestInject renamed to @Bean and moved to juneau-commons.
@RestInject@BeanAll semantics identical; only package and name changed.

Engine Type Renames​

OldNew
BeanContextMarshallingContext
BeanContext.DEFAULTMarshallingContext.DEFAULT
BeanContextableMarshallingContextable
BeanSessionMarshallingSession
BeanTraverseContextMarshallingTraverseContext
BeanTraverseSessionMarshallingTraverseSession
BeanRecursionExceptionMarshallingRecursionException
BeanStringSwapMarshallingStringSwap — also see @Marshalled(as=STRING) for the common case
BeanContextConverterMarshallingContextConverter
BeanDefMapperMarshallingDefMapper

New @Marshalled(as=STRING) Usage​

If you previously used BeanStringSwap to serialize a type via its toString() method, you can now use the annotation directly:

// Before
public class MyType extends BeanStringSwap { ... }
// or registered via BeanContext.Builder.swaps(MyTypeSwap.class)

// After — annotate the class directly
@Marshalled(as=STRING)
public class MyType {
@Override public String toString() { return "my-string-form"; }
}

Removed @MarshalledProp(properties) Attribute​

The @MarshalledProp(properties=...) attribute has been removed without a deprecation shim. The attribute was used to limit which child properties of a nested bean / map are rendered by serializers.

OldNewNotes
@MarshalledProp(properties="f1") public MyChildClass x1 — only f1 rendered on the nested childDrop the attribute (nested beans render in full); for per-property filtering apply @Marshalled(properties=...) at the child class level, or configure include / exclude lists on the MarshallingContext.BuilderHard removal. The marshalling-context-level filtering pre-dated @MarshalledProp(properties) and remains fully supported.
BeanPropertyMeta.applyChildPropertiesFilter(...) / BeanPropertyMeta.getProperties() (override-list accessor) and the underlying properties field/builder setterRemovedInternal types — no replacement.
org.apache.juneau.BeanMetaFilteredRemovedSole purpose was wrapping a BeanMeta with a filtered property list; no replacement needed.

@Beanp("*") on Non-Map Fields​

On a field whose type is not a Map, @Beanp("*") (or @Beanp(name="*")) no longer attempts to register a dyna property. The property name is now taken from the field via the configured PropertyNamer; other @Beanp attributes still apply. Map fields keep the dyna-property * behavior as before.

OldNewNotes
@Beanp("*") public String myField; registered a no-op dyna property keyed on *Field is named via the configured PropertyNamer (typically myField); * is treated as if absentBehavioral. Affects any non-Map field that was annotated with @Beanp("*"). Replace with @Beanp (no name) to keep the namer-derived name, or @Beanp(name="myField") to pin it explicitly.

Bean-Modeling Layer Split​

The bean-modeling layer has been split out of juneau-marshall into juneau-commons as part of the bean-layer split. The seven core bean-runtime types and the @Name annotation have moved into org.apache.juneau.commons.bean, and a new set of bean-modeling annotations and SPI types has been added there. juneau-commons now compiles standalone with no dependency on juneau-marshall.

Package Moves​

OldNewNotes
org.apache.juneau.BeanMaporg.apache.juneau.commons.bean.BeanMapUpdate imports.
org.apache.juneau.BeanMapEntryorg.apache.juneau.commons.bean.BeanMapEntryUpdate imports.
org.apache.juneau.BeanMetaorg.apache.juneau.commons.bean.BeanMetaUpdate imports.
org.apache.juneau.BeanPropertyMetaorg.apache.juneau.commons.bean.BeanPropertyMetaUpdate imports. The BeanPropertyMeta.Builder inner class moves with it.
org.apache.juneau.BeanPropertyValueorg.apache.juneau.commons.bean.BeanPropertyValueUpdate imports.
org.apache.juneau.BeanPropertyConsumerorg.apache.juneau.commons.bean.BeanPropertyConsumerUpdate imports.
org.apache.juneau.BeanProxyInvocationHandlerorg.apache.juneau.commons.bean.BeanProxyInvocationHandlerUpdate imports.
org.apache.juneau.annotation.Nameorg.apache.juneau.commons.bean.NameUpdate imports. Fully-qualified @org.apache.juneau.annotation.Name(...) references must be rewritten to @org.apache.juneau.commons.bean.Name(...).

The earlier move of BeanInterceptor (renamed from MarshallingInterceptor in org.apache.juneau.swap) is part of the same effort and is described above under the Engine Type Renames / Annotation Renames tables.

Type Renames​

OldNewNotes
BeanTypeInfo<T> (commons.bean)BeanInfo<T>Bean-modeling type-info seam. No deprecation shim. Marshalling-side ClassMeta<T> continues to extend it.

Method Renames​

OldNewNotes
Delegate#getClassMeta()Delegate#getBeanInfo()Returns BeanInfo<T>.
BeanMap#getClassMeta()BeanMap#getBeanInfo()Returns BeanInfo<T>.
BeanMeta#getClassMeta()BeanMeta#getBeanInfo()Returns BeanInfo<T>.
BeanPropertyMeta#getClassMeta()BeanPropertyMeta#getBeanInfo()Returns BeanInfo<?>.
BeanPropertyValue#getClassMeta()BeanPropertyValue#getBeanInfo()Returns BeanInfo<?>.
BeanMap#setMarshallingSession(BeanSession)BeanMap#setBeanSession(BeanSession)No deprecated shim. Existing call sites that pass a MarshallingSession (which implements BeanSession) compile unchanged.
BeanPropertyPostProcessor#process(Object, Object)BeanPropertyPostProcessor#process(Object, BeanPropertyMeta.Builder)Second parameter tightened from Object to BeanPropertyMeta.Builder now that the builder lives in commons.bean.

MarshallingContext.getClassMeta(Class) and MarshallingContext.getClassMeta(Type, Type...) are intentionally unchanged — they still resolve ClassMeta on the marshalling side. Only the bean-modeling-seam getClassMeta() accessors on Delegate / BeanMap / BeanMeta / BeanPropertyMeta / BeanPropertyValue were renamed.

Before / after for the most common signature change (the BeanPropertyPostProcessor):

// Before
public final class MyPostProcessor implements BeanPropertyPostProcessor {
@Override public void process(Object marshallingContext, Object builder) {
var b = (BeanPropertyMeta.Builder) builder;
// ...
}
}

// After
public final class MyPostProcessor implements BeanPropertyPostProcessor {
@Override public void process(Object marshallingContext, BeanPropertyMeta.Builder builder) {
// No cast needed.
}
}

Annotation Attribute Relocations​

Bean-modeling attributes have moved from the marshalling-side @MarshalledXxx annotations to new commons-side annotations. Both annotations are still consulted by the marshalling-side path, so a class may carry both during migration.

OldNewNotes
@Marshalled(properties=...) / p=...@BeanType(properties=...) / p=...Bean-modeling attribute.
@Marshalled(excludeProperties=...) / xp=...@BeanType(excludeProperties=...) / xp=...Bean-modeling attribute.
@Marshalled(readOnlyProperties=...) / ro=...@BeanType(readOnlyProperties=...) / ro=...Bean-modeling attribute.
@Marshalled(writeOnlyProperties=...) / wo=...@BeanType(writeOnlyProperties=...) / wo=...Bean-modeling attribute.
@Marshalled(unsorted=...)@BeanType(unsorted=...)Bean-modeling attribute.
@Marshalled(findFluentSetters=...)@BeanType(findFluentSetters=...)Bean-modeling attribute.
@Marshalled(propertyNamer=...)@BeanType(propertyNamer=...)Bean-modeling attribute.
@Marshalled(interfaceClass=...)@BeanType(interfaceClass=...)Bean-modeling attribute.
@Marshalled(stopClass=...)@BeanType(stopClass=...)Bean-modeling attribute.
@Marshalled(factory=...)@BeanType(factory=...)Bean-modeling attribute.
@MarshalledProp(name=...) / value@BeanProp(name=...) / valueProperty-level bean-modeling attribute.
@MarshalledProp(ro=...) / wo=...@BeanProp(ro=...) / wo=...Property-level bean-modeling attribute.
@MarshalledProp(type=...) / params=...@BeanProp(type=...) / params=...Property-level bean-modeling attribute.
@MarshalledProp(elementType=...)@BeanProp(elementType=...)Property-level bean-modeling attribute.
@MarshalledProp(factory=...)@BeanProp(factory=...)Property-level bean-modeling attribute.
@MarshalledIgnore (field / method / class / ctor)@BeanIgnoreBean-modeling exclusion. Targets unchanged.
@MarshalledIgnore(ignoreAccessors=...) on fields@BeanIgnore(ignoreAccessors=...)Bean-modeling exclusion.
@MarshalledCtor(properties=...)@BeanCtor(properties=...)Bean-modeling constructor mapping.
@MarshalledConfig(...) (bean-modeling settings)@BeanConfig(...)Visibility thresholds, beans*Require* toggles, fluent setters, property naming, not-a-bean exclusions, etc.

@Marshalled retains the marshalling-only attributes: as, description, dictionary, example, implClass, interceptor, typeName, typePropertyName. @MarshalledProp retains its marshalling-only attributes (e.g. dictionary, format). @MarshalledConfig retains its marshalling-only settings (swaps, type-property name, locale, mediaType, timeZone, etc.). Contributions from both the bean-modeling and marshalling-side annotations are merged when building the bean filter on the marshalling-side path.

Removed APIs​

OldNewNotes
BeanFilter.getMarshalledClass()BeanFilter.getBeanClass()Method removed (with the backing marshalledClass field on MarshalledFilter and the MarshalledFilter.Builder(Class<?>) / MarshalledFilter.create(Class<T>) overloads). Use getBeanClass() (returns ClassInfoTyped<?>) instead.

New commons.bean SPI Surface​

The following SPI seams have been added to org.apache.juneau.commons.bean and are typically encountered only by extension code or custom marshalling integrations:

SPIImplemented on the marshalling side by
BeanInfo<T>ClassMeta<T> (extends BeanInfo<T>).
BeanSessionMarshallingSession.
BeanFilterMarshalledFilter.
BeanRegistryLookupBeanRegistry.
BeanMetaInitializerMarshalledBeanMetaInitializer (with a NOOP default for the commons-side path).
BeanPropertyPostProcessorMarshalledPropertyPostProcessor (with a NOOP default for the commons-side path).
BeanTypeResolverMarshallingContext.

ClassInfo Additions​

New APIDescription
ClassInfo#isObject()Returns true when this class is exactly java.lang.Object.
ClassInfo#isOptional()Returns true when this class is java.util.Optional.

Visibility Widening​

Several previously protected or package-private members were widened to public so marshalling-side (and juneau-marshall-rdf) callers stay reachable across the new package boundary. Source- and binary-compatible — existing callers continue to compile and link.

  • BeanMeta.BeanMetaValue record made public (and its optBeanMeta() accessor).
  • BeanMeta#getConstructor(), getConstructorArgs(), hasConstructor(), newBean(Object) widened protected → public.
  • BeanMap#BeanMap(T, BeanMeta<T>) and BeanMap#setBeanSession(BeanSession) widened protected → public.
  • BeanPropertyMeta.Builder fields innerField, getter, setter, rawTypeMeta, swap, readTransform, writeTransform, dictionaryClasses, isUri, typeMeta widened package-private → public.
  • BeanPropertyValue#properties() widened protected → public.

JSR-330 Alignment + Spring-Lite Additions in commons.inject​

The org.apache.juneau.commons.inject package now exposes a JSR-330–aligned injection surface plus a Spring-lite configuration / conditional / lifecycle layer. Recognition of injection-related annotations is by fully qualified name — applications can drop in jakarta.inject.Inject, Spring @Autowired, etc., without Juneau pulling either API onto the compile classpath.

OldNewNotes
org.apache.juneau.annotation.Namedorg.apache.juneau.commons.inject.NamedMoved; no deprecated alias is kept at the old location. Update imports.
Local test @interface Inject / @interface Autowired / @interface PostConstruct matched the framework by simple name.Recognition is now strictly FQN-based via JsrSupport. Replace test mocks with the real Juneau-owned types (org.apache.juneau.commons.inject.Inject / PostConstruct), or declare stand-in types at the canonical FQN (jakarta.inject.Inject, org.springframework.beans.factory.annotation.Autowired) on the test classpath.Affects code that relied on simple-name matching to wire annotations defined inside test classes or non-canonical packages.
@PostConstruct discovered by simple class name.Discovered by FQN (org.apache.juneau.commons.inject.PostConstruct, jakarta.annotation.PostConstruct, javax.annotation.PostConstruct).Custom annotations named PostConstruct no longer trigger lifecycle invocation.
WritableBeanStore did not implement AutoCloseable.WritableBeanStore extends AutoCloseable. close() invokes @PreDestroy on resolved beans in LIFO order, aggregates failures as suppressed exceptions, and marks the store closed.Custom implementations must add void close() throws BeanCreationException (usually by delegating to a wrapped BasicBeanStore). RestContext.destroy() now wires this in automatically.
n/aNew annotations: @Inject, @Qualifier (meta), @Singleton, @Primary, @Order(int), @Configuration, @PostConstruct, @PreDestroy, @Conditional, @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty. New types: Provider<T>, Condition, ConditionContext, JsrSupport.All live in org.apache.juneau.commons.inject. See the 10.0.0 release notes for full semantics.
@Bean had only name() / value() / methodScope() / description().New int priority() default Integer.MAX_VALUE/2; attribute provides bean ordering when @Order is absent.Existing usages unchanged; the attribute defaults to mid-range so previously-unordered collections retain stable behavior.
Non-public @Bean methods on @Rest classesSilently ignored (only public methods were invoked)Invoked; protected, package-private and private methods are made accessible, as for @Configuration classes. Remove @Bean from any method you relied on being ignored. Under JPMS, open the package to org.apache.juneau.commons.
n/aWritableBeanStore.registerConfiguration(Class) / registerConfigurations(Class...) register a @Configuration class, recursively processing imports, deduplicating across calls, inheriting @Bean members from superclasses (parent-first), and honoring @Conditional annotations at both class and member level.Duplicate (type, name) registrations throw BeanCreationException. Class-level conditional failures cascade — the configuration and its imports are silently skipped.

Microservice + Jetty Refactor​

juneau-microservice-core has been renamed to juneau-microservice, and juneau-microservice-jetty no longer subclasses Microservice. The Jetty module now contributes a single JettyConfiguration class that the user adds to Microservice.Builder.configurations(...). The Java package org.apache.juneau.microservice is unchanged.

OldNewNotes
<artifactId>juneau-microservice-core</artifactId><artifactId>juneau-microservice</artifactId>Module rename. Aggregator pom is now juneau-microservice-parent. Update every consumer pom.
JettyMicroservice.create().args(args).servlet(MyResource.class).build().start()Microservice.create().args(args).configurations(JettyConfiguration.class, AppConfig.class).build().start() where AppConfig exposes @Bean Servlet rootResources() { return new MyResource(); }JettyMicroservice (subclass) deleted; the Jetty server is now driven by JettyServerComponent, a MicroserviceListener contributed by JettyConfiguration.
class MyJettyMicroservice extends JettyMicroservice { ... }Contribute a @Bean MicroserviceListener from your @Configuration class.The fanned-out MicroserviceListener invocation makes subclassing unnecessary.
JettyMicroserviceListener / BasicJettyMicroserviceListenerMicroserviceListener / BasicMicroserviceListenerJetty-specific listener interface deleted. Every MicroserviceListener bean is invoked on start() / stop() / config-change events.
JettyMicroservice.Builder.ports(int...) / .jettyXml(Object, boolean) / .jettyServerFactory(...)Contribute a @Bean JettySettings (with .ports(...) / .jettyXml(..., resolveVars)) and/or a @Bean JettyServerFactory.All Jetty programmatic knobs migrate from JettyMicroservice.Builder setters to value beans.
JettyMicroservice.Builder.servlet(...) / .servletAttribute(...)@Bean Servlet (auto-mounted at @Rest(path=...)) / Jetty/servletAttributes config section.The Jetty-specific builder surface is gone — use the bean store / standard config sections instead.
JettyMicroservice.getInstance().getServer()Microservice.getInstance().getBeanStore().getBean(JettyServerComponent.class).orElseThrow().getServer()Same for .getPort(), .getURI(), .getContextPath(), .getServletContextHandler().
JettyMicroservice.Builder.listener(JettyMicroserviceListener)Microservice.Builder.listener(MicroserviceListener) or @Bean MicroserviceListenerThe builder listener is registered into the bean store as the unnamed entry and participates in fan-out alongside @Bean-supplied listeners.

Test-module rename: juneau-utest → juneau-integration-tests​

The juneau-utest test module was renamed to juneau-integration-tests. This module is not published to Maven Central, so consumers who depend on it directly (rare) must update their artifact id. Test-helper annotations (@JettyMicroserviceTest, @SpringbootTest) and base classes are unchanged in package and class name.

juneau-my-jetty-microservice retired​

The juneau-my-jetty-microservice template module (a copy-this-project starter with its own App, RootResources and HelloWorldResource) is removed from the reactor and from juneau-distrib; there is no replacement artifact to depend on. Its zero-config developer experience moved into juneau-microservice-jetty, which now bundles the jetty.xml and juneau.cfg defaults.

OldNewNotes
Copy or extend the juneau-my-jetty-microservice template project (org.apache.juneau.microservice.jetty.template.App, ...)Depend on juneau-microservice-jetty and call JettyMicroservice.run(args, rootServlet)org.apache.juneau.microservice.jetty.JettyMicroservice is now a final static launcher (no instances, no subclassing). run(String[], Servlet) and run(String[], Servlet, boolean startConsole) register the servlet as a bean and start the microservice with JettyConfiguration; run(String[], WritableBeanStore, boolean, Class<?>...) is the power-user form for several @Bean Servlets, custom MicroserviceListeners or a parent bean store. All return the started Microservice.
The template's jetty.xml and juneau.cfgjetty.xml and juneau.cfg ship as resources inside juneau-microservice-jettyThe template's htdocs/about.txt placeholder and its assembly descriptor (bin.xml) are gone.

Juneau v10.0 promotes the next-generation, transport-agnostic REST client to the canonical names. The pre-existing Apache HttpClient 4.5–based stack moves under .classic packages and a new juneau-rest-client-classic Maven module so existing call sites keep working.

Maven module changes​

OldNewNotes
<artifactId>juneau-rest-client</artifactId><artifactId>juneau-rest-client</artifactId> (canonical, NG) and/or <artifactId>juneau-rest-client-classic</artifactId> (legacy)The historical juneau-rest-client artifact is now the transport-agnostic NG client with no org.apache.http.* dependency. The legacy HC4.5-based RestClient moved into the new juneau-rest-client-classic sibling artifact. Pull in both if you need both flavors; the aggregator artifacts (juneau-shaded-rest-client, juneau-shaded-all, juneau-microservice, juneau-rest-mock) already do.

Package renames​

OldNewNotes
org.apache.juneau.http.* (Apache HttpCore-based types in juneau-rest-common)org.apache.juneau.http.classic.*The historical juneau-rest-common types (HttpHeaders facade, Authorization, Range, BasicHeader, BasicStatusLine, response/exception classes, etc., ~170 types) moved under the .classic sub-package. Update import org.apache.juneau.http.*; to import org.apache.juneau.http.classic.*;.
org.apache.juneau.ng.http.* (early snapshot only)org.apache.juneau.http.*The new JDK-native types that briefly lived under ng.http.* graduated to the now-free canonical org.apache.juneau.http.* namespace. The @Remote / @RemoteGet / @RemotePost / etc. annotations stay at org.apache.juneau.http.remote.* (unchanged).
org.apache.juneau.ng.rest.client.* (early snapshot only)org.apache.juneau.rest.client.*The NG client and HTTP transport SPI are now canonical. The legacy RestClient (Apache HttpClient 4.5–based) is at org.apache.juneau.rest.client.classic.* in the new module.
org.apache.juneau.ng.rest.mock.* (early snapshot only)org.apache.juneau.rest.mock.*Canonical, paired with the NG RestClient.

Class renames (early-snapshot only)​

If you adopted early snapshots that exposed Ng* class names, update to the canonical names. No alias / deprecation shim is provided.

OldNewNotes
NgRestClient / NgRestClient.BuilderRestClient / RestClient.Builder (in org.apache.juneau.rest.client)Canonical name.
NgRestRequest / NgRestResponseRestRequest / RestResponse (in org.apache.juneau.rest.client)Canonical name.
NgMockRestClientMockRestClient (in org.apache.juneau.rest.mock)Canonical name for the NG mock client. The legacy mock client is at org.apache.juneau.rest.mock.classic.MockRestClient.
import org.apache.juneau.ng.http.*;import org.apache.juneau.http.*;Same for any ng.http.entity.* / ng.http.header.* / ng.http.part.* / ng.http.resource.* / ng.http.response.* sub-package.

Mock client layout​

Old (single-flavor)New (split)Notes
org.apache.juneau.rest.mock.MockRestClient (legacy)org.apache.juneau.rest.mock.classic.MockRestClientApache HttpClient 4.5–based mock client. Still extends the classic RestClient and implements HttpClientConnection. Same fluent surface — only the import changes.
n/aorg.apache.juneau.rest.mock.MockRestClient (new, NG)Transport-agnostic mock client paired with the canonical RestClient. Wires MockHttpTransport into a RestContext.
org.apache.juneau.rest.mock.MockRestRequest / MockRestResponseorg.apache.juneau.rest.mock.classic.MockRestRequest / MockRestResponseSubclasses of the classic RestRequest / RestResponse. The NG client uses org.apache.juneau.rest.client.RestRequest / RestResponse directly — there are no Mock* subclasses on the NG side.
MockServletRequest / MockServletResponse / MockHttpSession / MockPathResolverorg.apache.juneau.rest.mock.MockServletRequest / …MockServletResponse / …MockHttpSession / …MockPathResolverShared by both mock-client flavors. Unchanged location. MockServletRequest.debug(boolean) and MockServletResponse.getHeaders() and MockPathResolver were widened from package-private to public so the classic mock client can reach them across the new package boundary; no source change required for callers.
MockConsole / MockLoggerorg.apache.juneau.rest.mock.classic.MockConsole / MockLoggerClassic-only logger helpers.

juneau-rest-common package renames​

The package promotion changes the canonical imports for every juneau-rest-common consumer:

-import org.apache.juneau.http.*;            // pre-10.0: HC4.5 facade types
-import org.apache.juneau.http.entity.*; // pre-10.0: SerializedHttpEntity, BasicHttpEntity, ...
-import org.apache.juneau.http.header.*; // pre-10.0: BasicHeader, BasicMediaTypeHeader, Authorization, ...
-import org.apache.juneau.http.part.*; // pre-10.0: BasicPart, PartList, ...
-import org.apache.juneau.http.resource.*; // pre-10.0: BasicResource, ByteArrayResource, ...
-import org.apache.juneau.http.response.*; // pre-10.0: Ok, Created, NotFound, BasicHttpException, ...
+import org.apache.juneau.http.classic.*;
+import org.apache.juneau.http.classic.entity.*;
+import org.apache.juneau.http.classic.header.*;
+import org.apache.juneau.http.classic.part.*;
+import org.apache.juneau.http.classic.resource.*;
+import org.apache.juneau.http.classic.response.*;

The new canonical org.apache.juneau.http.* (and sub-packages entity / header / part / response) host the JDK-native NG types. The org.apache.juneau.http.remote.* annotation package (@Remote, @RemoteGet, etc.) did not move.

juneau-rest-common Module Split​

Continuing the REST Client and HTTP Stack Promotion described above, juneau-rest-common is now split into a pure, transport-neutral module and a new sibling that owns every Apache HttpClient 4.5–style facade. After the split, juneau-rest-common no longer drags org.apache.httpcomponents:httpcore onto every downstream consumer.

Maven module changes​

Old (single artifact)New (two artifacts)Notes
<artifactId>juneau-rest-common</artifactId> (carries httpcore transitively)<artifactId>juneau-rest-common</artifactId> (transport-neutral, no httpcore) and/or <artifactId>juneau-rest-common-classic</artifactId> (new, depends on httpcore)Code using only org.apache.juneau.http.* / org.apache.juneau.http.header.* / org.apache.juneau.http.remote.* / org.apache.juneau.http.part.* / org.apache.juneau.http.entity.* / org.apache.juneau.http.response.* (the NG types) needs only juneau-rest-common. Code that uses any org.apache.juneau.http.classic.* type needs the new juneau-rest-common-classic dependency. Aggregator artifacts (juneau-rest-client-classic, juneau-rest-mock, juneau-microservice, juneau-shaded-rest-server, juneau-shaded-rest-client, juneau-shaded-all, juneau-distrib) still pull both modules in transitively. juneau-rest-server and juneau-rest-server-springboot no longer pull juneau-rest-common-classic transitively as of v10.0 — server-side handlers that use any org.apache.juneau.http.classic.* type now need to add juneau-rest-common-classic explicitly. See juneau-rest-server Decoupled from Apache HttpClient 4.5 below.

Class moves (source-incompatible)​

EntityTag and EntityTags are pure value types with no org.apache.http.* dependency despite their historical package. They moved out of org.apache.juneau.http.classic.header so the transport-neutral header beans that already stored them (HttpEntityTagHeader, HttpEntityTagsHeader, IfMatch, IfNoneMatch, IfRange, ETag, top-level HttpHeaders) can stay free of the classic dependency.

OldNewNotes
import org.apache.juneau.http.classic.header.EntityTag;import org.apache.juneau.http.header.EntityTag;Update the import. FQCN is the only thing that changed; constructor / of(...) / accessors are unchanged.
import org.apache.juneau.http.classic.header.EntityTags;import org.apache.juneau.http.header.EntityTags;Update the import. FQCN is the only thing that changed.

Every other org.apache.juneau.http.classic.* type stayed in place — only the Maven coordinate that publishes them moved. import org.apache.juneau.http.classic.header.HeaderList; still resolves; you just need juneau-rest-common-classic on the classpath rather than juneau-rest-common.

@Remote annotation change​

@Remote is transport-neutral (both the NG and classic clients consume it). To keep @Remote out of the classic Maven coordinate, the headerList() method's return type was widened from Class<? extends HeaderList> to Class<?>, and the default sentinel changed from HeaderList.Void.class to java.lang.Void.class.

OldNewNotes
@Remote(headerList = MyHeaderList.class) (where MyHeaderList extends org.apache.juneau.http.classic.header.HeaderList)@Remote(headerList = MyHeaderList.class) (unchanged)Source-compatible — user annotations continue to work. The classic RemoteMeta now performs an instanceof HeaderList check at runtime before invoking the constructor, so non-HeaderList values are silently ignored rather than causing a ClassCastException.
Class<? extends HeaderList> hl = Remote.class.getMethod("headerList").getReturnType().asSubclass(HeaderList.class);Class<?> hl = remoteAnnotation.headerList(); followed by an explicit isAssignableFrom check before use.Reflective callers must cast/check explicitly.
Remote.headerList() default was HeaderList.Void.class.Default is java.lang.Void.class.The framework's isVoid check still recognizes the default and skips header-list resolution.

juneau-rest-server Decoupled from Apache HttpClient 4.5​

Completing the HC 4.5 removal started by the REST client / HTTP stack promotion and the juneau-rest-common module split described above, juneau-rest-server no longer imports org.apache.http.* and no longer depends on juneau-rest-common-classic, so its dependency tree is free of org.apache.httpcomponents:httpcore. The server-side public API was retyped onto the new transport-neutral types in org.apache.juneau.http.* — see the table below for the source-incompatible changes most callers will have to react to.

Maven dependency change​

The Maven coordinate stays the same; the transitive set shrinks:

Old (9.4)New (10.0)Notes
juneau-rest-server pulled juneau-rest-common-classic → httpcomponents:httpcore:4.4.16 transitively.juneau-rest-server pulls only juneau-rest-common. No org.apache.httpcomponents:* on the classpath.If your @Rest resource still references any org.apache.juneau.http.classic.* type (e.g. throws org.apache.juneau.http.classic.response.BadRequest, accepts a org.apache.juneau.http.classic.header.HeaderList, returns a org.apache.juneau.http.classic.resource.BasicResource), add juneau-rest-common-classic to that module's pom.xml explicitly.

The shaded aggregator artifacts (juneau-shaded-rest-server, juneau-shaded-rest-server-springboot, juneau-shaded-all, juneau-distrib) continue to bundle both flavors, so callers that pull them in see no Maven change.

Server-side public API changes​

The server-side surface that used to expose org.apache.http.* types was retyped. These signature changes are source-incompatible:

Old (9.4)New (10.0)Notes
org.apache.http.Header[] hdrs = req.getAllHeaders();org.apache.juneau.http.HttpHeader[] hdrs = req.getAllHeaders();RestRequest#getAllHeaders() retyped. The new HttpHeader is a JDK-only interface; getName() / getValue() are unchanged.
org.apache.http.RequestLine rl = req.getRequestLine();org.apache.juneau.http.HttpRequestLine rl = req.getRequestLine();getMethod() / getUri() are unchanged; getProtocolVersion() now returns HttpProtocolVersion instead of org.apache.http.ProtocolVersion.
org.apache.http.ProtocolVersion pv = req.getProtocolVersion();org.apache.juneau.http.HttpProtocolVersion pv = req.getProtocolVersion();Record with protocol() / major() / minor() accessors plus parse(String).
RequestHeader implements org.apache.http.HeaderRequestHeader implements org.apache.juneau.http.HttpHeaderSame for RequestHttpPart, RequestQueryParam, RequestFormParam, RequestPathParam, BasicNamedAttribute. They now implement the JDK-typed Juneau interfaces.
restContextBuilder.staticFiles(BasicStaticFiles.create()....headers(org.apache.http.Header...).build())....headers(org.apache.juneau.http.HttpHeader...).build()BasicStaticFiles / BasicStaticFiles.Builder retyped.
FluentRequestLineAssertion<…> over org.apache.http.RequestLineover org.apache.juneau.http.HttpRequestLineAssertion plumbing on FluentProtocolVersionAssertion follows the same pattern.
HttpStatusLine#getProtocolVersion() returned Stringreturns HttpProtocolVersionThe wire-level numeric major / minor parts are now available directly without re-parsing.

Class renames in org.apache.juneau.rest.httppart​

The four request-side part-list classes were renamed for consistency with the new HttpHeaderList / HttpPartList siblings in juneau-rest-common. No backward-compatibility shims are kept — callers must update imports and references.

OldNewNotes
org.apache.juneau.rest.httppart.RequestFormParamsorg.apache.juneau.rest.httppart.RequestFormParamListUsed in @RestOp method signatures (@RestPost public ... method(RequestFormParams formData)) and @RestPreCall / @RestPostCall hooks. Update the type reference.
org.apache.juneau.rest.httppart.RequestHeadersorg.apache.juneau.rest.httppart.RequestHeaderListSame pattern.
org.apache.juneau.rest.httppart.RequestPathParamsorg.apache.juneau.rest.httppart.RequestPathParamListSame pattern.
org.apache.juneau.rest.httppart.RequestQueryParamsorg.apache.juneau.rest.httppart.RequestQueryParamListSame pattern.

Response processor rename​

OldNewNotes
org.apache.juneau.rest.processor.HttpEntityProcessororg.apache.juneau.rest.processor.HttpBodyProcessorThe class was already internally rewritten to operate on org.apache.juneau.http.HttpBody rather than HC 4.5's org.apache.http.HttpEntity; the class name was kept for compatibility through earlier snapshots but is now renamed. The default response-processor chain registered by DefaultConfig is updated; any custom RestContext.responseProcessors(...) configuration that explicitly names HttpEntityProcessor.class needs to be updated to HttpBodyProcessor.class.

Legacy return-type handling​

@RestOp methods that still return classic org.apache.http.HttpResponse / org.apache.http.HttpEntity / org.apache.juneau.http.classic.resource.BasicResource instances keep working at runtime: a new org.apache.juneau.rest.processor.LegacyHttpResponseAdapter reflectively bridges those legacy return types into the canonical HttpBody / HttpHeader pipeline. The bridge is invoked by HttpBodyProcessor and HttpResponseProcessor only when the returned object is a recognized legacy type, so pure-canonical handlers pay no overhead. Note that this still requires juneau-rest-common-classic on the runtime classpath — juneau-rest-server itself no longer compiles against those types.

New transport-neutral types you can migrate to​

These are new in juneau-rest-common and replace the corresponding HC 4.5 / org.apache.juneau.http.classic.* types in juneau-rest-server's public API. They're available to user code too — typical migration is just an import swap.

Classic (HC 4.5–based)Transport-neutral (new)
org.apache.http.RequestLineorg.apache.juneau.http.HttpRequestLine (interface) + org.apache.juneau.http.request.HttpRequestLineBean (default impl)
org.apache.http.ProtocolVersionorg.apache.juneau.http.HttpProtocolVersion (record)
org.apache.juneau.http.classic.header.HeaderListorg.apache.juneau.http.header.HttpHeaderList
org.apache.juneau.http.classic.part.PartListorg.apache.juneau.http.part.HttpPartList
org.apache.juneau.http.classic.part.Basic{String,Boolean,Integer,Long,Date,Uri,CsvArray}Partorg.apache.juneau.http.part.Http{String,Boolean,Integer,Long,Date,Uri,CsvArray}Part
org.apache.juneau.http.classic.header.Basic{String,Boolean,Integer,Long,Date,EntityTag,EntityTags,MediaType,MediaRanges,StringRanges,Csv,Uri}Headerorg.apache.juneau.http.header.Http{String,Boolean,Integer,Long,Date,EntityTag,EntityTags,MediaType,MediaRanges,StringRanges,Csv,Uri}Header
org.apache.juneau.http.classic.resource.BasicResourceorg.apache.juneau.http.resource.HttpResourceBean

BasicHttpException and all ~62 named status subclasses in org.apache.juneau.http.response.* (BadRequest, Ok, NotFound, InternalServerError, …) gained the full classic fluent-setter surface (setHeader / setHeaders / setProtocolVersion / setStatusCode / setReasonPhrase / setLocale / setContent / setUnmodifiable), so server-side handlers that build responses via throw new BadRequest().setHeader(...).setReasonPhrase(...) keep their ergonomics after the classic → canonical package swap.

BeanQuery REST Binding (Queryable rebuilt on BeanQuery)​

The Queryable REST response converter — which parsed the Juneau-native &s= / &v= / &o= / &p= / &l= parameters and ran ObjectSearcher / ObjectSorter / ObjectViewer / ObjectPaginator — is rebuilt on the shared BeanQuery engine. An earlier 10.0 milestone briefly reshaped it onto a QueryProtocol SPI; that SPI never shipped in a release and is removed. Nothing here has been released, so there are no compatibility shims: the tables describe the change against 9.x. The parameter rename table, the ObjectSearcher replacement and the error contract are in the next section (ObjectSearcher / ... → BeanQuery). This section adds the settings bean, the removed accessors, the search-syntax changes and the error table.

Views runtime: server-mode tables now POST, not GET​

A juneau-console-views table with dataMode: "server" used to receive its DataTables draw state as GET query-string parameters. It now sends the JSON body built by JuneauDataTables.ajax, so the endpoint must be a @RestPost method taking @Content DataTablesRequest and calling DataTablesQuery.run/runArrays (see DataTables). Column-scoped ribbon options now ride the body as that column's search.value, $and-merged with the user's own search on the same column; param:-scoped ribbon options and the nested-table scope param are unchanged and still ride the URL as query parameters. Client-mode tables are unchanged. Pages must load juneau-datatables.js before juneau-views.js, or server mode fails loudly instead of falling back to GET.

QueryableSettings is now the BeanQuery tuning bean​

Tune the limits per resource with a QueryableSettings bean (defaultLimit, maxLimit, allowRegex, regexTimeout, countPolicy, and the clause/sort/length/depth caps):

@Bean
public QueryableSettings queryableSettings() {
return QueryableSettings.create().defaultLimit(20).maxLimit(500).build();
}

RequestQueryParamList native-arg accessors removed​

RemovedReplacement
RequestQueryParamList#getSearchArgs() / getSortArgs() / getViewArgs() / getPageArgs()req.getRequest(BeanQueryRequest.class) (or a BeanQueryRequest method argument)
NativeQueryProtocol.INSTANCE.parse(req) (early-10.0 replacement for the above)same

BeanQueryRequest returns the raw request values (getSearch(), getSort(), getView(), getPosition(), getLimit(), getOpts()), not pre-parsed SearchArgs / SortArgs / ViewArgs / PageArgs. To get rows, open a BeanQuerySession and call find(query) / findValues(query).

Search syntax​

OldNew
Implicit OR: status=open closedstatus=$in(open,closed) or status=$or(open,closed)
+foo -bar (must / must-not)name=$and(foo,$not(bar))
Multi-range: age=1-5 10-20age=$or($between(1,5),$between(10,20))
age=>30, age=<30, age=>=30age=$gt(30), age=$lt(30), age=$gte(30)
Partial dates: date=2020Explicit bounds: date=$between(2020-01-01,2020-12-31)
* wildcardUnchanged; ? (one character) is also supported
CaseBare values, $contains, $prefix are case-insensitive; $eq, $in, $ne are exact; $eqic is case-insensitive equality
Regex$regex is on by default; QueryableSettings.create().allowRegex(false) opts a resource out (then REGEX_DISABLED, HTTP 400)

Errors​

ExceptionHTTPBodyHeader
BeanQuerySyntaxException400The message; with problem details on, a "code" member tooX-BeanQuery-Error: <CODE> (for example UNKNOWN_COLUMN)
BeanQueryExecutionException500Query execution failed. (the cause is logged, never sent)none
@Request bean missing a setter or public no-arg constructorstartup failureIllegalArgumentExceptionnone

RestContext.convertThrowable does the mapping, so a BeanQuerySyntaxException thrown from any @RestOp method or converter becomes a 400; you no longer need to catch it yourself. This includes the DataTables adapter (DataTablesQuery): it lets the exception propagate, so a syntax error is HTTP 400 with the X-BeanQuery-Error header, and JuneauDataTables.ajax installs an xhr.dt listener that shows the server's message instead of the generic Ajax error.

ObjectSearcher / ObjectSorter / ObjectViewer / ObjectPaginator → BeanQuery​

The legacy in-memory query helpers from org.apache.juneau.objecttools (ObjectSearcher, ObjectSorter, ObjectViewer, ObjectPaginator, with their SearchArgs / SortArgs / ViewArgs / PageArgs argument types and the QueryArgs / QueryProtocol / NativeQueryProtocol / ProtocolQueryable layer from an early 10.0 milestone) are removed with no deprecation shim. The replacement is the shared BeanQuery engine in org.apache.juneau.commons.beanquery (module juneau-commons). One search language, one set of limits and one error model now cover Queryable responses, DataTables server-side processing and SQL-backed queries.

OldNewNotes
ObjectSearcher / ObjectSorter / ObjectViewer / ObjectPaginator (and SearchArgs / SortArgs / ViewArgs / PageArgs)org.apache.juneau.commons.beanquery.InMemoryBeanQueryContext<T> plus the session from ctx.getSession(rows)Build the context once per row type (InMemoryBeanQueryContext.create(Row.class).build()); it holds no rows. Open a session per request over a Collection, then call find(query) (bean rows) or findValues(query) (maps of the projected columns). Both return a Page whose rows() is the list.
n/aBeanQueryContext / BeanQuerySessionThe engine-neutral base types; InMemoryBeanQueryContext is the in-memory implementation and org.apache.juneau.beanquery.sql.SqlBeanQueryContext (module juneau-beanquery-sql) the SQL one.
BeanQuery carried by hand-parsed s/v/o/p/l argumentsorg.apache.juneau.http.BeanQueryRequest (module juneau-rest-common)A @Request bean (a concrete BeanQuery subclass) you can take as a @RestOp argument and pass straight to a session.
Queryable parameters s / v / o / p / lsearch / view / sort / position / limitSearches use the BeanQuery grammar, for example age=>30 becomes age=$gt(30). A trailing - on a sort column is still accepted; :desc is preferred.
BeanQueryRequest / ListQuery parameter optoptsEvery wire name now equals its property name.
Queryable with no l=: all rowsdefaultLimit 100, clamped to maxLimit 1000Set both per resource with a QueryableSettings bean.
Queryable &i= (ignore-case)removedIt was never parsed. Case sensitivity is per operator ($eq is exact; use $eqic, $contains or $prefix for case-insensitive matches; see the section below).
RequestQueryParamList#getSearchArgs() / getSortArgs() / getViewArgs() / getPageArgs()req.getRequest(BeanQueryRequest.class), or a BeanQueryRequest method argumentBeanQueryRequest returns the raw request values (getSearch(), getView(), getSort(), getPosition(), getLimit(), getOpts()), not pre-parsed argument objects.
QueryableSettings.protocol(...)removedQueryableSettings now tunes defaultLimit, maxLimit, allowRegex, regexTimeout, countPolicy and the clause/sort/length/depth caps.

Error contract. A query that cannot be parsed or resolved (an unknown column, a bad value, a disabled $regex, a negative position) throws BeanQuerySyntaxException. RestContext.convertThrowable maps it to HTTP 400 with an X-BeanQuery-Error: <CODE> header (BeanQueryRequest.ERROR_HEADER, for example UNKNOWN_COLUMN), so you no longer need to catch it yourself. A BeanQueryExecutionException (an engine-side failure) maps to HTTP 500 with the fixed body Query execution failed.; the cause is logged but never sent to the client. The DataTables adapter (DataTablesQuery) follows the same contract: it lets the exception propagate, so a syntax error is HTTP 400 with the X-BeanQuery-Error header (the client shows the message through an xhr.dt listener).

Request-bean validation. A concrete @Request method-argument class, which BeanQueryRequest is, must have a public no-arg constructor and a public setter for every annotated getter. A violation now throws IllegalArgumentException when the resource's operation table is built (at startup with @Rest(eagerInit=true), otherwise on the first request to the resource), instead of a NoSuchMethodException (HTTP 500) on the first request that supplied the part.

ListQuery → BeanQueryRequest​

org.apache.juneau.http.ListQuery (an interface whose getters bound multi-valued search and opt parameters) is replaced by the concrete class org.apache.juneau.http.BeanQueryRequest. It binds six single-valued parameters: search, view (a column projection), sort, position, limit and opts (renamed from opt); the $-language and the key=value opts bag live inside those strings and are decoded by the session. Each getter carries a @Schema description, so an operation taking a BeanQueryRequest documents all six parameters in Swagger automatically. Apps that extended ListQuery with extra getters now subclass BeanQueryRequest and redeclare a getter with their own @Query name. Saved-view endpoints that use view as an id must not take a BeanQueryRequest.

// Before (9.x): hand-wire the legacy helpers.
List<Person> rows = ObjectSearcher.create().run(beanSession, people, searchArgs);
rows = ObjectSorter.create().run(beanSession, rows, sortArgs);
rows = ObjectPaginator.create().run(beanSession, rows, pageArgs);

// After (10.0): one context per row type, one session per request.
private static final InMemoryBeanQueryContext<Person> PEOPLE =
InMemoryBeanQueryContext.create(Person.class).build();

@RestGet("/people")
public List<Person> getPeople(BeanQueryRequest q) {
try (var s = PEOPLE.getSession(people)) {
return s.find(q).rows();
}
}
// GET /people?search=age=$gte(18)&sort=name,age:desc&position=20&limit=10

For a resource that just returns a Collection of beans or maps, no code is needed: keep @Rest(converters=Queryable.class) and update the request parameter names. See Converters and the BeanQuery page.

Format-Control Round-Trip Hardening​

Closes out the format-control extension work described in the other Format-Control sections with a cross-serializer/parser round-trip test matrix and 18 production bug fixes. The breaking surface is narrow: two API signature widenings on TemporalFormat, plus a wire-format change on three binary serializers at non-NOT_SET BinaryFormat values.

OldNewNotes
TemporalFormat.format(Temporal value, ZoneId zone)TemporalFormat.format(TemporalAccessor value, ZoneId zone)Signature widened from Temporal to TemporalAccessor. Source-compatible for every existing caller — every Temporal is a TemporalAccessor. The widening is required so java.time.MonthDay (a TemporalAccessor but not a Temporal) reaches the dispatch path. No action needed for typical call sites; reflective callers that explicitly captured the parameter type as Temporal should widen to TemporalAccessor.
TemporalFormat.parse(String s, Class<? extends Temporal> type, ZoneId zone)TemporalFormat.parse(String s, Class<? extends TemporalAccessor> type, ZoneId zone)Same shape — Class<? extends Temporal> widened to Class<? extends TemporalAccessor>. Source-compatible for callers passing a Temporal subtype literal (e.g. Instant.class, LocalDateTime.class). Callers can now also pass MonthDay.class.
MonthDay bean property at non-default TemporalFormat setting → round-trip failed with BeanRuntimeException: Class 'java.time.MonthDay' could not be instantiatedMonthDay bean property at any TemporalFormat setting → round-trips via native MonthDay.toString() / MonthDay.parse() (--MM-DD shape)The configured TemporalFormat value is now silently ignored for MonthDay properties (the only stable wire shape is the native --MM-DD since no DateTimeFormatter from the standard ISO family matches that pattern, and every other format value is structurally meaningless without a year). Mirrors the existing LocalTime / OffsetTime carve-outs.
OffsetTime × TemporalFormat.MILLIS previously threw NumberFormatException on the parse side (the swap called Long.valueOf on what was actually an ISO string)Falls back to ISO_OFFSET_TIMESibling of the existing LocalTime × MILLIS and MonthDay × MILLIS carve-outs — OffsetTime has no canonical epoch-millis interpretation. Reflected in the updated MILLIS constant Javadoc. On the lenient parse side, numeric input still routes through fromEpochMillis (preserves the legacy TemporalFormat.parse(numeric, OffsetTime.class) behavior), and the new ISO_OFFSET_TIME wire form is also accepted.
byte[] bean property on ParquetSerializer / ParquetParser at non-NOT_SET BinaryFormat → emitted/read as a raw TYPE_BYTE_ARRAY Parquet columnEmits/reads as a UTF-8 string column containing the configured wire form (BASE64 / BASE64_URL / HEX / SPACED_HEX)Wire-format change. At any non-NOT_SET BinaryFormat value, Parquet now routes byte[] through the configured text wire form instead of native bytes. Downstream consumers that were reading the raw-bytes Parquet column now read a UTF-8 string column. At BinaryFormat.NOT_SET the behavior is unchanged (native TYPE_BYTE_ARRAY column). To keep the pre-10.0 wire shape, explicitly configure binaryFormat(BinaryFormat.NOT_SET) on the Parquet serializer / parser builders.
byte[] bean property on RdfThriftSerializer / RdfThriftParser / RdfProtoSerializer / RdfProtoParser at non-NOT_SET BinaryFormat → emitted/read as an xsd:base64Binary typed RDF literalEmits/reads as a plain RDF string literal containing the configured wire form (BASE64 / BASE64_URL / HEX / SPACED_HEX)Wire-format change. Same shape as the Parquet row above — at any non-NOT_SET BinaryFormat, the binary RDF serializers (RDF/Thrift, RDF/Proto) now route byte[] through the configured text wire form instead of an xsd:base64Binary typed literal. Downstream RDF consumers that depended on the typed-literal form should either (a) use the default BinaryFormat.NOT_SET (which now emits plain string literals containing the raw bytes), or (b) update the consumer to read the plain string form and decode it explicitly. MsgPack / CBOR / BSON are unchanged at every BinaryFormat value (they continue to emit native bytes opcodes).
BinaryFormat.BASE64_URL.parse("mQ") (or any non-3-aligned URL-safe payload without - / _ chars) threw IllegalArgumentException: Invalid BASE64 string lengthDecodes correctly via Base64.getUrlDecoder() (which accepts missing padding)Bug fix, not a breaking change. BinaryFormat.parse now honors the BASE64_URL constant directly before falling through to the format-agnostic wire-shape sniff. No source change required.
JsonParser.DEFAULT.parse("3.14", Object.class) returned java.lang.Float (or any JSON-family parser auto-classifying a bare decimal)Returns java.lang.DoubleBehavioral change — the shared classifier StringUtils.parseNumber no longer auto-compacts to Float when the lossless Float/Double toString() representations happen to match. Affects all JSON-family parsers (JSON / JSON5 / JSONL / XML / HTML / UON / UrlEncoding / JCS) plus any caller of StringUtils.parseNumber. Action needed only if you were doing assertInstanceOf(Float.class, parsedNumber) or parsedNumber instanceof Float on the auto-detect result — switch to Double. Callers that explicitly request Float.class / Float.TYPE are unaffected.

Format-Control: Duration / Period Defaults​

New org.apache.juneau.DurationFormat and org.apache.juneau.PeriodFormat enums plus MarshallingContext.Builder.durationFormat(...) / periodFormat(...) settings, mirrored on the three Marshalled annotations (@Marshalled / @MarshalledProp / @MarshalledConfig). The default Duration wire shape changed.

OldNewNotes
Default Duration serialization → ISO 8601 hours/minutes form (Duration.ofHours(48) → 'PT48H')Default is now DurationFormat.ISO_8601_WITH_DAYS (Duration.ofHours(48) → 'P2D')Behavioral default change. To restore the pre-10.0 wire shape, configure MarshallingContext.Builder.durationFormat(DurationFormat.ISO_8601) (or @MarshalledProp(durationFormat=DurationFormat.ISO_8601) per-property). The DurationFormat constants are ISO_8601, ISO_8601_WITH_DAYS, NANOS, MILLIS, SECONDS, HOCON.
n/aNew MarshallingContext.Builder.periodFormat(PeriodFormat) (default ISO_8601; also DAYS)Additive. Mirrored on the three Marshalled annotations.
Binary serializers (BSON / CBOR / MsgPack) wrote Duration / Period as stringsEmit native numeric wire types when durationFormat / periodFormat resolves to a numeric constant (NANOS / MILLIS / SECONDS)Source-compatible; wire shape narrows when configured to numeric.

Format-Control: Calendar / Date / Temporal / TimeZone / Locale Swap Deletions​

The legacy Temporal*Swap inner-class family and the named root-level temporal / locale / timezone swaps have been removed without deprecation shims. Configure formats via MarshallingContext setters or the new annotations instead. New enums CalendarFormat, DateFormat, TemporalFormat, TimeZoneFormat, LocaleFormat live under org.apache.juneau.

OldNewNotes
org.apache.juneau.swaps.TemporalCalendarSwap and all 17 inner-class variants (IsoOffsetDateTime, IsoInstant, IsoLocalDateTime, IsoZonedDateTime, etc.)@MarshalledProp(calendarFormat=CalendarFormat.ISO_XXX) / @Marshalled(calendarFormat=...) / MarshallingContext.Builder.calendarFormat(CalendarFormat.ISO_XXX)Hard removal — no @Deprecated shim. Replace @Swap(TemporalCalendarSwap.IsoYyy.class) with @MarshalledProp(calendarFormat=CalendarFormat.ISO_YYY). For builder chains: swaps(TemporalCalendarSwap.IsoYyy.class) → calendarFormat(CalendarFormat.ISO_YYY).
org.apache.juneau.swaps.TemporalDateSwap and all 17 inner-class variants@MarshalledProp(dateFormat=DateFormat.ISO_XXX) / @Marshalled(dateFormat=...) / MarshallingContext.Builder.dateFormat(DateFormat.ISO_XXX)Hard removal. Same migration shape. DateFormat constants: ISO variants, RFC_1123_DATE_TIME, MILLIS (default ISO_LOCAL_DATE_TIME).
org.apache.juneau.swaps.TemporalSwap and all 18 inner-class variants@MarshalledProp(temporalFormat=TemporalFormat.ISO_XXX) / @Marshalled(temporalFormat=...) / MarshallingContext.Builder.temporalFormat(TemporalFormat.ISO_XXX)Hard removal. TemporalFormat constants: DEFAULT (per-subtype default), ISO variants, RFC_1123_DATE_TIME, ISO_YEAR, ISO_YEAR_MONTH, MILLIS.
org.apache.juneau.swaps.XMLGregorianCalendarSwapBuilt-in — XMLGregorianCalendar properties are always XML-lexical regardless of any configured CalendarFormat. For Calendar / GregorianCalendar use CalendarFormat.XML_FORMAT as the opt-in.The always-XML behavior moved into the post-processor for XMLGregorianCalendar; no annotation needed.
swaps(TimeZoneSwap.class) / swaps(ZoneIdSwap.class) on builders, used to control root-level TimeZone / ZoneId wire shapeMarshallingContext.Builder.timeZoneFormat(TimeZoneFormat) (default ID; also OFFSET, NAME_LONG, NAME_SHORT) shared by both TimeZone and ZoneIdThe TimeZoneSwap / ZoneIdSwap types still exist but are now thin delegators that read the resolved TimeZoneFormat from the active session.
swaps(LocaleSwap.class) for root-level Locale wire shapeMarshallingContext.Builder.localeFormat(LocaleFormat) (default BCP_47; also UNDERSCORE)LocaleSwap is now a thin delegator.

Precedence: @MarshalledProp > @Marshalled > MarshallingContext setting > enum default.

Format-Control: Binary + Enum Swap and Builder Deletions​

The legacy ByteArraySwap family, the OutputStreamSerializer.Builder / InputStreamParser.Builder binaryFormat(...) setters, and the useEnumNames(boolean) setter have been removed without deprecation shims. Configure these via MarshallingContext.Builder or the new annotations instead. New enum org.apache.juneau.EnumFormat plus extended org.apache.juneau.BinaryFormat.

OldNewNotes
org.apache.juneau.swaps.ByteArraySwap and the Base64 / Hex / SpacedHex inner-class variants@MarshalledProp(binaryFormat=BinaryFormat.BASE64 / HEX / SPACED_HEX) / @Marshalled(binaryFormat=...) / MarshallingContext.Builder.binaryFormat(...)Hard removal. Replace @Swap(ByteArraySwap.Base64.class) with @MarshalledProp(binaryFormat=BinaryFormat.BASE64). New BinaryFormat.NOT_SET (default, falls through to surrounding session's native shape) and BinaryFormat.BASE64_URL (RFC 4648 §5) constants.
OutputStreamSerializer.Builder.binaryFormat(BinaryFormat) and InputStreamParser.Builder.binaryFormat(BinaryFormat)MarshallingContext.Builder.binaryFormat(BinaryFormat) (inherited by every serializer / parser builder via MarshallingContextable.Builder)The setting now drives both binary and textual sessions. The inherited setter has the same signature, so most builder chains compile unchanged.
BeanContext.Builder.useEnumNames(boolean) and the useEnumNames field on MarshallingContextMarshallingContext.Builder.enumFormat(EnumFormat.NAME)Hard removal. useEnumNames() / useEnumNames(true) → enumFormat(EnumFormat.NAME). Other EnumFormat constants: TO_STRING (default), LOWER_HYPHEN, UPPER_HYPHEN, LOWER_UNDERSCORE, LOWER, UPPER, ORDINAL, NOT_SET.
@Bean(useEnumNames=true)@Marshalled(enumFormat=EnumFormat.NAME)Annotation-attribute rename.
org.apache.juneau.csv.ByteArrayFormatorg.apache.juneau.csv.CsvByteArrayCellFormatRename to disambiguate from the new top-level BinaryFormat.
OutputStreamSerializerSession#serializeToString(Object) honored the configured binaryFormat for debug outputAlways emits BinaryFormat.HEXSource-compatible; behavioral. Provides a stable copy-pasteable hex dump for debug / display irrespective of the surrounding context's wire-format setting.

Format-Control: ClassSwap Deletion + Boolean / Float / Currency / Class Settings​

ClassSwap is deleted; bean-property Class<?> serialization is driven by @MarshalledProp(classFormat=...) (with ClassFormat.FQCN as the default). New paired enums BooleanFormat, FloatFormat, CurrencyFormat, ClassFormat plus MarshallingContext.Builder setters and matching @Marshalled(...) / @MarshalledProp(...) / @MarshalledConfig(...) attributes.

OldNewNotes
org.apache.juneau.swaps.ClassSwap@MarshalledProp(classFormat=ClassFormat.FQCN) / @Marshalled(classFormat=...) / MarshallingContext.Builder.classFormat(ClassFormat) for bean properties; for top-level Class<?> values the new org.apache.juneau.swaps.ClassFormatSwap is registered in DefaultSwaps and reads the resolved ClassFormat at swap timeHard deletion. Default ClassFormat.FQCN (≈ Class.getCanonicalName()) is functionally compatible with the old ClassSwap.getName() for top-level classes. Differences: FQCN returns "int[]" for int[].class (vs. "[I" from Class.getName()) — use ClassFormat.BINARY_NAME to recover the JVM binary form. Callers that explicitly wanted Class.getSimpleName() must use @MarshalledProp(classFormat=ClassFormat.SIMPLE_NAME) (serialize-only — parse(...) throws UnsupportedOperationException).
MarshallingContext had no top-level booleanFormat / floatFormat / currencyFormat / classFormat settingNew MarshallingContext.Builder.booleanFormat(BooleanFormat) / floatFormat(FloatFormat) / currencyFormat(CurrencyFormat) / classFormat(ClassFormat) settingsAdditive. Defaults: BooleanFormat.TRUE_FALSE, FloatFormat.NaN_AS_NULL (boxed Float / Double only — primitive float / double keep the legacy null-to-zero contract), CurrencyFormat.ISO_CODE, ClassFormat.FQCN.
Currency bean properties at the default CurrencyFormat were unrecognized (no default swap)Currency round-trips via DefaultSwaps registration at CurrencyFormat.ISO_CODE / NOT_SETBug fix, source-compatible.

JSON Strict-Mode Separation (JsonParser / JsonSerializer vs Json5Parser / Json5Serializer)​

JsonSerializer / JsonParser now strictly enforce RFC 8259 (double-quoted strings only). Single-quoted JSON, comments, trailing commas, and unquoted keys are rejected by JsonParser and no longer produced by JsonSerializer. JSON5 input / output must go through Json5Parser / Json5Serializer.

OldNewNotes
JsonSerializer.create().json5().build() — JSON5 output via the strict serializerUse Json5Serializer.create()....build() directlyHard removal of JsonSerializer.json5(). No deprecation shim.
JsonParser.Strict static instance / Strict inner subclassUse JsonParser.DEFAULT (now strict) for JSON; Json5Parser.DEFAULT for JSON5Hard removal. No deprecation shim.
JsonParser.DEFAULT.parse("{key:'val'}", ...) accepted JSON5 (unquoted keys, single quotes)Throws ParseException. Use Json5Parser.DEFAULT for JSON5 input.Hard behavioral break. Callers passing JSON5 strings to the default JSON parser will fail at runtime.
JsonSerializer.DEFAULT.serialize(bean) produced strict JSON (was historically inconsistent depending on builder flags)Always strict JSON (double-quoted keys / strings, no trailing commas, no comments)Behavioral. For JSON5 output use Json5Serializer.DEFAULT.serialize(...).
BeanContext.beanToStringSerializer used JsonSerializer for the bean-to-string fallbackUses Json5Serializer (more human-readable).Source-compatible — affects only the toString() shape of BeanMap and related types.
ComboRoundTrip_Tester used json() for both strict-JSON and JSON5 outputsUse json() for strict JSON, json5() for JSON5 — they are now distinct configurationsTest-only impact.

Sorted Bean Properties by Default​

Bean properties are now serialized in alphabetical order by default across all serializers. Previously the default was natural JVM order and opting in to sorted output required explicit configuration. The sortProperties API is fully removed and replaced by unsortedProperties with inverted semantics.

OldNewNotes
@Bean(sort=true) — opt-in to sorted output@BeanType(unsorted=true) — opt-out of default sorted outputHard rename with inverted semantics. Removing both attributes leaves properties sorted (was: unsorted).
BeanContext.Builder.sortProperties() / sortProperties(boolean)MarshallingContext.Builder.unsortedProperties() / unsortedProperties(boolean)Hard removal. Inverted semantics.
BeanContext.Builder.sortProperties(Class<?>...on)MarshallingContext.Builder.unsortedProperties(Class<?>...on)Same.
BeanContextable.Builder.sortProperties() / sortProperties(Class<?>...on)unsortedProperties() / unsortedProperties(Class<?>...on)Mirrored on every serializer / parser builder.
BeanFilter.Builder.sortProperties() / sortProperties(boolean)BeanFilter.Builder.unsortedProperties()Per-bean-filter opt-out.
BeanFilter.isSortProperties() / BeanSession.isSortProperties() / BeanMeta.isSortProperties()BeanFilter.isUnsortedProperties() / BeanSession.isUnsortedProperties() / (internal field renamed on BeanMeta)Inverted semantics.
All ~55 serializer / parser builder sortProperties() / sortProperties(Class<?>...on) overrides (JSON, XML, HTML, YAML, UON, URL Encoding, CSV, OpenAPI, MsgPack, CBOR, BSON, RDF, etc.)All replaced by unsortedProperties() / unsortedProperties(Class<?>...on) overridesHard rename across the entire builder hierarchy.
Static DEFAULT_SORTED constants on JsonSerializer etc.RemovedRedundant — DEFAULT is now sorted.

Native Iterator / Iterable / Stream / Enumeration Serialization​

Iterator, non-Collection Iterable, Enumeration, and java.util.stream.Stream are now serialized directly as arrays without IteratorSwap / EnumerationSwap materializing them to a LinkedList first. JSON / XML / UON / URL Encoding / OpenAPI write elements lazily; MsgPack / HTML / CSV collect to a List internally because the format requires up-front knowledge of size or column headers.

OldNewNotes
org.apache.juneau.swaps.IteratorSwapRemoved. Iterator handled natively.If you registered this swap explicitly, drop the registration.
org.apache.juneau.swaps.EnumerationSwapRemoved. Enumeration handled natively.Same.
Stream<T> and non-Collection Iterable<T> had no serialization support — fell through to bean serialization (or threw)Serialized directly as arrays. Stream<T> is auto-closed via try-with-resources after iteration.Additive new behavior.
Supplier<T> (standard JDK, not BeanSupplier) inside a bean property serialized as a bean wrapping the supplier itselfTransparently unwrapped by serializers as a single lazy value, recursively up to depth 10.Behavioral. Affects any place a Supplier<T> is exposed as a bean property.
ClassMeta had no isIterator() / isIterable() / isStream() / isStreamable() predicatesNew predicates plus new Category enum values ITERATOR, ITERABLE, STREAMNew API. POJO categories table moves these out of "swapped objects" (group 4b) into a new sequence-types group alongside Collections / arrays.

HTTP Annotation Moves and Attribute Removals​

The HTTP parameter annotations (@Header, @Query, @Path, @Content, @FormData, @Request, @Response, @StatusCode, @PathRemainder, @HasQuery, @HasFormData, @Contact, @License, @Tag) along with the constants (CollectionFormatType, FormatType, ParameterType) and the httppart.bean package (RequestBeanMeta, RequestBeanPropertyMeta, ResponseBeanMeta, ResponseBeanPropertyMeta, MethodInfoUtils) have moved from juneau-marshall into juneau-rest-common. Package names are unchanged (org.apache.juneau.http.annotation, org.apache.juneau.httppart.bean), so import statements do not need updating; Maven resolves the classes from the new module location automatically. Existing compiled code must be recompiled.

Several attributes on these annotations have been removed without deprecation shims:

OldNewNotes
@Query(serializer=..., parser=...) / @Header(serializer=..., parser=...) / @FormData(serializer=..., parser=...) / @Path(serializer=..., parser=...) / @PathRemainder(serializer=..., parser=...) / @Request(serializer=..., parser=...) / @Response(serializer=..., parser=...)New @HttpPartMarshalling(serializer=..., parser=...) annotation (in juneau-marshall, package org.apache.juneau.httppart) applied alongside the HTTP annotationHard removal. The HTTP annotations are now pure data annotations; custom HttpPartSerializer / HttpPartParser types are specified via the sibling @HttpPartMarshalling annotation.
@Repeatable on every HTTP annotation (@Query, @Header, @FormData, @Path, @Content, @PathRemainder, @Request, @Response, @StatusCode, @HasQuery, @HasFormData)Not repeatable — author one annotation per elementHard removal. Dynamic annotation stacking is no longer supported.
on() / onClass() attributes on all HTTP annotationsRemoved — apply the annotation directly to the target rather than via on() / onClass() proxiesHard removal.
@ContextApply on HTTP annotationsRemoved — the annotation no longer participates in the context-apply passHard removal.
10 XAnnotation companion classes: ContentAnnotation, FormDataAnnotation, HasFormDataAnnotation, HasQueryAnnotation, HeaderAnnotation, PathAnnotation, PathRemainderAnnotation, QueryAnnotation, RequestAnnotation, StatusCodeAnnotationRemoved (used by the deleted @ContextApply plumbing)Hard removal. ContactAnnotation, LicenseAnnotation, TagAnnotation, and ResponseAnnotation remain (still consumed by Swagger generation utilities).
org.apache.juneau.ng.http.remote.Body / Header / Path / Query (early snapshot only)@org.apache.juneau.http.annotation.Content / Header / Path / QueryThe NG duplicate annotations were deleted; the canonical RestClient (formerly NgRestClient) uses the standard annotations.

Request Attributes vs Session Properties Separation​

Request attributes and parser / serializer session properties are now completely separate concepts. Previously, defaultRequestAttributes from @Rest / @RestOp were automatically merged into session properties, conflating two distinct concerns.

OldNewNotes
@Rest(defaultRequestAttributes={"key: value"}) flowed automatically into the parser / serializer session for every requestdefaultRequestAttributes populates only RequestAttributes (accessed via RestRequest.getAttributes()). Session properties must be set explicitly via the new programmatic API on RestRequest.Behavioral break. Resource code that relied on the implicit merge must explicitly bridge it — see the rows below.
n/aRestRequest.setSerializerSessionProperty(String, Object) / setParserSessionProperty(String, Object)Set per-request session properties programmatically. Typically called from @RestPreCall / @RestPostCall or inside the REST method before parsing / serialization.
n/aRestRequest.setSerializerSessionProperties(Map<String,Object>) / setParserSessionProperties(Map<String,Object>)Set multiple session properties at once. Use req.setSerializerSessionProperties(req.getAttributes().asMap()) to restore the pre-10.0 implicit-merge behavior.

REST Session-Option Allowlist Refactor​

The standalone session-option allowlist machinery has been replaced by a noInherit attribute on the existing @Rest / @RestOp group of annotations. Programmatic RestContext.Builder / RestOpContext.Builder allowlist setters are gone (the builders themselves are gone — see the REST Configuration section above).

OldNewNotes
@NoInherit standalone annotationnoInherit attribute on @Rest / @RestOp / @RestGet / @RestPost / @RestPut / @RestPatch / @RestDelete / @RestOptionsHard removal. Use the annotation attribute directly, e.g. @RestGet(allowedSerializerOptions="myOpt", noInherit="allowedSerializerOptions"). Supported names include allowedParserOptions and allowedSerializerOptions (case-insensitive; SVL and comma-separated tokens expand).
org.apache.juneau.rest.util.StringTokenSet (REST-server allowlist driver)Internal merge handled by RestSessionOptionAllowlistMergeInternal class — only relevant if reflectively referenced. The juneau-commons StringTokenSet survives as a general-purpose token-set utility.
RestAllowedSerializerOptions / RestAllowedParserOptionsRemoved — keys live on @Rest / @RestOp(allowedSerializerOptions / allowedParserOptions) annotation attributesHard removal.
RestAllowedSessionOptionsMerge / NoInheritUtilsRemovedInternal merge helpers — no replacement needed.
RestContext.Builder.appendAllowedSerializerOptions(...) / appendAllowedParserOptions(...) / allowedSerializerOptions(...) / allowedParserOptions(...) settersExpress via @Rest(allowedSerializerOptions=..., allowedParserOptions=...) annotation attributesThe programmatic builder setters are gone with the builder itself.
RestOpContext.Builder.appendAllowed*() / allowedSerializerOptions(...) / allowedParserOptions(...) settersExpress via @RestOp(allowedSerializerOptions=..., allowedParserOptions=...) annotation attributesSame.
RestSessionOptionWire static literalsRestSharedConstants in juneau-rest-common — HEADER_JuneauSerializerOptions, HEADER_JuneauParserOptions, QUERY_juneauSerializerOptions, QUERY_juneauParserOptionsClass rename.
RestSessionOptionsSettings static literalsRestServerConstants in juneau-rest-server — SETTING_sessionOptions_rejectWhenAllowlistEmpty, SETTING_sessionOptions_failOnInvalidAllowlistEntry, etc.Class rename.

Legacy cp Bean-Store Classes Removed​

The pre-existing legacy bean-store / bean-creator classes in org.apache.juneau.cp have been deleted. The replacement types live in org.apache.juneau.commons.inject.

OldNewNotes
org.apache.juneau.cp.BasicBeanStoreorg.apache.juneau.commons.inject.BasicBeanStore (renamed from BasicBeanStore2)Constructor: BasicBeanStore.create().build() → new BasicBeanStore(). Static INSTANCE field preserved. BasicBeanStore.of(parent) → new BasicBeanStore(parent).
org.apache.juneau.cp.BeanCreatororg.apache.juneau.commons.inject.BeanInstantiatorHard rename. BeanCreator.of(MyBean.class, store) → BeanInstantiator.of(MyBean.class, store).
creator.arg(Type.class, value)instantiator.addBean(Type.class, value)Method rename.
creator.type(Impl.class) / creator.impl(instance) / creator.run()instantiator.type(Impl.class) / instantiator.impl(instance) / instantiator.run()Method names unchanged.
creator.orElse(default)instantiator.asOptional().orElse(default)Two-step replacement.
org.apache.juneau.cp.BeanBuilder (base class for domain fluent builders)org.apache.juneau.commons.inject.BeanInstantiator plus native builder fields on the domain typesHard removal. Domain builders now use native fields instead of extending BeanBuilder.
org.apache.juneau.cp.BeanCreateMethodFinderBeanStore.createBeanFromMethod(type, obj, predicate)Hard rename — method-on-store rather than free-standing finder class.
org.apache.juneau.cp.ContextBeanCreatorRetained at org.apache.juneau.cp.ContextBeanCreatorNot removed. Solves a different problem (persistent Context.Builder holder for repeated annotation application).
org.apache.juneau.commons.inject.CreatableBeanStoreRemovedUnused internal interface. WritableBeanStore is the canonical write-capable interface.

Bean-Store Convenience Renames​

The Xxx2-suffix transitional types have been promoted to the canonical name:

OldNewNotes
org.apache.juneau.commons.inject.BasicBeanStore2org.apache.juneau.commons.inject.BasicBeanStoreHard rename. The legacy org.apache.juneau.cp.BasicBeanStore is deleted — see "Legacy cp Bean-Store Classes Removed" above.
org.apache.juneau.rest.springboot.SpringBeanStore2org.apache.juneau.rest.springboot.SpringBeanStoreHard rename. The old name is removed.
Several builder fields/constructors typed as WritableBeanStore on EncoderSet.Builder, SerializerSet.Builder, ParserSet.Builder, ResponseProcessorList.Builder, RestOpArgList.Builder, RestMatcherList.Builder, RestGuardList.Builder, RestConverterList.Builder, RestOperations.Builder, RestChildren.BuilderNarrowed to BeanStore (read-only)Source-compatible — WritableBeanStore extends BeanStore, so existing call sites pass through unchanged. The narrower type clarifies that these builders only perform bean lookup, not registration.

Swagger v2: ParamInfo → ParameterInfo​

The legacy ParamInfo type in the Swagger v2 bean module (org.apache.juneau.bean.swagger.ParamInfo) has been fully replaced with ParameterInfo. All references in juneau-bean-swagger have been migrated.

OldNewNotes
org.apache.juneau.bean.swagger.ParamInfoorg.apache.juneau.bean.swagger.ParameterInfoHard rename. Update imports and references.

Multi-Key Cache and ConcurrentHashMapXKey Refactor (9.2.0)​

juneau-commons introduced a dedicated Cache<...> family that separates caching concerns from the base concurrent maps. The base maps were renamed for naming-convention consistency, and their constructors no longer accept caching parameters.

OldNewNotes
Concurrent2KeyHashMapConcurrentHashMap2KeyHard rename for naming-convention consistency.
Concurrent3KeyHashMapConcurrentHashMap3KeySame.
Concurrent4KeyHashMapConcurrentHashMap4KeySame.
Concurrent5KeyHashMapConcurrentHashMap5KeySame.
new ConcurrentXKeyHashMap(boolean disabled, Function supplier, ...) constructors carrying caching statePure multi-key concurrent maps (extends ConcurrentHashMap<TupleX<...>, V>); for caching use the new sibling Cache<K,V> / Cache2<K1,K2,V> / Cache3 / Cache4 / Cache5 builder-pattern API (Cache2.of(K1.class, K2.class, V.class).maxSize(...).supplier(...).build())Code using only the multi-key map needs a class-name update. Code using disabled / supplier parameters must migrate to the new CacheX family.
Null key in ConcurrentXKeyHashMap.get(...) / put(...) previously bypassed the cache silentlyThrows IllegalArgumentExceptionHard behavioral break.

Module Rename: juneau-all → juneau-shaded-all (9.2.0)​

The previous juneau-all Maven module has been removed in favor of juneau-shaded-all, part of the new juneau-shaded family of shaded (uber) JAR artifacts. No code changes are required — all imports and APIs are unchanged.

OldNewNotes
<artifactId>juneau-all</artifactId><artifactId>juneau-shaded-all</artifactId>Hard rename. Consider the more specific shaded artifacts (juneau-shaded-core, juneau-shaded-rest-client, juneau-shaded-rest-server, juneau-shaded-rest-server-springboot) if you don't need the full framework.

Utils Grab-Bag Removed → Shorts Facade + Domain Classes​

The org.apache.juneau.commons.utils.Utils grab-bag class has been deleted entirely — there is no deprecated delegator and no slimmed remnant. Its terse one/two-letter methods were split into two tiers:

  • Terse aliases now live in a single new facade, org.apache.juneau.commons.utils.Shorts. Every alias is a one-line delegation to a canonical, fully-descriptive method on a domain class.
  • Canonical logic lives in the domain classes: the new org.apache.juneau.commons.utils.ObjectUtils (null/empty checks, equality, comparison, coalescing, boolean folds, identity/stringify, optionals, and the abs/isBetween/isNotMinusOne numeric helpers), plus the existing StringUtils, CollectionUtils, ClassUtils, ThrowableUtils, IoUtils, FileUtils, and SystemUtils.

Migration: replace the wildcard static import and keep typing the same short tokens:

// Before
import static org.apache.juneau.commons.utils.Utils.*;

// After
import static org.apache.juneau.commons.utils.Shorts.*;

Most short names are unchanged (nn, n, eq, neq, h, id, s, r, cn, cns, f, cmp, lt, gte, uc, lc, o, oe, sz, …). The renames, relocations, and behavioral changes to be aware of:

Old (Utils.*)NewNotes
import static ...utils.Utils.*import static ...utils.Shorts.*Utils is deleted; Shorts is the terse facade.
firstNonNull(a, b, c)or(a, b, c)firstNonNull is retired. or(T...) is the varargs coalesce alias for ObjectUtils.coalesce(Object...). The old 2-arg def(value, default) alias was deleted — call or(value, default) or ObjectUtils.coalesce(value, default) directly.
opt(x) / opte()o(x) / oe()Delegate to ObjectUtils.optional / ObjectUtils.emptyOptional.
isArray(x)(no Shorts alias) — ClassUtils.isArray(x)Call the domain method directly (same no-alias rule as min/max/abs below).
isBetween(n, lo, hi)btw(n, lo, hi)Delegates to ObjectUtils.isBetween.
nullIfEmpty(x)nie(x)Delegates to StringUtils.nullIfEmpty / CollectionUtils.nullIfEmpty.
min(a, b) / max(a, b) / abs(n)ObjectUtils.min(...) / ObjectUtils.max(...) / ObjectUtils.abs(...)No Shorts alias — call the domain method directly (the idiomatic names are kept to avoid shadowing Math.min/max/abs).
memoize(supplier) / memoizer(supplier)Suppliers.memoize(...) / Suppliers.memoizer(...)Rehomed to org.apache.juneau.commons.function.Suppliers (no Shorts alias). Use import static org.apache.juneau.commons.function.Suppliers.*;.
l(...) (fixed-size list)Shorts.l(...) → CollectionUtils.list(...)Behavior changed: l(...) now builds a modifiable ArrayList (null varargs still pass through as null). The old fixed-size, Arrays.asList-backed behavior is still available as CollectionUtils.fixedSizeList(...) (no short alias).
m(...) (immutable map)Shorts.m(...) → CollectionUtils.map(...)Behavior changed: m(...) now builds a modifiable LinkedHashMap (all 11 overloads, 0–10 entries). The old immutable behavior is still available as CollectionUtils.immutableMap(...) (no short alias).
(new)st(...) → CollectionUtils.set(...)New terse alias for a modifiable, insertion-ordered LinkedHashSet.
(new)ss(...) → CollectionUtils.sortedSet(...)New terse alias for a TreeSet (null values filtered out).
emptyList() / emptySet() / emptyMap()(removed) — use java.util.Collections.emptyList() / emptySet() / emptyMap()CollectionUtils.emptyList/emptySet/emptyMap were removed as redundant with the JDK equivalents.
(new)lb(type) / mb() / mb(k, v) / stb(type)New builder aliases for CollectionUtils.listBuilder/mapBuilder/setBuilder. Note the set-builder alias is stb, not sb — sb is reserved for the StringBuilder factory below.
(new)sb(v) / sb(v...)New alias for StringUtils.newStringBuilder(Object) / newStringBuilder(Object...) — creates a StringBuilder pre-populated with the stringified value(s).
safe(...) / safeOpt(...) / etc.ThrowableUtils.safe(...) / safeCatch(...) / safeOpt(...) / safeOptCatch(...) / safeOrNull(...) / safeSupplier(...)The safe* short names are now the canonical ThrowableUtils methods themselves, not Shorts aliases — call ThrowableUtils.safe...(...) directly, or import static ...utils.ThrowableUtils.*;.
rex(msg, args)rex(...)Alias unchanged, but it and its siblings (brex/iaex/isex/uoex/uoroex/ioex/exex) no longer delegate to a ThrowableUtils canonical — they are self-contained exception factories directly in Shorts (a documented exception to the pure-delegation rule).

The short-name vocabulary is otherwise preserved, so call sites that already type the short tokens compile unchanged once the import is swapped. If you prefer the fully-descriptive names, call the domain classes directly (ObjectUtils.isNotNull(x), StringUtils.format(...), CollectionUtils.array(...), etc.) — the Shorts aliases and the canonical methods are interchangeable.

caution

f() / fs() / StringUtils.format(...) are now printf-only. These helpers previously accepted both printf (%s) and MessageFormat ({0}) placeholder styles. As of 10.0.0 they route straight through String.format semantics, so {0}-style placeholders render literally and doubled single quotes ('') are no longer collapsed. Migrate MessageFormat patterns to printf ({0}→%s; out-of-order {1} {0}→%2$s %1$s; escape any literal % as %%), or switch to the new Shorts.mf() / mfs() / StringUtils.mformat(...) helpers, which own the full MessageFormat grammar. The i18n path (Messages.getString, .properties bundles) and Logger / LogRecord MessageFormat logging are unaffected — they route through mf() internally. See the 10.0.0 release notes for the full breakdown.

Miscellaneous Utility Removals​

OldNewNotes
Tuple2Function / Tuple3Function / Tuple4Function / Tuple5FunctionFunction2 / Function3 / Function4 / Function5Hard rename — API is equivalent (same arity, same apply(...) signature).
Console.format(String, Object...)Shorts.f(String format, Object... args) (or StringUtils.format(...))Hard removal — duplicated functionality. Console.err(...) / Console.out(...) are unchanged.
BasicRuntimeException (concrete subclass used to wrap arbitrary checked exceptions)Shorts.rex(...) (with optional Throwable cause overload)Retired in favor of the terse Shorts exception factories (import static org.apache.juneau.commons.utils.Shorts.*;). Use rex(msg, args) or rex(cause, msg, args). Sibling helpers: iaex(...) (IllegalArgumentException), isex(...) (IllegalStateException), uoex(...) (UnsupportedOperationException), ioex(...) (IOException), brex(...) (BeanRuntimeException) — all with cause-accepting overloads. Message args are printf %s-style (not MessageFormat {0}); for genuine MessageFormat needs use mf()/mfs(). ThrowableUtils itself now holds only inspection/wrapping helpers (castException, findCause, safe(...), etc.) — the message-formatting factories live on Shorts.
ArrayUtils static methodsCollectionUtils static methods (same signatures: last(), append(), combine(), indexOf(), toList(), contains(), etc.)ArrayUtils is now @Deprecated and delegates to CollectionUtils. Migrate at your convenience; both will continue to work during the deprecation period.
ResettableSupplierOptionalSupplierClass rename.

AssertionUtils Argument/State Checks → Shorts.req* / Shorts.chk*​

The argument and state checks in org.apache.juneau.commons.utils.AssertionUtils are removed. Use the Shorts equivalents (import static org.apache.juneau.commons.utils.Shorts.*;). The thrown exception type and message format are unchanged. AssertionUtils.assertOneOf(...) is unchanged and stays in AssertionUtils.

Old (AssertionUtils.)New (Shorts.)Exception
assertArgNotNullOrBlank(name, value)reqnb(name, value)IllegalArgumentException (unchanged)
assertClassArrayArgIsType(name, type, array)reqcat(name, type, array)IllegalArgumentException (unchanged)
assertArgsNotNull(n1, v1, n2, v2, ...) (2-5 pairs)reqnn(n1, v1, n2, v2, ...) (2-5 pairs)IllegalArgumentException (unchanged)
assertArgNotNull(name, value)reqnn(name, value)IllegalArgumentException (unchanged)
assertArgNoNulls(name, array)reqnns(name, array)IllegalArgumentException (unchanged)
assertType(type, value[, exceptionSupplier])reqt(type, value[, exceptionSupplier])IllegalArgumentException (or the supplied exception, unchanged)
assertArg(expression, msg, args...)req(expression, msg, args...)IllegalArgumentException (unchanged)
assertState(expression, msg, args...)chk(expression, msg, args...)IllegalStateException (unchanged)
assertNotNull(value, msg, args...)chknn(value, msg, args...)IllegalStateException (unchanged)
assertOneOf(...)(unchanged, stays in AssertionUtils)
// Before
import static org.apache.juneau.commons.utils.AssertionUtils.*;
assertArgNotNull("name", name);
assertState(open, "Not open");

// After
import static org.apache.juneau.commons.utils.Shorts.*;
reqnn("name", name);
chk(open, "Not open");

BCT: juneau-bct Now Carries the Assertions and JUnit Extensions​

juneau-bct keeps its 9.2 coordinate and absorbs juneau-assertions and juneau-junit5, which are removed with no relocation POMs. It is a commons-only module, so it no longer pulls in juneau-marshall.

OldNewNotes
juneau-assertions / juneau-junit5 dependencyjuneau-bct dependencyDedupe if you used more than one. A 9.2 juneau-bct dependency keeps its artifactId.
org.apache.juneau.junit.bct (and …bct.annotations)org.apache.juneau.bctUpdate imports.
org.apache.juneau.assertionsorg.apache.juneau.bct.assertionsUpdate imports.
org.apache.juneau.junit5org.apache.juneau.bct.junitUpdate imports.

See the 10.0.0 release notes for the JSON-assertion methods removed from FluentObjectAssertion.

BCT: AssertionArgs Removed​

OldNewNotes
AssertionArgs.withConverter(customConverter).assertBean(expected, actual, "path")setConverter(() -> customConverter); assertBean(expected, actual, "path"); resetConverter();Hard removal of the AssertionArgs builder. Assertion methods now take an optional leading Supplier<String> parameter for custom messages: assertBean(() -> "custom msg", expected, actual, "propertyPath"). The default thread-local converter supplier is BasicBeanConverter.DEFAULT.

OpenAPI 3.1 Emission + API-docs Mixin Pack​

OpenAPI 3.1 emission is new in v10.0; existing Swagger v2 emission is unchanged. Composition is done by listing api-docs mixins in @Rest(mixins=...) rather than via a string knob — the apiFormat annotation member, system property, and RestContext.getApiFormat() accessor that briefly existed during the development cycle were removed before release in favor of compose-by-class.

OldNewNotes
Resource exposed only Swagger v2 at /api/*. No way to opt into OpenAPI 3.1 emission.Apply one or more api-docs mixins from org.apache.juneau.rest.server.docs: SwaggerMixin (/api), SwaggerUiMixin (/swagger), OpenApiMixin (/openapi, /openapi.json, /openapi.yaml), RedocMixin (/redoc). Subclasses of BasicRestServlet / BasicRestResource get all four (composed as @Rest(mixins={SwaggerUiMixin.class, RedocMixin.class})) automatically.The .json and .yaml paths are format-pinned (ignore Accept). The two UI mixins (SwaggerUiMixin, RedocMixin) declare @Rest(defaultAccept="text/html") so bare browser requests render HTML. Transitive resolution dedupes by class identity.
BasicRestOperations.getSwagger(RestRequest) and getOpenApi(RestRequest) interface methodsRemoved. The endpoints are now mounted via the api-docs mixin pack on BasicRestServlet / BasicRestResource. User-written BasicRestOperations implementers should remove their old getSwagger / getOpenApi overrides — the methods are no longer on the interface.If you need a different api-docs surface, add @Rest(noInherit={"mixins"}, mixins=...) on your subclass.
BasicGroupOperations.getChildrenSwagger(RestRequest) / getChildrenOpenApi(RestRequest) (the ?Swagger / ?OpenApi query mirrors on group resources) and the HasSwaggerQueryParam / HasOpenApiQueryParam matcher inner classesRemoved. Hit /api, /swagger, /openapi, or /redoc directly — they are always mounted on BasicRestServletGroup / BasicRestResourceGroup via the api-docs mixin pack.External docs that linked to /?Swagger=true need updating to /api (or /swagger).
RestRequest.getSwagger() was the only way to ask the server for its self-described API.RestRequest.getOpenApi() provides the OpenAPI 3.1 sibling. RestContext.getOpenApi(Locale) and RestContext.getOpenApiProvider() mirror the Swagger getters.The OpenAPI 3.1 document is generated by transforming the Swagger 2.0 emission to OpenAPI 3.1 JSON, so every Swagger-aware annotation (@Schema, @Content, @StatusCode, etc.) round-trips with no source changes.

Copy/Clone Utilities Moved to CopyUtils​

The copyOf(...) collection/map/array helper family and the null-safe reflective cloneOf(T) helper have been moved out of CollectionUtils and ObjectUtils respectively into a new, subclassable org.apache.juneau.commons.utils.CopyUtils. This is a clean break — there is no deprecation shim, and neither original class keeps or references the moved methods. Two per-module subclasses (SwaggerCopyUtils, OpenApiCopyUtils) add domain bean copyOf(<BeanType>) overloads that coexist with the inherited collection overloads, retiring the interim copyOrNull methods on the swagger-v2 / openapi-v3 beans.

OldNewNotes
org.apache.juneau.commons.utils.CollectionUtils.copyOf(...) (all collection / List / Map / Set / array + primitive-array overloads)org.apache.juneau.commons.utils.CopyUtils.copyOf(...)Hard move. CollectionUtils no longer declares copyOf.
org.apache.juneau.commons.utils.ObjectUtils.cloneOf(T)org.apache.juneau.commons.utils.CopyUtils.cloneOf(T)Hard move. ObjectUtils no longer declares cloneOf.
Swagger-v2 bean Type.copyOrNull(x) (interim statics on Contact, ExternalDocumentation, Info, Items, License, SchemaInfo, Xml)Unqualified copyOf(x) via import static org.apache.juneau.bean.swagger.SwaggerCopyUtils.*;The copyOrNull statics were removed from these 7 beans and re-expressed as copyOf(<BeanType>) overloads on SwaggerCopyUtils extends CopyUtils.
Openapi-v3 bean Type.copyOrNull(x) (interim statics on Components, Contact, Discriminator, ExternalDocumentation, Info, Items, License, OAuthFlow, Operation, RequestBodyInfo, SchemaInfo, Server, Xml)Unqualified copyOf(x) via import static org.apache.juneau.bean.openapi3.OpenApiCopyUtils.*;The copyOrNull statics were removed from these 13 beans and re-expressed as copyOf(<BeanType>) overloads on OpenApiCopyUtils extends CopyUtils.
ThrownStats.copyOrNull(x), LogConfig.copyOrNull(x), MarshallingContext.Builder.copyOrNull(x), Context.Builder.copyOrNull(x)Unchanged — these four single-site one-off types keep their copyOrNull(T).Intentionally not migrated to a CopyUtils subclass (they have a single call site each).

Static-import recipe: for a file that resolved copyOf / cloneOf through a wildcard static import (import static org.apache.juneau.commons.utils.CollectionUtils.*; or ...ObjectUtils.*;), add import static org.apache.juneau.commons.utils.CopyUtils.*; alongside the existing wildcard (keep the latter for the other helpers you still use). For a qualified call, rewrite CollectionUtils.copyOf(...) → CopyUtils.copyOf(...) and ObjectUtils.cloneOf(...) → CopyUtils.cloneOf(...). In the swagger-v2 / openapi-v3 modules, files that need a bean copyOf(<BeanType>) overload should static-import the module subclass (SwaggerCopyUtils / OpenApiCopyUtils), which supplies both the inherited collection overloads and the bean overloads via ordinary overload resolution.

BeanQuery Search Language: Immutable SearchOperator + Case-Sensitive $eq/$in/$ne​

The org.apache.juneau.commons.beanquery search language has four source-/behavior-breaking changes beyond the general feature additions covered in the release notes. See the BeanQuery Package topic page for the full search-language grammar.

1. SearchOperator is no longer mutable​

SearchOperator.create(name, help) now returns a Builder, not a SearchOperator, and the untyped .sql(Object) / .sqlRenderer() hook is gone in favor of typed .extension(Class<X>, X) / .extension(Class<X>). Any caller holding a reference to the old mutable SearchOperator (fluent setters returning this) must add a terminal .build() and migrate off .sql(...).

 SearchOperator startsWith = SearchOperator.create("$starts", "Matches a prefix.")
.types(SearchType.TEXT)
- .predicate((value, args) -> String.valueOf(value).startsWith(args.get(0)))
- .sql((col, args) -> col + " LIKE '" + args.get(0) + "%'");
+ .predicate((value, args) -> String.valueOf(value).startsWith(args.get(0)))
+ .extension(SearchSqlRenderer.class, (dialect, col, type, args) -> SqlFragment.of(col + " LIKE ?", args.get(0) + "%"))
+ .build();

2. $eq/$in/$ne are now exact and case-sensitive​

These three operators used to match case-insensitively. In v10.0 they are exact and case-sensitive. A caller relying on the old case-insensitive behavior should switch to the new $eqic operator, which is exact and case-insensitive — the same matching behavior the old $eq had.

-// v9.x: case-insensitive exact match.
-query.setSearch("status=$eq(active)"); // matched "Active", "ACTIVE", "active"
+// v10.0: $eq is exact and case-sensitive. Use $eqic for the old behavior.
+query.setSearch("status=$eqic(active)"); // matches "Active", "ACTIVE", "active"

3. Porting a search from another $-operator syntax​

A case-insensitive $eq(...) from another $-operator syntax differs from Juneau's $eq(...), which is exact and case-sensitive (item 2 above). The direct port of such an $eq(...) call is Juneau's $eqic(...), not $eq(...) — this is easy to miss if you are porting a search string from another syntax rather than migrating Juneau's own pre-10.0.0 $eq call sites.

-// Search string from another syntax, ported verbatim.
-search = "owner=$eq(jsmith)";
+// Juneau v10.0 port: $eq alone would now require an exact-case match.
+search = "owner=$eqic(jsmith)";

A bare (operator-less) search value keeps working the same way it always has in Juneau: a case-insensitive substring match on TEXT/ID columns, a dotted-tuple prefix on VERSION columns, and an exact match on NUMERIC/TIMESTAMP/ENUM/BOOLEAN columns — and for TIMESTAMP specifically, a bare date is an exact UTC-midnight instant, not "that whole day"; use $between(d, d+1) for a day range.

There is no linter or migration tool that flags an old $eq(...) call site automatically; this note is the whole mitigation.

4. A search value that does not parse is now a 400 (BAD_VALUE), and SQL renderers receive typed values​

Search literals are now parsed against the column's SearchType while the query is resolved, before either engine runs it. A NUMERIC value that is not a number, a BOOLEAN that is not true/false, or a TIMESTAMP that is not an offset date-time, local date-time, date or epoch-millisecond value now fails with BeanQuerySyntaxException code BAD_VALUE (HTTP 400) in both InMemoryBeanQueryContext and SqlBeanQueryContext. Previously the in-memory engine treated it as "no match" and the SQL engine surfaced a database error.

Custom SearchSqlRenderers now receive List<Object> args: the parsed values for a built-in value operator or an operator built with typedArgs(true), and the literal Strings otherwise. Renderers written against List<String> must change the parameter type.

-.extension(SearchSqlRenderer.class, (dialect, col, type, List<String> args) -> SqlFragment.of(col + " % ? = 0", args.get(0)))
+.typedArgs(true) // args.get(0) is a BigDecimal
+.extension(SearchSqlRenderer.class, (dialect, col, type, List<Object> args) -> SqlFragment.of(col + " % ? = 0", args.get(0)))

Prefer offset timestamps (2026-09-30T12:00:00Z) in searches that must agree between engines, because a local form is UTC in memory but the database session zone in SQL. A SQL dialect that does not support an operator such as $regex, or that does not declare supportsVersionOrdering() for the VERSION range operators, now rejects the query with BAD_VALUE rather than returning rows that differ from the in-memory engine.

PostgresDialect moved to the org.apache.juneau.beanquery.postgres package (module juneau-beanquery-postgres); update the import. SqlDialect.renderBare takes the typed Object value, and PostgresDialect.bind is gone.

-import org.apache.juneau.commons.beanquery.PostgresDialect;
+import org.apache.juneau.beanquery.postgres.PostgresDialect;

Admin Console: Page Contract Migration​

The admin-console modules (juneau-console, juneau-console-freemarker, juneau-console-views, juneau-console-widgets) changed shape several times during the 10.0 development cycle. None of the removed items below shipped in a 9.x release, so this section matters if you built a console page against an earlier 10.0 snapshot. The new model is described in Admin-Console Chrome: a <@console> page carries one JSON page contract in <script type="application/json" id="juneau-page"> and loads /juneau-console/juneau-console.js, which draws the header, nav, cards and footer in the browser.

RemovedReplacementNotes
FreeMarker globals pageBody, pageTab, pageInit, pageCss, pageToolkit, pageToolkitCss, pageToolkitJs, pageThemeCssDirectives inside <@console> (<@navigation>, <@node>, <@main/>, <@footer text="..."/>); jcHasToolkit("views") instead of pageToolkitConsoleTemplateValidator flags any template still referencing a removed global.
Chrome templates that did not use <@console>A chrome template containing <@console>A <@page> whose chrome renders no <@console> fails the render.
<@card type="js">, type="json", type="calendar"type="html" (body, template= or same-origin src=), type="datatables", or a type registered with JuneauConsole.registerCardThe valid built-in types are html (the default) and datatables. A removed type fails the render. The calendar runtime is still available as the opt-in "calendar" toolkit pack.
PageDef, Tab, Subtab, PageTable, CardGridTable, ViewCardBody and juneau-pages.js (org.apache.juneau.console.views)<@console> pages with <@navigation> nodes and <@card> directivesViewTable, RegionDef and RegionTable are kept.
ViewSlot, RibbonAction (org.apache.juneau.console.views)Region and ribbon JSON handled by juneau-regions.js and juneau-views.jsDeleted together with the deprecated Java view emitters.
CalendarLayout, SavedViewsMixin, SavedViewStore, InMemorySavedViewStore, SavedViewQuotaExceptionn/aCalendarLayout duplicated juneau-calendar.js. Keep saved views in your own endpoints, or use the browser-side localStorage persistence provider.
ThemePack, ThemeSettings, ThemePackSettings, ConsoleChromeMixin.Builder.pack(...) / pageBackgroundImage(...)<@theme> / <@token> in the template (value= for leaves, alias= for live var() references); ConsoleChromeMixin.create().theme("<stock-name>") for the served chrome.cssTheming is FTL-only.
/juneau-console/assets/logo and /juneau-console/assets/page-bg endpointsThe logo is a static chrome.css constant; the page background is the --jc-page-bg tokenThose paths now return 404 because no route exists.
juneau-rest-server-theme-packs module<@theme name="..."> stock themesRemove the dependency.
Top-level version key in the #juneau-page contract; PageContractAssert.hasVersion(String)contractVersion; hasContractVersion(String)Only affects code written against an earlier 10.0 snapshot. The value is unchanged ("1"). There is no fallback: the schema forbids version, and assertPage(...) throws if a contract still carries it.
Server-rendered nav, header, footer and card wrappers in the HTML bodywindow.JuneauConsole and PageContractAssertTests that matched that HTML should parse the contract instead.
// Before: assert against server-rendered chrome markup.
assertTrue(body.contains("<nav"));

// After: assert against the page contract.
assertPage(body)
.isValid()
.hasActiveNav("releases")
.hasCard("releases", "datatables")
.hasFooterText("Apache Juneau Release Manager");

PageContractAssert lives in org.apache.juneau.console.test (module juneau-console). Template failures are reported as HTTP 500 with a message naming the directive and the fix; in the browser, an unusable contract shows a .jc-console-error banner and logs [juneau-console] ... instead of rendering a blank page. See Admin-Console Chrome for the contract schema, the authoring directives and ConsoleTemplateValidator.

View Settings (juneau-console-views)​

The datatable View Settings dialog has these breaking changes. See Datatable View Settings for the new behavior.

  • columnSearchToggle ribbon type removed. Delete { type: 'columnSearchToggle' } from ribbonFront/ribbon lists and templates (an unknown type is ignored silently, so the button just disappears). Per-column search is the search icon in each header cell. Delete any CSS that targets .juneau-view-columnsearch-*.
  • schemaVersion is 2; readViewSettings returns {settings, reset}. Code that called NS.config.readViewSettings(...) and used the result directly must read .settings and may check .reset. Stored blobs at version 1, or with the old string-array sort, are discarded with a one-time notice; saved-view blobs at version 1 are rejected.
  • NS.config.defaultSortMembership removed. Sort is an ordered list of {column, direction} entries.
  • Options gains autoRefreshMs. Old blobs read as Off (0). Code that builds an Options object by hand should include it.
  • isPollSuspended is also true while the dialog is open. A view with pausePollingWhileEditing now pauses polling while the View Settings dialog is open.