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 87d6c39dfa docs: 9.5.0 release notes + migration guide for the 
format-control program (TODO-4/39/50/51/52/54/57)
87d6c39dfa is described below

commit 87d6c39dfab1a7a076bb5081cd14b959b4a3b6b3
Author: James Bognar <[email protected]>
AuthorDate: Fri May 22 10:15:34 2026 -0400

    docs: 9.5.0 release notes + migration guide for the format-control program 
(TODO-4/39/50/51/52/54/57)
    
    - Document the 14 wire-format enums (Duration/Period, Date/Time/Locale, 
Binary, Enum, UUID/BigInteger/BigDecimal, Boolean/Float/Currency/Class) and 
their @Marshalled / @MarshalledProp / @MarshalledConfig wiring.
    - Add format-control round-trip hardening section (TODO-57): TemporalFormat 
carryover for 8 subtypes including MonthDay, hasNativeBytes() capability check, 
18 closed production bugs, Ini/Markdown Json5Parser memoization, and the 
.mvn/maven.config --also-make reactor fix.
    - Migration guide: TemporalFormat signature widening (Temporal -> 
TemporalAccessor), MonthDay round-trip enablement, OffsetTime x MILLIS 
carve-out, Parquet + RdfThrift/RdfProto byte[] wire-format change at 
non-NOT_SET, JSON-family bare decimal Float -> Double behavioral change.
    - Document /sonarqube Cursor command, scripts/sonarqube.py, and the 
eclipse-warnings skill (TODO-39).
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/release-notes/9.5.0.md               | 227 ++++++++++++++++++++++++++++-
 pages/topics/23.01.V9.5-migration-guide.md |  15 ++
 2 files changed, 241 insertions(+), 1 deletion(-)

diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 61aaebb5ee..1dca23691a 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -6,10 +6,221 @@ title: "Release 9.5.0"
 
 **Date:** TBD
 
-Juneau 9.5.0 is a minor release with native TOML and YAML support, BSON 
(Binary JSON) support for MongoDB-interoperable binary serialization, CBOR 
(Concise Binary Object Representation) per RFC 8949 for IoT and constrained 
environments, full CSV serializer/parser support, JCS (JSON Canonicalization 
Scheme) per RFC 8785 for deterministic hashing and signing, RDF/THRIFT and 
RDF/PROTO binary format support, native serialization support for 
lazy-evaluated sequence types, large-dataset stream [...]
+Juneau 9.5.0 is a minor release with native TOML and YAML support, BSON 
(Binary JSON) support for MongoDB-interoperable binary serialization, CBOR 
(Concise Binary Object Representation) per RFC 8949 for IoT and constrained 
environments, full CSV serializer/parser support, JCS (JSON Canonicalization 
Scheme) per RFC 8785 for deterministic hashing and signing, RDF/THRIFT and 
RDF/PROTO binary format support, native serialization support for 
lazy-evaluated sequence types, large-dataset stream [...]
 
 ### juneau-marshall
 
+#### Duration/Period wire format controls (TODO-4)
+
+- Added new enum strategies:
+  - `org.apache.juneau.DurationFormat` (`ISO_8601`, `ISO_8601_WITH_DAYS`, 
`NANOS`, `MILLIS`, `SECONDS`, `HOCON`)
+  - `org.apache.juneau.PeriodFormat` (`ISO_8601`, `DAYS`)
+- Added global marshalling-context settings:
+  - `MarshallingContext.Builder.durationFormat(...)` / `periodFormat(...)`
+- Added annotation-level overrides:
+  - `@Marshalled(durationFormat=..., periodFormat=...)`
+  - `@MarshalledProp(durationFormat=..., periodFormat=...)`
+  - `@MarshalledConfig(durationFormat=..., periodFormat=...)`
+- Updated serializer sessions across 
JSON/HJSON/HOCON/UON/XML/HTML/Markdown/TOML/INI/CSV/PROTO plus 
BSON/CBOR/MessagePack so configured duration/period formats are honored 
consistently. Binary serializers emit native numeric wire types for numeric 
duration formats.
+- Parsers now sniff duration/period wire shapes more broadly and support mixed 
input styles, with parser-side format settings acting as hints for ambiguous 
numeric values.
+
+**Migration note (default behavior change):**
+
+- Default `Duration` serialization now uses `ISO_8601_WITH_DAYS` (for example 
`Duration.ofHours(48)` now writes as `'P2D'` instead of `'PT48H'`).
+- To restore prior wire output style, configure serializers explicitly:
+
+  ```java
+  JsonSerializer s = JsonSerializer.create()
+      
.marshallingContext(MarshallingContext.create().durationFormat(org.apache.juneau.DurationFormat.ISO_8601))
+      .build();
+  ```
+
+#### Date/time + Locale wire format controls (TODO-51)
+
+This is a **major-release breaking change**. The legacy `Temporal*Swap` 
inner-class family and named root-level temporal/locale/timezone swaps are 
removed without deprecation shims; configure formats via `MarshallingContext` 
or the new annotations instead.
+
+- Added new enum strategies under `org.apache.juneau`:
+  - `CalendarFormat` (default `ISO_OFFSET_DATE_TIME`; ISO variants, 
`RFC_1123_DATE_TIME`, `MILLIS`, plus an opt-in `XML_FORMAT` for 
`Calendar`/`GregorianCalendar`).
+  - `DateFormat` (default `ISO_LOCAL_DATE_TIME`; ISO variants, 
`RFC_1123_DATE_TIME`, `MILLIS`).
+  - `TemporalFormat` (default `DEFAULT` = per-subtype default; ISO variants, 
`RFC_1123_DATE_TIME`, `ISO_YEAR`, `ISO_YEAR_MONTH`, `MILLIS`).
+  - `TimeZoneFormat` (default `ID`; shared by `TimeZone` and `ZoneId`; 
`OFFSET`, `NAME_LONG`, `NAME_SHORT`).
+  - `LocaleFormat` (default `BCP_47`; also `UNDERSCORE`).
+- Added global marshalling-context settings:
+  - `MarshallingContext.Builder.calendarFormat(...)` / `dateFormat(...)` / 
`temporalFormat(...)` / `timeZoneFormat(...)` / `localeFormat(...)`.
+  - Mirrored on `MarshallingContextable.Builder` so every serializer/parser 
builder inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+  - `@Marshalled(calendarFormat=..., dateFormat=..., temporalFormat=..., 
timeZoneFormat=..., localeFormat=...)`
+  - `@MarshalledProp(calendarFormat=..., dateFormat=..., temporalFormat=..., 
timeZoneFormat=..., localeFormat=...)`
+  - `@MarshalledConfig(calendarFormat=..., dateFormat=..., temporalFormat=..., 
timeZoneFormat=..., localeFormat=...)`
+- Implemented uniform precedence resolution in 
`MarshalledPropertyPostProcessor`: `@MarshalledProp` > `@Marshalled` > 
`MarshallingContext` setting > enum default. Resolution installs `StringSwap` 
instances built directly from the enum's `format`/`parse` methods on the 
matching property type (`Calendar`/`GregorianCalendar`, `Date`, 
`TemporalAccessor` subtypes, `TimeZone`/`ZoneId`, `Locale`). 
`XMLGregorianCalendar` is hard-wired to XML lexical format regardless of any 
configured `CalendarF [...]
+- Updated serializer and parser sessions across 
JSON/HJSON/HOCON/UON/XML/HTML/Markdown/TOML/INI/CSV/PROTO plus 
BSON/CBOR/MessagePack and Parquet so configured calendar/date/temporal formats 
are honored at the root level. `Iso8601Utils` gained format-aware overloads 
that route through the session's resolved format.
+- Downgraded `TimeZoneSwap`, `ZoneIdSwap`, and `LocaleSwap` to thin delegators 
that read the resolved `TimeZoneFormat` / `LocaleFormat` from the active 
session, so root-level (de)serialization of `TimeZone`/`ZoneId`/`Locale` 
follows the same context configuration as bean properties.
+
+**Removed (hard break, no `@Deprecated`):**
+
+- `org.apache.juneau.swaps.TemporalCalendarSwap` and all 17 inner-class 
variants (`IsoOffsetDateTime`, `IsoInstant`, `IsoLocalDateTime`, etc.).
+- `org.apache.juneau.swaps.TemporalDateSwap` and all 17 inner-class variants.
+- `org.apache.juneau.swaps.TemporalSwap` and all 18 inner-class variants.
+- `org.apache.juneau.swaps.XMLGregorianCalendarSwap` (its always-XML behavior 
is now built into the post-processor for `XMLGregorianCalendar` properties; the 
`XML_FORMAT` `CalendarFormat` constant covers the opt-in case for `Calendar`).
+
+**Notable behavior — `MILLIS` for `TemporalFormat`:**
+
+- `Instant`, `OffsetDateTime`, `ZonedDateTime` use 
`toInstant().toEpochMilli()`.
+- `LocalDateTime` uses `ldt.toInstant(ZoneOffset.UTC).toEpochMilli()`.
+- `LocalDate` uses 
`ld.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli()`.
+- `YearMonth` and `Year` use the first day at midnight UTC.
+- `LocalTime` and `MonthDay` fall back to the per-subtype `DEFAULT` ISO string 
form.
+- This asymmetry is documented in the `MILLIS` constant's Javadoc.
+
+**Migration:**
+
+- Replace any `@Swap(TemporalCalendarSwap.IsoXxx.class)` with 
`@MarshalledProp(calendarFormat=CalendarFormat.ISO_XXX)`.
+- Replace `@Swap(TemporalDateSwap.IsoXxx.class)` with 
`@MarshalledProp(dateFormat=DateFormat.ISO_XXX)`.
+- Replace `@Swap(TemporalSwap.IsoXxx.class)` with 
`@MarshalledProp(temporalFormat=TemporalFormat.ISO_XXX)`.
+- For context-level configuration, replace 
`swaps(TemporalXxxSwap.IsoYyy.class)` on serializer/parser builders with 
`xxxFormat(XxxFormat.ISO_YYY)`.
+
+#### Binary + Enum wire format controls (TODO-52)
+
+This is a **major-release breaking change**. The legacy global `ByteArraySwap` 
family, the
+`OutputStreamSerializer.Builder` / `InputStreamParser.Builder` 
`binaryFormat(...)` setters, and the
+`useEnumNames(boolean)` setter are removed without deprecation shims; 
configure these via
+`MarshallingContext` or the new annotations instead.
+
+- Extended `org.apache.juneau.BinaryFormat` with two new constants:
+  - `NOT_SET` — sentinel meaning "no value configured" (falls through to the 
next-higher precedence level; mirrors the convention used by every other 
`<Type>Format` enum).
+  - `BASE64_URL` — RFC 4648 §5 URL-safe Base64 encoding (`-` / `_` instead of 
`+` / `/`, no padding on the wire but accepted on input).
+- `BinaryFormat` now exposes `format(byte[])` and `parse(String)` methods so a 
single enum drives both serialize and parse paths. The `parse(String)` method 
is intentionally format-agnostic — it sniffs the wire shape (spaced hex, 
URL-safe Base64, hex, standard Base64) and decodes accordingly so parsers 
accept any of the formats produced by `format(byte[])` regardless of the 
configured constant.
+- Added new `org.apache.juneau.EnumFormat` with the constants `NOT_SET`, 
`TO_STRING` (default; calls `Enum.toString()`), `NAME` (calls `Enum.name()`), 
`LOWER_HYPHEN` (e.g. `MY_VALUE` → `my-value`), `UPPER_HYPHEN`, 
`LOWER_UNDERSCORE`, `LOWER`, `UPPER`, and `ORDINAL` (numeric on the wire). 
`format(Enum)` and `parse(String, Class)` cover both directions; the parser is 
lenient (case-insensitive, hyphen/underscore-insensitive, accepts ordinal as 
integer) so any wire shape produced by any cons [...]
+- Added global marshalling-context settings on `MarshallingContext.Builder`:
+  - `binaryFormat(BinaryFormat)` (default `NOT_SET`).
+  - `enumFormat(EnumFormat)` (default `TO_STRING`).
+  - Mirrored on `MarshallingContextable.Builder` so every serializer / parser 
builder inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+  - `@Marshalled(binaryFormat=..., enumFormat=...)`
+  - `@MarshalledProp(binaryFormat=..., enumFormat=...)`
+  - `@MarshalledConfig(binaryFormat=..., enumFormat=...)`
+- Implemented uniform precedence resolution in 
`MarshalledPropertyPostProcessor`: `@MarshalledProp` > `@Marshalled` > 
`MarshallingContext` setting > enum default. Resolution installs the 
appropriate session-aware `ObjectSwap` on each `byte[]` / enum bean property. 
For `EnumFormat.ORDINAL` the swap produces a `Number` so binary serializers 
emit a native int.
+- Downgraded the global `byte[]` default swap 
(`org.apache.juneau.swaps.BinarySwap`) to a thin session-aware delegate that 
reads the resolved `BinaryFormat` from the active `MarshallingContext` at swap 
time. Skips binary serializers (BSON / CBOR / MsgPack / Proto / Parquet emit 
native bytes), CSV (defers to `csv.CsvByteArrayCellFormat`), and OpenAPI (uses 
its schema-directed BYTE / BINARY / BINARY_SPACED encoding). When the resolved 
format is `NOT_SET`, the swap is a no-op so `byte[]` fa [...]
+- `OutputStreamSerializerSession#serializeToString(Object)` now always emits 
`BinaryFormat.HEX` regardless of the configured `binaryFormat`, providing a 
stable copy-pasteable hex dump for debug / display irrespective of the 
surrounding context's wire-format setting.
+
+**Removed (hard break, no `@Deprecated`):**
+
+- `org.apache.juneau.swaps.ByteArraySwap` and the `Base64`, `Hex`, `SpacedHex` 
inner-class variants. Replace `@Swap(ByteArraySwap.Base64.class)` etc. with 
`@MarshalledProp(binaryFormat=BASE64)` etc.
+- `OutputStreamSerializer.Builder.binaryFormat(BinaryFormat)` and 
`InputStreamParser.Builder.binaryFormat(BinaryFormat)`. The setting is now on 
`MarshallingContext.Builder` (and inherited by every serializer / parser 
builder via `MarshallingContextable.Builder`) so the same wire-format setting 
drives both binary and textual sessions.
+- `BeanContext.Builder.useEnumNames(boolean)` and the `useEnumNames` field on 
`MarshallingContext`. Replace `useEnumNames()` with 
`enumFormat(EnumFormat.NAME)`; replace `@Bean(useEnumNames=true)` with 
`@Marshalled(enumFormat=EnumFormat.NAME)`.
+
+**Renamed:**
+
+- `org.apache.juneau.csv.ByteArrayFormat` → 
`org.apache.juneau.csv.CsvByteArrayCellFormat`. The new name disambiguates the 
CSV-only cell-encoding role from the new top-level `BinaryFormat`.
+
+**Migration:**
+
+- Replace `@Swap(ByteArraySwap.Base64.class)` with 
`@MarshalledProp(binaryFormat=BinaryFormat.BASE64)`.
+- Replace `@Swap(ByteArraySwap.Hex.class)` with 
`@MarshalledProp(binaryFormat=BinaryFormat.HEX)`.
+- Replace `@Swap(ByteArraySwap.SpacedHex.class)` with 
`@MarshalledProp(binaryFormat=BinaryFormat.SPACED_HEX)`.
+- Replace `OutputStreamSerializer.Builder#binaryFormat(...)` / 
`InputStreamParser.Builder#binaryFormat(...)` calls with the inherited 
`binaryFormat(...)` from `MarshallingContextable.Builder` (no source change 
needed for builder chains; the inherited setter has the same signature).
+- Replace `useEnumNames()` / `useEnumNames(true)` with 
`enumFormat(EnumFormat.NAME)`.
+
+#### UUID + BigInteger/BigDecimal wire format controls (TODO-50, Phase 3)
+
+- Added new enum strategies under `org.apache.juneau`:
+  - `UuidFormat` (default `STANDARD`; also `NO_DASHES` for the 
32-hex-character compact form, and `URN` for the RFC 4122 `urn:uuid:` 
namespace).
+  - `BigNumberFormat` (default `NUMBER`; also `STRING` for always-quoted 
output, and `AUTO` which emits a bare numeric token when the value is JS-safe 
(`|v| ≤ 2^53−1`) and a quoted string otherwise to avoid silent precision loss 
in JavaScript clients).
+- Both enums expose static `format(...)` and `parse(...)` helpers; parsers are 
intentionally lenient and accept every textual shape produced by any constant 
regardless of the configured value (the setting is informational only on parse).
+- Binary serializers (BSON / CBOR / MsgPack) continue to emit native UUID 
bytes and native big-number types regardless of the textual format setting; the 
new enums apply to textual wires only.
+- Added global marshalling-context settings on `MarshallingContext.Builder`:
+  - `uuidFormat(UuidFormat)` (default `STANDARD`).
+  - `bigNumberFormat(BigNumberFormat)` (default `NUMBER`).
+  - Mirrored on `MarshallingContextable.Builder` so every serializer / parser 
builder inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+  - `@Marshalled(uuidFormat=..., bigNumberFormat=...)`
+  - `@MarshalledProp(uuidFormat=..., bigNumberFormat=...)`
+  - `@MarshalledConfig(uuidFormat=..., bigNumberFormat=...)`
+- Extended uniform precedence resolution in `MarshalledPropertyPostProcessor`: 
`@MarshalledProp` > `@Marshalled` > `MarshallingContext` setting > enum 
default. Resolution installs an `ObjectSwap<T, Object>` on `UUID` / 
`BigInteger` / `BigDecimal` properties whose `swap(...)` returns either a 
`Number` (bare numeric token) or a `String` (quoted), so binary serializers 
continue to see native types and text serializers branch on the formatted 
value. For `BigNumberFormat.AUTO` the swap dynami [...]
+
+This completes the umbrella TODO-50 wire-format-control plan (Phases 1, 2, and 
3 are all delivered in 9.5.0).
+
+#### Boolean / Float / Currency / Class wire format controls (TODO-54)
+
+Round-2 follow-up to TODO-50 extending the `MarshallingContext` format-control 
surface to four additional types. Same template (precedence chain, lenient 
parsers, binary-native fall-through) as the UUID / BigNumber phases.
+
+- Added four new enums under `org.apache.juneau`:
+  - `BooleanFormat` (default `TRUE_FALSE`; also `ZERO_ONE` for numeric `0`/`1` 
tokens, `YES_NO` / `Y_N` / `ON_OFF` for textual forms). `ZERO_ONE` emits a bare 
numeric token (not a quoted string) on JSON.
+  - `FloatFormat` (`NaN_AS_NULL` (default per spec, but install-skipped on 
primitive `float`/`double` — see note below), `NaN_AS_STRING` for quoted 
`"NaN"` / `"Infinity"` / `"-Infinity"`, `NaN_AS_NUMBER` for the bare token 
(non-strict JSON), `NaN_AS_ERROR` for fail-fast).
+  - `CurrencyFormat` (default `ISO_CODE` — locale-independent, 
round-trip-safe; also `SYMBOL` and `DISPLAY_NAME` which are locale-sensitive 
and best-effort on parse).
+  - `ClassFormat` (default `FQCN` — `Class.getCanonicalName()` with 
`getName()` fallback for local/anonymous classes; also `BINARY_NAME` for 
`Class.getName()` with `$` separators, and `SIMPLE_NAME` for 
`Class.getSimpleName()` which is serialize-only — parsing `SIMPLE_NAME` throws 
`UnsupportedOperationException`).
+- Each enum exposes static `format(...)` / `parse(...)` helpers; parsers are 
intentionally lenient and accept every textual shape produced by any constant 
regardless of the configured value (the setting is informational only on parse).
+- Added global marshalling-context settings on `MarshallingContext.Builder`:
+  - `booleanFormat(BooleanFormat)` (default `TRUE_FALSE`).
+  - `floatFormat(FloatFormat)` (default `NaN_AS_NULL`).
+  - `currencyFormat(CurrencyFormat)` (default `ISO_CODE`).
+  - `classFormat(ClassFormat)` (default `FQCN`).
+  - Mirrored on `MarshallingContextable.Builder` and overridden on 
`Serializer.Builder` / `Parser.Builder` so every serializer / parser builder 
inherits the fluent setters with concrete return types.
+- Added annotation-level overrides on the three Marshalled annotations:
+  - `@Marshalled(booleanFormat=..., floatFormat=..., currencyFormat=..., 
classFormat=...)`
+  - `@MarshalledProp(booleanFormat=..., floatFormat=..., currencyFormat=..., 
classFormat=...)`
+  - `@MarshalledConfig(booleanFormat=..., floatFormat=..., currencyFormat=..., 
classFormat=...)`
+- Extended uniform precedence resolution in `MarshalledPropertyPostProcessor`: 
`@MarshalledProp` > `@Marshalled` > `MarshallingContext` setting > enum 
default. Resolution installs an `ObjectSwap` on `Boolean` / `Float` / `Double` 
/ `Currency` properties as appropriate. Binary serializers continue to see 
native primitives via the swap's `swap(...)` short-circuit on 
`OutputStreamSerializerSession`.
+
+**Breaking changes:**
+
+- **`ClassSwap` deleted outright.** The legacy 
`org.apache.juneau.swaps.ClassSwap` is removed in 9.6. Its responsibilities 
split into two replacements:
+  - For bean properties, an `ObjectSwap<Class, String>` is installed by 
`MarshalledPropertyPostProcessor` when `@MarshalledProp(classFormat=...)` or 
`@Marshalled(classFormat=...)` is configured.
+  - For standalone `Class<?>` values (root-level, map keys, list elements), a 
new `org.apache.juneau.swaps.ClassFormatSwap` is registered in `DefaultSwaps` 
and reads `MarshallingContext.classFormat` at swap-time.
+  - Default behavior (`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 `BINARY_NAME` if you need the JVM binary form. Callers 
that explicitly wanted `getSimpleName()` must move to 
`@MarshalledProp(classFormat=ClassFormat.SIMPLE_NAME)`.
+  - Replace `@Swap(impl=ClassSwap.class)` with 
`@MarshalledProp(classFormat=ClassFormat.FQCN)` (or simply remove it — the new 
default matches old behavior for top-level classes).
+
+**Notable caveats:**
+
+- **`FloatFormat.NaN_AS_NUMBER`** emits the bare `NaN` / `Infinity` / 
`-Infinity` token, which is **not** strict JSON per RFC 8259. Use only with 
lenient consumers (Juneau's own JSON parser, V8, several Python libraries). 
Strict consumers reject these tokens.
+- **`FloatFormat` on primitive `float` / `double` fields:** Juneau's bean 
machinery applies a null-to-primitive-default convention (e.g. `m.put("d1", 
null)` → `d1 = 0.0`) that conflicts with swap-driven `NaN_AS_NULL` semantics. 
To preserve this contract, the post-processor installs the float swap only on 
**boxed** `Float` / `Double` properties for context-level format settings. 
Explicit `@MarshalledProp(floatFormat=...)` or `@Marshalled(floatFormat=...)` 
overrides install for the explici [...]
+- **`CurrencyFormat.SYMBOL` / `DISPLAY_NAME` round-trip is best-effort and 
locale-dependent on parse.** ISO_CODE is the only constant with strict 
round-trip guarantees — `$` can resolve to USD / CAD / AUD / etc.; the parser 
prefers the locale's default currency on ambiguity and throws 
`IllegalArgumentException` when no match is found.
+- **`ClassFormat.SIMPLE_NAME` is serialize-only** — `parse(...)` throws 
`UnsupportedOperationException`. Use `FQCN` (default) or `BINARY_NAME` for 
round-trippable wires.
+
+#### Format-control round-trip hardening (TODO-57)
+
+Closes out the format-control extension work (TODO-4 / TODO-50 / TODO-52 / 
TODO-54) with a cross-serializer/parser round-trip test matrix over all 16 
`*Format` enums and 8 additional `TemporalFormat` subtypes. The matrix surfaced 
and closed 18 production bugs across the format-control dispatch layer; new 
public helpers and capabilities were added to support the fixes.
+
+**Highlights:**
+
+- **`TemporalFormat` carryover** — all 10 supported `TemporalAccessor` 
subtypes (`Instant`, `LocalDateTime`, `LocalDate`, `OffsetDateTime`, 
`ZonedDateTime`, `Year`, `YearMonth`, `LocalTime`, `OffsetTime`, `MonthDay`) 
now round-trip across every `TemporalFormat` value. Date-bearing-only / 
time-bearing-only formats are handled lossily via the existing 
`DefaultingTemporalAccessor` mechanism (year defaults to 1970, time defaults to 
00:00:00, etc.).
+- **`MonthDay` support** — `java.time.MonthDay` is a `TemporalAccessor` but 
**not** a `Temporal`, so it was previously falling through to standard bean 
serialization (and failing on parse for lack of a public no-arg constructor). 
Now fully round-trippable via a new sibling `TemporalAccessor`-keyed swap 
factory; configured `TemporalFormat` values are silently ignored for `MonthDay` 
properties (the only stable wire shape is the native `--MM-DD` `toString()` / 
`parse()` form, since no `Date [...]
+- **`hasNativeBytes()` capability check** — new public methods on 
`OutputStreamSerializerSession` and `InputStreamParserSession` (default `true`) 
consulted by `BinarySwap.match` and 
`MarshalledPropertyPostProcessor.binarySwap`. Overridden to `false` on 
`ParquetSerializerSession` / `ParquetParserSession` / 
`RdfStreamSerializerSession` / `RdfStreamParserSession`. **Wire-format 
change**: at any non-`NOT_SET` `BinaryFormat`, Parquet and the two binary RDF 
serializers (RDF/Thrift, RDF/Proto)  [...]
+- **18 production bugs closed** across the format-control dispatch layer. 
Aggregated by theme:
+  - **Format-hint correctness across binary serializers** — MsgPack `INT8` / 
`INT16` / NEGFIXINT sign-extension on parse (negative `Period` / `BigInteger` 
values no longer come back as unsigned magnitudes); Proto text-format `Float` / 
`Double` decimal-literal sign loss and `0`-prefix tokenization for `0.x` / 
`0eN` / `0f` shapes; Proto `nan` / `inf` identifier-vs-token disambiguation.
+  - **JSON-family precision tier (Bug #5)** — `StringUtils.parseNumber` no 
longer auto-classifies bare decimals as `Float` when targeting `Number` / 
`Object` / `Double`. JSON / JSON5 / JSONL / XML / HTML / UON / UrlEncoding / 
JCS parsers now return `Double` for `parse("3.14", Object.class)` and preserve 
full double precision in `Double` bean properties. *Caller-visible if you were 
instance-checking the parsed `Number` for `Float`* — see migration guide.
+  - **Bean-property `Map<Enum, V>` key coercion (Bug #7b)** — Hjson / Hocon / 
Proto / Bson parsers now thread the bean property's `ClassMeta<K>` for the map 
key into the key-coercion step, so typed `Map<MyEnum, V>` properties round-trip 
cleanly at every `EnumFormat` value. Proto-specific ordinal-keyed maps 
(`EnumFormat.ORDINAL`) now emit bare integer field tags (`0: "first"`) instead 
of quoted-string keys, which previously triggered Proto's 
adjacent-string-literal concatenation on parse.
+  - **Parquet schema fidelity (Bugs #7a, #11)** — Parquet now correctly 
round-trips `UUID` (via `LOGICAL_TYPE_UUID`), `Class<?>` (via the default-swap 
dispatch), `Enum` at `EnumFormat.ORDINAL` (via `INT32` column), and `byte[]` 
(via the `TYPE_BYTE_ARRAY` schema element without `convertedType`).
+  - **Parser dispatch for collection-element / top-level `byte[]` (Bug #12)** 
— Toml / Proto / Hjson / Hocon parsers now consult `BinarySwap.unswap` for 
`byte[]` collection elements and top-level values at non-`NOT_SET` formats, so 
wire forms like `"0001ff807f102030"` (HEX) decode to the correct 8-byte array 
instead of a 16-byte ASCII char array.
+  - **`BinaryFormat.BASE64_URL` hint (Bug #9)** — `BinaryFormat.parse(String)` 
now honors the `BASE64_URL` constant directly (routing to 
`Base64.getUrlDecoder()` which accepts missing padding) before falling through 
to the format-agnostic wire-shape sniff. Non-3-aligned URL-safe payloads 
without `-` / `_` chars no longer fail with "Invalid BASE64 string length".
+  - **Hocon parser-fragility (Bugs #10, #15)** — 
`HoconWriter.QUOTE_VALUE_CHARS` extended to cover `=` (BASE64 padding), `+` 
(`+NNNN` timezone offsets), and `$` (nested-class `BINARY_NAME`); 
`HoconTokenizer` recursion guard now throws a clean `IOException` instead of 
`StackOverflowError`; `HoconParserSession.parseArray` / `parseObject` 
array-flatten bug fixed (nested arrays no longer collapse via spurious 
array-concatenation).
+  - **`ClassFormat.FQCN` nested-types / arrays parser-fragility (Bug #16)** — 
`ClassFormat.parse` now resolves nested types (`java.util.Map.Entry` → 
`java.util.Map$Entry`), array suffixes (`int[]`, `java.lang.String[][]`), and 
leaf primitives (`Class.forName("int")` previously rejected; now resolves 
through a primitive-name table).
+  - **`Currency` default-swap registration (Bug #6)** — `Currency` added to 
`DefaultSwaps` (sibling of the existing `Locale` / `TimeZone` / `ZoneId` 
entries) so `Currency` bean properties round-trip at the default 
`CurrencyFormat.ISO_CODE` / `NOT_SET` levels.
+  - **UrlEncoding-expanded empty `byte[]` (Bug #13)** — empty `byte[]` 
properties now round-trip as `byte[0]` instead of `null` through the 
expanded-params UrlEncoding flavor.
+  - **`MonthDay` round-trip support (Bug #18)** and **`OffsetTime × MILLIS` 
swap asymmetry (Bug #17)** — see "API changes" below.
+
+**API changes:**
+
+- **`TemporalFormat.format(...)` and `parse(...)` signatures widened** from 
`Temporal` to `TemporalAccessor`:
+  - `format(Temporal, ZoneId)` → `format(TemporalAccessor, ZoneId)`
+  - `parse(String, Class<? extends Temporal>, ZoneId)` → `parse(String, 
Class<? extends TemporalAccessor>, ZoneId)`
+
+  Source-compatible for callers passing `Temporal` instances (every existing 
`Temporal` is a `TemporalAccessor`). Required so `MonthDay` (which is a 
`TemporalAccessor` but not a `Temporal`) reaches the dispatch path. Flag in 
migration guide.
+- **New `TemporalFormat.isMillisNumeric(Class<? extends TemporalAccessor>)`** 
static helper — returns `true` iff `format(value, MILLIS)` emits a 
`Long`-parseable numeric string for the subtype. Returns `false` for 
`LocalTime`, `OffsetTime`, and `MonthDay` (the three subtypes whose `MILLIS` 
branch falls back to the per-subtype `DEFAULT` ISO string form). Single source 
of truth for the `MILLIS` wire-type decision; consumed by both `temporalSwap` 
and the new `temporalAccessorSwap` factory.
+- **New `MarshallingContext` capability methods** on the binary session base 
classes (default `true`; see "Highlights" above for the wire-format 
implications):
+  - `OutputStreamSerializerSession.hasNativeBytes()`
+  - `InputStreamParserSession.hasNativeBytes()`
+- **New `MarshalledPropertyPostProcessor` internals** — sibling 
`temporalAccessorSwap` swap factory keyed at `TemporalAccessor.class` (vs 
`temporalSwap`'s `Temporal.class`), and a new 
`isTemporalAccessorType(Class<?>)` predicate (negative-space complement: 
`TemporalAccessor.class.isAssignableFrom(c) && ! 
Temporal.class.isAssignableFrom(c)`). Written as the negative-space complement 
so a future JDK addition of another non-`Temporal` `TemporalAccessor` is picked 
up automatically. Wired sym [...]
+- **`OffsetTime × TemporalFormat.MILLIS`** now falls back to `ISO_OFFSET_TIME` 
(sibling of the existing `LocalTime` and `MonthDay` carve-outs) — `OffsetTime` 
has no canonical epoch-millis interpretation. The carve-out is reflected in the 
updated `MILLIS` constant Javadoc and consumed via `isMillisNumeric`.
+
+**Performance:**
+
+- `IniParserSession` and `MarkdownParserSession` now memoize their internal 
`Json5Parser` instance per session instead of constructing a fresh 
`Json5Parser.DEFAULT` per parse call. Behavior-preserving; eliminates repeated 
builder-and-instance construction in tight-loop parsing of INI / Markdown 
content.
+
+**Build-config change (developer-only):**
+
+- New `.mvn/maven.config` containing `--also-make`. Affects developers running 
`mvn -pl <module> …` from the workspace root: upstream modules are now 
automatically added to the active reactor instead of being resolved from 
`~/.m2/repository`. Inert without `-pl`, so root-level builds (`mvn clean 
install`, `./scripts/test.py`, `./scripts/push.py`) and CI are unaffected. 
Fixes a long-standing developer-loop footgun where in-tree edits to upstream 
modules wouldn't be picked up by `mvn -pl j [...]
+
 #### Typed `JsonSchema` bean generation bridge (TODO-8)
 
 - Added `JsonSchemaBeanGenerator` in `juneau-bean-jsonschema` to generate 
typed `JsonSchema` beans from Java types via `JsonSchemaGenerator`.
@@ -2698,3 +2909,17 @@ JsonPatch back = par.parse(wire, JsonPatch.class);
 ```
 
 See [juneau-bean-jsonpatch](/docs/topics/JuneauBeanJsonPatch) for the full 
topic.
+
+### Tooling
+
+#### `/sonarqube` Cursor command + `scripts/sonarqube.py` + `eclipse-warnings` 
skill (TODO-39)
+
+Added a paired terminal/chat workflow for triaging SonarCloud findings on a 
source file, package, or Maven module — symmetric with the existing `/coverage` 
+ `scripts/coverage.py` flow.
+
+- **Terminal layer:** `python3 scripts/sonarqube.py <path>` queries the 
SonarCloud Web API for the public `apache_juneau` project and prints a per-file 
block with rule id, severity, line, and message. Caches the latest fetch under 
`target/.sonar-issues.json` (gitignored). Works anonymously by default; 
`SONAR_TOKEN` is honored if set in the environment but never echoed.
+- **Filters:** `--severity {BLOCKER,CRITICAL,MAJOR,MINOR,INFO}`, `--rule 
java:Sxxx` (repeatable), `--type 
{CODE_SMELL,BUG,VULNERABILITY,SECURITY_HOTSPOT}`, `--branch <branch>` (default 
`master`), `--with-suppress-hint` to print a `@SuppressWarnings("java:Sxxx")` 
line per finding, `--max <N>` (default 200), and `--run` / `-r` to refresh the 
cache.
+- **Cursor command:** `/sonarqube` resolves the same shorthand as `/coverage` 
(file, package, FQCN, module shorthand) and then runs the script.
+- **Live-local IDE layer:** for triaging and applying SonarLint quick-fixes 
against the working tree (uncommitted edits), the `eclipse-warnings` Cursor 
skill at `.cursor/skills/eclipse-warnings/SKILL.md` drives the AssistAI Eclipse 
MCP server. It auto-activates on phrases like *"fix the warnings"*, *"clean up 
sonar issues"*, *"fix sonarlint warnings"*, *"apply quick-fixes"*. The two 
layers are complementary: the script lists committed/pushed-code findings; the 
skill applies fixes to the  [...]
+- **Legacy TSV-export workflow** under `scripts/README_SONARQUBE.md` is 
preserved for batch categorization but is no longer the primary path.
+
+See `.cursor/commands/sonarqube.md` and the `5.2. SonarQube Script` section in 
`AGENTS.md` for the full reference.
diff --git a/pages/topics/23.01.V9.5-migration-guide.md 
b/pages/topics/23.01.V9.5-migration-guide.md
index 5f5c305c27..0ca4fe9ecb 100644
--- a/pages/topics/23.01.V9.5-migration-guide.md
+++ b/pages/topics/23.01.V9.5-migration-guide.md
@@ -435,5 +435,20 @@ These are new in `juneau-rest-common` and replace the 
corresponding HC 4.5 / `or
 
 `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` [...]
 
+## Format-Control Round-Trip Hardening (TODO-57)
+
+Closes out the format-control extension work (TODO-4 / TODO-50 / TODO-52 / 
TODO-54) 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.
+
+| Old | New | Notes |
+|-----|-----|-------|
+| `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  [...]
+| `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 instantiated` | `MonthDay` 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 th [...]
+| `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_TIME` | Sibling 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 le [...]
+| `byte[]` bean property on `ParquetSerializer` / `ParquetParser` at 
non-`NOT_SET` `BinaryFormat` → emitted/read as a raw `TYPE_BYTE_ARRAY` Parquet 
column | Emits/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  [...]
+| `byte[]` bean property on `RdfThriftSerializer` / `RdfThriftParser` / 
`RdfProtoSerializer` / `RdfProtoParser` at non-`NOT_SET` `BinaryFormat` → 
emitted/read as an `xsd:base64Binary` typed RDF literal | Emits/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 [...]
+| `BinaryFormat.BASE64_URL.parse("mQ")` (or any non-3-aligned URL-safe payload 
without `-` / `_` chars) threw `IllegalArgumentException: Invalid BASE64 string 
length` | Decodes 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.Double` | **Behavioral change (Bug #5 closure)** — see release notes 
for the full rationale. 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 / [...]
+
 <!-- Additional rows will be populated as 9.5 breaking changes land. See 
todo/TODO-17 for the
 ongoing 9.5.0 audit. -->


Reply via email to