flowable/flowable-engine · error · FlowableException

Couldn't deserialize object in variable

Error message

Couldn't deserialize object in variable '${name}'

What it means

SerializableType rehydrates a persisted variable by reading bytes back through ObjectInputStream. This FlowableException wraps any failure in readObject — typically ClassNotFoundException (the class changed or is missing at deserialization time) or stream corruption (InvalidClassException from serialVersionUID mismatch).

Solutions

  1. Ensure the class stored in the variable exists with an identical serialVersionUID (declare an explicit private static final long serialVersionUID) on all nodes that read the variable
  2. Check the wrapped cause: ClassNotFoundException means missing class/jar on this node — add the dependency
  3. Avoid version-sensitive serialized objects in variables: store JSON/plain values or upgrade via a data migration
  4. If bytes are corrupted, delete/reinitialize the variable rather than retrying deserialization

Example fix

// before
public class CustomerDTO { private String name; }
// after
public class CustomerDTO implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the class can round-trip before relying on serialized variables
Object roundTrip = new ObjectInputStream(
    new ByteArrayInputStream(new ObjectOutputStream(new ByteArrayOutputStream()) {{ writeObject(dto); flush(); }}.toByteArray())).readObject();

Type guard

if (!dto.getClass().getPackage().getName().startsWith("com.myapp")) {
    throw new IllegalStateException("Refusing to store third-party class in serialized variable");
}

Try / catch

try {
    Object value = execution.getVariable(name);
} catch (FlowableException e) {
    if (e.getCause() instanceof ClassNotFoundException) {
        log.error("Missing class for serialized variable {}: {}", name, e.getCause().getMessage());
    } else if (e.getCause() instanceof InvalidClassException) {
        log.error("serialVersionUID mismatch for variable {}: {}", name, e.getCause().getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: Reading a serialized variable from the database when the value's class is not on the classpath of the node deserializing, when the class's serialVersionUID changed between write and read, or when the stored byte array is corrupted/truncated.

Common situations: Rolling deployments where the variable class was renamed/refactored without a fixed serialVersionUID; cluster nodes with different versions of the application jar; restoring a DB dump into an app missing the domain classes.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/816a65386ed108bf. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-variable-service/src/main/java/org/flowable/variable/service/impl/types/SerializableType.java:161

            oos = createObjectOutputStream(baos);
            oos.writeObject(value);
        } catch (Exception e) {
            throw new FlowableException("Couldn't serialize value '" + value + "' in variable '" + valueFields.getName() + "'", e);
        } finally {
            IoUtil.closeSilently(oos);
        }
        return baos.toByteArray();
    }

    public Object deserialize(byte[] bytes, ValueFields valueFields) {
        ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
        try {
            ObjectInputStream ois = createObjectInputStream(bais);
            Object deserializedObject = ois.readObject();

            return deserializedObject;
        } catch (Exception e) {
            throw new FlowableException("Couldn't deserialize object in variable '" + valueFields.getName() + "'", e);
        } finally {
            IoUtil.closeSilently(bais);
        }
    }
    
    protected VariableServiceConfiguration getVariableServiceConfiguration(ValueFields valueFields) {
        String engineType = getEngineType(valueFields.getScopeType());
        Map<String, AbstractEngineConfiguration> engineConfigurationMap = Context.getCommandContext().getEngineConfigurations();
        AbstractEngineConfiguration engineConfiguration = engineConfigurationMap.get(engineType);
        if (engineConfiguration == null) {
            for (AbstractEngineConfiguration possibleEngineConfiguration : engineConfigurationMap.values()) {
                if (possibleEngineConfiguration instanceof HasVariableServiceConfiguration) {
                    engineConfiguration = possibleEngineConfiguration;
                }
            }
        }
        
        if (engineConfiguration == null) {

View on GitHub (pinned to d6d39ce1c6)