Skip to main content

Release 10.0.0

Date: unreleased (in development)

Juneau 10.0.0 is a major version release representing a significant milestone for the Apache Juneau project. This release folds in the never-shipped 9.5 development line and also delivers a broad code-quality and maintainability uplift via an extensive SonarQube/SonarLint cleanup pass across the entire codebase.

Major version bump note​

This is the first release in the 10.x line. The jump from 9.x to 10.0 reflects accumulated breaking changes in the API surface and internal architecture that warranted a major version boundary, following Semantic Versioning conventions.

Consumers upgrading from 9.4.x or earlier should consult the V10.0 Migration Guide for a complete list of breaking changes.

Code quality improvements​

This release includes an extensive SonarQube / SonarLint cleanup pass across the entire Juneau codebase, addressing hundreds of code-quality findings including:

  • Removed dead/unreachable code and unused variables.
  • Fixed resource-leak issues (try-with-resources, explicit close calls).
  • Resolved potentially null-dereference paths and improved null-safety.
  • Replaced raw types with proper generic parameterization.
  • Addressed improper exception handling patterns (swallowed exceptions, overly broad catch blocks).
  • Cleaned up redundant casts, unnecessary object creation, and other minor inefficiencies.
  • Suppressed or resolved remaining SonarQube rule violations with documented rationale.

No functional behavior was changed as part of this cleanup; all changes were purely mechanical code-quality improvements.

Module index​

The new features and changes are grouped by module. Each page below holds one section per module (or module combination).

Marshall, commons and config​

juneau-marshall, juneau-commons, juneau-config, juneau-core, juneau-bct, juneau-bean.

REST server​

juneau-rest-server and its auth, metrics, tracing and view modules; juneau-rest-common.

REST client​

juneau-rest-client.

Microservice​

juneau-microservice, the Jetty and Tomcat microservices, juneau-microservice-test.

Other modules​

bean libraries, secrets, bean query, petstore, build and packaging.

Bug Fixes​

  • Prototext compact output now separates fields after a closing }, and repeated/nested fields round-trip. PrototextSerializer.DEFAULT (no .ws()) wrote nothing after a message's closing brace, giving }ins { and }nums:. Every message now ends with a newline, so compact output reads e.g. ins {n: 1\nname: "a"\n}\nnums: [4]\n (visible output change for compact mode; .ws() output is unchanged and old compact output still parses). PrototextParser now also accepts a single occurrence of a repeated field for a list/array property (a one-element List<Bean> previously failed). Lists of lists were written as strings (["[1]", "[2, 3]"]); since protobuf text format has no nested-list syntax, each inner list is now a _value message (grid {_value: [2, 3]}), and the parser unwraps it when the target type is a list or array. Without a declared target type (e.g. parsing into a JsonMap) the wrapper stays a map with a _value key.

  • Fixed RemoteClient treating Calendar/GregorianCalendar arguments as beans. Calendar values are now sent as scalar parts, alongside Date and the java.time types.

  • Fixed JavaHttpTransport sending replayable request bodies chunked even when their length is known. Repeatable bodies with a known content length (StringBody, ByteArrayBody, etc.) are now sent with fixed-length framing (Content-Length) instead of Transfer-Encoding: chunked, which some servers and proxies handle poorly. Unknown-length streaming bodies (e.g. StreamBody) remain chunked.

  • Fixed RRPC method calls never dispatching over POST. Every HTTP POST to an @RestOp(method="RRPC") operation previously returned a 404 instead of reaching the target method. RrpcRestOpSession derived the RRPC method key by splitting the request path on the last /, but RRPC keys are of the form methodName/(paramTypes) and themselves contain a /, so the method name was stripped off and the lookup always fell through to NotFound. The key is now derived from the already-computed URL-path-match remainder, so RRPC (REST-RPC) calls over POST dispatch correctly.

  • Fixed Json5Map / Json5List frozen (.unmodifiable()) instances still being mutable through several paths. The Unmodifiable variants of Json5Map and Json5List (juneau-marshall) only overrode a subset of the mutator surface — put(String,Object)/remove(Object) for the map, add(int,·)/remove(int)/set(int,·) for the list — so a caller holding a supposedly-frozen instance could still mutate it through the remaining paths: clear(), putAll/addAll, compute*/merge/replace*, the keySet()/entrySet()/values() views and their iterators, and (for Json5List) the full Deque surface inherited from LinkedList. Both classes now override the complete mutator surface, mirroring the sibling MarshalledMap/MarshalledList immutable-beans convention, so mutating a frozen Json5Map/Json5List always throws UnsupportedOperationException as documented. Migration: none for correctly-written callers; this only affects code that was inadvertently relying on the freeze-bypass to mutate a "frozen" instance.

  • Fixed the JSON serializer dropping carriage returns and emitting other control characters raw. JsonSerializer, Json5Serializer and the other JsonWriter-based serializers (juneau-marshall) previously silently dropped \r from string values and attribute names, and wrote every other control character (U+0000-U+001F except \n, \t, \b, \f, plus U+007F) unescaped, which is invalid JSON per RFC 8259 and is rejected by strict parsers such as Python's json.loads. The JSON serializer now escapes carriage returns as \r and all other control characters as \u00xx (lowercase hex) instead of dropping or emitting them raw. JsonTokenWriter likewise now escapes U+007F. Migration: output for strings containing \r or control characters changes (\r is now preserved as \r); consumers that depended on \r being stripped must normalize line endings themselves.

  • Fixed ReadinessState incorrectly shared across multiple default-configured microservices in one JVM. JettyServerComponent/TomcatServerComponent previously flipped ReadinessState.resolve(ms.getBeanStore()) on start/stop without ever registering a bean back into that store, and HealthAggregator resolved readiness from the health-probe servlet's own (different) RestContext bean store — so on the default (no app @Bean ReadinessState) path, both sides silently fell back to the single JVM-wide ReadinessState.shared() singleton. Stopping any one default-configured microservice therefore flipped /readyz to 503 for every other default-configured microservice running in the same JVM — a rolling-deployment hazard for any process embedding more than one microservice. Each JettyServerComponent/TomcatServerComponent now constructs (or reuses an app-supplied @Bean) one per-service ReadinessState instance and publishes that identical instance into both ms.getBeanStore() and the auto-mounted HealthServlet's own RestContext bean store, so /readyz reflects only its own service's lifecycle. ReadinessState.shared() remains as the fallback for non-microservice embeddings (a plain servlet deployment, or any caller that never registers a bean) — it is no longer the microservice bridge. Migration: none for normal usage; a process embedding only one microservice per JVM is unaffected either way.

  • Present-but-empty RestContext.* boolean settings no longer throw (leniency improvement). Landed alongside the new RestContextProperties @ConfigProperties bean (see New Features above): previously, a present-but-empty value for a boolean RestContext.* setting (e.g. RestContext.eagerInit=) threw BeanCreationException at injection time, because the old @Value boolean field's strict conversion had no tolerance for an empty string. As of 10.0.0 this is resolved leniently to false instead, for all 8 affected settings: disableContentParam, renderResponseStackTraces, problemDetails, virtualThreads, responseTraceparent, mdcAsyncPropagation, eagerInit, and lazyChildren. Migration: none for normal usage (a set-but-blank boolean setting is an unusual configuration to begin with); code that relied on the old throw to catch a misconfigured blank value must add its own explicit check.

  • SVL variables in a RestContext.* boolean setting now resolve through the resource's own var resolver (capability widening). Landed alongside the same RestContextProperties change: a $C{...} / $R{...} / $S{...}-style variable embedded in a boolean RestContext.* value (e.g. RestContext.virtualThreads=$C{MyConfig/virtualThreads}) is now resolved through the owning resource's VarResolver rather than VarResolver.DEFAULT, so resource-scoped variables like $C{...} (config) and $R{...} (request) now work where they previously didn't. Migration: none for normal usage; this only expands which variables resolve successfully.

  • Fixed Jetty aborting keep-alive connections after a chunked request body whose terminating chunk arrives late. RequestContent now drains any unread remainder of the request body after parsing (juneau-rest-server), so Jetty no longer aborts the connection. Previously this surfaced client-side as "header parser received no bytes" / NoHttpResponseException on the next request over the same keep-alive connection.

  • Fixed BoundedServletInputStream returning 0 instead of -1 at end of stream. Every read overload (juneau-rest-server) now honors the InputStream contract: -1 at end of input, including when the body is exactly @Rest(maxInput) bytes; a body larger than the limit still throws IOException.

Breaking Changes​

  • ApacheHc45Transport strips every credential header on a cross-origin redirect (behaviour change). On a redirect to a different scheme, host or port, the Cookie from the client's cookie store and the Authorization from its credentials provider, preemptive authentication or 401 challenge answer are now removed along with the headers you set on the request. This holds for transport-built and caller-supplied clients alike; before, a caller-supplied client still sent its cookie-store Cookie and credentials-provider Authorization to the new origin. Same-origin redirects are unchanged, and a chain that returns to the first origin restores that origin's credentials: in an A to B to A chain (an SSO round trip, say) the final hop back to A carries the headers you set, the cookie-store Cookie and the credentials-provider Authorization again, while every hop that is not at the first origin stays stripped. The SSRF-guarded @Remote redirect path (PolicyEnforcedRedirects) follows the same first-origin rule for every transport that uses it (JDK, OkHttp, HC4.5, HC5, mock) and now refuses a redirect Location with user info (the transport-built HC4.5 client does too). A returning hop is restored only for GET/HEAD; a 307/308 replay of another method after leaving the first origin stays stripped. Proxy-Authorization is now removed on a tunnelled route, and httpClient(...) refuses a legacy AbstractHttpClient such as DefaultHttpClient. This also changes the J0823 behaviour: a retry at the returning hop carries the restored credentials. The SSRF-guarded @Remote path now strips the cookie-store Cookie on a cross-origin hop too; before, only the headers set on the request were removed there. See REST Security.

  • juneau-my-jetty-microservice removed. The template-project module has been retired in favor of the new JettyMicroservice facade + bundled defaults in juneau-microservice-jetty. See the New Features section above for migration guidance.

  • juneau-examples-rest{,-jetty,-springboot,-jetty-ftest} removed. The four-module legacy example family has been retired in favor of the new juneau-petstore-{core,jetty,springboot} showcase family. The legacy modules carried an unpatched Hibernate ORM 5.6.x SQL-injection vulnerability (CVE-2026-0603) that had no upstream fix because Hibernate 5.6.x is end-of-life; deletion closes that exposure surface. The accompanying scripts/start-examples-rest-jetty.py and scripts/start-examples-rest-springboot.py launchers are replaced by scripts/start-petstore-jetty.py and scripts/start-petstore-springboot.py. The five legacy 18.0x.JuneauPetstore* topic pages (which described the retired external apache/juneau-petstore repository) and the three 19.0x.JuneauExamplesRest* topic pages are replaced by the single new juneau-petstore Overview page.

  • Public ObjectRest class removed. The org.apache.juneau.objecttools.ObjectRest class is gone; its URL-addressed GET/PUT/POST/DELETE traversal engine survives only as an internal PathTraversal helper (not a supported public API). Per-consumer migration:

    • ResponseContent.asObjectRest() / asObjectRest(Class) (juneau-rest-client) — removed. Replace with response.getContent().as(JsonMap.class).getAt(path, type), or the RFC 6901 form response.getContent().as(JsonMap.class).at(pointer).
    • Reflective helpers — ObjectRest.invokeMethod(...) / getPublicMethods(...) were dropped; use ObjectIntrospector directly if you need that capability.
    • Unaffected — MarshalledMap/MarshalledList (and JsonMap/JsonList) getAt/putAt/postAt/deleteAt are unchanged (now backed by PathTraversal); the companion exception is now PathTraversalException (public, renamed to match PathTraversal); the Traversable REST response converter and Swagger/OpenAPI $ref (#/) resolution behavior are unchanged.
    • New replacement APIs — the new MarshalledNode typed tree façade and JsonPointer (see New Features above) provide typed navigation and RFC 6901 JSON-Pointer addressing over the same collections model.
  • REST debug/logging system fully redesigned (juneau-rest-server) (hard break, no deprecation shim). The entire multi-signal debug/logging mechanism has been replaced by a single JUL-logger-level-driven model (see New Features above). Removed with no replacement annotation, attribute, or config key: @Rest(debug=...) / @RestOp(debug=...), the nested @Debug annotation and its value()/on()/format()/level()/config() members, debugDefault/debugEnablement/debugOn; the debug/ package (DebugEnablement, BasicDebugEnablement, DebugConfig, DebugRule, DebugFormat, and friends); the CallLogger logging SPI (CallLogger, CallLoggerRule, CallLoggingDetail, BasicCallLogger, BasicTestCallLogger, and friends) including Rest#callLogger(); and the juneau.restLogger.* config keys. Replacement: set the resource's own java.util.logging.Logger level (INFO/FINE/FINEST) and, if you need custom formatting, implement the new RestDebugFormatter SPI. RestRequest.isDebug() is kept but redefined as derived/read-only. See the V10 Migration Guide for the full mapping, including a footgun callout about raising a resource's logger level for unrelated application logging.

  • Next-generation RestClient debug logging redesigned around JUL levels + RichLogger (hard break, no shim). The NG client (org.apache.juneau.rest.client.RestClient) removes RestLogger, RestLogEntry, RestLogLevelResolver, BasicRestLogger, builder logger(...), and per-request RestRequest.debug(). Replacement is single-signal, level-driven debug on the configured logger name: INFO (basic), FINE (headers), FINEST (headers + bounded bodies). New builder entry points are debugLoggerName(String) (default RestClient.class.getName()) and debugFormatter(RestClientDebugFormatter) (default BasicRestClientDebugFormatter). For tests, capture emitted records with RichLogger.getLogger(name).captureEvents(Level) and assert against LogRecords.

  • Marshaller instance API kept as read/write; static of/to shortcuts reintroduced on concrete classes. An interim plan to rename the top-level convenience methods on Marshaller, CharMarshaller, and StreamMarshaller to to/of was not carried through — the instance methods remain read/write, and to/of ship instead as static shortcuts on the concrete marshaller classes (e.g. Json, Xml, Protobuf), delegating to each class's DEFAULT instance:

    • Instance (unchanged names): Marshaller.read(Object, Class<T>) / read(Object, Type, Type...); Marshaller.write(Object, Object). CharMarshaller adds a String-based read(String, ...) and a String-returning write(Object); StreamMarshaller adds a byte[]-based read(byte[], ...) and a byte[]-returning write(Object).
    • Static shortcuts (new, on each concrete class): Xxx.to(input, type, ...) parses via Xxx.DEFAULT.read(...); Xxx.of(object) serializes via Xxx.DEFAULT.write(...) — e.g. Json.to(json, MyBean.class) / Json.of(myBean).
    • Streaming/record cursors (capability-gated) — only available when the underlying serializer/parser implements the corresponding marker interface (TokenReadable/TokenWritable, RecordReadable/RecordWritable, ArrayRecordReadable/ArrayRecordWritable): instance readTokens/writeTokens, readRecords/writeRecords, readArrayRecords/writeArrayRecords; and their static counterparts toTokens/ofTokens, toRecords/ofRecords, toArrayRecords/ofArrayRecords.
  • Marshaller readable/compact instance constants removed in favor of variant classes. The Json5.DEFAULT_READABLE, Ini.DEFAULT_READABLE, and Hjson.DEFAULT_COMPACT public constants have been removed. Use the new first-class variant marshaller classes instead (see New Features above): Json5R (readable JSON5), IniR (readable INI), and HjsonC (compact Hjson). Each variant is a subclass of its base facade with its own DEFAULT and the full static of(...)/to(...) shortcut surface, so migration is typically a direct swap:

    • Json5.DEFAULT_READABLE.write(x) → Json5R.of(x) (or Json5R.DEFAULT for an instance).
    • Ini.DEFAULT_READABLE.write(x) → IniR.of(x).
    • Hjson.DEFAULT_COMPACT.write(x) → HjsonC.of(x).

    The in-tree call sites (BasicSwaggerProviderSession, BasicOpenApiProviderSession, and the AtomJsonExample) were migrated to the variant classes.

    Note: the RecordReader.read() and RecordWriter.write() cursor methods are a separate, unrelated low-level cursor contract — they were never part of this rename discussion. If you adopted the earlier to/of-as-instance-methods preview, revert those call sites back to .read(/.write( on the marshaller instance, or switch to the static shortcut on the concrete class (Json.to(...)/Json.of(...)) if you don't need a custom-configured instance.

  • SerializerSet / ParserSet lookup methods now return Optional<...>. Every media-type lookup method on SerializerSet and ParserSet now returns a java.util.Optional instead of null on no-match, aligning these classes with the Optional-returning idiom already used by their consumers (RestResponse.getSerializerMatch(), RequestContent.getParserMatch()). Changed signatures:

    • SerializerSet: getSerializer(MediaType) / getSerializer(String) → Optional<Serializer>; getSerializerMatch(MediaType) / getSerializerMatch(String) → Optional<SerializerMatch>; getStreamSerializer(MediaType) / getStreamSerializer(String) → Optional<OutputStreamSerializer>; getWriterSerializer(MediaType) / getWriterSerializer(String) → Optional<WriterSerializer>.
    • ParserSet: getParser(MediaType) / getParser(String) → Optional<Parser>; getParserMatch(MediaType) / getParserMatch(String) → Optional<ParserMatch>.
    • The getSerializers(), getParsers(), and getSupportedMediaTypes() collection getters are unchanged (they were never null).
    • Both MediaType overloads now guard null and return Optional.empty() (previously ParserSet.getParserMatch(MediaType) threw an NPE on a null argument).
    • Migration: unwrap with .map(...) / .orElseThrow(...) / .ifPresent(...), or .orElse(null) to preserve a prior null contract. For the HTTP-server direction, throwing the status-correct exception is idiomatic — NotAcceptable (406) for a failed Accept/serializer lookup and UnsupportedMediaType (415) for a failed Content-Type/parser lookup. The next-gen RestClient.getSerializerForMediaType(String), getParserForMediaType(String), and getMatchingParser(String) lookup methods likewise now return Optional<...>.
  • RestOpContext matcher getters changed from arrays to List. RestOpContext.getPathMatchers(), getOptionalMatchers(), and getRequiredMatchers() previously returned raw arrays (UrlPathMatcher[] / RestMatcher[] respectively); as of 10.0.0 they return an immutable, cached List instead (List<UrlPathMatcher> / List<RestMatcher>), closing a mutable-state-exposure hole where a caller holding the returned array could corrupt the op context's routing/matcher state. This is source-incompatible for any caller that assigned the result to an array-typed variable or indexed it with [i]. Migration: switch to List-based access (.get(i) / .size() / iteration), or call .toArray(new UrlPathMatcher[0]) (or new RestMatcher[0]) if an array is specifically required.

  • Next-gen RestClient no longer falls back to JSON implicitly (behavioral change). The next-generation org.apache.juneau.rest.client.RestClient previously treated a fully-unconfigured client as "JSON in / JSON out" and also used a lone registered parser/serializer regardless of media type. Both implicit behaviors are removed: content negotiation now resolves a parser/serializer only via an exact media-type match or an explicitly-configured default, otherwise it resolves to nothing.

    • New opt-in builder knobs restore the old behavior deliberately: RestClient.Builder.defaultParser(Parser) and RestClient.Builder.defaultSerializer(Serializer) (e.g. .defaultParser(JsonParser.DEFAULT).defaultSerializer(JsonSerializer.DEFAULT)).
    • Resolution precedence is now: exact media-type match → explicitly-configured default → none.
    • A response whose Content-Type matches no registered parser (and with no default parser configured) now fails with UnsupportedMediaType (HTTP 415) instead of silently parsing as JSON — affecting ResponseBody.as(...) and remote-proxy (@Remote) response deserialization. On the request/serializer side, serializing a POJO body with no matching or default serializer now throws a clear client-side IllegalStateException rather than defaulting to JSON.
    • Migration: if you relied on the out-of-the-box JSON behavior, add .defaultParser(JsonParser.DEFAULT) and/or .defaultSerializer(JsonSerializer.DEFAULT) to your RestClient.Builder. The classic org.apache.juneau.rest.client.classic.RestClient is unaffected.
  • Test modules merged into juneau-bct. juneau-assertions and juneau-junit5 have been folded into org.apache.juneau:juneau-bct, the 9.2 Bean-Centric Testing coordinate, which is now one commons-only module that also carries the fluent assertions and the JUnit 5 extensions. The two old artifacts are removed (no relocation POMs), and the BCT packages moved (see below). Migration:

    • Dependency: replace any juneau-assertions / juneau-junit5 dependency with a single juneau-bct dependency (dedupe where you used more than one). An existing 9.2 juneau-bct dependency keeps its coordinate; only its version and imports change.
    • Package renames: org.apache.juneau.assertions → org.apache.juneau.bct.assertions; org.apache.juneau.junit5 → org.apache.juneau.bct.junit; org.apache.juneau.junit.bct (and its …bct.annotations sub-package) → org.apache.juneau.bct. Update imports accordingly.
  • New 10.0 modules named after their projects. The modules added during 10.0 development were renamed before release, so code built against a 10.0.0 snapshot needs the new artifactIds and packages:

    • Juneau Terminal: juneau-run → juneau-terminal; juneau-run-report → juneau-terminal-report (org.apache.juneau.terminal.report); juneau-rest-server-terminal → juneau-terminal-region (org.apache.juneau.terminal.region); juneau-rest-server-terminal-bus → juneau-terminal-bus (org.apache.juneau.terminal.bus). The JUNEAU_RUN_* environment variables are now JUNEAU_TERMINAL_*.
    • Juneau Console: juneau-rest-server-console-ui → juneau-console (org.apache.juneau.console); juneau-rest-server-console-ui-freemarker → juneau-console-freemarker (org.apache.juneau.console.freemarker); juneau-rest-server-views → juneau-console-views and juneau-rest-server-views-markdown → juneau-console-views-markdown (org.apache.juneau.console.views); juneau-rest-server-widgets → juneau-console-widgets (org.apache.juneau.console.widgets); juneau-rest-server-datatables → juneau-console-datatables (org.apache.juneau.console.datatables); juneau-rest-server-bus → juneau-console-bus and juneau-rest-server-bus-websocket → juneau-console-bus-websocket (org.apache.juneau.console.bus).
    • Juneau BCT: juneau-test → juneau-bct (org.apache.juneau.test.* → org.apache.juneau.bct.*).
    • Removed JSON-assertion methods on FluentObjectAssertion: asJson(), asJsonSorted(), isJson(String), isSameJsonAs(Object), isSameSortedJsonAs(Object), asString(WriterSerializer), and isSameSerializedAs(Object, WriterSerializer) are gone (the assertions module no longer depends on juneau-marshall). Where you still want JSON/serialized comparisons, pass a serializer explicitly: assertObject(x).asString(Json5Serializer.DEFAULT).is("{…}"). WriterSerializer now implements ThrowingFunction<Object,String> and OutputStreamSerializer implements ThrowingFunction<Object,byte[]> directly, so a serializer instance can be passed anywhere a Function/ThrowingFunction is expected (no bridge method needed). The asString(Function<T,String>) transform is retyped to asString(ThrowingFunction<? super T,String>), and a new isSameSerializedAs(Object, ThrowingFunction<? super T,String>) overload replaces the removed WriterSerializer overload.
  • Microservice demo resources moved out of the shipping jars into a new unpublished example module. Seven low-value demo @Rest resources — LogsResource, LogParser, DirectoryResource, ShutdownResource, SampleRootResource, ConfigResource (formerly org.apache.juneau.microservice.resources in juneau-microservice) and DebugResource (formerly org.apache.juneau.microservice.jetty.resources in juneau-microservice-jetty) — have relocated to the new org.apache.juneau.microservice.examples package in a new sibling module, juneau-microservice-examples. juneau-microservice and juneau-microservice-jetty no longer ship these demo resources, shrinking both their public API surface and their file-system-touching security footprint. The new module is deliberately unpublished — it is not listed in juneau-bom or any -bundle aggregator, and exists only to build/test in the reactor — so it is not a Maven Central dependency you can pull in; treat the classes as copy-and-adapt reference samples (juneau-petstore-jetty is the one in-tree consumer, now depending on the module directly). Migration: update any import of the old FQCNs (org.apache.juneau.microservice.resources.* / org.apache.juneau.microservice.jetty.resources.*) to org.apache.juneau.microservice.examples.*. Separately, the two classes that stayed in core — LogEntryFormatter and TraceContext — moved out of the now-removed ...microservice.resources package into org.apache.juneau.microservice.logging (non-breaking behaviorally, but a package change for any code that imported them directly).

    • Admin samples hardened against cross-site requests. ShutdownResource now answers POST only (a GET returns 405). LogsResource no longer accepts the method query parameter: its view, parse and download actions are plain GET routes (GET /logs/view/{path}, /logs/parse/{path} and /logs/download/{path}, replacing the VIEW, PARSE and DOWNLOAD methods), its listing no longer offers a delete link, and a delete must be a real DELETE. ConfigResource drops its HTML edit form (GET /config/edit and the form POST); update the config with PUT. A new AdminBoundary.registration(prefix, allowedHosts...) returns a ServletFilterRegistration that puts all three behind a CsrfBoundary. With no host names it answers only localhost, 127.0.0.1 and [::1], so any other Host is refused with 421, reads included. The Jetty petstore registers it, so a cross-site write to /config, /logs or /shutdown there is refused with 403 and an X-Request-Boundary header. Behavior change: the petstore's admin resources now answer loopback host names only; set the new petstore.admin.allowedHosts setting (comma-separated, no ports; independent of petstore.api.allowedHosts) to reach them under any other name. Command-line callers are unaffected when they send a PUT or DELETE, or a POST with a JSON content type. ShutdownResource takes how it exits as a constructor argument (new ShutdownResource(exit), default System.exit). See Protecting the admin resources.
  • String formatting helpers f() / fs() (and StringUtils.format) are now printf-only. The general-purpose formatting helpers on org.apache.juneau.commons.utils.Shorts — f(String,Object...) and fs(String,Object...) — together with StringUtils.format(...), previously accepted both java.util.Formatter printf syntax (%s, %d, %1$s) and java.text.MessageFormat syntax ({0}, {1,number}, ''-quoting). As of 10.0.0 they are printf-only: a pattern is passed straight through to String.format(...) semantics, so {0}-style placeholders are emitted literally and single quotes are no longer treated as MessageFormat escapes (a doubled '' now renders as '', not ').

    • Drop-in MessageFormat replacement: new mf(String,Object...) / mfs(String,Object...) helpers on Shorts (backed by StringUtils.mformat(...)) render the full MessageFormat grammar ({0}, {1,number}, {2,date}, ''-quoting). Any caller that genuinely relied on MessageFormat behavior should migrate f(...) → mf(...) and fs(...) → mfs(...).
    • Migration: for the common case, change MessageFormat patterns to printf — f("Value {0} of {1}", a, b) → f("Value %s of %s", a, b); for out-of-order/reused indices use positional printf — {1} {0} → %2$s %1$s, {0}…{0} → %1$s…%1$s. Any literal % in a pattern now needs escaping as %%. Where you truly need the MessageFormat engine (typed {0,number}/{0,date}, externally-authored i18n patterns), switch that call to mf()/mfs().
    • i18n unaffected: Messages.getString(key, args) (resource-bundle i18n) and LogRecord.getMessage() (logging) continue to render MessageFormat-style .properties/log patterns — they now route through mf() internally, so {0}-style entries in your resource bundles keep working exactly as before. Juneau's RichLogger MessageFormat-style logging is likewise unchanged.
    • The dual-syntax StringFormat engine still exists and remains directly usable (StringFormat.of(...)) for callers that want both grammars in one pattern; only the f()/fs()/StringUtils.format entry points were narrowed to printf.
  • AssertionUtils argument/state checks removed; use Shorts.req* (IAE) / Shorts.chk* (ISE) via import static org.apache.juneau.commons.utils.Shorts.*;. assertArg*, assertType, assertClassArrayArgIsType, assertState and AssertionUtils.assertNotNull are deleted from org.apache.juneau.commons.utils.AssertionUtils; only assertOneOf remains. Exception types and messages are unchanged. See the V10.0 Migration Guide for the full rename table.

  • Shorts.or(boolean...) renamed to Shorts.any(boolean...); new null-safe Shorts.nm(x, X::f). or(boolean...) (true if any argument is true) made or(Boolean, boolean) ambiguous with the first-non-null or(T...), so it is now any(boolean...) and the boolean overload is gone with no alias. A primitive or(a, b) call that used to be an any-true now resolves to or(T...) and returns the first non-null (always a), so migrate such calls to any. and(boolean...) is unchanged. nm(x, mapper) (backed by ObjectUtils.mapOrNull) returns null for a null input without calling the mapper, replacing x == null ? null : f(x).

  • juneau-commons logging API rename: Logger -> RichLogger (hard break, no shim). The public commons logging type org.apache.juneau.commons.logging.Logger is removed and replaced by org.apache.juneau.commons.logging.RichLogger with no compatibility alias. Migration is mechanical for most call sites: update imports and static factories (Logger.getLogger(...) -> RichLogger.getLogger(...)). The rename ships with new logging capabilities: MessageGenerator-driven formatting (printf or MessageFormat), canonical-vs-view logger identities, weak-valued logger retention with capture pinning, hierarchical parent-chain capture propagation, and generalized JUL-record capture/listener APIs. As part of this landing, five previously silent WARNING emission sites in the REST logging path now publish through JUL parent/root handlers instead of being dropped by delegate wiring.

  • REST debug test capture consolidated onto RichLogger (juneau-rest-server / juneau-rest-mock). org.apache.juneau.rest.server.logging.CapturingRestDebugFormatter and its dedicated rest-server Mockito unit test are removed. The replacement capture path is logger-name based: capture on the resource logger with RichLogger.getLogger(MyResource.class).captureEvents(Level.FINEST) and assert against captured LogRecords. This captures both op-routed records (<hostResourceClass>.<methodName>) and 404/no-op records (<resourceClass>) via the new hierarchical capture propagation.

  • LogRecord.formatted(...) no longer supports source-detection placeholders (behavioral change). org.apache.juneau.commons.logging.LogRecord removed its unreliable JUL-style stack-walk source detection — the findSource() walk and the getSourceClassName() / getSourceMethodName() overrides — along with the {source}, {class}, and {method} named placeholders in formatted(String). As a result, positional %N$s Formatter-style specifiers passed to formatted(...) shift down by one index, since the removed source argument no longer occupies a slot (e.g. what was %3$s for the logger name is now %2$s). Migration: remove any {source}/{class}/{method} placeholders from format strings (no replacement — source detection was unreliable and is not coming back), and renumber any positional %N$s specifiers down by one.

  • Serializer/Parser I/O narrowed to streams — File and StringBuilder no longer accepted. The core serialize/parse I/O surface now accepts only Reader / Writer / InputStream / OutputStream (plus the existing scalar String / byte[] / CharSequence shortcut forms). The File and StringBuilder instanceof branches were removed from the internal SerializerPipe / ParserPipe I/O plumbing, so passing a File or StringBuilder to Serializer.write(Object, Object), Parser.read(Object, ...), or any session createPipe(...) now throws "Cannot convert object of type ... to a Writer/OutputStream/InputStream" at runtime. (Note: this is a runtime contract change — the methods are typed Object, so callers passing File/StringBuilder still compile.)

    • The same narrowing applies to the token/record streaming surface: writeTokens(...) / readTokens(...) / writeRecords(...) / writeArrayRecords(...) (and the Marshaller ofTokens/ofRecords/ofArrayRecords shortcuts) no longer accept File/StringBuilder either — the *TokenWriter.forOutput(...) factories (JSON / JSONL / CBOR / MsgPack) are stream-only.

    • Migration — File: wrap it yourself.

      // Serialize to a file:
      try (Writer w = Files.newBufferedWriter(path)) { // char-based formats
      JsonSerializer.DEFAULT.write(bean, w);
      }
      try (OutputStream os = Files.newOutputStream(path)) { // byte-based formats
      MsgPackSerializer.DEFAULT.write(bean, os);
      }

      // Parse from a file:
      try (Reader r = Files.newBufferedReader(path)) { // char-based formats
      var bean = JsonParser.DEFAULT.read(r, MyBean.class);
      }
    • Migration — StringBuilder: serialize to a StringWriter and read .toString() afterward (or just call the String-returning shortcut Json.of(bean) / serializer.write(bean)).

      var sw = new StringWriter();
      JsonSerializer.DEFAULT.write(bean, sw);
      String json = sw.toString();
    • The SerializerPipe / ParserPipe classes are retained (they still own the stream close/flush lifecycle, charset decoding, debug capture, and position tracking for the surviving stream types) — only their File/StringBuilder handling and the now-dead fileCharset pipe-constructor argument were removed. The fileCharset(...) builder setting was subsequently removed as well (see the next entry).

  • fileCharset(...) serializer/parser builder setting and getFileCharset() getter removed. With the stream-only I/O change (above), the file-charset setting became a dead no-op — the narrowed SerializerPipe/ParserPipe plumbing no longer reads it. It has now been removed entirely:

    • Builder methods removed: WriterSerializer.Builder#fileCharset(Charset), ReaderParser.Builder#fileCharset(Charset), and the session-builder variants WriterSerializerSession.Builder#fileCharset(Charset) / ReaderParserSession.Builder#fileCharset(Charset).
    • Getters removed: WriterSerializer#getFileCharset() / ReaderParser#getFileCharset() (protected) and WriterSerializerSession#getFileCharset() / ReaderParserSession#getFileCharset() (public).
    • Annotation attributes removed: @SerializerConfig(fileCharset=...) and @ParserConfig(fileCharset=...).
    • Session property keys removed: the "fileCharset" (and qualified "WriterSerializerSession.fileCharset" / "ReaderParserSession.fileCharset") session property(...) keys are no longer recognized.
    • Migration: none needed for correctness — the setting had no effect after the stream-only I/O change. Remove any lingering .fileCharset(...) builder calls or @SerializerConfig/@ParserConfig fileCharset attributes. If you relied on File-charset behavior, decode the Reader/InputStream yourself before passing it in (see the File migration snippet above).
  • Version.equals() / hashCode() / compareTo() are now strict (exact); prefix matching moved to Version.matches(Version). org.apache.juneau.commons.lang.Version previously used prefix-tolerant equality — a shorter version was considered equal to any longer version sharing its prefix (Version.of("1.2").equals(Version.of("1.2.3")) → true), while hashCode() (Arrays.hashCode(parts)) and compareTo(...) were length-sensitive. That combination violated the equals/hashCode/compareTo contract and could corrupt HashSet/HashMap/TreeSet/TreeMap membership. As of 10.0.0:

    • equals(Object) is now exact — two versions are equal only when their full component sequences are identical, so Version.of("1.2"), Version.of("1.2.0"), and Version.of("1.2.3") are now all unequal. hashCode() (unchanged, Arrays.hashCode(parts)) and compareTo(...) (unchanged, already strict) are now mutually consistent with equals.
    • The removed boolean equals(Version) overload's prefix semantics live on as a new boolean matches(Version) method — Version.of("1.2").matches(Version.of("1.2.3")) → true. Use matches(...) wherever you previously relied on prefix/compatibility comparison. Note that matches(...) is intentionally not consistent with hashCode() and must never be used for hash-/tree-based collection membership.
    • Unaffected: isAtLeast(...) / isAtMost(...) (and therefore VersionRange, client-version negotiation, and the FluentVersionAssertion comparison methods, which use compareTo) are unchanged.
    • Migration: replace any v1.equals(v2) call on two Version values that depended on prefix matching with v1.matches(v2). Exact equality (assertEquals, Set/Map keys) now behaves per the corrected contract with no code change.
  • juneau-bean DTO collection/array getters no longer expose live internal state (behavioral change). Getters on the DTO beans that previously returned the bean's live internal collection or array now return a defensive, immutable view so that caller-side mutation can no longer corrupt bean state:

    • Collection / Map / Set getters in juneau-bean-jsonapi (JsonApiResource, JsonApiDocument, JsonApiResourceIdentifier, JsonApiLink, JsonApiVersion, JsonApiRelationship, JsonApiError), juneau-bean-jsonschema (JsonSchema), juneau-bean-mcp-v20250618 (ListPromptsResult, ListToolsResult, ListResourcesResult, ReadResourceResult, CallToolResult, GetPromptResult, Prompt, CallToolRequest, GetPromptRequest, ServerCapabilities, ClientCapabilities, JsonSchema), juneau-bean-openapi-v3 (OpenApi, Callback, Parameter, Components, PathItem, SchemaInfo), juneau-bean-hal (HalResource), juneau-bean-rfc7807 (Problem), and juneau-bean-swagger-v2 (ParameterInfo, ResponseInfo) now return an unmodifiable view. Attempting to add/put/remove/clear on a returned collection now throws UnsupportedOperationException. The unset-vs-empty distinction is preserved exactly (a getter that returned null when the property was never set still returns null, so serialization output is unchanged); use the bean's setX(...) / addX(...) methods to mutate.
    • Array getters in juneau-bean-atom (CommonEntry.getAuthors() / getCategories() / getContributors() / getLinks(), Feed.getEntries()) and the mutable-Calendar getter CommonEntry.getUpdated() now return a defensive copy (array clone() / Calendar clone) rather than the live field, and the corresponding setters store a defensive copy of their argument. Mutating a returned array/Calendar (or an array previously handed to a setter) no longer affects the bean. Getter return types are unchanged (X[] stays X[]).
    • Migration: callers that mutated a value returned from one of these getters must instead go through the bean's setter/adder API. No source changes are required for read-only usage.
  • BasicHeader.hashCode() is now consistent with equals(Object) (behavioral change). org.apache.juneau.http.classic.header.BasicHeader (juneau-rest-common-classic) previously computed equals(Object) from name + value while hashCode() returned the identity hash. That inconsistency violated the equals/hashCode contract and broke HashMap/HashSet membership for equal-but-distinct BasicHeader instances. As of 10.0.0 hashCode() is derived from name + value, so two headers that compare equal now share a hash bucket. Migration: none for normal usage; only affects code that relied on identity-hash bucketing of BasicHeader (none found in the codebase).

  • Fluent request/response header & param assertions are now null-tolerant (behavioral change). FluentRequestHeaderAssertion, FluentRequestQueryParamAssertion, and FluentRequestFormParamAssertion (juneau-rest-server) and FluentResponseHeaderAssertion (juneau-rest-client-classic) previously threw a NullPointerException when constructed against a null header/part (e.g. asserting on a header that was never sent). As of 10.0.0 a null value is tolerated: the assertion is built with a null test value instead, so it behaves as if testing a part with no value — isNull() passes, and transform methods (asString(), asInteger(), asBoolean(), as(Class), etc.) yield null-valued assertions rather than throwing. Migration: none for normal usage; code that relied on the constructor throwing for a missing header/param must now check the resulting assertion (e.g. isNull()) explicitly.

  • HTTP request/response/entity/part getters in juneau-rest-common, juneau-rest-common-classic, and juneau-rest-client-classic no longer expose live internal state (behavioral change). To prevent cross-caller/cross-thread state corruption (several of the affected objects are shared singletons — e.g. the Ok.OK response and the ~33/~22 pre-built exception/response INSTANCEs), the following now return defensive copies / unmodifiable views rather than the live field:

    • byte[] getters (ByteArrayEntity/StreamEntity/FileEntity/ReaderEntity/StringEntity.asBytes()), HeaderElement[] getters (BasicHeader.getElements(), ResponseHeader.getElements()), and CSV token-array getters (BasicCsvHeader/BasicCsvArrayPart/HttpCsvHeader/HttpCsvArrayPart.orElse(...)) now return a clone()/copy. Mutating a returned array no longer affects the source object or any shared constant.
    • The cached remote-interface metadata (RrpcInterfaceMeta/RrpcInterfaceMethodMeta/Policy, and RemoteMeta's header list) now hands out copies/unmodifiable views instead of the live cached array/list.
    • Migration: none for read-only usage; callers that mutated a returned array/list must instead rebuild the object through its builder/setter API.
  • juneau-petstore-core collection getters no longer expose live store state (behavioral change). For consistency with the already-hardened PetStore.getPets(), PetStore.getOrders() / getUsers() now return an immutable snapshot (List.copyOf(...)), and Pet.getTags() returns an unmodifiable view while Pet.setTags(...) stores a defensive copy. Migration: none for read-only usage (demo/sample domain code).

  • juneau-marshall public class org.apache.juneau.marshall.httppart.Constants removed. This obsolete public constants holder — exposing the CF_* (collection-format), TYPE_* (data-type), and FORMAT_* (data-format) String constants — was unreferenced anywhere in the framework and has been deleted. It is distinct from the still-supported org.apache.juneau.marshall.Constants (which is unaffected). Migration: none expected for normal usage; any external code that referenced these constants should inline the equivalent literal values (e.g. "csv", "string", "int32") or use the HttpPartSchema API, which is the supported way to express HTTP-part collection formats / types / formats.

  • juneau-bean-openapi-v3 copy-constructors now deep-copy nested copyable beans (bug fix). Several org.apache.juneau.bean.openapi3 copy-constructors previously copied a bean-typed field shallowly (this.x = copyFrom.x;), so a copy and its original shared one mutable nested-bean instance and a mutation to one silently corrupted the other. These fields are now deep-copied via the type's null-safe copyOrNull(...): OpenApi.components/externalDocs, Operation.externalDocs/requestBody, PathItem.get/put/post/delete/options/head/patch/trace, OAuthFlows.implicit/password/clientCredentials/authorizationCode, SecuritySchemeInfo.flows, Parameter.schema, MediaType.schema, HeaderInfo.schema, and SchemaInfo.discriminator. The equivalent juneau-bean-swagger-v2 copy-constructors were already deep-copying these fields and are unchanged. Migration: none; copy() now returns a fully independent deep copy as documented.

  • juneau-rest-server ThrownStats.copy(ThrownStats) static method removed; replaced by null-safe copyOrNull(ThrownStats). The org.apache.juneau.rest.server.stats.ThrownStats deep-copy factory public static ThrownStats copy(ThrownStats) has been removed and replaced by public static ThrownStats copyOrNull(ThrownStats value), which returns the same deep copy but additionally returns null for a null argument (the old copy(null) threw a NullPointerException). This aligns the method with the new null-safe copyOrNull(T) convenience convention (see API Changes). Migration: replace any ThrownStats.copy(x) call with ThrownStats.copyOrNull(x).

  • Classic HTTP bean families move to a "funnel + nested Unmodifiable snapshot" immutability model; setUnmodifiable() removed. The juneau-rest-common-classic bean hierarchies — BasicHttpResponse (+ ~22 leaves), BasicHttpException (+ ~33 leaves), BasicHttpEntity (+ 6 leaves: ByteArrayEntity, FileEntity, ReaderEntity, SerializedEntity, StreamEntity, StringEntity), BasicStatusLine, BasicResource (+ 5 leaves: ByteArrayResource, FileResource, ReaderResource, StreamResource, StringResource), and the HeaderList / PartList lists — replace the old internal mutable-flag idiom (a setUnmodifiable() latch plus a per-mutator assertModifiable() guard) with a single mutation funnel per class and a nested X.Unmodifiable snapshot type.

    • setUnmodifiable() is removed. It froze the receiver itself in place. Migrate to x.unmodifiable(), which instead returns a new, frozen point-in-time snapshot and leaves the receiver mutable — unless the receiver is already an X.Unmodifiable snapshot, in which case it returns this (idempotent).

      // Before (9.x):
      Ok ok = new Ok();
      ok.setUnmodifiable(); // ok itself is now frozen

      // After (10.0.0):
      Ok ok = new Ok();
      Ok frozen = ok.unmodifiable(); // ok is still mutable; frozen is a new, separate snapshot
    • isUnmodifiable() is retained, source-compatible, now implemented as a type check against a new UnmodifiableBean marker interface rather than a backing boolean field.

    • BasicHttpResponse and BasicHttpEntity become generic and effectively abstract (X<SELF extends X<SELF>>, CRTP), which also drops each leaf's covariant setter overrides. Raw-type callers (BasicHttpResponse r = ...) keep compiling with at most an unchecked-raw warning; leaf-typed callers (Ok o = ...) are unaffected; leaf-typed INSTANCE constants (e.g. Ok.INSTANCE, NotFound.INSTANCE) keep their exact leaf types — no caller churn. Any direct instantiation of the root (new BasicHttpResponse(...) / new BasicHttpEntity(...)) must be rebased onto a concrete leaf.

    • BasicHttpException stays non-generic — the JLS (§8.1.2) forbids a generic Throwable subclass — so only the setUnmodifiable() removal applies there; exception leaves keep their covariant setter overrides.

    • equals() / hashCode() become content-based on these beans (previously identity-based) — a semantic change for any code relying on identity equality. For exceptions, equality is computed over the status line, headers, and message (excluding the value-less content wrapper).

    • ControlledArrayList is removed. HeaderList / PartList are reparented directly onto ArrayList, with a nested Unmodifiable subclass overriding the full collection-mutator set (add/remove/set/clear/addAll/… plus iterator mutation) to throw. List equals()/hashCode() are now content-only, ignoring mutability.

    • Two bug fixes bundled with this migration: the Thrown response header now reports the logical (declared) exception type rather than a frozen INSTANCE snapshot's synthetic $Unmodifiable subclass name, so client-side exception reconstruction from a frozen shared instance (e.g. BadRequest.INSTANCE) no longer breaks; and BasicStatusLine's copy constructor now faithfully copies reasonPhraseCatalog (previously dropped on copy).

    • Migration: replace every x.setUnmodifiable() call with x = x.unmodifiable() (or use the returned snapshot directly), and audit any direct new BasicHttpResponse(...) / new BasicHttpEntity(...) construction to instantiate a concrete leaf instead.

    • BasicResource becomes an abstract self-typed (CRTP) root. BasicResource is now BasicResource<SELF extends BasicResource<SELF>> (mirroring BasicHttpEntity/BasicHttpResponse) and can no longer be instantiated directly — use a concrete leaf (StreamResource, ByteArrayResource, StringResource, FileResource, ReaderResource). The breaking surface is narrow: .as(BasicResource.class) and @Remote methods declared to return BasicResource/HttpResource still work, because RestClient response bodies are now materialized into a StreamResource (which IS-A BasicResource); StreamResource gained an HttpResponse constructor for this. Only direct/reflective new BasicResource(...) instantiation breaks.

    • Canonical juneau-rest-common HTTP stack brought to the immutable-beans paradigm. BasicHttpResponse is now an abstract self-typed (CRTP) root BasicHttpResponse<SELF extends BasicHttpResponse<SELF>>; BasicHttpException is a funnel-only root (no CRTP — a generic Throwable is illegal per JLS §8.1.2). The old setUnmodifiable() / assertModifiable() flag idiom is removed on these public types and on the HttpHeaderList / HttpPartList collections — use unmodifiable() (a point-in-time snapshot) and isUnmodifiable(). Mutating a frozen instance now throws UnsupportedOperationException (was IllegalStateException). equals/hashCode on the list twins are content-only. (Mirrors the classic juneau-rest-common-classic migration.)

  • jsonschema bean family converted to CRTP self-typing. JsonSchema and JsonSchemaProperty are now self-typed roots (JsonSchema<SELF extends JsonSchema<SELF>>, JsonSchemaProperty<SELF extends JsonSchemaProperty<SELF>>); the ~185 covariant setter-narrowing overrides on JsonSchemaProperty/JsonSchemaPropertySimpleArray/JsonSchemaRef were removed. Fluent setters return the leaf type. Direct instantiation uses the diamond form (new JsonSchema<>()); marshalling is unchanged. Source-visible generification of these exported beans is a 10.0.0 change (raw declarations may warrant a <?> wildcard).

  • atom bean family converted to CRTP self-typing. CommonEntry is now a self-typed root CommonEntry<SELF extends CommonEntry<SELF>>; the 42 covariant setter-narrowing overrides on Feed/Entry/Source were removed (fluent setters return the leaf type). Marshalling is unchanged. Source-visible generification of this exported bean is a 10.0.0 change (raw declarations may warrant a <?> wildcard).

  • html5 bean family converted to CRTP self-typing. HtmlElement and its 5 intermediates (HtmlElementVoid/Text/RawText/Mixed/Container) are now abstract self-typed roots (HtmlElement<SELF extends HtmlElement<SELF>>, etc.); ~7,282 covariant setter-narrowing overrides were removed across the 108 leaf element beans, whose fluent setters now return the leaf type. Leaves stay concrete/non-generic, so HtmlBuilder.a() / new Div() and marshalling are unaffected. Caveat: external code that chains the generic accessors getAttr(Class,…)/getChild(Class,…) off a raw HtmlElement/intermediate must switch to HtmlElement<?> or a concrete leaf (raw subtyping now erases inherited generic members).

  • SerializerWriter family converted to CRTP self-typing. SerializerWriter is now an abstract self-typed root (SerializerWriter<SELF extends SerializerWriter<SELF>> extends Writer); its fluent methods — including the java.io.Writer append(...) overloads — return SELF, and ~330 covariant narrowing overrides were removed across the 13 format writers. The concrete-and-base writers JsonWriter/XmlWriter/HtmlWriter became abstract generic bases, each with a new concrete leaf (BasicJsonWriter/BasicXmlWriter/BasicHtmlWriter) used at instantiation sites; JcsWriter/SimpleHtmlWriter retain only their real overrides. Serializer output is byte-identical. Impact is internal to juneau-marshall (no downstream module references these writer types); external code that declared a raw JsonWriter/XmlWriter/HtmlWriter variable and chained fluent calls should switch to the wildcard form (JsonWriter<?>) or the new Basic* leaf.

  • Schema.exclusiveMaximum() / Schema.exclusiveMinimum() boolean annotation members removed. These @Deprecated(forRemoval) legacy Swagger 2.0/OpenAPI 3.0/JSON Schema Draft 04 boolean forms on org.apache.juneau.commons.Schema are gone. Migration: use the existing short-form boolean aliases emax() / emin() instead (unchanged); for JSON Schema Draft 2020-12 numeric semantics, exclusiveMaximumValue() / exclusiveMinimumValue() (also unchanged) remain available and continue to take precedence when set.

  • HttpPartSchema.T_OBJECT_CSV / T_OBJECT_PIPES / T_OBJECT_SSV / T_OBJECT_TSV constants removed, along with their sole-purpose builder methods tObjectCsv() / tObjectPipes() / tObjectSsv() / tObjectTsv(). A collectionFormat on an object-type part is not a valid OpenAPI 2.0 concept (collectionFormat is defined only for array types), and these presets had zero usage in the framework. The array-type equivalents (T_ARRAY_CSV, T_ARRAY_PIPES, T_ARRAY_SSV, T_ARRAY_TSV) are unaffected. Migration: none expected for normal usage; code relying on these invalid presets should build the schema via HttpPartSchema.create().tObject() directly (with no collection format).

  • XmlSerializer.DEFAULT_XS_NAMESPACE removed. This unused protected namespace constant (xs → http://www.w3.org/2001/XMLSchema) had no consumers anywhere in the framework. DEFAULT_JUNEAU_NAMESPACE is unaffected. Migration: none expected for normal usage; any external subclass that referenced the constant should declare its own namespace via Namespace.of("xs", "http://www.w3.org/2001/XMLSchema").

  • @ParentProperty through collections/maps now injects the enclosing bean (behavioral change). Previously, when a bean annotated with @ParentProperty was nested inside a List, Set, array, or Map — rather than being a direct bean property — the parser injected the immediately-containing container (e.g. the JsonList/JsonMap) into the @ParentProperty member. As of 10.0.0, the parser instead injects the nearest enclosing bean, skipping all intermediate containers regardless of nesting depth; a bean parsed at the document root or directly inside a top-level collection now gets a null parent instead of throwing an exception. This makes the canonical AddressBook/List<Person> example round-trip correctly, and makes the behavior consistent across all formats (JSON, XML, HTML, UON, URL-encoding, YAML, MsgPack, CBOR, BSON, HOCON, HJSON). Migration: if a collection/map-nested @ParentProperty member was typed to accept the container (e.g. typed as List/JsonList), retype it to the enclosing bean type (e.g. AddressBook) — members already typed as the enclosing bean now work where they previously threw IllegalArgumentException at parse time. Round-tripping a bean that holds a back-reference to its parent still requires ignoreRecursions() on the serializer.

  • Queryable REST response converter rebuilt on the BeanQuery engine (juneau-rest-server). The Queryable converter — which parsed the Juneau-native &s=/&v=/&o=/&p=/&l= parameters and ran ObjectSearcher/ObjectSorter/ObjectViewer/ObjectPaginator — now reads &search=/&view=/&sort=/&position=/&limit=/&opts= and runs the BeanQuery engine. The intermediate QueryProtocol SPI (QueryProtocol/QueryArgs/QueryResult/NativeQueryProtocol/ProtocolQueryable) from an earlier 10.0 milestone never shipped in a release and is removed; QueryableSettings is now the BeanQuery tuning bean (no protocol(...)). Migration: rename the request parameters and move searches to the BeanQuery grammar; see the V10 Migration Guide. Behavior changes to note: the default page size is now 100 (was "all rows") and is capped at 1000; $regex is on by default; the vestigial &i= (ignore-case) parameter is gone (case sensitivity is per operator); BeanQueryRequest binds opts, not opt; the QueryMenuItem widget form fields were renamed to match.

  • RequestQueryParamList native-arg accessors removed (juneau-rest-server). getSearchArgs()/getSortArgs()/getViewArgs()/getPageArgs() are removed, and so is the NativeQueryProtocol.INSTANCE.parse(req) helper that briefly replaced them. Migration: take a BeanQueryRequest method argument, or call req.getRequest(BeanQueryRequest.class); its getters return the raw request strings/numbers. To get rows, open a BeanQuerySession and call find(query) / findValues(query).

  • @Request bean setters and constructor are verified when the operation table is built (juneau-rest-server). A concrete (non-interface) @Request method-argument class with an annotated getter that has no matching public setter, or with no public no-arg constructor, used to fail with NoSuchMethodException (HTTP 500) on the first request that supplied that part. It now throws IllegalArgumentException (for example Request bean org.example.MyQuery has @Query getter getFoo() with no matching setter setFoo(String).) when the resource's operation table is assembled: at startup with @Rest(eagerInit=true), otherwise on the first request to that resource. Interface @Request beans are unaffected. Migration: add the missing setter / public no-arg constructor.

API Changes​

  • Null-safe static copyOrNull(T) convenience methods added to copyable bean/builder types. A new public static X copyOrNull(X value) method was added next to the existing instance copy() on each type below. It returns a deep copy of the argument, or null if the argument is null, replacing the x == null ? null : x.copy() null-check-ternary boilerplate that appeared throughout the bean copy-constructors (all in-tree call sites were swept to use it). The name is deliberately distinct from the instance copy(), and is copyOrNull (rather than copyOf) specifically to avoid shadowing the pervasively static-imported org.apache.juneau.commons.utils.CollectionUtils.copyOf(...) collection/array overloads used inside many of these same types and across the Context.Builder subclass hierarchy (a member named copyOf would hide the wildcard static import). A future clean-break will move the copy helpers into a subclassable CopyUtils and unify these copyOrNull methods back under the copyOf name via ordinary overload resolution. Types receiving copyOrNull:

    • juneau-bean-swagger-v2 (org.apache.juneau.bean.swagger): Contact, License, Info, Items, SchemaInfo, Xml, ExternalDocumentation (7).
    • juneau-bean-openapi-v3 (org.apache.juneau.bean.openapi3): Contact, License, Info, Items, SchemaInfo, Xml, ExternalDocumentation, Server, Components, Discriminator, OAuthFlow, Operation, RequestBodyInfo (13). The last five were added by the shallow-copy correctness sweep (see behavioral changes below).
    • juneau-microservice: LogConfig.
    • juneau-marshall builder types: MarshallingContext.Builder (static MarshallingContext.Builder copyOrNull(MarshallingContext.Builder)) and Context.Builder (static Context.Builder<?> copyOrNull(Context.Builder<?> value), wildcard-typed because the builder uses a self-referential SELF type parameter that a static method cannot name).
  • New subclassable CopyUtils; CollectionUtils.copyOf and ObjectUtils.cloneOf moved to it (clean break). The copyOf(...) family (all collection / map / array overloads) has been moved out of org.apache.juneau.commons.utils.CollectionUtils and the generic null-safe cloneOf(T) has been moved out of org.apache.juneau.commons.utils.ObjectUtils into a new org.apache.juneau.commons.utils.CopyUtils class (with a protected constructor so it can be subclassed). This is a hard move with no deprecation shim — CollectionUtils no longer declares (or references) copyOf, and ObjectUtils no longer declares cloneOf. Two per-module subclasses add domain bean overloads that coexist with the inherited collection overloads via ordinary Java overload resolution, retiring the interim copyOrNull methods on those beans and unifying them back under the copyOf name:

    • juneau-bean-swagger-v2: org.apache.juneau.bean.swagger.SwaggerCopyUtils extends CopyUtils — copyOf(...) for Contact, ExternalDocumentation, Info, Items, License, SchemaInfo, Xml (7).
    • juneau-bean-openapi-v3: org.apache.juneau.bean.openapi3.OpenApiCopyUtils extends CopyUtils — copyOf(...) for Components, Contact, Discriminator, ExternalDocumentation, Info, Items, License, OAuthFlow, Operation, RequestBodyInfo, SchemaInfo, Server, Xml (13).
    • The four single-site one-off types intentionally keep their copyOrNull(T) and are unchanged: ThrownStats (juneau-rest-server), LogConfig (juneau-microservice), and the MarshallingContext.Builder / Context.Builder<?> marshall builders.
    • Migration: replace any CollectionUtils.copyOf(...) with CopyUtils.copyOf(...) and any ObjectUtils.cloneOf(...) with CopyUtils.cloneOf(...). For files that resolved copyOf/cloneOf through a import static ...CollectionUtils.*; / ...ObjectUtils.*; wildcard, add import static org.apache.juneau.commons.utils.CopyUtils.*; (keep the existing wildcard for the other helpers you still use). Swagger-v2 / openapi-v3 call sites that need a bean copyOf(<BeanType>) overload should static-import the module subclass (org.apache.juneau.bean.swagger.SwaggerCopyUtils / org.apache.juneau.bean.openapi3.OpenApiCopyUtils) instead, which supplies both the inherited collection overloads and the bean overloads.
  • RestRequest.isDebug() redefined as derived/read-only (juneau-rest-server). The method signature is unchanged, but as part of the REST debug/logging redesign (see Breaking Changes) it now returns true when the resolved JUL logger for the current request is FINE-or-finer, computed on read rather than backed by a settable flag. RestRequest.setDebug() / setDebug(Boolean) and the corresponding RestResponse setter are removed.

  • One tone vocabulary: StatusTone (juneau-console-widgets / juneau-console-views / juneau-console) (hard break, no shim). The duplicate Tone enum is removed; every status/colour tone now uses StatusTone (INFO, SUCCESS, WARNING, ERROR, NEUTRAL), whose wire tokens are info/success/warning/error/neutral. Everything that spoke the older ok/warn/danger/accent/idle dialects was renamed to match:

    • Badge.tone takes StatusTone. Replace Tone.WARN with StatusTone.WARNING, Tone.DANGER with StatusTone.ERROR and Tone.ACCENT with StatusTone.INFO; SUCCESS and NEUTRAL are unchanged. The rendered data-juneau-badge-tone attribute values change accordingly: accent becomes info, warn becomes warning, danger becomes error (success and neutral are unchanged). Update any custom CSS or JS selectors such as .jc-badge[data-juneau-badge-tone="danger"] to ="error".
    • Java stat beans removed. StatTile, StatBar, SegmentedBadge (and its Segment), StatItem and the avatar-presence Status are gone from org.apache.juneau.console.widgets. The client-side quickStats envelope, its JavaScript painter and the .jc-stat-* CSS are unchanged, so emit the same JSON quickStats tiles (for example from your own record type) instead of these beans. Stat-tile tones are now restricted to the five StatusTone wire tokens; any other tone string paints the tile with no is- tone class.
    • Probe status classes and tokens. jc-probe-ok/-warn/-fail become jc-probe-success/-warning/-error (jc-probe-neutral is unchanged), and the theme tokens --jc-probe-{ok,warn,fail}-{bg,text,dot} become --jc-probe-{success,warning,error}-{bg,text,dot}. Update any page script that maps a verdict to a probe class, and any stylesheet that overrides the tokens.
    • Helpers pill. pill(label, tone) in juneau-helpers.js now takes success/warning/error/neutral (previously ok/warn/error/idle), and its dot class juneau-view-helper-pill-dot--<old> becomes juneau-view-helper-pill-dot--<new>.
    • Progress bars. The state classes is-ok/is-warn/is-exceeds become is-success/is-warning/is-error, and the tokens --jc-progress-{ok,warn,exceeds} become --jc-progress-{success,warning,error}.
    • QuickStats tile colours now follow the badge tag palette (--jc-tone-* aliases --jc-tag-*-text) instead of a grey ramp, so theme overrides of the --jc-tag-* tokens recolour stat tiles too.
    • New typed overload: Render.pill(StatusTone) renders a status pill from a StatusTone directly, so callers need not pass a tone string.