#StackBounty: #mongodb #spring-boot #geojson #spring-data-mongodb #bson Why do I get CodecConfigurationException when doing a nearSpher…

Bounty: 50

This problem is puzzling me. So, I have a UserModel where I am storing the last known coordinates of the user using a org.springframework.data.mongodb.core.geo.GeoJsonPoint object which translates to

"lastKnownCoordinates": {
        "type": "Point",
        "coordinates": [77.596503, 12.966267]
    }

I have an excursions collections which maps to an ExcursionsModel object. Every excursion has a geolocation property which stores its location using the GeoJsonPoint specification like this:

"geolocation": {
        "coordinates": [73.739978, 15.606188],
        "type": "Point"
    }

Now, when a user tries to add a nearby filter to get excursions within a specified distance, I add a $nearSphere criteria on the query object (org.springframework.data.mongodb.core.query.Query) like this

if (userRequest.getDistInMeters() != null) {
    query.addCriteria(Criteria.where("geolocation")
                        .nearSphere(userModel.getLastKnownCoordinates())
                        .maxDistance(userRequest.getDistInMeters())
    );
}

Technically, this should translate to the following:

...
"geolocation" : {
  $nearSphere: {
     $geometry: {
        type : "Point",
        coordinates : [ 77.596503, 12.966267 ]
     },
     $maxDistance: 10000
  }
}...

(if the distInMeters = 10000)

But, I it rather translates to:

...
"geolocation" : { 
    "$nearSphere" : { 
        "$geometry" : { 
            "$java" : Point [x=-3.703790, y=40.416775] 
        } 
    } 
}...

Note that the conversion of the GeoJsonPoint is not correct. I also get the following exception

org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class org.springframework.data.mongodb.core.geo.GeoJsonPoint.

Full transcript of the output:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class org.springframework.data.mongodb.core.geo.GeoJsonPoint.] with root cause
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class org.springframework.data.mongodb.core.geo.GeoJsonPoint.
    at org.bson.internal.CodecCache.getOrThrow(CodecCache.java:57)
    at org.bson.internal.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:64)
    at org.bson.internal.ChildCodecRegistry.get(ChildCodecRegistry.java:52)
    at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:197)
    at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:212)
    at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:195)
    at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:212)
    at org.bson.codecs.DocumentCodec.writeValue(DocumentCodec.java:195)
    at org.bson.codecs.DocumentCodec.writeMap(DocumentCodec.java:212)
    at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:154)
    at org.bson.codecs.DocumentCodec.encode(DocumentCodec.java:45)
    at org.bson.BsonDocumentWrapper.getUnwrapped(BsonDocumentWrapper.java:195)
    at org.bson.BsonDocumentWrapper.isEmpty(BsonDocumentWrapper.java:115)
    at com.mongodb.internal.operation.DocumentHelper.putIfNotNullOrEmpty(DocumentHelper.java:43)
    at com.mongodb.internal.operation.FindOperation.getCommand(FindOperation.java:792)
    at com.mongodb.internal.operation.FindOperation.access$1600(FindOperation.java:77)
    at com.mongodb.internal.operation.FindOperation$4.create(FindOperation.java:858)
    at com.mongodb.internal.operation.CommandOperationHelper.executeCommandWithConnection(CommandOperationHelper.java:219)
    at com.mongodb.internal.operation.FindOperation$1.call(FindOperation.java:631)
    at com.mongodb.internal.operation.FindOperation$1.call(FindOperation.java:625)
    at com.mongodb.internal.operation.OperationHelper.withReadConnectionSource(OperationHelper.java:462)
    at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:625)
    at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:77)
    at com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor.execute(MongoClientDelegate.java:190)
    at com.mongodb.client.internal.MongoIterableImpl.execute(MongoIterableImpl.java:135)
    at com.mongodb.client.internal.MongoIterableImpl.iterator(MongoIterableImpl.java:92)
    at com.example.comercial.backend.dao.ExcursionDao.getAllExcursions(ExcursionsDao.java:140)
    at com.example.comercial.backend.service.ExcursionsService.getAllExcursions(ExcursionsService.java:70)
    at com.example.comercial.backend.controller.ExcursionsController.getAllExcursions(ExcursionsController.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.AbstractRequestLoggingFilter.doFilterInternal(AbstractRequestLoggingFilter.java:289)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1589)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)

Code snippet from the ExcursionsDao class where the exception is thrown:

...
FindIterable<Document> findIterable = mongoTemplate.getCollection("excursions")
                .find(query.getQueryObject())
                .skip(pageable.getPageSize() * (pageable.getPageNumber() - 1))
                .limit(pageable.getPageSize());

if (userRequest.getOrderBy() != null && userRequest.getSortOrder() != null) {
    if (userRequest.getSortOrder().getSortOrder().equals(SortOrder.ASC.getSortOrder())) {
        findIterable.sort(Sorts.ascending(userRequest.getOrderBy().getOrderBy()));
    } else {
        findIterable.sort(Sorts.descending(userRequest.getOrderBy().getOrderBy()));
    }
}

ArrayList<ExcursionModel> excursionModelList = new ArrayList<>();

for (Document document : findIterable) {             // Line 140 where the exception is thrown
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    excursionModelList.add(objectMapper.convertValue(document, ExcursionModel.class));
}

I am using the defaults for the mongo connection which actually initializes with the GeoJsonCodecProvider.

From the MongoClientSettings class:

public final class MongoClientSettings {
    private static final CodecRegistry DEFAULT_CODEC_REGISTRY =
            fromProviders(asList(new ValueCodecProvider(),
                    new BsonValueCodecProvider(),
                    new DBRefCodecProvider(),
                    new DBObjectCodecProvider(),
                    new DocumentCodecProvider(new DocumentToDBRefTransformer()),
                    new IterableCodecProvider(new DocumentToDBRefTransformer()),
                    new MapCodecProvider(new DocumentToDBRefTransformer()),
                    new GeoJsonCodecProvider(),
                    new GridFSFileCodecProvider(),
                    new Jsr310CodecProvider(),
                    new BsonCodecProvider()));

What alternatives I have tried which didn’t work

1. I wrote the following serializer (taken from an SO answer I forgot) for the GeoJsonPoint object

public class GeoJsonPointSerializer extends JsonSerializer<GeoJsonPoint> {

    @Override
    public void serialize(final GeoJsonPoint value, final JsonGenerator gen, final SerializerProvider serializers)
            throws IOException {
        gen.writeStartObject();
        gen.writeStringField("type", value.getType());
        gen.writeArrayFieldStart("coordinates");
        gen.writeObject(value.getCoordinates());
        gen.writeEndArray();
        gen.writeEndObject();
    }

}

and used it for the geolocation property in the ExcursionsModel like this

@JsonSerialize(using = GeoJsonPointSerializer.class)
@GeoSpatialIndexed(name = "geoIndex", type = GeoSpatialIndexType.GEO_2DSPHERE)
private GeoJsonPoint geolocation;

2.
I created an instance of CodecRegistry and added PointCodec to it along with the default codec registries from the MongoClientSettings and then initialized the FilterIterable class with this codec registry.

CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
                MongoClientSettings.getDefaultCodecRegistry(),
                CodecRegistries.fromCodecs(new PointCodec(MongoClientSettings.getDefaultCodecRegistry()))
        );

FindIterable<Document> findIterable = mongoTemplate.getCollection("excursions").withCodecRegistry(codecRegistry)
                .find(query.getQueryObject())
                .skip(pageable.getPageSize() * (pageable.getPageNumber() - 1))
                .limit(pageable.getPageSize());

I get the same exception thrown in all the cases.

Note:
I am aware of the other questions on StackOverflow which looks similar to this one but their goal is a bit different to what I want to achieve. I want to use the query object to keep the query dynamic.

Dependencies I am using:

spring-boot-starter-data-mongodb | 2.3.3.RELEASE

mongodb-core-driver | 4.0.5

spring-data-mongdb | 3.0.3.RELEASE


Get this bounty!!!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.