hibernate/hibernate-orm · error · QueryException

H2 json_value only support literal json paths, but got " + a

Error message

H2 json_value only support literal json paths, but got " + arguments.jsonPath()

What it means

The H2 json_exists() emulation must rewrite the SQL/JSON path into a chain of H2 dereference operators, which requires reading the concrete path text at translation time. If the path argument is a bind parameter or any non-literal expression, walker.getLiteralValue() fails and Hibernate throws this QueryException while rendering. Note: the message text says 'json_value' even though it comes from the json_exists emulation - the path is ultimately rendered through H2JsonValueFunction.renderJsonPath.

Source

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

		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()
		);
		sqlAppender.appendSql( " is not null" );
	}
}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Inline the path as a literal: json_exists(d.doc, '$.flags[0]')
  2. Keep the path literal and parameterize varying parts with PASSING: json_exists(d.doc, '$.flags[$i]' passing :idx as i)
  3. Use a native H2 query for fully dynamic paths
  4. Maintain a fixed set of literal-path queries instead of one parameterized query

Example fix

// before - throws on H2
select json_exists(d.doc, :path) from Document d

// after - literal path with passing for the variable index
select json_exists(d.doc, '$.flags[$i]' passing :idx as i) from Document d
Defensive patterns

Strategy: validation

Validate before calling

// Heuristic lint: json_exists paths must be string literals on H2
private static final Pattern PARAM_EXISTS_PATH =
    Pattern.compile("(?i)json_exists\\s*\\([^,]+,\\s*:\\w+");

static void assertLiteralJsonPaths(String hql) {
    if (PARAM_EXISTS_PATH.matcher(hql).find()) {
        throw new IllegalArgumentException(
            "json_exists path must be a string literal on H2; use PASSING for variable parts");
    }
}

Type guard

// Java predicate (type-guard analogue) for Criteria/SQM path expressions
static boolean isLiteralPath(org.hibernate.query.sqm.tree.expression.SqmExpression<?> pathExpr) {
    return pathExpr instanceof org.hibernate.query.sqm.tree.expression.SqmLiteral<?>;
}

Try / catch

try {
    return session.createQuery(hql, Boolean.class).getResultList();
} catch (org.hibernate.QueryException e) {
    if (e.getMessage() != null && e.getMessage().contains("only support literal json paths")) {
        // Note: the message says 'json_value' but this is the json_exists path check
        throw new IllegalArgumentException("Inline the json_exists path as a literal: " + hql, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL on H2 with a non-literal json_exists path: select json_exists(d.doc, :path) from Document d, or a path built by concatenation or a function call.

Common situations: Parameterized reporting queries where the JSON path arrives from the UI or configuration; H2 unit tests failing while the production database accepts parameterized paths.

Related errors


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