{"record":{"id":"61b153e1425e51f5","repo":"hibernate/hibernate-orm","slug":"unsupported-jdbcoperation-type-s","errorCode":null,"errorMessage":"Unsupported JdbcOperation type: %s","messagePattern":"Unsupported JdbcOperation type: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/action/queue/internal/exec/AbstractStepExecutor.java","lineNumber":98,"sourceCode":"\t\t\tfinal boolean execute = beforeOperationExecution( flushOperation );\n\n\t\t\t// No-op operations: only carry post-execution callback, skip SQL execution\n\t\t\tif ( flushOperation.getKind() != MutationKind.NO_OP && execute ) {\n\t\t\t\tfinal var bindPlan = flushOperation.getBindPlan();\n\t\t\t\tif ( bindPlan.getGeneratedValuesCollector() != null ) {\n\t\t\t\t\t// we need to execute these without batching\n\t\t\t\t\texecuteWithGeneratedValues( flushOperation );\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfinal var jdbcOperation = flushOperation.getJdbcOperation();\n\t\t\t\t\tif ( jdbcOperation instanceof PreparableMutationOperation preparable ) {\n\t\t\t\t\t\texecutePreparable( preparable, flushOperation );\n\t\t\t\t\t}\n\t\t\t\t\telse if ( jdbcOperation instanceof SelfExecutingUpdateOperation selfExecuting ) {\n\t\t\t\t\t\texecuteSelfExecuting( selfExecuting, flushOperation );\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthrow new IllegalStateException(\n\t\t\t\t\t\t\t\t\"Unsupported JdbcOperation type: \" + jdbcOperation.getClass().getName() );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tafterOperationExecution( flushOperation, newlyManagedEntityConsumer, fixupOperationConsumer );\n\t\t}\n\t}\n\n\tprotected boolean beforeOperationExecution(FlushOperation flushOperation) {\n\t\tfinal var preExecutionCallback = flushOperation.getPreExecutionCallback();\n\t\tif ( preExecutionCallback == null ) {\n\t\t\treturn true;\n\t\t}\n\t\tfinal boolean execute = preExecutionCallback.beforeExecution( (SessionImplementor) session );\n\t\tflushOperation.setExecutionSkipped( !execute );\n\t\treturn execute;\n\t}","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/action/queue/internal/exec/AbstractStepExecutor.java#L80-L116","documentation":"Thrown during flush by the graph-based ActionQueue's step executor when a queued FlushOperation's JdbcOperation is neither a PreparableMutationOperation (normal prepared SQL mutation) nor a SelfExecutingUpdateOperation (mutation that executes itself, e.g. soft-delete handling). It is an internal dispatch invariant: the executor only knows how to run those two shapes, so any other JdbcOperation implementation is rejected. In practice the class name in the message points to a custom/third-party mutation operation or an internal Hibernate bug, not to an entity mapping mistake.","triggerScenarios":"session.flush() or transaction commit runs a FlushOperation whose getJdbcOperation() returns a custom JdbcOperation implementation; typically produced by an integration that generates its own mutation operations, or an extension compiled against a different Hibernate version where the operation SPI differed.","commonSituations":"Custom OGM-like drivers or audit/history extensions supplying their own MutationOperation; upgrading to the 8.0 graph-based flush queue while an extension only supports the legacy queue; misrouted soft-delete/custom-SQL strategies.","solutions":["Inspect the class name in the message - it names the exact rejected JdbcOperation implementation; identify which library produces it and remove or upgrade that integration.","If the operation comes from an extension that only supports the legacy ordering, set hibernate.flush.queue.type=legacy as a workaround (valid values are 'graph' and 'legacy').","If you implement MutationOperation yourself, make getJdbcOperation() return a PreparableMutationOperation or a SelfExecutingUpdateOperation.","If no custom code is involved, capture the failing flush in a minimal test and report it against the Hibernate action-queue component."],"exampleFix":"// before\n<persistence-unit>\n  <properties>\n    <property name=\"hibernate.flush.queue.type\" value=\"graph\"/>\n  </properties>\n</persistence-unit>\n// after - fall back to the legacy action queue while the integration is fixed\n<persistence-unit>\n  <properties>\n    <property name=\"hibernate.flush.queue.type\" value=\"legacy\"/>\n  </properties>\n</persistence-unit>","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    session.flush();\n}\ncatch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Unsupported JdbcOperation type\")) {\n        // identify the integration named in the message, roll back, switch queue type or remove it\n        tx.rollback();\n    }\n    throw e;\n}","preventionTips":["Avoid custom JdbcOperation/MutationOperation implementations; prefer @SQLInsert/@SQLUpdate custom SQL which stays a PreparableMutationOperation","Smoke-test flush for every entity touched by a mutation extension after upgrading Hibernate major versions","Pin extensions (soft-delete, auditing, sharding) to versions built for your Hibernate line"],"tags":["hibernate","flush","action-queue","jdbc-operation","internal-invariant"],"backgroundTag":"unsupported-operation-type","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}