{"record":{"id":"3a81ab8f10f8288b","repo":"prestodb/presto","slug":"plan-serialization-error-3a81ab","errorCode":"PLAN_SERIALIZATION_ERROR","errorMessage":"Cannot serialize plan to JSON","messagePattern":"Cannot serialize plan to JSON","errorType":"error_code","errorClass":"PrestoException","httpStatus":null,"severity":"error","filePath":"presto-main-base/src/main/java/com/facebook/presto/sql/planner/CanonicalPlanGenerator.java","lineNumber":1224,"sourceCode":"    private boolean shouldMergeJoinNodes(JoinType type)\n    {\n        return type.equals(JoinType.INNER);\n    }\n\n    private VariableReferenceExpression rename(VariableReferenceExpression variable, String nameHint, Context context)\n    {\n        VariableReferenceExpression newVariable = variableAllocator.newVariable(Optional.empty(), nameHint, variable.getType());\n        context.mapExpression(variable, newVariable);\n        return newVariable;\n    }\n\n    private String writeValueAsString(Object object)\n    {\n        try {\n            return objectMapper.writeValueAsString(object);\n        }\n        catch (JsonProcessingException e) {\n            throw new PrestoException(PLAN_SERIALIZATION_ERROR, \"Cannot serialize plan to JSON\", e);\n        }\n    }\n\n    private static EquiJoinClause canonicalize(EquiJoinClause criteria, Context context)\n    {\n        VariableReferenceExpression left = inlineAndCanonicalize(context.getExpressions(), criteria.getLeft());\n        VariableReferenceExpression right = inlineAndCanonicalize(context.getExpressions(), criteria.getRight());\n        return left.compareTo(right) > 0 ? new EquiJoinClause(left, right) : new EquiJoinClause(right, left);\n    }\n\n    private static Optional<EquiJoinClause> toEquiJoinClause(RowExpression expression)\n    {\n        if (!(expression instanceof CallExpression)) {\n            return Optional.empty();\n        }\n        CallExpression callExpression = (CallExpression) expression;\n        boolean isValid = callExpression.getDisplayName().equals(EQUAL.getFunctionName().getObjectName())\n                && callExpression.getArguments().size() == 2","sourceCodeStart":1206,"sourceCodeEnd":1242,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-main-base/src/main/java/com/facebook/presto/sql/planner/CanonicalPlanGenerator.java#L1206-L1242","documentation":"CanonicalPlanGenerator.writeValueAsString serializes objects (canonical plans/subplans) with Jackson during canonical planning. On JsonProcessingException it throws PLAN_SERIALIZATION_ERROR. Same family as CanonicalPlan serialization but triggered from the generator's internal write path, meaning a generated canonical structure could not be converted to JSON.","triggerScenarios":"Canonical planning of a distributed plan (e.g. for EXPLAIN or worker exchange of canonical plans) where a node/field lacks a Jackson serializer or holds an unexpected runtime type.","commonSituations":"Custom connectors exposing non-serializable table handles; Presto version mismatches between coordinator and workers; plugins adding expressions/types without codec registration.","solutions":["Check the wrapped JsonProcessingException cause to identify the unserializable class","Register missing Jackson serializers/modules for custom types via the plugin's JSON binding setup","Align Presto versions across the cluster and retry; upgrade if the failing node type is a known fixed bug"],"exampleFix":"// plugin side\n// before\npublic ObjectMapper getContext(Class<?> type) { return new ObjectMapper(); }\n// after\npublic ObjectMapper getContext(Class<?> type) {\n    return jsonMapperBuilder().modules(new MyConnectorModule()).build();\n}","handlingStrategy":"try-catch","validationCode":"// Verify custom types serialize before canonical planning runs\ntry { objectMapper.writeValueAsString(myConnectorTableHandle); } catch (JsonProcessingException e) { /* fix serializer registration */ }","typeGuard":null,"tryCatchPattern":"try { canonicalPlanGenerator.generate(plan); } catch (PrestoException e) { if (e.getErrorCode().equals(PLAN_SERIALIZATION_ERROR.toErrorCode())) { inspectCauseAndRegisterSerializer(e.getCause()); } else throw e; }","preventionTips":["Register Jackson serializers for every custom connector handle/expression type","Avoid storing non-serializable objects (clients, lambdas) in plan metadata","Test canonical planning in CI for custom connectors","Use identical builds across the cluster"],"tags":["json","serialization","planner"],"backgroundTag":"plan-serialization-failed","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}