Supported JDK Datatypes
Juneau provides first-class, direct serializer/parser support for a wide range of JDK datatypes — no extra modules, no opt-in registration, no swap setup required for the common cases. This page enumerates the supported types, their default wire forms, and the per-type knobs that let you adjust the wire form across all formats.
Design principle: first-class > swaps
Juneau handles common JDK datatypes through direct serializer/parser code paths plus per-type *Format
enums (e.g. TemporalFormat, DurationFormat, BinaryFormat, …), not through
Default Swaps. Swaps are reserved for two scenarios:
- End-user customization of how a third-party type should be marshalled.
- A handful of internal types whose wire form is genuinely a single canonical encoding with no useful
knobs (e.g.
StackTraceElement,MatchResult).
The primitive optionals (OptionalInt / OptionalLong / OptionalDouble) and BitSet are handled on the
first-class path — the optionals mirror the Optional<T> plumbing directly, and BitSet carries its own
bitSetFormat builder knob (see below).
The advantage of the first-class path is that the format choice is a builder setter rather than a swap-replacement dance:
// First-class: per-type format knob on the builder.
var s = JsonSerializer.create().temporalFormat(TemporalFormat.ISO_LOCAL_DATE_TIME).build();
// Versus the swap-based alternative you'd need without it.
var s = JsonSerializer.create().swaps(MyCustomInstantSwap.class).build();
This mirrors the Jackson jackson-datatype-* family conceptually but without the per-type module wiring —
Juneau ships the support in-box.
Numbers
| Type | Default wire form | Knob |
|---|---|---|
byte / Byte | number | — |
short / Short | number | — |
int / Integer | number | — |
long / Long | number | — |
float / Float | number | floatFormat |
double / Double | number | floatFormat |
BigInteger | number (or string when overflow risk) | bigNumberFormat |
BigDecimal | number (or string when overflow risk) | bigNumberFormat |
The bigNumberFormat knob (BigNumberFormat.DEFAULT | STRING | NUMBER) lets you force big-number wire
emission as a quoted string for JavaScript-safe round-trips.
floatFormat covers NaN/Infinity handling (FloatFormat.DEFAULT writes lenient JSON;
FloatFormat.STRICT forces a string).
Booleans, chars, strings
| Type | Default wire form | Knob |
|---|---|---|
boolean / Boolean | true / false | booleanFormat (allows 0/1) |
char / Character | string of length 1 | — |
String | string | trimStrings |
CharSequence / StringBuilder / StringBuffer | string | — |
Optional family
| Type | Wire form | Empty form | Notes |
|---|---|---|---|
Optional<T> | unwrapped T when present | omitted (or null if keepNullProperties) | parse: absent or null → Optional.empty() |
OptionalInt | unwrapped int when present | omitted (or null if keepNullProperties) | parse: absent or null → OptionalInt.empty() |
OptionalLong | unwrapped long when present | omitted (or null if keepNullProperties) | parse: absent or null → OptionalLong.empty() |
OptionalDouble | unwrapped double when present | omitted (or null if keepNullProperties) | parse: absent or null → OptionalDouble.empty() |
Optional.empty() is treated identically to null by the inclusion machinery (governed by
keepNullProperties). See Null & Inclusion Policies for the full
contract, including how per-property Nulls coercion interacts.
Dates, times, and durations (java.time)
| Type | Default wire form | Knob |
|---|---|---|
Instant | ISO-8601 instant string | temporalFormat |
LocalDate | ISO-8601 date string | temporalFormat |
LocalDateTime | ISO-8601 local-datetime string | temporalFormat |
LocalTime | ISO-8601 local-time string | temporalFormat |
ZonedDateTime | ISO-8601 zoned-datetime string | temporalFormat |
OffsetDateTime | ISO-8601 offset-datetime string | temporalFormat |
OffsetTime | ISO-8601 offset-time string | temporalFormat |
Year / YearMonth / MonthDay | ISO-8601 partial string | temporalFormat |
ZoneId | zone-id string (e.g. "America/New_York") | — |
ZoneOffset | zone-offset string (e.g. "+05:00") | — (handled via ZoneId) |
Duration | ISO-8601 duration string ("PT15M") | durationFormat |
Period | ISO-8601 period string ("P1Y2M") | periodFormat |
The temporalFormat knob (enum) selects ISO-8601 (DEFAULT), local without zone, RFC-1123, or epoch millis.
The durationFormat and periodFormat knobs select ISO-8601 (default) or numeric milli /
day forms.
Legacy java.util.Calendar / java.util.Date are also supported via calendarFormat / dateFormat.
Identifiers and URI types
| Type | Default wire form | Knob |
|---|---|---|
UUID | string (e.g. "550e8400-…") | uuidFormat |
URI / URL | string | — |
Locale | BCP-47 language tag string | localeFormat |
TimeZone | timezone-id string | timezoneFormat |
Currency | ISO-4217 currency-code string | currencyFormat |
Class<?> | class-name string | classFormat |
Binary
| Type | Default wire form | Knob |
|---|---|---|
byte[] | Base-64 string | binaryFormat (Base-64 / Base-64-URL / hex / SPACED-HEX) |
InputStream / Reader | inline content | — |
BitSet | set-bit-index list string (e.g. "0,2,5") | bitSetFormat (INDICES / BITS / HEX) |
The bitSetFormat knob selects the textual wire form: INDICES (default, ascending set-bit indices as a
comma-delimited token), BITS (little-endian bit string, e.g. "101001"), or HEX (hex-encoded
little-endian byte array). It is configurable at all four precedence levels (@MarshalledProp,
@Marshalled, @MarshalledConfig, and the bitSetFormat(...) builder setter), exactly like the other
*Format enums.
Enums and collections
| Type | Default wire form | Notes |
|---|---|---|
enum | string (enumFormat = NAME) or ordinal int | per-type via enumFormat |
EnumSet<E> | JSON array of enum names | parses back to EnumSet for declared parameter EnumSet<E>; falls through to a generic Set<E> for raw / unparameterized declarations |
EnumMap<E,V> | JSON object keyed by enum name | parses back to EnumMap for declared parameter EnumMap<E,V>; falls through to a generic Map<E,V> for raw / unparameterized declarations |
BitSet | set-bit-index list string (bitSetFormat: INDICES / BITS / HEX) | first-class direct support — no swap; round-trips via BitSetFormat |
Collection<E> / List<E> / Set<E> / Queue<E> / Deque<E> | JSON array | concrete implementation chosen by declared type |
Map<K,V> | JSON object | concrete implementation chosen by declared type |
Iterator<E> / Iterable<E> / Stream<E> | JSON array (serialize only) | parse not supported |
Juneau vs Jackson datatype modules
Jackson ships per-type "datatype modules" (jackson-datatype-jsr310, jackson-datatype-jdk8,
jackson-datatype-guava, …) that you opt into individually. Juneau collapses that surface in two ways:
- JDK datatypes (
java.time,Optional*,BitSet, etc.) are supported in-box by direct serializer/parser code paths plus typed*Formatenums for the format choice — no module to register. - Third-party datatypes continue to follow the Swap /
@Swap machinery, which is symmetric to a Jackson custom
JsonSerializer/JsonDeserializerpair but additionally usable across every Juneau format.
The net result: most Spring-Boot-style "I need java.time in my JSON" wiring is unnecessary in Juneau —
JsonSerializer.DEFAULT.write(Instant.now()) Just Works.