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. -->