1.9.0
A feature release introducing two new optional artifacts (Micrometer instrumentation and a compile-time annotation processor), Java record support, a schema introspection API, and substantial performance work — while staying fully compatible with Java 11 and existing 1.8.x record classes.
New features
- Micrometer instrumentation — new optional
fixedformat4j-micrometerartifact (#120) — decorator-based metrics for any Micrometer registry (Spring Boot Actuator, Quarkus, Micronaut, plain Java).FixedFormatMetrics.of(registry).instrument(manager)publishesfixedformat.load/fixedformat.exporttimers, a parse-error counter, and a metadata-cache gauge; wrapper factories add reader line counters. The core artifact is unchanged and gains no dependencies; steady-state overhead is tens of nanoseconds per operation, only on instrumented managers (#140). See Metrics. - Compile-time annotation validation — new optional
fixedformat4j-processorartifact (#118) — validates@Field/@Recordconfiguration duringjavac, turning misconfigurations into compile errors instead of runtimeFixedFormatExceptions. Checks patterns, enum widths, null sentinels, rest-of-line rules, record-length overflow, and overlapping offsets. Strictly opt-in; zero runtime cost. Note: the two layout checks are stricter than the runtime and can surface latent bugs in existing code. See Compile-time validation. - Java
recordsupport (#119) —@Recordclasses can now be Javarecordtypes (JDK 16+). Annotate the components directly;load()binds through the canonical constructor,export()reads the accessors. All annotations work exactly as on getters, including nested records, repeating fields, and null sentinels. The artifact still runs on Java 11 — record binding activates only when a record class is encountered. - Schema introspection API —
FixedFormatIntrospector(#117) —introspect(Class<?>)returns one immutableFieldInfoper@Field(ordered by offset): name, offset, length, type, alignment, padding, null sentinels, formatter, repeat count, nested-record flag. Delivered as a separate interface so third-partyFixedFormatManagerimplementations stay compatible. See Schema introspection. @Field.nullValue— literal null sentinel string (#130) — complement tonullCharfor feeds where the null marker is a mixed-character string (e.g."9998"). Strictly opt-in; mutually exclusive withnullChar.- Pluggable type registry (#116) —
FixedFormatManagerImpl.builder().registerType(UUID.class, UUIDFormatter.class).build()registers a custom type-to-formatter mapping once at startup instead of repeatingformatter=on every field. FixedFormatWriter— write-side IO symmetry (#114) — fluent builder API for writing fixed-format files toWriter,OutputStream, orPath, fromIterableor lazyStreamsources, with charset and line-separator control. Heterogeneous record lists supported; instances are thread-safe.FixedFormatReader.openStream()— lazy stream processing (#115) — returns a lazyStream(untyped or filtered to a record type) so arbitrarily large files can be processed with bounded memory. Caller owns the stream lifecycle via try-with-resources.
Performance improvements
Repeating fields (@Field(count > 1)) are significantly faster: per-class metadata caching makes loading ~1.9× and exporting ~1.25× faster in the bundled JMH benchmark. ByTypeFormatter resolves its delegate once instead of reflectively per call, enum constants are cached, and decimal export skips DecimalFormat entirely (~15% faster small-record export, and output is now guaranteed locale-independent — previously a JVM defaulting to a non-Latin-digit locale could export digits the parser couldn't round-trip).
Bug fixes
- LEFT-aligned signed numbers no longer lose their leading digit —
Sign.PREPEND/APPENDfreed the sign slot from the wrong side for LEFT alignment, corrupting the value (e.g.5exported as"+ "and round-tripped as0). RIGHT-aligned output is byte-for-byte unchanged. - NUMERIC enum ordinal 0 survives a round trip with
paddingChar='0'— an all-zeros field now loads as the ordinal-0 constant instead ofnull. - Parse failures keep their root cause — date/time formatters now chain the underlying
ParseException/DateTimeParseExceptioninstead of discarding it.
Dependency changes
- commons-lang3 removed — replaced with Java 11 natives.
fixedformat4jnow has a single compile-scope dependency:slf4j-api. Action required only if your project relied on the transitive commons-lang3 — declare it directly in that case.
Build
- All modules compile with
--release 11, validating builds on newer JDKs against the Java 11 API signature.
Full details (including documented known limitations) in the changelog.
Full diff: 1_8_1...1_9_0