hibernate/hibernate-orm · error · QueryException

H2 json_table() only supports literal json paths, but got "

Error message

H2 json_table() only supports literal json paths, but got " + arguments.jsonPath()

What it means

For json_table() on H2, Hibernate rewrites the query with a system_range-based unnest when the path targets an array. Deciding array-vs-object shape requires inspecting the literal path text (isArrayAccess looks for a trailing [*]); when the path argument is not a Literal - typically a bind parameter - the query transformer throws this exception during SQM-to-SQL translation.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/function/json/H2JsonTableFunction.java:145

	}

	private static class JsonTableQueryTransformer implements QueryTransformer {
		private final FunctionTableGroup functionTableGroup;
		private final JsonTableArguments arguments;
		private final int maximumArraySize;

		public JsonTableQueryTransformer(FunctionTableGroup functionTableGroup, JsonTableArguments arguments, int maximumArraySize) {
			this.functionTableGroup = functionTableGroup;
			this.arguments = arguments;
			this.maximumArraySize = maximumArraySize;
		}

		@Override
		public QuerySpec transform(CteContainer cteContainer, QuerySpec querySpec, SqmToSqlAstConverter converter) {
			final boolean isArray;
			if ( arguments.jsonPath() != null ) {
				if ( !( arguments.jsonPath() instanceof Literal literal) ) {
					throw new QueryException( "H2 json_table() only supports literal json paths, but got " + arguments.jsonPath() );
				}
				final String rawJsonPath = (String) literal.getLiteralValue();
				isArray = isArrayAccess( rawJsonPath );
			}
			else {
				// We have to assume this is an array
				isArray = true;
			}
			if ( isArray ) {
				final TableGroup parentTableGroup = querySpec.getFromClause().queryTableGroups(
						tg -> tg.findTableGroupJoin( functionTableGroup ) == null ? null : tg
				);
				final PredicateContainer predicateContainer;
				if ( parentTableGroup != null ) {
					predicateContainer = parentTableGroup.findTableGroupJoin( functionTableGroup );
				}
				else {
					predicateContainer = querySpec;

View on GitHub (pinned to fad1729dce)

Solutions

  1. Inline the literal path: json_table(d.doc, '$.items[*]' columns(name varchar))
  2. Pass variable parts through the PASSING clause inside a literal path: json_table(d.doc, '$.items[$i]' passing :idx as i columns(...))
  3. Switch tests to Testcontainers so the dialect matches production
  4. Use a native H2 query for fully dynamic paths

Example fix

// before - throws on H2
select t.name from Document d, json_table(d.doc, :path columns(name varchar)) t

// after - literal array path
select t.name from Document d, json_table(d.doc, '$.items[*]' columns(name varchar)) t
Defensive patterns

Strategy: validation

Validate before calling

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

static void assertLiteralJsonPaths(String hql) {
    if (PARAM_TABLE_PATH.matcher(hql).find()) {
        throw new IllegalArgumentException(
            "json_table path must be a string literal on H2; the array/object shape must be known at translation time");
    }
}

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, Object[].class).getResultList();
} catch (org.hibernate.QueryException e) {
    if (e.getMessage() != null && e.getMessage().contains("only supports literal json paths")) {
        throw new IllegalArgumentException(
            "H2 json_table needs a literal path so it can detect array access; inline it: " + hql, e);
    }
    throw e;
}

Prevention

When it happens

Trigger: HQL on H2 with a non-literal json_table path: select t.name from Document d, json_table(d.doc, :path columns(name varchar)) t. The check in JsonTableQueryTransformer.transform() at H2JsonTableFunction.java:145 rejects any path that is not a Literal.

Common situations: Generic 'flatten any JSON path' repository methods parameterized by path; H2 test profiles failing while the production profile on a database that accepts parameter paths passes.

Related errors


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