Skip to content

1.9.0

Choose a tag to compare

@jeyben jeyben released this 12 Jun 17:51
· 72 commits to master since this release

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-micrometer artifact (#120) — decorator-based metrics for any Micrometer registry (Spring Boot Actuator, Quarkus, Micronaut, plain Java). FixedFormatMetrics.of(registry).instrument(manager) publishes fixedformat.load/fixedformat.export timers, 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-processor artifact (#118) — validates @Field/@Record configuration during javac, turning misconfigurations into compile errors instead of runtime FixedFormatExceptions. 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 record support (#119) — @Record classes can now be Java record types (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 immutable FieldInfo per @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-party FixedFormatManager implementations stay compatible. See Schema introspection.
  • @Field.nullValue — literal null sentinel string (#130) — complement to nullChar for feeds where the null marker is a mixed-character string (e.g. "9998"). Strictly opt-in; mutually exclusive with nullChar.
  • Pluggable type registry (#116) — FixedFormatManagerImpl.builder().registerType(UUID.class, UUIDFormatter.class).build() registers a custom type-to-formatter mapping once at startup instead of repeating formatter= on every field.
  • FixedFormatWriter — write-side IO symmetry (#114) — fluent builder API for writing fixed-format files to Writer, OutputStream, or Path, from Iterable or lazy Stream sources, with charset and line-separator control. Heterogeneous record lists supported; instances are thread-safe.
  • FixedFormatReader.openStream() — lazy stream processing (#115) — returns a lazy Stream (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 digitSign.PREPEND/APPEND freed the sign slot from the wrong side for LEFT alignment, corrupting the value (e.g. 5 exported as "+ " and round-tripped as 0). 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 of null.
  • Parse failures keep their root cause — date/time formatters now chain the underlying ParseException/DateTimeParseException instead of discarding it.

Dependency changes

  • commons-lang3 removed — replaced with Java 11 natives. fixedformat4j now 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