How It Works

Read Path (~31ns)

Application code reads the volatile field directly. This compiles to a MOV instruction + memory barrier. No proxy. No method call. No IO. No reflection. This is the hot path — measured at ~31ns/op over 1,000,000 reads (~32M ops/sec).

Write Path (event-driven)

When a config file changes:

  1. OS WatchService detects the file modification (~50ms)
  2. MARZ reads and parses the file
  3. Diff against cached state — only changed keys are identified
  4. For each changed key, the reverse index finds the bound field(s)
  5. CAS deduplication prevents duplicate events (vim double-write pattern)
  6. Volatile field.set(bean, newValue) — immediately visible to all threads
  7. MarzEvent published to Spring’s ApplicationEventPublisher

Why volatile?

Every @Marz field must be declared volatile. This isn’t optional — MARZ validates at startup and fails fast with a clear error if you forget. The volatile keyword provides a Java Memory Model (JMM) happens-before guarantee: when the WatchService thread writes a new value, every application thread sees it on their next read. ~31ns measured cost. Zero locking. Zero blocking.

Self-Registration

If a @Marz field’s key doesn’t exist in the config file, MARZ writes it with the defaultValue at startup. Your config file becomes self-documenting — every @Marz field in the application is visible in one file without reading source code.

Listening for Changes

MARZ publishes a MarzEvent to Spring’s event system on every change:

@EventListener
public void onConfigChange(MarzEvent event) {
    log.info("Key '{}' changed from {} to {}",
        event.getKey(), event.getOldValue(), event.getNewValue());
}