Christophe Douy
Christophe Douy

Reputation: 863

Spring Mongo Aggregate & LocalDateTime issue

I'm having the following error when using Mongo's aggregate with a Java 8 LocalDateTime criteria.

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

with the following piece of code

@SpringBootApplication
public class MongojavatimeApplication implements CommandLineRunner {

    @Autowired
    private MongoTemplate template;

    public static void main(String[] args) {
        SpringApplication.run(MongojavatimeApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        Criteria c = Criteria.where("createdDate").gt(LocalDateTime.now().minusDays(30));
        template.aggregate(Aggregation.newAggregation(Aggregation.match(c)), "TestJavaTime", TestJavaTime.class);
    }
}

You'll find few tests here, LocalDateTime works fine with a Spring repository, a classical query with the Criteria API using a MongoTemplate, but throws this error when creating an Aggregate query. https://github.com/Farael49/spring-mongo-aggregate-localdatetime

I also did a little test replacing the LocalDateTime with the java util Date to show it's not throwing a codec error.

Is there something I can do, or is it a Mongo Driver/Spring issue ?

Thanks

Upvotes: 1

Views: 3538

Answers (2)

cbartosiak
cbartosiak

Reputation: 785

If you want to use LocalDateTime directly you should provide a codec like this:

public enum LocalDateTimeCodec
        implements Codec<LocalDateTime> {

    INSTANCE;

    @Override
    public void encode(
            BsonWriter writer,
            LocalDateTime value,
            EncoderContext encoderContext) {

        writer.writeDateTime(
                value.toInstant(ZoneOffset.UTC)
                     .toEpochMilli()
        );
    }

    @Override
    public LocalDateTime decode(
            BsonReader reader,
            DecoderContext decoderContext) {

        return Instant.ofEpochMilli(reader.readDateTime())
                .atOffset(ZoneOffset.UTC)
                .toLocalDateTime();
    }

    @Override
    public Class<LocalDateTime> getEncoderClass() {
        return LocalDateTime.class;
    }
}

You can register it this way:

@Bean
public MongoDbFactory mongoDbFactory() throws Exception {
    CodecRegistry registry = CodecRegistries.fromRegistries(
        CodecRegistries.fromCodecs(LocalDateTimeCodec.INSTANCE),
        MongoClient.getDefaultCodecRegistry()
    );
    MongoClientOptions options = MongoClientOptions
        .builder()
        .codecRegistry(registry)
        .build();
    return new SimpleMongoDbFactory(new MongoClient(host, options), dbName);
}

where host and dbName might be autowired fields of some configuration class.

Upvotes: 2

robjwilkins
robjwilkins

Reputation: 5652

I think your problem is due to the mongodb java driver not knowing how to serialise the LocalDateTime object. There is a good solution to this problem here: Cannot serialize LocalDate in Mongodb

in your code amending it like this might work:

@Override
public void run(String... args) throws Exception {
    LocalDateTime startDateTime = LocalDateTime.now().minusDays(30);
    Instant startInstant = startDateTime.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
    Criteria c = Criteria.where("createdDate").gt(Date.from(startInstant));
    template.aggregate(Aggregation.newAggregation(Aggregation.match(c)), "TestJavaTime", TestJavaTime.class);
}

Upvotes: 2

Related Questions