Why do I get CodecConfigurationException when doing a nearSphere query using GeoJsonPoint?

Viewed 1020

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

2 Answers

You are mixing spring mongo and mongo client. For ex for model you are using spring mongo class - for query you are using mongo client directly. Fix would be to use either of those.

Either change from GeoJsonPoint to com.mongodb.client.model.geojson.Point or change query to use spring like

Criteria criteria = Criteria.where(key).is(value);
Query query = Query.query(criteria).skip(num).limit(num);
List<ExcursionsModel> excursions = mongoTemplate.find(query, ExcursionsModel.class);

Working Example (Spring Mongo Lib )

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.GeoNearOperation;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.index.GeospatialIndex;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;

import java.util.ArrayList;
import java.util.List;

@SpringBootApplication
public class So63886374Application {

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

    @Autowired
    private MongoTemplate mongoTemplate;

    public void queryData()  {
        //Distance from fort worth - 45 miles - output dallas - 200 miles - austin/dallas - 300 miles - austin/dallas/houston
        Criteria criteria = Criteria.where("geolocation").nearSphere(new GeoJsonPoint(-97.3327459, 32.753177)).maxDistance(200*1609.34);
        Query query = Query.query(criteria);
        List<ExcursionsModel> excursionsModels = mongoTemplate.find(query, ExcursionsModel.class);
        excursionsModels.forEach(es -> System.out.println(es.getGeolocation()));

        //Aggregation query to capture distance
        NearQuery nearQuery = NearQuery.near(-97.3327459, 32.753177, Metrics.MILES).maxDistance(200);
        GeoNearOperation geoNearOperation = Aggregation.geoNear(nearQuery, "distanceFromFW");
        Aggregation aggregation = Aggregation.newAggregation(geoNearOperation);
        List<ExcursionsModel> excursionsModelsAg = mongoTemplate.aggregate(aggregation, ExcursionsModel.class, ExcursionsModel.class).getMappedResults();
        excursionsModelsAg.forEach(es -> System.out.println(es.getDistanceFromFW()));
    }

    @Document
    public class ExcursionsModel {
        @GeoSpatialIndexed(name = "geoIndex", type = GeoSpatialIndexType.GEO_2DSPHERE)
        private GeoJsonPoint geolocation;
        private long distanceFromFW;
        public void setGeolocation(GeoJsonPoint geolocation) {
            this.geolocation = geolocation;
        }

        public GeoJsonPoint getGeolocation() {
            return geolocation;
        }

        public long getDistanceFromFW() {
            return distanceFromFW;
        }

        public void setDistanceFromFW(long distanceFromFW) {
            this.distanceFromFW = distanceFromFW;
        }
    }

    public void insertData()  {
        List<ExcursionsModel> excursionsModels = new ArrayList<>();
        mongoTemplate.indexOps(ExcursionsModel.class).ensureIndex(new GeospatialIndex("geolocation").named("geoIndex").typed(GeoSpatialIndexType.GEO_2DSPHERE));
        ExcursionsModel austin = new ExcursionsModel();
        austin.setGeolocation(new GeoJsonPoint( -97.7436995, 30.2711286));
        ExcursionsModel houston = new ExcursionsModel();
        houston.setGeolocation(new GeoJsonPoint(-95.3676974, 29.7589382));
        ExcursionsModel dallas = new ExcursionsModel();
        dallas.setGeolocation(new GeoJsonPoint(-96.7968559, 32.7762719));
        excursionsModels.add(austin);
        excursionsModels.add(houston);
        excursionsModels.add(dallas);
        mongoTemplate.insert(excursionsModels, ExcursionsModel.class);
    }

    public void dropData() {
        mongoTemplate.dropCollection(ExcursionsModel.class);
    }

    @Bean
    public ApplicationRunner runner() {
        return args -> {
            dropData();
            insertData();
            queryData();
        };
    }

}

Output

Point [x=-96.796856, y=32.776272] //Dallas
Point [x=-97.743700, y=30.271129] //Austin
31
173

Example (Mongo Client - Using Pojo Codec)

public void queryData()  {
    //Distance from fort worth - 45 miles - output dallas - 200 miles - austin/dallas - 300 miles - austin/dallas/houston
    MongoClient mongoClient = MongoClients.create();
    MongoDatabase database = mongoClient.getDatabase("test");
    Point refPoint = new Point(new Position(-97.3327459, 32.753177));
    Bson filters = Filters.nearSphere("geolocation", refPoint, 200*1609.34, 0.0);
    CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
                fromProviders(PojoCodecProvider.builder().register(ExcursionsModel.class).build()));
    FindIterable<ExcursionsModel> excursionsModels = database.getCollection("excursionsmodel", ExcursionsModel.class).withCodecRegistry(pojoCodecRegistry).find(filters);
        excursionsModels.forEach(es -> System.out.println(es.getGeolocation()));
    }

public static class ExcursionsModel {
    private Point geolocation;    
    public void setGeolocation(Point geolocation) {
        this.geolocation = geolocation;
    }
    public Point getGeolocation() {
        return geolocation;
    }
}

Output

Point{coordinate=Position{values=[-96.7968559, 32.7762719]}}
Point{coordinate=Position{values=[-97.7436995, 30.2711286]}}

dis You created Index On Collection like this?

db.user.createIndex({ geolocation : "2dsphere" } );

Please run this command & then Try again.

Related