I usually post about Persistence on Twitter - you can follow me there:

1. Overview

This tutorial will continue to explore some of the core features of Spring Data MongoDB – the @DBRef annotation and lifecycle events.

2. @DBRef

The mapping framework doesn’t support storing parent-child relations and embedded documents within other documents. What we can do though is – we can store them separately and use a DBRef to refer between the documents.

When the object is loaded from MongoDB, those references will be eagerly resolved and we’ll get back a mapped object that looks the same as if it had been stored embedded within our master document.

Let’s look at some code:

private EmailAddress emailAddress;

EmailAddress looks like:

public class EmailAddress {
    private String id;
    private String value;
    // standard getters and setters

Note that the mapping framework doesn’t handle cascading operations. So – for instance – if we trigger a save on a parent, the child won’t be saved automatically – we’ll need to explicitly trigger the save on the child if we want to save it as well.

This is exactly where lifecycle events really come in handy.

3. Lifecycle Events

Spring Data MongoDB publishes some very useful lifecycle events – such as onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad and onAfterConvert.

To intercept one of the events, we need to register a subclass of AbstractMappingEventListener and override one of the methods here. When the event is dispatched, our listener will be called and domain object passed in.

3.1. Basic Cascade Save

Let’s look at the example we had earlier – saving the user with the emailAddress. We can now listen to the onBeforeConvert event which will be called before a domain object goes into the converter:

public class UserCascadeSaveMongoEventListener extends AbstractMongoEventListener<Object> {
    private MongoOperations mongoOperations;

    public void onBeforeConvert(final Object source) {
        if (source instanceof User && 
          ((User) source).getEmailAddress() != null) {
            mongoOperations.save(((User) source).getEmailAddress());

Now we just need to register the listener into MongoConfig:

public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
    return new UserCascadeSaveMongoEventListener();

Or as xml:

<bean class="org.baeldung.event.UserCascadeSaveMongoEventListener" />

And we have cascading semantics all done – albeit only for the user.

3.2. A Generic Cascade Implementation

Let’s now improve the previous solution by making the cascade functionality generic. Let’s start by defining a custom annotation:

public @interface CascadeSave {

Let’s now work on our custom listener to handle these fields generically and not have to cast to any specific entity:

public class CascadeSaveMongoEventListener extends AbstractMongoEventListener<Object> {

    private MongoOperations mongoOperations;

    public void onBeforeConvert(Object source) {
          new CascadeCallback(source, mongoOperations));

So we’re using the reflection utility out of Spring and we’re running our own callback on all fields that meet our criteria:

public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

    if (field.isAnnotationPresent(DBRef.class) && 
      field.isAnnotationPresent(CascadeSave.class)) {
        Object fieldValue = field.get(getSource());
        if (fieldValue != null) {
            FieldCallback callback = new FieldCallback();
            ReflectionUtils.doWithFields(fieldValue.getClass(), callback);


As you can see, we’re looking for fields that have both the DBRef annotation as well as CascadeSave. Once we find these fields, we save the child entity.

Let’s look at the FieldCallback class which we’re using to check if the child has an @Id annotation:

public class FieldCallback implements ReflectionUtils.FieldCallback {
    private boolean idFound;

    public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {

        if (field.isAnnotationPresent(Id.class)) {
            idFound = true;

    public boolean isIdFound() {
        return idFound;

Finally, to make it all work togather, we of course need to emailAddress field to now be correctly annotated:

private EmailAddress emailAddress;

3.3. The Cascade Test

Let’s now have a look at a scenario – we save a User with emailAddress and the save operation cascades to this embedded entity automatically:

User user = new User();
EmailAddress emailAddress = new EmailAddress();

Let’s check our database:

    "_id" : ObjectId("55cee9cc0badb9271768c8b9"),
    "name" : "Brendan",
    "age" : null,
    "email" : {
        "value" : "b@gmail.com"

4. Conclusion

In this article we illustrated some cool features of Spring Data MongoDB – the @DBRef annotation, lifecycle events and how we can handle cascading intelligently.

The implementation of all these examples and code snippets can be found in my github project – this is an Eclipse based project, so it should be easy to import and run as it is.

I usually post about Persistence on Twitter - you can follow me there:

  • Tobias Wittwer

    Thx for this great article, it helped me a lot.
    I’m using Spring Data REST and I tried to create a extra Repository for addresses to access also the addresses them self (in the style of a books author relationship).
    But then the address isn’t saved any more, if it is embedded in the person.
    I checked the values of the source at the before convert event and there the address property of the person entity is null.
    I don’t know where the address is cutted out, could you give me a hint

    Thanking you in anticipation

    • Hey Tobias,
      That sounds like an interesting usecase.
      Unfortunately, as it’s most often the case with these kinds of issues – it’s not something that can be debugged by just describing it. Code is really the only way to go here – to see exactly what’s going on.
      My suggestion is to create a simple, easy to run test that shows the problem – and then email me the link to it and I’d be happy to have a look.