hibernate/hibernate-orm · error · QueryException
H2 json_table() only supports literal default expressions, b
Error message
H2 json_table() only supports literal default expressions, but got {defaultExpression} What it means
In the H2 json_table() emulation, a column-level default <expr> on empty clause is implemented by baking the default value into the generated coalesce/cast SQL text at translation time. Only compile-time Literal values can be baked in; a bind parameter or computed expression as the default is rejected with this QueryException while the column mappings are built.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/dialect/function/json/H2JsonTableFunction.java:709
}
return addSelectableMappings( selectableMappings, tableIdentifierVariable, columnDefinition.columns(), nextClauseLevel, readExpression, converter );
}
protected void addSelectableMappings(List<SelectableMapping> selectableMappings, String tableIdentifierVariable, JsonTableOrdinalityColumnDefinition definition, int clauseLevel, SqmToSqlAstConverter converter) {
addSelectableMapping(
selectableMappings,
definition.name(),
ordinalityExpression( tableIdentifierVariable, clauseLevel ),
converter.getCreationContext().getTypeConfiguration().getBasicTypeForJavaType( Long.class )
);
}
protected void addSelectableMappings(List<SelectableMapping> selectableMappings, JsonTableValueColumnDefinition definition, String parentReadExpression, SqmToSqlAstConverter converter) {
final JsonValueEmptyBehavior emptyBehavior = definition.emptyBehavior();
final Literal defaultExpression;
if ( emptyBehavior != null && emptyBehavior.getDefaultExpression() != null ) {
if ( !( emptyBehavior.getDefaultExpression() instanceof Literal literal ) ) {
throw new QueryException( "H2 json_table() only supports literal default expressions, but got " + emptyBehavior.getDefaultExpression() );
}
defaultExpression = literal;
}
else {
defaultExpression = null;
}
final String baseReadExpression = determineElementReadExpression( definition.name(), definition.jsonPath(), parentReadExpression );
final String elementReadExpression = castValueExpression( baseReadExpression, definition.type(), defaultExpression, converter );
addSelectableMapping(
selectableMappings,
definition.name(),
elementReadExpression,
definition.type().getJdbcMapping()
);
}
private String castValueExpression(String baseReadExpression, CastTarget castTarget, @Nullable Literal defaultExpression, SqmToSqlAstConverter converter) {View on GitHub (pinned to fad1729dce)
Solutions
- Use a literal default: columns(name varchar default 'N/A' on empty)
- Apply the variable default outside the function: select coalesce(t.name, :fallback) from ... json_table(...) t
- Default to null inside json_table and substitute the default in application code
- Fall back to a native query if the default must be a computed expression
Example fix
// before - throws on H2 (non-literal default expression) select t.name from Document d, json_table(d.doc, '$.items[*]' columns(name varchar default :fallback on empty)) t // after - literal default inside, variable default applied with coalesce outside select coalesce(t.name, :fallback) from Document d, json_table(d.doc, '$.items[*]' columns(name varchar default 'N/A' on empty)) t
Defensive patterns
Strategy: validation
Validate before calling
// Heuristic lint: json_table column defaults must be literals on H2
private static final Pattern NONLITERAL_DEFAULT =
Pattern.compile("(?i)default\\s+(:\\w+|[a-z_]+\\s*\\()");
static void assertLiteralDefaults(String hql) {
if (NONLITERAL_DEFAULT.matcher(hql).find()) {
throw new IllegalArgumentException(
"json_table column defaults must be literals on H2; apply variable defaults with coalesce() outside");
}
} Type guard
// Java predicate (type-guard analogue) for the default expression node
static boolean isLiteralDefault(org.hibernate.sql.ast.tree.expression.Expression defaultExpr) {
return defaultExpr instanceof org.hibernate.sql.ast.tree.expression.Literal;
} 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 default expressions")) {
// Rewrite: move the dynamic default outside the json_table call
throw new IllegalArgumentException(
"Use a literal default or coalesce(t.col, :fallback) instead", e);
}
throw e;
} Prevention
- Write column defaults as literals inside json_table and apply variable defaults via coalesce() outside
- Remember HQL string literals use single quotes: default 'N/A' on empty
- Keep per-dialect integration tests for json_table column clauses
When it happens
Trigger: HQL on H2 with a non-literal column default: json_table(d.doc, '$.items[*]' columns(name varchar default :fallback on empty)) or columns(name varchar default concat('n','a') on empty). emptyBehavior.getDefaultExpression() is not a Literal, so addSelectableMappings throws.
Common situations: Wanting per-request default values for missing JSON fields; the same query rendering fine on dialects that inline defaults as ordinary SQL expressions, then failing on the H2 test profile.
Related errors
- H2 json_table() only supports literal json paths, but got "
- H2 json_table() only supports literal json paths, but got {j
- H2 json_value only support literal json paths, but got " + a
- H2 json_query only support literal json paths, but got " + j
- Can't emulate null on error clause on H2
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1416f94cdcc6806f.
Report an issue: GitHub.