Event Listeners

KraftAdmin allows you to run your own code when something happens inside the administration panel.

For example, you can:

  • Send an email after an order is created
  • Prevent a record from being deleted
  • Write an audit log after a record is deleted
  • Run custom logic after an update
  • React when a custom action is executed

You do this by placing @KraftAdminOn on a method in a Spring bean.

@Component
public class OrderListeners {

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterCreate.class },
        entityClass = Order.class
    )
    public void onOrderCreated(KraftAdminEvent.AfterCreate event) {
        // Your custom logic
    }
}

KraftAdmin automatically discovers the listener and calls it when the matching event occurs.


Basic Example

Suppose your application has an Order entity.

@Component
public class OrderListeners {

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterCreate.class },
        entityClass = Order.class
    )
    public void onOrderCreated(KraftAdminEvent.AfterCreate event) {

        Order order = (Order) event.getEntity();

        System.out.println(
            "Order created: " + order.getId()
        );
    }
}

Whenever an Order is created through KraftAdmin, the method is called after the creation has completed.

The event contains information about what happened, including the affected entity.


The @KraftAdminOn Annotation

@KraftAdminOn(
    vararg val events: KClass<out KraftAdminEvent>,
    val resource: String = "",
    val entityClass: KClass<*> = Any::class,
    val order: Int = 0
)

The annotation has four configuration options:

PropertyPurpose
eventsThe events the method listens for
resourceRestricts the listener to a resource name
entityClassRestricts the listener to a specific entity type
orderControls execution order between listeners

events

The events property defines which events should trigger the method.

@KraftAdminOn(
    events = {
        KraftAdminEvent.AfterCreate.class
    }
)
public void handleCreate(KraftAdminEvent.AfterCreate event) {
}

You can listen to multiple events with one method:

@KraftAdminOn(
    events = {
        KraftAdminEvent.AfterCreate.class,
        KraftAdminEvent.AfterUpdate.class
    }
)
public void handleChanges(KraftAdminEvent event) {
}

Use multiple events when the same logic should run for more than one type of operation.

For example, you might want to clear a cache after both creating and updating a record.


Entity-Specific Listeners

The most common way to configure a listener is with entityClass.

@KraftAdminOn(
    events = { KraftAdminEvent.AfterCreate.class },
    entityClass = Order.class
)
public void onOrderCreated(KraftAdminEvent.AfterCreate event) {
    Order order = (Order) event.getEntity();

    // Handle the created order
}

This listener only runs when the event affects an Order.

It does not run for:

  • Users
  • Products
  • Vehicles
  • Any other resource

This is generally the preferred approach because it is type-safe and makes the listener’s purpose clear.


Resource-Based Listeners

You can also filter events using the resource name.

@KraftAdminOn(
    events = { KraftAdminEvent.AfterCreate.class },
    resource = "Order"
)
public void onOrderCreated(KraftAdminEvent.AfterCreate event) {
}

The resource value is matched against the event’s resource name.

This is useful when:

  • You know the resource name but do not want to reference the entity class
  • The resource is provided dynamically
  • You are integrating with a resource provider

An empty resource value means that the listener is not filtered by resource name.

@KraftAdminOn(
    events = { KraftAdminEvent.AfterDelete.class }
)
public void auditDelete(KraftAdminEvent.AfterDelete event) {
    // Can receive delete events from multiple resources
}

entityClass vs resource

Both options can be used to restrict which events a listener receives.

Using entityClass

@KraftAdminOn(
    events = { KraftAdminEvent.AfterUpdate.class },
    entityClass = Order.class
)

This checks the actual entity type.

Using resource

@KraftAdminOn(
    events = { KraftAdminEvent.AfterUpdate.class },
    resource = "Order"
)

This checks the resource name.

For most application code, entityClass is the recommended option.


Before and After Events

KraftAdmin provides events that happen before and after an operation.

The difference is important.

Before Events

Before events run before an operation is completed.

For example:

@KraftAdminOn(
    events = { KraftAdminEvent.BeforeDelete.class },
    entityClass = Order.class
)
public void preventDelete(KraftAdminEvent.BeforeDelete event) {

    Order order = (Order) event.getEntity();

    if (order.getStatus() == OrderStatus.SHIPPED) {
        throw new IllegalStateException(
            "Cannot delete a shipped order"
        );
    }
}

If the listener throws an exception, the operation is stopped.

This makes before events useful for validation and business rules.

Before Operation
        ↓
Listener runs
        ↓
Exception?
   ↙          ↘
 Yes           No
  ↓             ↓
Cancel       Continue
operation    operation

Typical uses include:

  • Preventing deletion
  • Validating business rules
  • Blocking updates
  • Checking application state
  • Enforcing additional conditions

After Events

After events run after an operation has completed.

@KraftAdminOn(
    events = { KraftAdminEvent.AfterCreate.class },
    entityClass = Order.class
)
public void onOrderCreated(KraftAdminEvent.AfterCreate event) {

    Order order = (Order) event.getEntity();

    emailService.sendConfirmation(order);
}

Typical uses include:

  • Sending notifications
  • Writing audit logs
  • Clearing caches
  • Triggering integrations
  • Updating external systems

Preventing an Operation

A listener can prevent a before-operation from continuing by throwing an exception.

@KraftAdminOn(
    events = { KraftAdminEvent.BeforeDelete.class },
    entityClass = Order.class
)
public void preventDeleteIfShipped(
    KraftAdminEvent.BeforeDelete event
) {
    Order order = (Order) event.getEntity();

    if (order.getStatus() == OrderStatus.SHIPPED) {
        throw new IllegalStateException(
            "Cannot delete a shipped order"
        );
    }
}

The exception stops the operation.

This allows you to enforce rules that are specific to your application.

For example:

if (vehicle.isCurrentlyRented()) {
    throw new IllegalStateException(
        "A rented vehicle cannot be deleted."
    );
}

Listening to Custom Actions

Event listeners can also react to custom KraftAdmin actions.

For example, if you have an action called approve-order, you can listen for the action event.

@KraftAdminOn(
    events = { KraftAdminEvent.BeforeAction.class },
    entityClass = Order.class
)
public void beforeOrderAction(
    KraftAdminEvent.BeforeAction event
) {
    Order order = (Order) event.getEntity();

    // Run logic before the custom action
}

This can be useful for:

  • Validating whether an action is allowed
  • Recording an audit entry
  • Sending notifications
  • Performing additional checks

For example:

@KraftAdminOn(
    events = { KraftAdminEvent.BeforeAction.class },
    entityClass = Order.class
)
public void validateOrderAction(
    KraftAdminEvent.BeforeAction event
) {
    Order order = (Order) event.getEntity();

    if (order.getStatus() == OrderStatus.CANCELLED) {
        throw new IllegalStateException(
            "Actions cannot be performed on cancelled orders."
        );
    }
}

Execution Order

Multiple listeners can listen to the same event.

The order property controls the order in which they run.

@KraftAdminOn(
    events = { KraftAdminEvent.AfterCreate.class },
    entityClass = Order.class,
    order = 1
)
public void firstListener(
    KraftAdminEvent.AfterCreate event
) {
}
@KraftAdminOn(
    events = { KraftAdminEvent.AfterCreate.class },
    entityClass = Order.class,
    order = 100
)
public void secondListener(
    KraftAdminEvent.AfterCreate event
) {
}

The listener with the lower order runs first.

order = 1
    ↓
order = 10
    ↓
order = 100

The default order is 0.

Use order when one listener must run before another.


Multiple Listeners

You can create multiple listener classes.

@Component
public class OrderListeners {

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterCreate.class },
        entityClass = Order.class
    )
    public void sendConfirmation(
        KraftAdminEvent.AfterCreate event
    ) {
        // Send confirmation email
    }
}
@Component
public class AuditListeners {

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterDelete.class }
    )
    public void auditDelete(
        KraftAdminEvent.AfterDelete event
    ) {
        // Write audit record
    }
}

This allows you to keep different types of application logic separate.

For example:

  • OrderListeners — order-specific business logic
  • AuditListeners — audit logging
  • NotificationListeners — email and notifications
  • IntegrationListeners — external services

Example: Complete Order Listener

The following example demonstrates several common use cases.

@Component
public class OrderListeners {

    private final EmailService emailService;

    public OrderListeners(EmailService emailService) {
        this.emailService = emailService;
    }

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterCreate.class },
        entityClass = Order.class
    )
    public void onOrderCreated(
        KraftAdminEvent.AfterCreate event
    ) {
        Order order = (Order) event.getEntity();

        emailService.sendConfirmation(order);
    }

    @KraftAdminOn(
        events = { KraftAdminEvent.BeforeDelete.class },
        entityClass = Order.class
    )
    public void preventInvalidDelete(
        KraftAdminEvent.BeforeDelete event
    ) {
        Order order = (Order) event.getEntity();

        if (order.getStatus() == OrderStatus.SHIPPED) {
            throw new IllegalStateException(
                "Cannot delete a shipped order."
            );
        }
    }

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterDelete.class },
        entityClass = Order.class
    )
    public void auditDelete(
        KraftAdminEvent.AfterDelete event
    ) {
        // Write an audit record
    }

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterUpdate.class },
        entityClass = Order.class
    )
    public void onOrderUpdated(
        KraftAdminEvent.AfterUpdate event
    ) {
        Order order = (Order) event.getEntity();

        // React to the update
    }
}

Running Background Work

KraftAdmin listeners run as normal application code.

If your listener performs slow work, such as sending a request to an external service, you can move that work to a background executor.

For example:

@KraftAdminOn(
    events = { KraftAdminEvent.AfterUpdate.class },
    entityClass = Order.class
)
public void onOrderUpdated(
    KraftAdminEvent.AfterUpdate event
) {
    CompletableFuture.runAsync(() -> {
        // Slow background work
        emailService.sendUpdateNotification(
            (Order) event.getEntity()
        );
    });
}

You can also use Spring’s asynchronous support if it is enabled in your application.

@Async
@KraftAdminOn(
    events = { KraftAdminEvent.AfterUpdate.class },
    entityClass = Order.class
)
public void onOrderUpdated(
    KraftAdminEvent.AfterUpdate event
) {
    // Runs asynchronously when Spring async support is configured
}

KraftAdmin does not require listeners to use a specific background execution strategy. Your application remains in control of how asynchronous work is scheduled.


Listener Requirements

A listener method should:

  1. Be declared on a Spring-managed bean.
  2. Be annotated with @KraftAdminOn.
  3. Listen to one or more KraftAdmin events.
  4. Accept the matching event as its parameter.

Example:

@Component
public class MyListeners {

    @KraftAdminOn(
        events = { KraftAdminEvent.AfterCreate.class }
    )
    public void handle(
        KraftAdminEvent.AfterCreate event
    ) {
        // Listener logic
    }
}

Because the class is a Spring component, KraftAdmin can discover it during application startup.


Choosing the Right Event

Use before events when you need to make a decision about an operation.

Should this operation be allowed?
        ↓
Use a Before event

Use after events when you want to react to something that already happened.

The operation is complete.
What should happen next?
        ↓
Use an After event

A simple rule:

RequirementEvent type
Prevent an operationBefore...
Validate an operationBefore...
React after creationAfterCreate
React after an updateAfterUpdate
Audit a deletionAfterDelete
React to a custom actionBeforeAction or the relevant action event

Summary

@KraftAdminOn gives your application a simple way to react to activity inside KraftAdmin.

You can use it to:

  • Run code before an operation
  • Prevent an operation
  • Run code after an operation
  • Listen to a specific entity
  • Listen by resource name
  • React to custom actions
  • Control listener execution order

The listener system is intentionally lightweight: KraftAdmin tells you that something happened, and your application decides what to do next.