1. Overview

Spring Data MongoDB module improves readability and usability when interacting with a MongoDB database in Spring projects.

In this tutorial, we’ll focus on how to handle the ZonedDateTime Java objects when reading and writing into a MongoDB database.

2. Setup

To work with Spring Data MongoDB module, we need to add the following dependency:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>3.4.7</version>
</dependency>

The latest version of the library can be found here.

Let’s define a model class called Action (with a ZonedDateTime attribute):

@Document
public class Action {
    @Id
    private String id;

    private String description;
    private ZonedDateTime time;
    
    // constructor, getters and setters 
}

To interact with the MongoDB, we’ll also create an interface that extends the MongoRepository:

public interface ActionRepository extends MongoRepository<Action, String> { }

Now we’ll define a test that will insert an Action object into a MongoDB and assert that it was stored with the correct time. In the assert evaluation, we’re removing the nanoseconds information since the MongoDB Date type has a precision of milliseconds:

@Test
public void givenSavedAction_TimeIsRetrievedCorrectly() {
    String id = "testId";
    ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);

    actionRepository.save(new Action(id, "click-action", now));
    Action savedAction = actionRepository.findById(id).get();

    Assert.assertEquals(now.withNano(0), savedAction.getTime().withNano(0)); 
}

Out of the box, we will get the following error when running our test:

org.bson.codecs.configuration.CodecConfigurationException:
  Can't find a codec for class java.time.ZonedDateTime

Spring Data MongoDB has no ZonedDateTime converters defined. Let’s see how we can configure them.

3. The MongoDB Converters

We can handle ZonedDateTime objects (across all models) by defining a converter for reading from a MongoDB and one for writing into it.

For reading, we’re converting from a Date object into a ZonedDateTime object. In the next example, we use the ZoneOffset.UTC since Date object does not store zone information:

public class ZonedDateTimeReadConverter implements Converter<Date, ZonedDateTime> {
    @Override
    public ZonedDateTime convert(Date date) {
        return date.toInstant().atZone(ZoneOffset.UTC);
    }
}

Then, we’re converting from a ZonedDateTime object into a Date object. We can add the zone information to another field if needed:

public class ZonedDateTimeWriteConverter implements Converter<ZonedDateTime, Date> {
    @Override
    public Date convert(ZonedDateTime zonedDateTime) {
        return Date.from(zonedDateTime.toInstant());
    }
}

Since Date objects do not store a zone offset, we use UTC in our examples. With the ZonedDateTimeReadConverter and the ZonedDateTimeWriteConverter added to the MongoCustomConversions, our test will now pass.

A simple printing of the stored object will look like this:

Action{id='testId', description='click', time=2018-11-08T08:03:11.257Z}

To learn more about how to register MongoDB converters, we can refer to this tutorial.

4. Conclusions

In this quick article, we saw how to create MongoDB converters in order to handle Java ZonedDateTime objects.

The implementation of all of these snippets can be found over on GitHub.