NPE in BaseSqmToSqlAstConverter.withTreatRestriction when using treat() with @ElementCollection join on SINGLE_TABLE inheritance

When using CriteriaBuilder.treat() to downcast a Root to a subclass entity, and then joining
an @ElementCollection field on that treated root, Hibernate throws a NullPointerException
because “elementTableGroup” is null.

Environment:

  • Hibernate ORM: 6.x / 7.x (hibernate-core:7.1.14)
  • Spring Boot: 4.0.1
  • Database: PostgreSQL

Entity Setup:

  • Parent entity with @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  • Child entity with @ElementCollection (Set)

Code to reproduce:

// Parent entity
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class ParentEntity {
    @Id
    private String id;
}

// Child entity
@Entity
@DiscriminatorValue("CHILD")
public class ChildEntity extends ParentEntity {
    @ElementCollection
    @CollectionTable(name = "child_items", joinColumns = @JoinColumn(name = "child_id"))
    @Column(name = "item_id")
    private Set<String> itemIds;
}

// Criteria query that fails
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ParentEntity> query = cb.createQuery(ParentEntity.class);
Root<ParentEntity> root = query.from(ParentEntity.class);

// This line causes NPE
Path<?> path = cb.treat(root, ChildEntity.class).join("itemIds", JoinType.LEFT);

Stack Trace:

java.lang.NullPointerException: Cannot invoke "org.hibernate.sql.ast.tree.from.TableGroup.getModelPart()" because "elementTableGroup" is null
    at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.withTreatRestriction(BaseSqmToSqlAstConverter.java:5285)
    at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.createExpression(BaseSqmToSqlAstConverter.java:4411)
    at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitTableGroup(BaseSqmToSqlAstConverter.java:4184)
    at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQualifiedAttributeJoin(BaseSqmToSqlAstConverter.java:4099)
    ...

Expected behavior:
The treat() + join() on @ElementCollection should work and produce a valid SQL query
with proper joins to the collection table.

Actual behavior:
NullPointerException during SQM to SQL AST conversion.

Workaround:
Move the @ElementCollection mapping to the parent entity (which works but is not ideal
from a domain modeling perspective).

Thanks for the report. Please try to create a reproducer with our test case template and if you are able to reproduce the issue, create a bug ticket in our issue tracker and attach that reproducer.

Hello @beikov ,

Thanks for reply, I was able to reproduce using the test template. However, I’m not able to create an issue on the issue tracker. Whenever I click the “Create” button I get an error. Maybe something is blocked on my network. Can you please assist me in creating the ticket.

This is my reproducer hibernate-test-case-templates/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/JPAUnitTestCase.java at main · khaledbaghdadii/hibernate-test-case-templates

I’ll paste below the ticket title and description.

Title:

NPE in BaseSqmToSqlAstConverter.withTreatRestriction when using treat() with @ElementCollection join on SINGLE_TABLE inheritance

Description:

When using CriteriaBuilder.treat() to downcast a Root to a subclass entity, and then joining
an @ElementCollection field on that treated root, Hibernate throws a NullPointerException
because “elementTableGroup” is null.

Environment:

  • Hibernate ORM 7.x
  • H2 / PostgreSQL

Entity Setup:

  • Parent entity with @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
  • Child entity with @ElementCollection (Set)

Code that fails:

CriteriaBuilder cb = em.getCriteriaBuilder();CriteriaQuery query = cb.createQuery(ParentEntity.class);Root root = query.from(ParentEntity.class);

// This line causes NPEPath path = cb.treat(root, ChildEntity.class).join(“itemIds”, JoinType.LEFT);

query.select(root).distinct(true).where(path.in(“item-A”));em.createQuery(query).getResultList(); // NPE here

Stack trace:

2026-02-04 15:29:41 INFO  jpa:102 - HHH008540: Processing PersistenceUnitInfo [name: templatePU]
2026-02-04 15:29:41 INFO  core:41 - HHH000001: Hibernate ORM core version 7.2.3.Final
2026-02-04 15:29:41 INFO  core:143 - HHH000205: Loaded properties from resource hibernate.properties: {hibernate.format_sql=true, jakarta.persistence.validation.mode=NONE, hibernate.dialect=org.hibernate.dialect.H2Dialect, hibernate.connection.username=sa, hibernate.connection.url=jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1, hibernate.max_fetch_depth=5, hibernate.show_sql=false, hibernate.jdbc.batch_versioned_data=true, hibernate.connection.driver_class=org.h2.Driver, hibernate.connection.password=****, hibernate.cache.region_prefix=hibernate.test, hibernate.connection.pool_size=5, hibernate.cache.region.factory_class=org.hibernate.testing.cache.CachingRegionFactory, hibernate.service.allow_crawling=false, hibernate.session.events.log=true}
2026-02-04 15:29:41 WARN  CachingRegionFactory:46 - org.hibernate.testing.cache.CachingRegionFactory should be only used for testing.
2026-02-04 15:29:41 INFO  cache:49 - HHH90001028: Second-level cache region factory [org.hibernate.testing.cache.CachingRegionFactory]
2026-02-04 15:29:41 WARN  pooling:85 - HHH10001002: Using built-in connection pool (not intended for production use)
2026-02-04 15:29:42 WARN  deprecation:152 - HHH90000025: H2Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2026-02-04 15:29:42 INFO  pooling:179 - HHH10001005: Database info:
	Database JDBC URL [jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1]
	Database driver: H2 JDBC Driver
	Database dialect: H2Dialect
	Database version: 2.4.240
	Default catalog/schema: DB1/PUBLIC
	Autocommit mode: false
	Isolation level: READ_COMMITTED
	JDBC fetch size: 100
	Pool: DriverManagerConnectionProvider
	Minimum pool size: 1
	Maximum pool size: 5
2026-02-04 15:29:43 INFO  core:56 - HHH000490: Using JTA platform [org.hibernate.engine.transaction.jta.platform.internal.WildFlyStandAloneJtaPlatform]
Hibernate: 
    drop table if exists child_item_ids cascade 
Hibernate: 
    drop table if exists parent_entity cascade 
Hibernate: 
    create table child_item_ids (
        child_id varchar(255) not null,
        item_id varchar(255)
    )
Hibernate: 
    create table parent_entity (
        entity_type varchar(31) not null check ((entity_type in ('JPAUnitTestCase$ParentEntity','CHILD'))),
        id varchar(255) not null,
        name varchar(255),
        primary key (id)
    )
Hibernate: 
    alter table if exists child_item_ids 
       add constraint FKkpxl9trkcrwwqs8xe53w9qpkm 
       foreign key (child_id) 
       references parent_entity
Hibernate: 
    insert 
    into
        parent_entity
        (name, entity_type, id) 
    values
        (?, 'CHILD', ?)
Hibernate: 
    insert 
    into
        parent_entity
        (name, entity_type, id) 
    values
        (?, 'CHILD', ?)
Hibernate: 
    insert 
    into
        child_item_ids
        (child_id, item_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        child_item_ids
        (child_id, item_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        child_item_ids
        (child_id, item_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        child_item_ids
        (child_id, item_id) 
    values
        (?, ?)
Hibernate: 
    drop table if exists child_item_ids cascade 
Hibernate: 
    drop table if exists parent_entity cascade 
2026-02-04 15:29:43 ERROR pooling:148 - Connection leak detected: there are 1 unclosed connections upon shutting down pool jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1
2026-02-04 15:29:43 ERROR pooling:341 - HHH10001023: Connection leak detected: there are 1 unclosed connections

java.lang.NullPointerException: Cannot invoke "org.hibernate.sql.ast.tree.from.TableGroup.getModelPart()" because "elementTableGroup" is null

	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.withTreatRestriction(BaseSqmToSqlAstConverter.java:5336)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.createExpression(BaseSqmToSqlAstConverter.java:4461)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitTableGroup(BaseSqmToSqlAstConverter.java:4234)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQualifiedAttributeJoin(BaseSqmToSqlAstConverter.java:4149)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQualifiedAttributeJoin(BaseSqmToSqlAstConverter.java:466)
	at org.hibernate.query.sqm.SemanticQueryWalker.visitSetJoin(SemanticQueryWalker.java:232)
	at org.hibernate.query.sqm.tree.domain.SqmSetJoin.accept(SqmSetJoin.java:85)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitInListPredicate(BaseSqmToSqlAstConverter.java:8179)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitInListPredicate(BaseSqmToSqlAstConverter.java:466)
	at org.hibernate.query.sqm.tree.predicate.SqmInListPredicate.accept(SqmInListPredicate.java:154)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitWhereClause(BaseSqmToSqlAstConverter.java:2550)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.querySpec(BaseSqmToSqlAstConverter.java:2083)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQuerySpec(BaseSqmToSqlAstConverter.java:2056)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQuerySpec(BaseSqmToSqlAstConverter.java:466)
	at org.hibernate.query.sqm.tree.select.SqmQuerySpec.accept(SqmQuerySpec.java:127)
	at org.hibernate.query.sqm.spi.BaseSemanticQueryWalker.visitQueryPart(BaseSemanticQueryWalker.java:253)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQueryPart(BaseSqmToSqlAstConverter.java:1939)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitSelectStatement(BaseSqmToSqlAstConverter.java:1638)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitSelectStatement(BaseSqmToSqlAstConverter.java:466)
	at org.hibernate.query.sqm.tree.select.SqmSelectStatement.accept(SqmSelectStatement.java:251)
	at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.translate(BaseSqmToSqlAstConverter.java:808)
	at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.buildInterpretation(ConcreteSqmSelectQueryPlan.java:469)
	at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:384)
	at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:355)
	at org.hibernate.query.sqm.internal.SqmQueryImpl.doList(SqmQueryImpl.java:374)
	at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:153)
	at org.hibernate.query.Query.getResultList(Query.java:121)
	at org.hibernate.bugs.JPAUnitTestCase.testTreatWithElementCollectionJoin(JPAUnitTestCase.java:79)


Expected behavior:
The treat() + join() on @ElementCollection should work and produce a valid SQL query
with proper joins to the collection table.

Actual behavior:
NullPointerException during SQM to SQL AST conversion.

Reproducer attached: hibernate-test-case-templates/orm/hibernate-orm-7/src/test/java/org/hibernate/bugs/JPAUnitTestCase.java at main · khaledbaghdadii/hibernate-test-case-templates

Thanks for the reproducer. Did you login via Google or some other SSO provider?
We recently figured out that most of the problems come when you authenticate via an external SSO provider. Also see the discussion in our chat: #hibernate-user > Jira issue creation

It seems you must “join” the Hibernate Jira first. There should be a “Log In” Button in the Account-Popup (where it has links to Profile and Account Settings). After clicking it, you should be prompted to join Hibernate’s Jira.

Hello,

Thank you! This was the issue indeed and I was able to post

Thanks. Could you please post the link to the Jira ticket here for cross reference?

Sure, this is the Jira Ticket [HHH-20142] NPE in BaseSqmToSqlAstConverter.withTreatRestriction when using treat() with @ElementCollection join on SINGLE_TABLE inheritance - Hibernate JIRA

1 Like

Helo @beikov ,

I see no updates on my issue. Does this mean it will not be considered or is it still awaiting validation from the core team?

It just means that nobody from the community had the time yet to look into this issue. If you are a Red Hat or IBM customer, you can create a support ticket for this matter, which will allow us to prioritize this. Otherwise, you will just have to wait until someone finds the time to look into this.

@Khaled_Baghdadi Don’t expect any results soon. I created bug tickets that are three years old which had zero reactions. Here is another person waiting almost a year already.