javamongodbbsonmorphia

Why can't MongoDB Morphia load data before some other has been saved?


I’m trying to map objects via Morphia (MongoDB). This works fine, they are correctly in the database and I can load them from the database. But as soon as I restart my application, I get following exception when I try to load them:

Exception in thread "pool-1-thread-1"
org.bson.codecs.configuration.CodecConfigurationException:
Failed to decode 'ClassB'. Decoding errored with:
A class could not be found for the discriminator: 'ClassA'.

Tracestack:

Exception in thread "pool-1-thread-1" org.bson.codecs.configuration.CodecConfigurationException: Failed to decode 'ClassB'. Decoding errored with: A class could not be found for the discriminator: 'ClassA'.
at dev.morphia.mapping.codec.pojo.EntityDecoder.getCodecFromDocument(EntityDecoder.java:110)
at dev.morphia.mapping.codec.pojo.EntityDecoder.getCodecFromDocument(EntityDecoder.java:110)
at dev.morphia.mapping.codec.pojo.EntityDecoder.decode(EntityDecoder.java:46)
at dev.morphia.mapping.codec.pojo.MorphiaCodec.decode(MorphiaCodec.java:66)
at dev.morphia.mapping.codec.CollectionCodec.decode(CollectionCodec.java:56)
at dev.morphia.mapping.codec.MorphiaCollectionCodec.decode(MorphiaCollectionCodec.java:22)
at dev.morphia.mapping.codec.MorphiaCollectionCodec.decode(MorphiaCollectionCodec.java:11)
at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:96)
at dev.morphia.mapping.codec.pojo.EntityDecoder.decodeModel(EntityDecoder.java:66)
at dev.morphia.mapping.codec.pojo.EntityDecoder.decodeProperties(EntityDecoder.java:87)
at dev.morphia.mapping.codec.pojo.EntityDecoder.decode(EntityDecoder.java:43)
at dev.morphia.mapping.codec.pojo.MorphiaCodec.decode(MorphiaCodec.java:66)
at dev.morphia.mapping.codec.pojo.EntityDecoder.decode(EntityDecoder.java:48)
at dev.morphia.mapping.codec.pojo.MorphiaCodec.decode(MorphiaCodec.java:66)
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:87)
at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:42)
at org.bson.internal.LazyCodec.decode(LazyCodec.java:48)
at org.bson.codecs.BsonDocumentCodec.readValue(BsonDocumentCodec.java:104)
at com.mongodb.internal.operation.CommandResultDocumentCodec.readValue(CommandResultDocumentCodec.java:63)
at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:87)
at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:42)
at com.mongodb.internal.connection.ReplyMessage.<init>(ReplyMessage.java:51)
at com.mongodb.internal.connection.InternalStreamConnection.getCommandResult(InternalStreamConnection.java:476)
at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:366)
at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:279)
at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:100)
at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:490)
at com.mongodb.internal.connection.CommandProtocolImpl.execute(CommandProtocolImpl.java:71)
at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:253)
at com.mongodb.internal.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:202)
at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:118)
at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:110)
at com.mongodb.internal.operation.CommandOperationHelper.executeCommand(CommandOperationHelper.java:345)
at com.mongodb.internal.operation.CommandOperationHelper.executeCommand(CommandOperationHelper.java:336)
at com.mongodb.internal.operation.CommandOperationHelper.executeCommandWithConnection(CommandOperationHelper.java:222)
at com.mongodb.internal.operation.FindOperation$1.call(FindOperation.java:658)
at com.mongodb.internal.operation.FindOperation$1.call(FindOperation.java:652)
at com.mongodb.internal.operation.OperationHelper.withReadConnectionSource(OperationHelper.java:583)
at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:652)
at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:80)
at com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor.execute(MongoClientDelegate.java:170)
at com.mongodb.client.internal.MongoIterableImpl.execute(MongoIterableImpl.java:135)
at com.mongodb.client.internal.MongoIterableImpl.iterator(MongoIterableImpl.java:92)
at dev.morphia.query.MorphiaQuery.prepareCursor(MorphiaQuery.java:320)
at dev.morphia.query.MorphiaQuery.iterator(MorphiaQuery.java:201)
at dev.morphia.query.MorphiaQuery.iterator(MorphiaQuery.java:196)
at dev.morphia.query.MorphiaQuery.iterator(MorphiaQuery.java:41)
at java.base/java.lang.Iterable.forEach(Iterable.java:74)
at mypacket.DatabaseManager.get(DatabaseManager.java:76)

This exception occurs while loading data. I found out that it only occurs if I don’t save data to the db before. I could break down the problem to following code:

The classes to be mapped:

ClassA:

@NoArgsConstructor
@Entity
public class ClassA extends ClassB {

    public ClassA(String value) {
        super(value);
    }
}

ClassB:

@RequiredArgsConstructor
@NoArgsConstructor
@Entity
public class ClassB {
 
    @Id
    private int id = 0;
    @NonNull
    private String value;
}

ContainerClass:

@Entity
public class ContainerClass {

    @Id @AutoID
    private int id;
    @Getter
    private List<ClassB> list = new ArrayList<>();
}

The code to execute:

DB connection:

datastore = Morphia.createDatastore(MongoClients.create(), "myproject");
Mapper mapper = datastore.getMapper();
mapper.mapPackage("mypacket");
datastore.ensureIndexes();

Saving data:

ContainerClass c = new ContainerClass();
c.getList().add(new ClassA("A class 1"));
c.getList().add(new ClassB("B class 1"));
datastore.save(c);

Loading data:

List<ContainerClass> list = get(ContainerClass.class);
System.out.println("found " + list.size() + " elements");

public static <T> List<T> get(Class<T> clazz) {
    List<T> list = new ArrayList<>();
    datastore.find(clazz).forEach(e -> list.add(e));
    return list;
}

Steps of debugging:

A: When I save (only one object) before I load all data. This works fine: found X elements

B: When I only try to load data without saving before I get the exception above.

The database entry:

{
    "_id": 0,
    "_t": "ContainerClass",
    "list": [{
        "_id": 0,
        "_t": "ClassA",
        "value": "A class 1"
    }, {
        "_id": 0,
        "_t": "ClassB",
        "value": "B class 1"
    }]
}

Some additional data:

Envirement:

Assess the cause of the problem:

This looks like a bug to me. The affected classes cannot be 'loaded' correctly during the start. Anyhow morphia solves this problem itself temporary as soon as an object has been mapped.


Solution

  • You should always map your entities as part of initialization. Without that, Morphia won't know what to map that discriminator to.