{"record":{"id":"9b53adce27618365","repo":"hibernate/hibernate-orm","slug":"can-t-emulate-on-error-clause-on-h2","errorCode":null,"errorMessage":"Can't emulate on error clause on H2","messagePattern":"Can't emulate on error clause on H2","errorType":"exception","errorClass":"QueryException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/function/json/H2JsonExistsFunction.java","lineNumber":31,"sourceCode":"\n/**\n * H2 json_exists function.\n */\npublic class H2JsonExistsFunction extends JsonExistsFunction {\n\n\tpublic H2JsonExistsFunction(TypeConfiguration typeConfiguration) {\n\t\tsuper( typeConfiguration, true, true );\n\t}\n\n\t@Override\n\tprotected void render(\n\t\t\tSqlAppender sqlAppender,\n\t\t\tJsonExistsArguments arguments,\n\t\t\tReturnableType<?> returnType,\n\t\t\tSqlAstTranslator<?> walker) {\n\t\t// Json dereference errors by default if the JSON is invalid\n\t\tif ( arguments.errorBehavior() != null && arguments.errorBehavior() != JsonExistsErrorBehavior.ERROR ) {\n\t\t\tthrow new QueryException( \"Can't emulate on error clause on H2\" );\n\t\t}\n\t\tfinal String jsonPath;\n\t\ttry {\n\t\t\tjsonPath = walker.getLiteralValue( arguments.jsonPath() );\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthrow new QueryException( \"H2 json_value only support literal json paths, but got \" + arguments.jsonPath() );\n\t\t}\n\t\targuments.jsonDocument().accept( walker );\n\t\tsqlAppender.appendSql( \" is not null and \" );\n\t\tH2JsonValueFunction.renderJsonPath(\n\t\t\t\tsqlAppender,\n\t\t\t\targuments.jsonDocument(),\n\t\t\t\targuments.isJsonType(),\n\t\t\t\twalker,\n\t\t\t\tjsonPath,\n\t\t\t\targuments.passingClause()\n\t\t);","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/function/json/H2JsonExistsFunction.java#L13-L49","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Drop the on error clause and rely on the default ERROR behavior","Guard with a JSON validity predicate instead: where d.doc is json, so invalid documents never reach the dereference","Run these specific tests with Testcontainers against a database that supports the clause (e.g. Oracle or PostgreSQL)","Catch the underlying database error in application code if a lenient probe is needed"],"exampleFix":"// before - throws on H2\nselect json_exists(d.doc, '$.flags[0]' false on error) from Document d\n\n// after - default ERROR; filter invalid documents beforehand\nselect json_exists(d.doc, '$.flags[0]') from Document d where d.doc is json","handlingStrategy":"validation","validationCode":"// Reject TRUE/FALSE ON ERROR for json_exists on H2 before execution\nstatic void assertTranslatable(SessionFactory sf, String hql) {\n    if (sf.getJdbcServices().getDialect() instanceof org.hibernate.dialect.H2Dialect) {\n        String h = hql.toLowerCase();\n        if (h.contains(\"json_exists\")\n                && (h.contains(\"true on error\") || h.contains(\"false on error\"))) {\n            throw new IllegalArgumentException(\n                \"H2 cannot emulate 'true/false on error' for json_exists; drop the clause\");\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    return session.createQuery(hql, Boolean.class).getResultList();\n} catch (org.hibernate.QueryException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"on error clause on H2\")) {\n        // Retry with default ERROR ON ERROR after filtering invalid documents\n        return session.createQuery(stripClause(hql, \"on error\"), Boolean.class).getResultList();\n    }\n    throw e;\n}","preventionTips":["Use 'where doc is json' as a portable pre-filter instead of TRUE/FALSE ON ERROR probes","Keep H2 in the test matrix but assert dialect capability before using non-default JSON clauses","For production-parity JSON tests, prefer Testcontainers over H2 in-memory profiles"],"tags":["hibernate","h2","json","hql","json-exists","sql-dialect","unit-testing"],"backgroundTag":"json-on-error-clause-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}