hibernate/hibernate-orm · error · IllegalQueryOperationException

Insert conflict 'do update' clause with constraint name is n

Error message

Insert conflict 'do update' clause with constraint name is not supported

What it means

Hibernate 6.6+ HQL upserts use 'insert ... on conflict do update', optionally with a named constraint target ('on conflict on constraint <name>'). H2LegacySqlAstTranslator.visitConflictClause throws IllegalQueryOperationException for that named-constraint form because the H2 conflict rendering cannot bind the upsert to a specific constraint name.

Source

Thrown at hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacySqlAstTranslator.java:179

		}
		else {
			super.visitUpdateStatementOnly( statement );
		}
	}

	@Override
	protected void renderDmlTargetTableExpression(NamedTableReference tableReference) {
		super.renderDmlTargetTableExpression( tableReference );
		if ( getClauseStack().getCurrent() != Clause.INSERT ) {
			renderTableReferenceIdentificationVariable( tableReference );
		}
	}

	@Override
	protected void visitConflictClause(ConflictClause conflictClause) {
		if ( conflictClause != null ) {
			if ( conflictClause.isDoUpdate() && conflictClause.getConstraintName() != null ) {
				throw new IllegalQueryOperationException( "Insert conflict 'do update' clause with constraint name is not supported" );
			}
		}
	}

	@Override
	public void visitCteContainer(CteContainer cteContainer) {
		// H2 has various bugs in different versions that make it impossible to use CTEs with parameters reliably
		withParameterRenderingMode(
				SqlAstNodeRenderingMode.INLINE_PARAMETERS,
				() -> super.visitCteContainer( cteContainer )
		);
	}

	@Override
	protected boolean needsCteInlining() {
		// CTEs in H2 are just so buggy, that we can't reliably use them
		return true;
	}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove the constraint name: 'on conflict do update set ...' - Hibernate infers the conflict target from the unique key
  2. If disambiguation is needed, use the column-list target 'on conflict (col1, col2) do update'
  3. Move the statement to a native H2 MERGE ... KEY query
  4. Migrate the test database off the legacy dialect (H2 2.x with the current H2Dialect), which renders conflict clauses natively

Example fix

// before (HQL, throws on H2 legacy dialect)
insert into Person (id, name) select p.id, p.name from StagePerson p
  on conflict on constraint uk_person_id do update set name = excluded.name

// after (target-less conflict clause)
insert into Person (id, name) select p.id, p.name from StagePerson p
  on conflict do update set name = excluded.name
Defensive patterns

Strategy: validation

Validate before calling

Dialect d = sessionFactory.getJdbcServices().getDialect();
if (d instanceof org.hibernate.community.dialect.H2LegacyDialect && hql.contains("on conflict on constraint")) {
    hql = hql.replaceFirst("on constraint \\w+", "");
}

Type guard

static boolean namedConflictTargetSafe(Dialect d) {
    return !(d instanceof org.hibernate.community.dialect.H2LegacyDialect);
}

Try / catch

try {
    q = session.createQuery(hql);
} catch (org.hibernate.query.IllegalQueryOperationException e) {
    if (e.getMessage().contains("constraint name")) {
        q = session.createQuery(hql.replaceFirst("on constraint \\w+", ""));
    } else throw e;
}

Prevention

When it happens

Trigger: An HQL insert with 'on conflict on constraint <constraintName> do update set ...' running under the H2 legacy dialect (H2LegacyDialect / H2LegacySqlAstTranslator). The target-less form and the column-list target 'on conflict (cols) do update' do not hit this throw.

Common situations: H2 test databases pinned to the legacy dialect after a Hibernate upgrade; test fixtures copied from PostgreSQL production queries that name constraints; multi-database test matrices where only the H2-legacy member fails.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/0d7c6b6eeae5adfe. Report an issue: GitHub.