Search code examples
javamongodbpolymorphismmicronaut

Mongodb polymorphism Java object unable to determine concrete class


I am using Mongodb with Micronaut and reading polymorphic Java object gives me error because Mongodb is unable to determine the concrete implementation class by its type.

myPet: { "_id" : ObjectId("5f96f4633dbd690c548c2a38"), pets: [{"type" : "Cat" }, {"type" : "Dog" }]}

public class MyPet {
  private List<Animal> pets = new ArrayList<Animal>(0);

  public List<Animal> getPets() {
    return pets;
  }

  public void setPets(List<Animal> pets) {
    this.pets = pets;
  }
}

public class Cat implements Animal {
  private String type = "Cat";

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }
}

public class Dog implements Animal {
  private String type = "Dog";

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }
}

public interface Animal {
  String getType();
}

I tried

MongoCollection<MyPet> myPetColl = db.getCollection("myPet", MyPet.class);
myPetColl.find()

but get below error

Unexpected error occurred: An exception occurred when decoding using the AutomaticPojoCodec.
Decoding into a 'MyPet' failed with the following exception:

Failed to decode 'MyPet'. Decoding 'content' errored with: Cannot find a public constructor for 'Animal'.

A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.
org.bson.codecs.configuration.CodecConfigurationException: An exception occurred when decoding using the AutomaticPojoCodec.
Decoding into a 'MyPet' failed with the following exception:

Failed to decode 'MyPet'. Decoding 'content' errored with: Cannot find a public constructor for 'Animal'.

A custom Codec or PojoCodec may need to be explicitly configured and registered to handle this type.
        at org.bson.codecs.pojo.AutomaticPojoCodec.decode(AutomaticPojoCodec.java:40)
        at com.mongodb.internal.operation.CommandResultArrayCodec.decode(CommandResultArrayCodec.java:52)
        at com.mongodb.internal.operation.CommandResultDocumentCodec.readValue(CommandResultDocumentCodec.java:60)
        at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:84)
        at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:41)
        at org.bson.internal.LazyCodec.decode(LazyCodec.java:48)
        at org.bson.codecs.BsonDocumentCodec.readValue(BsonDocumentCodec.java:101)
        at com.mongodb.internal.operation.CommandResultDocumentCodec.readValue(CommandResultDocumentCodec.java:63)
        at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:84)
        at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:41)
        at com.mongodb.internal.connection.ReplyMessage.<init>(ReplyMessage.java:51)
        at com.mongodb.internal.connection.InternalStreamConnection.getCommandResult(InternalStreamConnection.java:412)
        at com.mongodb.internal.connection.InternalStreamConnection.access$900(InternalStreamConnection.java:75)
        at com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:397)
        at com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:375)
        at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:676)
        at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:643)
        at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:513)
        at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:510)
        at com.mongodb.internal.connection.AsynchronousChannelStream$BasicCompletionHandler.completed(AsynchronousChannelStream.java:230)
        at com.mongodb.internal.connection.AsynchronousChannelStream$BasicCompletionHandler.completed(AsynchronousChannelStream.java:213)
        at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:127)
        at java.base/sun.nio.ch.Invoker.invokeDirect(Invoker.java:158)
        at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.implRead(UnixAsynchronousSocketChannelImpl.java:560)
        at java.base/sun.nio.ch.AsynchronousSocketChannelImpl.read(AsynchronousSocketChannelImpl.java:277)
        at java.base/sun.nio.ch.AsynchronousSocketChannelImpl.read(AsynchronousSocketChannelImpl.java:298)
        at com.mongodb.internal.connection.AsynchronousSocketChannelStream$AsynchronousSocketChannelAdapter.read(AsynchronousSocketChannelStream.java:136)
        at com.mongodb.internal.connection.AsynchronousChannelStream.readAsync(AsynchronousChannelStream.java:109)
        at com.mongodb.internal.connection.InternalStreamConnection.readAsync(InternalStreamConnection.java:510)
        at com.mongodb.internal.connection.InternalStreamConnection.access$1000(InternalStreamConnection.java:75)
        at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:633)
        at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:618)
        at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:513)
        at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:510)
        at com.mongodb.internal.connection.AsynchronousChannelStream$BasicCompletionHandler.completed(AsynchronousChannelStream.java:230)
        at com.mongodb.internal.connection.AsynchronousChannelStream$BasicCompletionHandler.completed(AsynchronousChannelStream.java:213)
        at java.base/sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:127)
        at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishRead(UnixAsynchronousSocketChannelImpl.java:437)
        at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:191)
        at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
        at java.base/sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:312)
        at java.base/sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:834)

Is there a way to return the correct class type with if-condition:

if (obj.type == "Dog") return Dog.class else Cat.class

like in Jackson

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.PROPERTY,
        property = "type")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Dog.class, name = "Dog"),
        @JsonSubTypes.Type(value = Cat.class, name = "Cat"),
})

Solution

  • You can use @BsonDiscriminator annotation to tell MongoDB driver which field is used as a discriminator key. Here is a working example:

    import org.bson.codecs.pojo.annotations.BsonDiscriminator;
    
    @BsonDiscriminator(key = Animal.DISCRIMINATOR_KEY)
    public interface Animal {
        String DISCRIMINATOR_KEY = "type";
    
        String getType();
    }
    
    public class Cat implements Animal {
        @Override
        public String getType() {
            return Cat.class.getName();
        }
    }
    
    public class Dog implements Animal {
        @Override
        public String getType() {
            return Dog.class.getName();
        }
    }
    

    As you can see, I'm using full class name as a value for type field. Unfortunately custom discriminator values (different then full class name) does not work yet in Micronaut MongoDB Client now. It is because of this issue, which still not solved: https://github.com/micronaut-projects/micronaut-mongodb/issues/10