{"record":{"id":"f790e9be60be1376","repo":"hibernate/hibernate-orm","slug":"h2-json-table-only-supports-literal-json-paths","errorCode":null,"errorMessage":"H2 json_table() only supports literal json paths, but got \" + arguments.jsonPath()","messagePattern":"H2 json_table\\(\\) only supports literal json paths, but got \" \\+ arguments\\.jsonPath\\(\\)","errorType":"exception","errorClass":"QueryException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/function/json/H2JsonTableFunction.java","lineNumber":145,"sourceCode":"\t}\n\n\tprivate static class JsonTableQueryTransformer implements QueryTransformer {\n\t\tprivate final FunctionTableGroup functionTableGroup;\n\t\tprivate final JsonTableArguments arguments;\n\t\tprivate final int maximumArraySize;\n\n\t\tpublic JsonTableQueryTransformer(FunctionTableGroup functionTableGroup, JsonTableArguments arguments, int maximumArraySize) {\n\t\t\tthis.functionTableGroup = functionTableGroup;\n\t\t\tthis.arguments = arguments;\n\t\t\tthis.maximumArraySize = maximumArraySize;\n\t\t}\n\n\t\t@Override\n\t\tpublic QuerySpec transform(CteContainer cteContainer, QuerySpec querySpec, SqmToSqlAstConverter converter) {\n\t\t\tfinal boolean isArray;\n\t\t\tif ( arguments.jsonPath() != null ) {\n\t\t\t\tif ( !( arguments.jsonPath() instanceof Literal literal) ) {\n\t\t\t\t\tthrow new QueryException( \"H2 json_table() only supports literal json paths, but got \" + arguments.jsonPath() );\n\t\t\t\t}\n\t\t\t\tfinal String rawJsonPath = (String) literal.getLiteralValue();\n\t\t\t\tisArray = isArrayAccess( rawJsonPath );\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// We have to assume this is an array\n\t\t\t\tisArray = true;\n\t\t\t}\n\t\t\tif ( isArray ) {\n\t\t\t\tfinal TableGroup parentTableGroup = querySpec.getFromClause().queryTableGroups(\n\t\t\t\t\t\ttg -> tg.findTableGroupJoin( functionTableGroup ) == null ? null : tg\n\t\t\t\t);\n\t\t\t\tfinal PredicateContainer predicateContainer;\n\t\t\t\tif ( parentTableGroup != null ) {\n\t\t\t\t\tpredicateContainer = parentTableGroup.findTableGroupJoin( functionTableGroup );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tpredicateContainer = querySpec;","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/function/json/H2JsonTableFunction.java#L127-L163","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inline the literal path: json_table(d.doc, '$.items[*]' columns(name varchar))","Pass variable parts through the PASSING clause inside a literal path: json_table(d.doc, '$.items[$i]' passing :idx as i columns(...))","Switch tests to Testcontainers so the dialect matches production","Use a native H2 query for fully dynamic paths"],"exampleFix":"// before - throws on H2\nselect t.name from Document d, json_table(d.doc, :path columns(name varchar)) t\n\n// after - literal array path\nselect t.name from Document d, json_table(d.doc, '$.items[*]' columns(name varchar)) t","handlingStrategy":"validation","validationCode":"// Heuristic lint: json_table paths must be string literals on H2\nprivate static final Pattern PARAM_TABLE_PATH =\n    Pattern.compile(\"(?i)json_table\\\\s*\\\\([^,]+,\\\\s*:\\\\w+\");\n\nstatic void assertLiteralJsonPaths(String hql) {\n    if (PARAM_TABLE_PATH.matcher(hql).find()) {\n        throw new IllegalArgumentException(\n            \"json_table path must be a string literal on H2; the array/object shape must be known at translation time\");\n    }\n}","typeGuard":"// Java predicate (type-guard analogue) for Criteria/SQM path expressions\nstatic boolean isLiteralPath(org.hibernate.query.sqm.tree.expression.SqmExpression<?> pathExpr) {\n    return pathExpr instanceof org.hibernate.query.sqm.tree.expression.SqmLiteral<?>;\n}","tryCatchPattern":"try {\n    return session.createQuery(hql, Object[].class).getResultList();\n} catch (org.hibernate.QueryException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"only supports literal json paths\")) {\n        throw new IllegalArgumentException(\n            \"H2 json_table needs a literal path so it can detect array access; inline it: \" + hql, e);\n    }\n    throw e;\n}","preventionTips":["Inline json_table paths as literals; the H2 emulation reads the path text to detect '[*]' array access","Use PASSING for variable parts inside a literal path","Match test and production dialects (Testcontainers) for queries relying on dialect-specific JSON support"],"tags":["hibernate","h2","json","hql","json-table","json-path","bind-parameter"],"backgroundTag":"non-literal-json-path","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}