hibernate/hibernate-orm · error · IllegalStateException

Dialect returns null TemporaryTableStrategy for temporary ta

Error message

Dialect returns null TemporaryTableStrategy for temporary table {} of type {}

What it means

Hibernate uses temporary tables to execute multi-table bulk HQL (UPDATE/DELETE on JOINED-inheritance or secondary-table entities). StandardTemporaryTableExporter.getDefaultTemporaryTableStrategy maps the TemporaryTable's kind (LOCAL/GLOBAL/PERSISTENT) to dialect.getLocalTemporaryTableStrategy()/getGlobalTemporaryTableStrategy()/getPersistentTemporaryTableStrategy(). Since Hibernate 7.1 the base Dialect returns a non-null strategy only for getSupportedTemporaryTableKind() and null for the other kinds (Dialect.java:3996-4010), so asking for a kind the dialect does not support throws IllegalStateException('Dialect returns null TemporaryTableStrategy ...') the first time temp-table SQL (create/drop/truncate) is generated.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/temptable/StandardTemporaryTableExporter.java:67

	}

	@Deprecated(forRemoval = true, since = "7.1")
	protected String getTruncateTableCommand() {
		return dialect.getTemporaryTableTruncateCommand();
	}

	protected String getTruncateTableCommand(TemporaryTableStrategy temporaryTableStrategy) {
		return temporaryTableStrategy.getTemporaryTableTruncateCommand();
	}

	private TemporaryTableStrategy getDefaultTemporaryTableStrategy(TemporaryTable temporaryTable) {
		final TemporaryTableStrategy temporaryTableStrategy = switch ( temporaryTable.getTemporaryTableKind() ) {
					case LOCAL -> dialect.getLocalTemporaryTableStrategy();
					case GLOBAL -> dialect.getGlobalTemporaryTableStrategy();
					case PERSISTENT -> dialect.getPersistentTemporaryTableStrategy();
				};
		if ( temporaryTableStrategy == null ) {
			throw new IllegalStateException(
					"Dialect returns null TemporaryTableStrategy for temporary table " + temporaryTable.getQualifiedTableName() + " of type " + temporaryTable.getTemporaryTableKind() );
		}
		return temporaryTableStrategy;
	}

	@Override
	public String getSqlCreateCommand(TemporaryTable temporaryTable) {
		final TemporaryTableStrategy temporaryTableStrategy = getDefaultTemporaryTableStrategy( temporaryTable );
		final var buffer = new StringBuilder( getCreateCommand( temporaryTableStrategy ) ).append( ' ' );
		buffer.append( temporaryTable.getQualifiedTableName() );
		buffer.append( '(' );

		for ( TemporaryTableColumn column : temporaryTable.getColumnsForExport() ) {
			buffer.append( column.getColumnName() ).append( ' ' );
			final int sqlTypeCode = column.getJdbcMapping().getJdbcType().getDdlTypeCode();
			final String databaseTypeName = column.getSqlTypeDefinition();

			buffer.append( databaseTypeName );

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove the explicit temporary-table mutation-strategy setting and let Hibernate choose the dialect-appropriate default
  2. Match the strategy to the dialect: local_temporary_table only on LOCAL dialects (MySQL, HSQLDB, Transact-SQL family), global_temporary_table only on GLOBAL dialects (H2, Oracle, HANA, DB2)
  3. In a custom dialect, override getLocalTemporaryTableStrategy()/getGlobalTemporaryTableStrategy() to return a real TemporaryTableStrategy (StandardLocalTemporaryTableStrategy/StandardGlobalTemporaryTableStrategy) instead of the null default
  4. If the mismatch cannot be fixed by config, execute the bulk operation via native SQL

Example fix

// before: GLOBAL temp-table strategy forced on a LOCAL-only dialect
Map<String,Object> cfg = new HashMap<>();
cfg.put("hibernate.query.mutation_strategy", "global_temporary_table"); // -> IllegalStateException on MySQL

// after: let the dialect pick
// (remove the property entirely; Dialect default is used)
Defensive patterns

Strategy: validation

Validate before calling

// At startup, assert the configured bulk-id strategy is actually usable on this dialect
static void assertTemporaryTableStrategyConsistent(Dialect dialect, TemporaryTableKind configuredKind) {
    TemporaryTableStrategy strategy = switch (configuredKind) {
        case LOCAL -> dialect.getLocalTemporaryTableStrategy();
        case GLOBAL -> dialect.getGlobalTemporaryTableStrategy();
        case PERSISTENT -> dialect.getPersistentTemporaryTableStrategy();
    };
    if (strategy == null) {
        throw new IllegalStateException(dialect + " has no " + configuredKind
            + " temporary table strategy; remove the mutation-strategy override or fix the dialect");
    }
}

Try / catch

try {
    em.createQuery("delete from JoinedRoot e where e.flag = :f").executeUpdate();
} catch (IllegalStateException e) {
    if (e.getMessage().startsWith("Dialect returns null TemporaryTableStrategy")) {
        // config mismatch: fall back to per-row operations or native bulk SQL for this dialect
        throw new ConfigurationException("Remove the temporary-table mutation strategy override for this dialect", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Configuring an SQM mutation strategy whose temp-table kind is unsupported by the dialect -- e.g. enabling the global-temporary-table bulk-id strategy on a LOCAL-only dialect (MySQL, HSQLDB, SQL Server/Sybase) or the local-temporary strategy on a GLOBAL dialect (H2, Oracle, HANA, DB2) -- and then executing a bulk UPDATE/DELETE against a JOINED entity. Also caused by a custom Dialect overriding getSupportedTemporaryTableKind()/the strategy getters inconsistently (returning null).

Common situations: Copying hibernate.query.mutation_strategy / bulk-id settings between projects running different databases; performance tuning that pins the temporary-table strategy without checking dialect support; upgrading to Hibernate 7.1 where these strategy getters became nullable; custom dialect subclasses that return null.

Related errors


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