This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Data REST 5.1.2!

Events

Event Lifecycle and Object State

"Before*" events are published right before the repository call, "After*" events right after it. For example, PATCH /people/1 runs as follows:

  1. Person 1 is loaded from the repository.

  2. The patch is applied to it.

  3. BeforeSaveEvent is published with the patched Person.

  4. save(…) is called.

  5. AfterSaveEvent is published with the object returned by save(…).

The following table shows which requests trigger each event and which object the event carries:

Event Triggered By Object Passed to Handler

BeforeCreateEvent

POST on a collection resource, PUT on a non-existent item resource

New domain object, deserialized from the request body

AfterCreateEvent

Same as BeforeCreateEvent

Object returned by save(…)

BeforeSaveEvent

PUT or PATCH on an existing item resource

Existing entity with the request applied

AfterSaveEvent

Same as BeforeSaveEvent

Object returned by save(…)

BeforeLinkSaveEvent

PUT, POST, or PATCH on an association resource

Parent entity with the association already updated. The linked value is the new property value: the referenced entity, or the complete collection or map.

AfterLinkSaveEvent

Same as BeforeLinkSaveEvent

Parent entity returned by save(…), with the same linked value

BeforeLinkDeleteEvent

DELETE on a single-valued association resource (/people/1/address) or on an item of a multi-valued one (/people/1/addresses/42)

Parent entity with the reference already removed. The linked value is the removed entity for single-valued associations, and the remaining collection or map for multi-valued ones.

AfterLinkDeleteEvent

Same as BeforeLinkDeleteEvent

Parent entity returned by save(…), with the same linked value

BeforeDeleteEvent

DELETE on an item resource

Entity loaded from the repository

AfterDeleteEvent

Same as BeforeDeleteEvent

Same instance, after deleteById(…)

The object passed to BeforeSaveEvent depends on the HTTP method:

  • PUT replaces the state of the existing entity with the request body. Omitted properties are reset, except the identifier, version, read-only and Jackson-ignored properties, and associations to exported repositories, which keep their persisted values. See PUT.

  • PATCH changes only the patched properties. Handlers receive the full entity, not the partial request body. For example, PATCH /people/1 with { "firstName": "Dave" } publishes a Person with the new firstName and the persisted lastName.

Requests on non-existent item resources behave as follows:

  • PUT creates the entity and publishes create events. If PUT for creation is disabled, the request is rejected with 405 Method Not Allowed and no events are published.

  • PATCH and DELETE return 404 Not Found and publish no events.

On association resources, PUT replaces the association, POST adds to a collection, and PATCH adds to a collection or map. Single-valued associations can only be set with PUT.

Writing an ApplicationListener

You can subclass an abstract class that listens for these kinds of events and calls the appropriate method based on the event type. To do so, override the methods for the events in question, as follows:

public class BeforeSaveEventListener extends AbstractRepositoryEventListener {

  @Override
  public void onBeforeSave(Object entity) {
    ... logic to handle inspecting the entity before the Repository saves it
  }

  @Override
  public void onAfterDelete(Object entity) {
    ... send a message that this entity has been deleted
  }
}

One thing to note with this approach, however, is that it makes no distinction based on the type of the entity. You have to inspect that yourself.

Writing an Annotated Handler

Another approach is to use an annotated handler, which filters events based on domain type.

To declare a handler, create a POJO and put the @RepositoryEventHandler annotation on it. This tells the BeanPostProcessor that this class needs to be inspected for handler methods.

Once the BeanPostProcessor finds a bean with this annotation, it iterates over the exposed methods and looks for annotations that correspond to the event in question. For example, to handle BeforeSaveEvent instances in an annotated POJO for different kinds of domain types, you could define your class as follows:

@RepositoryEventHandler (1)
public class PersonEventHandler {

  @HandleBeforeSave
  public void handlePersonSave(Person p) {
    // … you can now deal with Person in a type-safe way
  }

  @HandleBeforeSave
  public void handleProfileSave(Profile p) {
    // … you can now deal with Profile in a type-safe way
  }
}
1 It’s possible to narrow the types to which this handler applies by using (for example) @RepositoryEventHandler(Person.class).

The domain type whose events you are interested in is determined from the type of the first parameter of the annotated methods.

To register your event handler, either mark the class with one of Spring’s @Component stereotypes (so that it can be picked up by @SpringBootApplication or @ComponentScan) or declare an instance of your annotated bean in your ApplicationContext. Then the BeanPostProcessor that is created in RepositoryRestMvcConfiguration inspects the bean for handlers and wires them to the correct events. The following example shows how to create an event handler for the Person class:

@Configuration
public class RepositoryConfiguration {

  @Bean
  PersonEventHandler personEventHandler() {
    return new PersonEventHandler();
  }
}
Spring Data REST events are customized Spring application events. By default, Spring events are synchronous, unless they get republished across a boundary (such as issuing a WebSocket event or crossing into a thread).