prestodb/presto · error · PrestoException

PLAN_SERIALIZATION_ERROR

PLAN_SERIALIZATION_ERROR

Error message

Cannot serialize plan to JSON

What it means

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.

Source

Thrown at presto-main-base/src/main/java/com/facebook/presto/sql/planner/CanonicalPlanGenerator.java:1224

    private boolean shouldMergeJoinNodes(JoinType type)
    {
        return type.equals(JoinType.INNER);
    }

    private VariableReferenceExpression rename(VariableReferenceExpression variable, String nameHint, Context context)
    {
        VariableReferenceExpression newVariable = variableAllocator.newVariable(Optional.empty(), nameHint, variable.getType());
        context.mapExpression(variable, newVariable);
        return newVariable;
    }

    private String writeValueAsString(Object object)
    {
        try {
            return objectMapper.writeValueAsString(object);
        }
        catch (JsonProcessingException e) {
            throw new PrestoException(PLAN_SERIALIZATION_ERROR, "Cannot serialize plan to JSON", e);
        }
    }

    private static EquiJoinClause canonicalize(EquiJoinClause criteria, Context context)
    {
        VariableReferenceExpression left = inlineAndCanonicalize(context.getExpressions(), criteria.getLeft());
        VariableReferenceExpression right = inlineAndCanonicalize(context.getExpressions(), criteria.getRight());
        return left.compareTo(right) > 0 ? new EquiJoinClause(left, right) : new EquiJoinClause(right, left);
    }

    private static Optional<EquiJoinClause> toEquiJoinClause(RowExpression expression)
    {
        if (!(expression instanceof CallExpression)) {
            return Optional.empty();
        }
        CallExpression callExpression = (CallExpression) expression;
        boolean isValid = callExpression.getDisplayName().equals(EQUAL.getFunctionName().getObjectName())
                && callExpression.getArguments().size() == 2

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Check the wrapped JsonProcessingException cause to identify the unserializable class
  2. Register missing Jackson serializers/modules for custom types via the plugin's JSON binding setup
  3. Align Presto versions across the cluster and retry; upgrade if the failing node type is a known fixed bug

Example fix

// plugin side
// before
public ObjectMapper getContext(Class<?> type) { return new ObjectMapper(); }
// after
public ObjectMapper getContext(Class<?> type) {
    return jsonMapperBuilder().modules(new MyConnectorModule()).build();
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify custom types serialize before canonical planning runs
try { objectMapper.writeValueAsString(myConnectorTableHandle); } catch (JsonProcessingException e) { /* fix serializer registration */ }

Try / catch

try { canonicalPlanGenerator.generate(plan); } catch (PrestoException e) { if (e.getErrorCode().equals(PLAN_SERIALIZATION_ERROR.toErrorCode())) { inspectCauseAndRegisterSerializer(e.getCause()); } else throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: Custom connectors exposing non-serializable table handles; Presto version mismatches between coordinator and workers; plugins adding expressions/types without codec registration.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/3a81ab8f10f8288b. Report an issue: GitHub.