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

Reply via email to