{"record":{"id":"7da629e552c6c05f","repo":"alibaba/spring-ai-alibaba","slug":"runtimeexception-wrapping-transaction-failure-no","errorCode":null,"errorMessage":"RuntimeException wrapping transaction failure (no literal message; wraps cause e)","messagePattern":"RuntimeException wrapping transaction failure \\(no literal message; wraps cause e\\)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/mongo/MongoSaver.java","lineNumber":513,"sourceCode":"\t\t\tString checkpointDocId = CHECKPOINT_PREFIX + threadId;\n\t\t\tDocument checkpointDoc = checkpointCollection.find(clientSession, new BasicDBObject(\"_id\", checkpointDocId))\n\t\t\t\t\t.first();\n\n\t\t\tCollection<Checkpoint> checkpoints = Collections.emptyList();\n\t\t\tif (checkpointDoc != null) {\n\t\t\t\tString checkpointsStr = checkpointDoc.getString(DOCUMENT_CONTENT_KEY);\n\t\t\t\tif (checkpointsStr != null) {\n\t\t\t\t\tcheckpoints = deserializeCheckpoints(checkpointsStr);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tclientSession.commitTransaction();\n\t\t\treturn new Tag(threadName, checkpoints);\n\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tclientSession.abortTransaction();\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t\tfinally {\n\t\t\tclientSession.close();\n\t\t}\n\t}\n\n\t/**\n\t * Builder class for MongoSaver.\n\t */\n\tpublic static class Builder {\n\t\tprivate MongoClient client;\n\t\tprivate StateSerializer stateSerializer;\n\n\t\tpublic Builder client(MongoClient client) {\n\t\t\tthis.client = client;\n\t\t\treturn this;\n\t\t}\n","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/checkpoint/savers/mongo/MongoSaver.java#L495-L531","documentation":"Any failure inside release()'s Mongo transaction (query errors, transaction errors, network failures, duplicate release) is caught, the transaction is aborted, and the original exception is rethrown wrapped in a RuntimeException with no message of its own. The real cause is in getCause(), so debugging requires unwrapping it.","triggerScenarios":"Any exception thrown between startTransaction() and commitTransaction() in release(): Mongo query failures, write conflicts, network drops, driver errors, or the IllegalStateExceptions thrown earlier in the method being caught by this same catch block.","commonSituations":"MongoDB deployments without replica sets (transactions unsupported) — the commit/startTransaction fails; transient network issues; wrong database credentials surfacing as wrapped exceptions; generic catch blocks in user code logging only e.getMessage() and seeing 'null'.","solutions":["Log the full cause: ex.getCause() (or print the whole stack trace) to find the root error","Ensure MongoDB runs as a replica set (transactions require one); run as a single-node replica set in dev","Catch RuntimeException around release() and unwrap getCause() to branch on specific root causes","Enable driver logging to see the underlying Mongo command failure"],"exampleFix":"// before\ncatch (RuntimeException e) { log.error(\"release failed: \" + e.getMessage()); }\n// after\ncatch (RuntimeException e) {\n    Throwable cause = e.getCause() != null ? e.getCause() : e;\n    log.error(\"release failed\", cause);\n}","handlingStrategy":"retry","validationCode":"// pre-check: transactions require a replica set\nboolean isReplicaSet = mongoClient.getClusterDescription()\n    .getServerDescriptions().stream()\n    .allMatch(sd -> sd.getType() == ServerType.REPLICA_SET_PRIMARY\n        || sd.getType() == ServerType.REPLICA_SET_SECONDARY);","typeGuard":null,"tryCatchPattern":"try {\n    return saver.release(cfg);\n} catch (RuntimeException e) {\n    Throwable root = e;\n    while (root.getCause() != null && root.getCause() != root) root = root.getCause();\n    if (root instanceof MongoTransientTransactionException || root instanceof MongoSocketException) {\n        return retryWithBackoff(() -> saver.release(cfg));\n    }\n    throw root;\n}","preventionTips":["Always log the full stack trace (not just getMessage()) for this wrapper","Run MongoDB as a replica set — standalone servers reject transactions","Use retry with backoff for transient transaction/network failures","Keep release() call paths free of other exceptions so the cause is the true root"],"tags":["mongo","transaction","wrapped-exception","runtime-exception"],"backgroundTag":"database-query-failed","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}