morphia icon indicating copy to clipboard operation
morphia copied to clipboard

Unable to read polymorphic types with Morphia with or without the default discriminator

Open matheuscirillo opened this issue 8 months ago • 4 comments

Server Version: 8.0.4 Driver Version: 4.11.5 Morphia Version: 2.4.15

So I have a simple structure:

Interceptor.java

@Entity(discriminatorKey = "type")
public abstract class Interceptor {

    @Id
    protected String id;
    protected String type;

    public Interceptor(String type) {
        this.type = type;
    }
    
    // getters and setters omitted..
}

It's subclasses:

ScriptInterceptor.java

@Entity(discriminator = "script", discriminatorKey = "type")
public class ScriptInterceptor extends Interceptor {

    private String language;
    private String source;

    public ScriptInterceptor() {
        super("script");
    }
    
    // getters and setters omitted...
}

XsltInterceptor.java

@Entity(discriminator = "xslt", discriminatorKey = "type")
public class XsltInterceptor extends Interceptor {

    private String source;

    public XsltInterceptor() {
        super("xslt");
    }
    
    // getters and setters omitted...
}

The document is successfully inserted, but when reading it seems that Morphia is unable to deserialize it. The document looks like this:

Image

The error I'm getting is:

Caused by: org.bson.codecs.configuration.CodecConfigurationException: Failed to decode 'Interceptor'. Decoding errored with: my.package.ScriptInterceptor
............
Caused by: dev.morphia.mapping.MappingException: my.package.ScriptInterceptor
	at dev.morphia.mapping.DiscriminatorLookup.lookup(DiscriminatorLookup.java:69)
	at dev.morphia.mapping.codec.pojo.EntityDecoder.getCodecFromDocument(EntityDecoder.java:105)
	... 84 more
Caused by: java.lang.ClassNotFoundException: my.package.ScriptInterceptor
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)

I already tried to use the default discriminator (_t) but I got the very same error.

I'm really confused right now. Why is this happening?

matheuscirillo avatar Apr 01 '25 23:04 matheuscirillo

I apologize for the delay. I've been hyperfocused on some things and neglecting responsibilities elsewhere. I'll try to recreate this and see if I can't find an answer for you.

evanchooly avatar Aug 28 '25 00:08 evanchooly

/duplicate 2.5.1, 3.0.0

evanchooly avatar Aug 28 '25 02:08 evanchooly

Issue Duplication Results

Processed by @evanchooly

#3557 created for milestone "2.5.1" ✅ #3558 created for milestone "3.0.0"


Triggered by comment: /duplicate 2.5.1, 3.0.0

github-actions[bot] avatar Aug 28 '25 02:08 github-actions[bot]

There's a few gaps in the description but I think I've filled them in correctly. I can't recreate this locally, so I'm not sure if I really have or not. Here's the test case I used:

public class TestPolymorphicQueries extends TestBase {
    @Test
    public void testQuery() {
        Holder holder = new Holder();
        holder.interceptors = new Interceptor[]{
            new ScriptInterceptor(),
            new XsltInterceptor()
        };

        getDs().save(holder);
        Holder first = getDs().find(Holder.class).first();
        assertNotNull(first);
        assertEquals(first.interceptors, holder.interceptors);
    }

    @Entity
    static class Holder {
        @Id
        private ObjectId id;

        Interceptor[] interceptors;
    }
    @Entity(discriminatorKey = "type")
    static abstract class Interceptor {

        @Id
        protected String id;
        protected String type;

        public Interceptor(String type) {
            this.type = type;
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof Interceptor)) {
                return false;
            }
            Interceptor that = (Interceptor) o;
            return Objects.equals(id, that.id) && Objects.equals(type, that.type);
        }

        @Override
        public int hashCode() {
            return Objects.hash(id, type);
        }
        // getters and setters omitted..
    }

    @Entity(discriminator = "script", discriminatorKey = "type")
    static class ScriptInterceptor extends Interceptor {

        private String language;
        private String source;

        public ScriptInterceptor() {
            super("script");
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof ScriptInterceptor)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            ScriptInterceptor that = (ScriptInterceptor) o;
            return Objects.equals(language, that.language) && Objects.equals(source, that.source);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), language, source);
        }

        // getters and setters omitted...
    }

    @Entity(discriminator = "xslt", discriminatorKey = "type")
    static class XsltInterceptor extends Interceptor {

        private String source;

        public XsltInterceptor() {
            super("xslt");
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof XsltInterceptor)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }
            XsltInterceptor that = (XsltInterceptor) o;
            return Objects.equals(source, that.source);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), source);
        }

        // getters and setters omitted...
    }
}

evanchooly avatar Oct 13 '25 23:10 evanchooly