skip to content

What is InMemoryAuditEventRepository, what are its limitations, and how do you provide a custom AuditEventRepository?

level: seniorimportance: should knowfreq 22%

answer

  1. AuditEventRepository = add() + find()
  2. InMemory = ring buffer, default 4000, heap
  3. volatile + per-instance + bounded
  4. custom bean activates chain AND replaces default
  5. DB / Kafka / SIEM backend for durability

basics

~20 s

InMemoryAuditEventRepository is Spring's default store: a fixed-size (default 4000) in-memory circular buffer of AuditEvents. It's per-instance and lost on restart. For durability you implement the AuditEventRepository interface (add + find) backed by a database or log/SIEM and declare it as a bean.

solid answer

~40 s

AuditEventRepository is the interface behind /actuator/auditevents, with add(AuditEvent) and find(principal, after, type) methods. Spring provides InMemoryAuditEventRepository — a bounded ring buffer holding the most recent events (default capacity 4000), stored in heap. Its limitations: not persistent (gone on restart), not shared across instances (each node has its own buffer), bounded (old events evicted), and it puts memory pressure if you raise the capacity. Crucially, Spring Boot only activates the audit infrastructure when an AuditEventRepository bean exists, and it doesn't auto-register InMemoryAuditEventRepository — you usually declare it. For production durability you implement your own AuditEventRepository that writes to a database, append-only log, or SIEM, and declare it as the bean; its find() implementation powers the endpoint's queries (or you can make find() a no-op if the endpoint is only a live view).

code

java · 27 lines
java
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.stereotype.Repository;
import java.time.Instant;
import java.util.List;

@Repository // declaring an AuditEventRepository bean activates the whole audit chain
public class JdbcAuditEventRepository implements AuditEventRepository {

    private final AuditEventDao dao; // your JDBC/JPA layer

    public JdbcAuditEventRepository(AuditEventDao dao) { this.dao = dao; }

    @Override
    public void add(AuditEvent event) {
        // scrub sensitive keys from event.getData() before persisting!
        dao.insert(event.getTimestamp(), event.getPrincipal(),
                   event.getType(), toJson(event.getData()));
    }

    @Override
    public List<AuditEvent> find(String principal, Instant after, String type) {
        return dao.query(principal, after, type); // powers /actuator/auditevents
    }

    private String toJson(Object data) { /* ... */ return "{}"; }
}

go deeper

for a junior

Know the default store is in-memory and lost on restart.

for a middle

Know it's a bounded ring buffer (default 4000) and that you implement AuditEventRepository for durability.

for a senior

Explain per-instance/volatile/bounded limits and how a custom bean activates and replaces the default.

for a principal

Weigh sync-write latency, PII scrubbing, retention, and SIEM forwarding vs replacing the repository.

**The interface.** `org.springframework.boot.actuate.audit.AuditEventRepository` defines: - `void add(AuditEvent event)` — store an event. - `List<AuditEvent> find(String principal, Instant after, String type)` — query, with any argument null meaning 'no filter'. This is what `/actuator/auditevents` calls. **The default: `InMemoryAuditEventRepository`.** - Backed by a fixed-size array used as a **circular (ring) buffer**; when full, the oldest event is overwritten. - **Default capacity is 4000** events; configurable via the constructor `new InMemoryAuditEventRepository(int capacity)` or a setter. - Thread-safe (synchronized around the buffer). **Limitations.** 1. **Volatile** — heap-only; every restart wipes history. Useless for compliance/forensics that must survive deploys. 2. **Per-instance** — in a horizontally scaled service, each replica has its own buffer, so the endpoint on one node shows only that node's events. No global view. 3. **Bounded** — beyond capacity, old events silently vanish (eviction), so bursts can lose data. 4. **Memory cost** — raising capacity to retain more increases heap usage and GC pressure. 5. **No indexing/rich query** — `find` scans the buffer; fine for thousands, not millions. **Providing a custom repository.** Implement `AuditEventRepository` and declare it as a bean. Because `AuditAutoConfiguration` is `@ConditionalOnBean(AuditEventRepository.class)` and the `auditListener` bean is `@ConditionalOnMissingBean`, your bean both activates the whole chain and replaces the in-memory default. Typical backends: - **Relational DB** — `add` inserts a row (principal, type, timestamp, serialized data JSON); `find` runs a filtered query. - **Append-only log / message queue / SIEM** — `add` emits to Kafka/Splunk/ELK for centralized, durable, cross-instance auditing; `find` may query the store or be a limited/no-op view. **Design considerations.** - **Serialization of `data`** — the map can hold arbitrary objects; persist as JSON and beware of non-serializable or sensitive values. - **PII / secrets** — never store credentials; scrub the data map before writing. - **Write path performance** — `add` runs synchronously on the request thread (via the listener during `publishEvent`); a slow DB write can add latency. Consider async/batched writes, but weigh losing events on crash. - **Retention** — a DB needs a retention/rotation policy; the endpoint's `find` should bound results. - **Endpoint semantics** — if you centralize in a SIEM, you might keep `InMemoryAuditEventRepository` for a live per-node view AND forward via a custom `@EventListener`, rather than replacing the repository. **When to use which.** In-memory: dev, single instance, ephemeral live view. Custom durable repository/forwarding: production, multi-instance, compliance, forensics, alerting.

  • What is the default capacity of InMemoryAuditEventRepository and what happens when it's exceeded?
    4000 events. It's a circular buffer, so the oldest events are overwritten (evicted) once full — older audit history is silently lost.
  • If you run three replicas with the in-memory repository, what does /actuator/auditevents on one node show?
    Only that node's events. The buffer is per-instance and not shared, so you get a partial view; centralize via a shared DB or SIEM for a global trail.
  • How does declaring your own AuditEventRepository bean interact with the default?
    The audit auto-config is @ConditionalOnBean(AuditEventRepository), and there is no auto-registered InMemory default; your bean both activates the chain and is the sole repository, so find()/add() route to your implementation.

saying these in an interview costs you the question

  • Assuming InMemoryAuditEventRepository persists across restarts.
  • Thinking a single node's endpoint shows the whole cluster's audit trail.
  • Believing the in-memory repo is auto-registered so no bean is needed.
  • Ignoring that add() runs on the request thread and a slow custom write adds latency.

context