{"record":{"id":"e39dd4ae80ced6e4","repo":"hibernate/hibernate-orm","slug":"jdbc-parameter-value-not-bound","errorCode":null,"errorMessage":"JDBC parameter value not bound - ","messagePattern":"JDBC parameter value not bound - ","errorType":"exception","errorClass":"ExecutionException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java","lineNumber":4765,"sourceCode":"\tprotected void renderFetchPlusOffsetExpressionAsSingleParameter(\n\t\t\tExpression fetchClauseExpression,\n\t\t\tExpression offsetClauseExpression,\n\t\t\tint offset) {\n\t\tif ( fetchClauseExpression instanceof Literal fetchLiteral ) {\n\t\t\tfinal Number fetchCount = (Number) fetchLiteral.getLiteralValue();\n\t\t\tif ( offsetClauseExpression instanceof Literal offsetLiteral ) {\n\t\t\t\tfinal Number offsetCount = (Number) offsetLiteral.getLiteralValue();\n\t\t\t\tappendSql( fetchCount.intValue() + offsetCount.intValue() + offset );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfinal JdbcParameter offsetParameter = (JdbcParameter) offsetClauseExpression;\n\t\t\t\tfinal int offsetValue = offset + fetchCount.intValue();\n\t\t\t\tfinal int parameterPosition = addParameterBinder(\n\t\t\t\t\t\toffsetParameter,\n\t\t\t\t\t\t(statement, startPosition, jdbcParameterBindings, executionContext) -> {\n\t\t\t\t\t\t\tfinal JdbcParameterBinding binding = jdbcParameterBindings.getBinding( offsetParameter );\n\t\t\t\t\t\t\tif ( binding == null ) {\n\t\t\t\t\t\t\t\tthrow new ExecutionException( \"JDBC parameter value not bound - \" + offsetParameter );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfinal Number bindValue = (Number) binding.getBindValue();\n\t\t\t\t\t\t\t//noinspection unchecked\n\t\t\t\t\t\t\toffsetParameter.getExpressionType().getSingleJdbcMapping().getJdbcValueBinder().bind(\n\t\t\t\t\t\t\t\t\tstatement,\n\t\t\t\t\t\t\t\t\tbindValue.intValue() + offsetValue,\n\t\t\t\t\t\t\t\t\tstartPosition,\n\t\t\t\t\t\t\t\t\texecutionContext.getSession()\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\trenderParameterAsParameter( parameterPosition, offsetParameter );\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tfinal JdbcParameter offsetParameter = (JdbcParameter) offsetClauseExpression;\n\t\t\tfinal JdbcParameter fetchParameter = (JdbcParameter) fetchClauseExpression;\n\t\t\tfinal FetchPlusOffsetParameterBinder fetchBinder = new FetchPlusOffsetParameterBinder(","sourceCodeStart":4747,"sourceCodeEnd":4783,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java#L4747-L4783","documentation":"During pagination emulation, some dialects render fetch+offset as one combined JDBC parameter (renderFetchPlusOffsetExpressionAsSingleParameter). A custom binder is registered that resolves the offset parameter's value from JdbcParameterBindings at execution time; if the offset parameter has no binding, this ExecutionException is thrown. It means the SQL contains a limit/offset placeholder whose value was never supplied for this execution.","triggerScenarios":"Executing a paginated query (setFirstResult/setMaxResults or HQL limit/offset with a parameterized offset) on a dialect that merges fetch and offset into a single parameter, where jdbcParameterBindings.getBinding(offsetParameter) returns null - e.g. a named offset parameter that was never set, a stale cached query plan rebound with different parameters, or a code path re-executing a prepared plan without refreshing limit bindings.","commonSituations":"Using offset/fetch with bind parameters (e.g. hql '... offset :o rows') and forgetting setParameter; query plan cache returning a plan after in-clause parameter expansion changed parameter positions; follow-on locking or scrollable results combined with pagination; upgrading Hibernate where limit emulation switched to the combined-parameter path for the dialect.","solutions":["Set every named limit/offset parameter explicitly before execution (query.setParameter(\"o\", off))","Prefer setFirstResult()/setMaxResults() over hand-written offset/fetch parameters in HQL","Clear the query plan cache (hibernate.query.plan_cache_max_size / evict) after dialect or query changes to drop stale plans","Reproduce with hibernate.query.plan_cache_max_size=0; if it disappears, it is a plan-cache binding issue - upgrade Hibernate"],"exampleFix":"// before\nQuery<Order> q = session.createQuery(\"from Order o offset :off rows\", Order.class);\nq.list(); // offset never bound\n\n// after\nQuery<Order> q = session.createQuery(\"from Order o\", Order.class)\n        .setFirstResult(off);\nq.list();","handlingStrategy":"validation","validationCode":"// Before execution, verify every named parameter of a paginated query is bound\nfor (String np : query.getParameterMetadata().getNamedParameterNames()) {\n    if (!query.isBound(np)) { // or track bound names yourself\n        throw new IllegalStateException(\"Unbound parameter: \" + np);\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    results = query.list();\n} catch (org.hibernate.QueryExecutionException | ExecutionException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"JDBC parameter value not bound\")) {\n        // log query + pagination settings, ensure setFirstResult/setMaxResults used, then retry once after plan cache evict\n    } else throw e;\n}","preventionTips":["Always paginate via setFirstResult/setMaxResults instead of HQL offset/fetch parameters","Set every named parameter before list()/getResultList()","Evict the query plan cache after dialect config changes"],"tags":["hibernate","pagination","jdbc-parameter","limit-offset","execution"],"backgroundTag":"jdbc-parameter-binding-missing","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}