Metrics with Micrometer

Since 1.9.0 the optional artifact fixedformat4j-micrometer instruments fixedformat4j with Micrometer, the metrics facade used by Spring Boot Actuator, Quarkus, Micronaut, and plain Java applications. The core fixedformat4j artifact is unchanged and gains no dependencies — when the module is absent there is zero cost.

Add the dependency

<dependency>
  <groupId>com.ancientprogramming.fixedformat4j</groupId>
  <artifactId>fixedformat4j-micrometer</artifactId>
  <version>1.9.0</version>
</dependency>
implementation 'com.ancientprogramming.fixedformat4j:fixedformat4j-micrometer:1.9.0'

Wiring

All instrumentation is decorator-based via FixedFormatMetrics:

MeterRegistry registry = ...;            // any Micrometer registry
FixedFormatMetrics metrics = FixedFormatMetrics.of(registry);

// instrument a manager — load/export timers, parse-error counter, class gauge
FixedFormatManager manager = metrics.instrument(new FixedFormatManagerImpl());

// instrument file processing — inject the manager and wrap the reader strategies
FixedFormatReader reader = FixedFormatReader.builder()
    .manager(manager)
    .addMapping(CustomerRecord.class, LinePattern.prefix("C"))
    .excludeLines(metrics.countLines(line -> false))   // counts every processed line
    .unmatchStrategy(metrics.countUnmatched(UnmatchStrategy.skip()))
    .parseErrorStrategy(metrics.countParseErrors(ParseErrorStrategy.skipAndLog()))
    .build();

In Spring Boot, expose the instrumented manager as a bean — Actuator provides the MeterRegistry:

@Bean
public FixedFormatManager fixedFormatManager(MeterRegistry registry) {
  return FixedFormatMetrics.of(registry).instrument(new FixedFormatManagerImpl());
}

Published meters

Metric Type Tags Meaning
fixedformat.load timer record.class Duration of each load() call
fixedformat.export timer record.class Duration of each export() call (both overloads)
fixedformat.parse.errors counter record.class, field ParseException occurrences (the exception still propagates)
fixedformat.reader.lines.processed counter Lines read, including excluded and unmatched ones
fixedformat.reader.lines.unmatched counter Lines matching no registered LinePattern
fixedformat.reader.lines.errors counter Lines that matched but failed to parse
fixedformat.metadata.cache.classes gauge manager.instance Distinct @Record classes processed through the instrumented manager

A spiking fixedformat.parse.errors rate is a leading indicator of upstream format drift — exactly the class of problem fixed-width integrations otherwise suffer silently.

Double-count with reader wiring: when the reader’s manager is instrumented (as in the wiring example above) and countParseErrors is used, a parse failure on a matched line increments both fixedformat.reader.lines.errors (coarse, line-level) and fixedformat.parse.errors (granular, tagged by record.class + field). This is by design — the two meters answer different questions — but operators should not sum them to count total failures.

Gauge semantics: the library’s internal metadata cache is ClassValue-based and deliberately not enumerable (it must never pin classloaders in hot-reload environments), so the gauge counts the distinct record classes observed by the instrumented manager instance — the measurable equivalent of “classes currently cached” for the normal one-manager setup. The tracking set holds classes weakly for the same classloader-safety reason: record classes that become unreachable (e.g. after a hot-reload) drop out of the count. Each manager publishes its own gauge, disambiguated by the manager.instance tag, so several instrumented managers can share one registry.

Performance

Meters are resolved once, not per call: the instrumented manager caches its timers per record class (in a GC-safe ClassValue, so cached timers never pin a record class’s classloader), and the reader wrappers resolve their counters when the wrapper is created. The steady-state cost per load()/export() is therefore just the timer sample — two System.nanoTime() calls — plus a set lookup for the gauge; per reader line it is a single counter increment. Measured with the JMH benchmark in the benchmarks module (-P micrometer-bench), the instrumented manager adds on the order of tens of nanoseconds per operation. Uninstrumented code paths and applications without this module are completely unaffected.