PrimaryKey lookup does not work with nested mapped super classes

  • Jmix-Framework: 2.8.2
  • Jmix Studio: 3.0.0-261
  • Idea 2026.1.3 Ultimate

Saving an entity instance which is designed by two nested mapped superclasses fails. The code looks like supporting it, but it actually does not return the ancestor of the ancestor.

  • appid_BaseEntity is a mapped super class entity and defines the primary key attribute, version etc.
  • appid_LinkedBaseEntity is a mapped super itself and extends appid_BaseEntity. It defines a foreign key to another entity
  • appid_DataEntity1 extends LinkedBaseEntity with further specific attributes

When creating a appid_DataEntity1 instance by using the DataManager without discard saving context, there is a reload attempt after saving. It tries to resolve the primary key attribute by meta classes.

java.lang.IllegalStateException: Entity appid_DataEntity1 has no primary key
	at io.jmix.data.impl.JpqlQueryBuilder.getPrimaryKeyProperty(JpqlQueryBuilder.java:391) ~[jmix-data-2.8.2.jar:na]
	at io.jmix.data.impl.JpqlQueryBuilder.buildResultQuery(JpqlQueryBuilder.java:223) ~[jmix-data-2.8.2.jar:na]
	at io.jmix.data.impl.JpqlQueryBuilder.getResultQueryString(JpqlQueryBuilder.java:171) ~[jmix-data-2.8.2.jar:na]
	at io.jmix.data.impl.JpqlQueryBuilder.getQuery(JpqlQueryBuilder.java:184) ~[jmix-data-2.8.2.jar:na]
	at io.jmix.eclipselink.impl.JpaDataStore.createQuery(JpaDataStore.java:603) ~[jmix-eclipselink-2.8.2.jar:na]
	at io.jmix.eclipselink.impl.JpaDataStore.loadOne(JpaDataStore.java:149) ~[jmix-eclipselink-2.8.2.jar:na]
	at io.jmix.core.datastore.AbstractDataStore.loadAllAfterSave(AbstractDataStore.java:443) ~[jmix-core-2.8.2.jar:na]
	at io.jmix.core.datastore.AbstractDataStore.save(AbstractDataStore.java:239) ~[jmix-core-2.8.2.jar:na]
	at io.jmix.eclipselink.impl.JpaDataStore.save(JpaDataStore.java:244) ~[jmix-eclipselink-2.8.2.jar:na]
	at io.jmix.core.impl.UnconstrainedDataManagerImpl.lambda$saveContextToStore$7(UnconstrainedDataManagerImpl.java:280) ~[jmix-core-2.8.2.jar:na]
	at io.micrometer.observation.Observation.observe(Observation.java:564) ~[micrometer-observation-1.15.11.jar:1.15.11]
	at io.jmix.core.impl.UnconstrainedDataManagerImpl.saveContextToStore(UnconstrainedDataManagerImpl.java:280) ~[jmix-core-2.8.2.jar:na]
	at io.jmix.core.impl.UnconstrainedDataManagerImpl.save(UnconstrainedDataManagerImpl.java:237) ~[jmix-core-2.8.2.jar:na]
	at io.jmix.core.impl.UnconstrainedDataManagerImpl.save(UnconstrainedDataManagerImpl.java:156) ~[jmix-core-2.8.2.jar:na]

I debugged it to MetadataTools line 246 (ancestor = ancestor.getAncestor();) returning null.

    @Nullable
    public String getPrimaryKeyName(MetaClass metaClass) {
        String pkProperty = (String) metaClass.getAnnotations().get(PRIMARY_KEY_ANN_NAME);
        if (pkProperty != null) {
            return pkProperty;
        } else {
            MetaClass ancestor = metaClass.getAncestor();
            while (ancestor != null) {
                pkProperty = (String) ancestor.getAnnotations().get(PRIMARY_KEY_ANN_NAME);
                if (pkProperty != null)
                    return pkProperty;
                ancestor = ancestor.getAncestor();
            }
        }
        return null;
    }

As some additional information, this is how the meta class are initialized:

  • appid_BaseEntity
    • ancestors = [ ] (0)
    • descendants = [ … ] (4)
  • appid_LinkedBaseEntity
    • ancestors = [ ] (0)
    • descendants = [ … ] (11)
  • appid_DataEntity1
    • ancestors = [ app_idLinkedBaseEntity, appid_BaseEntity ] (2)
    • descendants = [ ] (0)

I tried to force loading the model as a workaround in advance and a different order - without any success. I guess, I cannot influence and start avoid using nested mapped super classes.

Hi,

Thanks for the detailed report. We tried to reproduce the issue by building a data model with two nested @MappedSuperclass levels (BaseEntity defining the @Id/@VersionLinkedBaseEntity with an additional reference → concrete @Entity), and saving an instance via DataManager with the default (non-discard) save context.

However, we could not reproduce the problem: MetadataTools.getPrimaryKeyName() correctly walks the full ancestor chain and returns the primary key, and the save + reload works fine.

Could you please share a minimal demo project that reproduces the issue? That would let us pinpoint the exact configuration that breaks the ancestor lookup and file a proper issue.

Thanks!

Regards,
Igor

Hi @i.meshcheriakov ,

thank you, for your answer. I just added additional information just before your reply - did you read this? The ancestors list of the mapped meta classes are empty in my case. Since it happens on a scheduled background job, might that be a timing issue?

It will take some time to produce and example possible to share.

Yes, it looks like at this moment metadata are not fully initialized yet.
Could you share more details about your background job?

I just sent you the (reduced) project as a direct message which still shows the behavior.

It turned out that the problem in your project is caused by the entity name duplication: both MessageData and MessageDataList have the same @JmixEntity name. Fix this and remove the MetaModelUtil hacks.

We’ll improve the model validation in the future: Fail fast on entity name duplication · Issue #5423 · jmix-framework/jmix

Regards,
Konstantin

1 Like