FasterXML/jackson-databind · error · IllegalArgumentException

Missing constructor (broken JDK (de)serialization?)

Error message

Missing constructor (broken JDK (de)serialization?)

What it means

InnerClassProperty's JDK-deserialization constructor failed to recover the inner-class Constructor from the AnnotatedConstructor wrapper. InnerClassProperty instances are Java-serializable, and on readObject the transient Constructor must be rebuilt from the annotated form; if the annotated form is null (corrupted stream or a subclass that nulled _annotated), the instance cannot be reconstructed.

Source

Thrown at src/main/java/tools/jackson/databind/deser/impl/InnerClassProperty.java:49

    public InnerClassProperty(SettableBeanProperty delegate,
            Constructor<?> ctor)
    {
        super(delegate);
        _creator = ctor;
    }

    /**
     * Constructor used with JDK Serialization; needed to handle transient
     * Constructor, wrap/unwrap in/out-of Annotated variant.
     */
    protected InnerClassProperty(SettableBeanProperty src, AnnotatedConstructor ann)
    {
        super(src);
        _annotated = ann;
        _creator = (_annotated == null) ? null : _annotated.getAnnotated();
        if (_creator == null) {
            throw new IllegalArgumentException("Missing constructor (broken JDK (de)serialization?)");
        }
    }

    @Override
    protected SettableBeanProperty withDelegate(SettableBeanProperty d) {
        if (d == this.delegate) {
            return this;
        }
        return new InnerClassProperty(d, _creator);
    }

    /*
    /**********************************************************
    /* Deserialization methods
    /**********************************************************
     */

    @Override

View on GitHub (pinned to 87876ca5c0)

Solutions

  1. Do not Java-serialize Jackson deserializers; rebuild the ObjectMapper in the target JVM instead.
  2. Ensure both sides of Java serialization run the same Jackson version and have the same inner class on the classpath.
  3. If you must transport state, serialize the configuration (modules, mixins) and rebuild the mapper, not the deserializer instances.
  4. Upgrade Jackson on both ends; older versions had known bugs in InnerClassProperty re-resolution.

Example fix

// before
ObjectOutputStream out = ...;
out.writeObject(mapper.getDeserializerFor(Foo.class)); // later readObject -> throws

// after (rebuild in target JVM)
ObjectMapper mapper = JsonMapper.builder().addModule(...).build();
ValueDeserializer<Foo> d = mapper.getDeserializerFor(Foo.class);
Defensive patterns

Strategy: retry

Validate before calling

// cannot validate from user code; avoid Java-serializing deserializers

Type guard

// no type guard; recovery is rebuilding the mapper

Try / catch

try { (ObjectInputStream) in.readObject(); }
catch (IllegalArgumentException e) {
    if (e.getMessage().contains("broken JDK (de)serialization")) {
        // rebuild ObjectMapper locally instead of transporting it
    } else throw e;
}

Prevention

When it happens

Trigger: Java-serializing a BeanDeserializer that contains an InnerClassProperty and deserializing it in a JVM/classloader where the AnnotatedConstructor cannot be re-resolved; a corrupted or hand-crafted serialized stream; subclassing InnerClassProperty and clearing _annotated.

Common situations: Distributing pre-built ObjectMapper/deserializer caches via Java serialization across mismatched Jackson versions; classloader boundary changes (modular runtime, OSGi) where reflection metadata is unavailable; tests that serialize/deserialize deserializers directly.

Related errors


AI-assisted analysis of FasterXML/jackson-databind@87876ca5c0 (2026-08-11). Data as JSON: /api/errors/c637e573619401da. Report an issue: GitHub.