This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/docs by this push:
new e0310194d9 Add 10.0.0 release note for immutable-beans paradigm
(TODO-271); remove stale setUnmodifiable clause
e0310194d9 is described below
commit e0310194d973a1228d827aa438296320b42ad27b
Author: James Bognar <[email protected]>
AuthorDate: Tue Jul 21 09:12:01 2026 -0400
Add 10.0.0 release note for immutable-beans paradigm (TODO-271); remove
stale setUnmodifiable clause
Co-authored-by: Cursor <[email protected]>
---
pages/release-notes/10.0.0.md | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 5ce82dcff6..7211ba67b5 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -730,7 +730,6 @@ _TBD — to be filled in as development continues._
- **`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 tw [...]
- **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 `INSTANCE`s),
the following now return defensive copies / unmodifiable views rather than the
live field:
- - `setUnmodifiable()` on the classic `BasicHttpResponse` /
`BasicHttpException` now also freezes the backing `HeaderList` (previously only
the status line was frozen), so a frozen shared instance can no longer be
mutated through `getHeaders()`.
- `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.
@@ -747,6 +746,26 @@ _TBD — to be filled in as development continues._
- **`juneau-rest-server` `ThrownStats.copy(ThrownStats)` static method
removed; replaced by null-safe `copyOrNull(ThrownStats)` (TODO-260).** 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 `NullPointerExcepti [...]
+- **Classic HTTP bean families move to a "funnel + nested `Unmodifiable`
snapshot" immutability model; `setUnmodifiable()` removed (TODO-271).** 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`, `Rea [...]
+ - **`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).
+
+ ```java
+ // 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 ro [...]
+ - **`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.
+
_Other entries TBD — to be filled in before release. See also the major
version bump note above._
### Deprecations