hibernate/hibernate-orm · error · IllegalQueryOperationException

Can't emulate conflict clause with constraint name for more

Error message

Can't emulate conflict clause with constraint name for more than one row to insert

What it means

getUniqueConstraintNameThatMayFail implements upsert on dialects (e.g., MariaDB per MariaDBSqlAstTranslator:186) by attempting the insert and catching the unique-constraint failure identified by constraint name. That emulation can only attribute a failure to a single row, so when the conflict clause carries a constraint name (and no constraint column names) but the statement inserts more than one row — a values list with >1 tuple, or an insert-select not capped to first-row-only — it throws IllegalQueryOperationException.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java:863

		visitInsertStatement( sqlAst );

		return new JdbcOperationQueryInsertImpl(
				getSql(),
				getParameterBinders(),
				getAffectedTableNames(),
				getUniqueConstraintNameThatMayFail(sqlAst)
		);
	}

	protected String getUniqueConstraintNameThatMayFail(InsertSelectStatement sqlAst) {
		final ConflictClause conflictClause = sqlAst.getConflictClause();
		if ( conflictClause == null || !conflictClause.getConstraintColumnNames().isEmpty() ) {
			return null;
		}
		else {
			if ( sqlAst.getSourceSelectStatement() != null && !isFetchFirstRowOnly( sqlAst.getSourceSelectStatement() )
					|| sqlAst.getValuesList().size() > 1 ) {
				throw new IllegalQueryOperationException( "Can't emulate conflict clause with constraint name for more than one row to insert" );
			}
			return conflictClause.getConstraintName() == null ? "" : conflictClause.getConstraintName();
		}
	}

	protected JdbcSelect translateSelect(SelectStatement selectStatement) {
		logDomainResultGraph( selectStatement.getDomainResultDescriptors() );
		logSqlAst( selectStatement );

		// we need to make a cope here for later since visitSelectStatement clears it :(
		final LockOptions lockOptions = this.lockOptions;

		visitSelectStatement( selectStatement );

		final int rowsToSkip;
		final JdbcOperationQuerySelect jdbcSelect = new JdbcOperationQuerySelect(
				getSql(),
				getParameterBinders(),

View on GitHub (pinned to fad1729dce)

Solutions

  1. Insert one row per statement so the emulation can attribute the constraint failure.
  2. Specify the conflict as constraint column names ('on conflict (col1, col2)') instead of a named constraint — the method returns null for that shape and the multi-row path proceeds.
  3. Omit the constraint name entirely if the single unique constraint makes it redundant.
  4. Execute a native MariaDB 'INSERT ... ON DUPLICATE KEY UPDATE' / 'INSERT IGNORE' statement for multi-row upserts.

Example fix

// before — multi-row insert + named constraint on MariaDB
int n = session.createQuery(
    "insert into Person (id,name) values (:i1,:n1),(:i2,:n2) on conflict on constraint uk_name do nothing")
    .executeUpdate();

// after — one row per statement (or conflict column names)
int n = session.createQuery(
    "insert into Person (id,name) values (:i,:nm) on conflict (name) do nothing")
    .setParameter("i", 1).setParameter("nm", "a").executeUpdate();
Defensive patterns

Strategy: validation

Validate before calling

boolean constraintNameUpsert = hql.contains("on conflict on constraint");
int rows = countValueTuples(hql); // rows aggregated into the insert
if (constraintNameUpsert && rows > 1) {
    // MariaDB-style emulation cannot attribute multi-row failures to one constraint
    throw new IllegalArgumentException("Split into single-row inserts or use conflict column names");
}

Try / catch

try { session.createQuery(hql).executeUpdate(); }
catch (org.hibernate.query.IllegalQueryOperationException e) {
    if (e.getMessage().contains("constraint name for more than one row")) {
        splitIntoSingleRowInserts(hql).forEach(q -> q.executeUpdate());
    } else { throw e; }
}

Prevention

When it happens

Trigger: HQL/JPA insert with 'on conflict on constraint <name> do nothing' (empty constraint column names) plus either multiple value tuples ('values (...),(...)') or an insert-from-select whose source is not restricted to fetch first row only, executed on a dialect using the constraint-name failure emulation (MariaDB).

Common situations: Porting PostgreSQL-style upsert HQL to MariaDB; batch-insert helpers that aggregate rows into one multi-values statement; enabling the Hibernate 6.5+ HQL INSERT ... ON CONFLICT syntax on MariaDB.

Related errors


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