hibernate/hibernate-orm · error · QueryException

Can't emulate on error clause on H2

Error message

Can't emulate on error clause on H2

What it means

H2's json_exists() emulation renders the document/path dereference chain and checks IS NOT NULL; a dereference in H2 raises an error on invalid JSON, so the emulation inherently implements 'error on error'. The TRUE ON ERROR and FALSE ON ERROR variants cannot be produced, and the translator throws when it encounters them.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/function/json/H2JsonExistsFunction.java:31

/**
 * H2 json_exists function.
 */
public class H2JsonExistsFunction extends JsonExistsFunction {

	public H2JsonExistsFunction(TypeConfiguration typeConfiguration) {
		super( typeConfiguration, true, true );
	}

	@Override
	protected void render(
			SqlAppender sqlAppender,
			JsonExistsArguments arguments,
			ReturnableType<?> returnType,
			SqlAstTranslator<?> walker) {
		// Json dereference errors by default if the JSON is invalid
		if ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonExistsErrorBehavior.ERROR ) {
			throw new QueryException( "Can't emulate on error clause on H2" );
		}
		final String jsonPath;
		try {
			jsonPath = walker.getLiteralValue( arguments.jsonPath() );
		}
		catch (Exception ex) {
			throw new QueryException( "H2 json_value only support literal json paths, but got " + arguments.jsonPath() );
		}
		arguments.jsonDocument().accept( walker );
		sqlAppender.appendSql( " is not null and " );
		H2JsonValueFunction.renderJsonPath(
				sqlAppender,
				arguments.jsonDocument(),
				arguments.isJsonType(),
				walker,
				jsonPath,
				arguments.passingClause()
		);

View on GitHub (pinned to fad1729dce)

Solutions

  1. Drop the on error clause and rely on the default ERROR behavior
  2. Guard with a JSON validity predicate instead: where d.doc is json, so invalid documents never reach the dereference
  3. Run these specific tests with Testcontainers against a database that supports the clause (e.g. Oracle or PostgreSQL)
  4. Catch the underlying database error in application code if a lenient probe is needed

Example fix

// before - throws on H2
select json_exists(d.doc, '$.flags[0]' false on error) from Document d

// after - default ERROR; filter invalid documents beforehand
select json_exists(d.doc, '$.flags[0]') from Document d where d.doc is json
Defensive patterns

Strategy: validation

Validate before calling

// Reject TRUE/FALSE ON ERROR for json_exists on H2 before execution
static void assertTranslatable(SessionFactory sf, String hql) {
    if (sf.getJdbcServices().getDialect() instanceof org.hibernate.dialect.H2Dialect) {
        String h = hql.toLowerCase();
        if (h.contains("json_exists")
                && (h.contains("true on error") || h.contains("false on error"))) {
            throw new IllegalArgumentException(
                "H2 cannot emulate 'true/false on error' for json_exists; drop the clause");
        }
    }
}

Try / catch

try {
    return session.createQuery(hql, Boolean.class).getResultList();
} catch (org.hibernate.QueryException e) {
    if (e.getMessage() != null && e.getMessage().contains("on error clause on H2")) {
        // Retry with default ERROR ON ERROR after filtering invalid documents
        return session.createQuery(stripClause(hql, "on error"), Boolean.class).getResultList();
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL on the H2 dialect: select json_exists(d.doc, '$.flags[0]' false on error) from Document d, or the same with true on error. JsonExistsErrorBehavior.TRUE and FALSE are rejected; the default ERROR (or no clause at all) is accepted.

Common situations: Developers use json_exists(... false on error) as a 'safe' validity probe that works on other databases, then the H2-based unit test (the default in many quickstarts) fails; switching tests from Testcontainers PostgreSQL to fast in-memory H2.

Related errors


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