skip to content

How do you publish a custom audit event, and what is the structure of an AuditEvent?

level: middleimportance: should knowfreq 30%

answer

  1. AuditEvent = principal + type + data map (+ timestamp)
  2. wrap in AuditApplicationEvent, publishEvent
  3. AuditListener → repository.add()
  4. or inject AuditEventRepository directly
  5. type is a free-form String you choose

basics

~10 s

Build an AuditEvent(principal, type, data-map), wrap it in an AuditApplicationEvent, and publish it with ApplicationEventPublisher.publishEvent(...). Spring's AuditListener stores it in the AuditEventRepository so it appears at /actuator/auditevents.

solid answer

~40 s

An AuditEvent has a timestamp (Instant), a principal (String — who), a type (String — a short code you choose, e.g. "PASSWORD_CHANGED"), and a data Map<String,Object> of details. To record one, publish an AuditApplicationEvent, which wraps an AuditEvent, through the ApplicationEventPublisher. Spring Boot registers an AuditListener (an AbstractAuditListener) that listens for AuditApplicationEvent and calls repository.add(...) on the AuditEventRepository, so the event lands in the store behind /actuator/auditevents. Alternatively you can inject AuditEventRepository and call add() directly, but going through the event bus is the idiomatic decoupled path. There are convenient constructors: a Map-based one and a varargs "key=value" String form. Remember the audit infrastructure only activates when an AuditEventRepository bean exists.

code

java · 25 lines
java
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.listener.AuditApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import java.util.Map;

@Service
public class AccountService {

    private final ApplicationEventPublisher publisher;

    public AccountService(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void changePassword(String username) {
        // ... perform the change ...
        AuditEvent event = new AuditEvent(
            username,
            "PASSWORD_CHANGED",
            Map.of("channel", "self-service"));
        publisher.publishEvent(new AuditApplicationEvent(event));
        // AuditListener stores it -> visible at /actuator/auditevents
    }
}

go deeper

for a junior

Know the four fields and that you can create your own event types.

for a middle

Know both recording paths and the AuditApplicationEvent → AuditListener → repository flow.

for a senior

Discuss decoupling via the event bus, sync vs async publishing, and the no-repository silent-drop gotcha.

for a principal

Design a taxonomy of audit types and forward events to external SIEM via a custom listener.

**`AuditEvent` structure.** `org.springframework.boot.actuate.audit.AuditEvent` is immutable and has: - `Instant getTimestamp()` — set automatically to now if you use the constructor without a timestamp. - `String getPrincipal()` — the identity the event concerns. - `String getType()` — a free-form classification string. Built-in ones from Spring Security are `AUTHENTICATION_SUCCESS`, `AUTHENTICATION_FAILURE`, `AUTHENTICATION_SWITCH`, `AUTHORIZATION_FAILURE`; for custom events you invent your own (`ACCOUNT_LOCKED`, `PASSWORD_CHANGED`, ...). - `Map<String, Object> getData()` — arbitrary structured context. Constructors: - `new AuditEvent(String principal, String type, Map<String,Object> data)` - `new AuditEvent(Instant timestamp, String principal, String type, Map<String,Object> data)` - `new AuditEvent(String principal, String type, String... data)` — each string is either a plain key or `"key=value"`. **Two ways to record an event.** 1. **Publish an application event (idiomatic).** Wrap your `AuditEvent` in `org.springframework.boot.actuate.audit.listener.AuditApplicationEvent` and publish it via `ApplicationEventPublisher`. Spring Boot auto-registers an `AuditListener` (subclass of `AbstractAuditListener`) whose `onAuditEvent(AuditEvent)` calls `auditEventRepository.add(event)`. This keeps producers decoupled from storage. 2. **Call the repository directly.** Inject `AuditEventRepository` and call `add(AuditEvent)`. Simpler but couples your code to the repository. **The listener chain.** `AuditApplicationEvent` is a normal Spring `ApplicationEvent`, so anything on the context event bus can also react to it (you could add your own `@EventListener` to forward audits to SIEM, for example). The framework's `AuditListener` is one such listener that persists to the repository. **Prerequisite / gotcha.** `AuditAutoConfiguration` is `@ConditionalOnBean(AuditEventRepository.class)`. Without a repository bean, no `AuditListener` is registered — your published `AuditApplicationEvent` is silently ignored (nobody stores it) and nothing appears at the endpoint. Declare `InMemoryAuditEventRepository` (or a custom repo) as a bean. **Threading / ordering.** Event publishing is synchronous by default; `add()` runs on the caller's thread inside `publishEvent`. If you make application events async, ordering and the request's SecurityContext availability change — be deliberate. **When to use.** Emit custom audit events for domain-significant, security-relevant actions (privilege changes, data exports, admin overrides) so they show up alongside auth events in one trail.

  • What is the difference between publishing an AuditApplicationEvent and calling AuditEventRepository.add() directly?
    Publishing goes through the ApplicationEventPublisher; Spring's AuditListener persists it, and any other @EventListener can also react — producers stay decoupled from storage. Calling add() directly is simpler but couples your code to the repository and bypasses other listeners.
  • Your published audit event never shows up. What's the most likely cause?
    No AuditEventRepository bean, so AuditAutoConfiguration didn't register the AuditListener and the event has no consumer. Declare an InMemoryAuditEventRepository (or custom) bean.

saying these in an interview costs you the question

  • Thinking type must be one of a fixed enum — it is a free-form String.
  • Believing publishEvent stores it even without a repository bean.
  • Assuming AuditApplicationEvent and AuditEvent are the same class (one wraps the other).
  • Putting secrets/passwords into the data map that then gets exposed via the endpoint.

context