FasterXML / FasterXML/jackson-databind
Jackson3 Deserialization with Properties Based constructor fails with tools.jackson.databind.exc.InvalidDefinitionException
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 1.5k
- Avg merge
- 3d 6h
- Merged PRs (30d)
- 28
Description
### Search before asking
- [x] I searched in the [issues](https://github.com/FasterXML/jackson-databind/issues) and found nothing similar.
### Describe the bug
Deserialization fails with InvalidDefinitionException: Cannot construct instance of `AuditEvent` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
### Version Information
3.0.3
### Reproduction
The Test attempts to serialize and deserialize the AuditEvent class described below but fails on Deserialization with:
`tools.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `AuditEvent` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{"timestamp":"2026-01-17T21:00:44.532975Z","principal":"user","type":"type","data":{"keyA":"ValueA","KeyB":"ValueB"}}"; line: 1, column: 2]
at tools.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:70)
at tools.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1950)
at tools.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:448)
at tools.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1480)
at tools.jackson.databind.deser.bean.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1432)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:480)
at tools.jackson.databind.deser.bean.BeanDeserializer.deserialize(BeanDeserializer.java:200)
at tools.jackson.databind.deser.DeserializationContextExt.readRootValue(DeserializationContextExt.java:265)`
Class for Serialization/Deserialization tests:
```java
/**
* Copied from Spring Boot 4.0.0 for testing purposes.
* {@link org.springframework.boot.actuate.audit.AuditEvent}
* Simplified version for Jackson3 Deserializer issues.
*/
public class AuditEvent implements Serializable {
private final Instant timestamp;
private final String principal;
private final String type;
private final Map data;
/**
* Create a new audit event for the current time from data provided as name-value
* pairs.
* @param principal the user principal responsible
* @param type the event type
* @param data the event data in the form 'key=value' or simply 'key'
*/
public AuditEvent(@Nullable String principal, String type, String... data) {
this(Instant.now(), principal, type, convert(data));
}
/**
* Create a new audit event.
* @param timestamp the date/time of the event
* @param principal the user principal responsible
* @param type the event type
* @param data the event data
*/
public AuditEvent(Instant timestamp, @Nullable String principal, String type, Map data) {
Assert.notNull(timestamp, "'timestamp' must not be null");
Assert.notNull(type, "'type' must not be null");
this.timestamp = timestamp;
this.principal = (principal != null) ? principal : "";
this.type = type;
this.data = Collections.unmodifiableMap(data);
}
private static Map convert(String[] data) {
Map result = new HashMap<>();
for (String entry : data) {
int index = entry.indexOf('=');
if (index != -1) {
result.put(entry.substring(0, index), entry.substring(index + 1));
}
else {
result.put(entry, null);
}
}
return result;
}
............................
```
The Test:
```java
@ActiveProfiles("test")
@SpringJUnitConfig({JacksonConfig.class})
@WebMvcTest
public class AuditEventDeserializeMvcItVal {
@Autowired
private JsonMapper jsonMapper;
private AuditEvent startAuditEvent;
private String startAuditEventJson;
private String startAuditEventString;
private JsonMapper jsonMapperManual;
@BeforeEach
void serialize(){
startAuditEvent = new AuditEvent("user", "type", "keyA=ValueA","KeyB=ValueB");
startAuditEventJson = jsonMapper.writeValueAsString(startAuditEvent);
startAuditEventString = startAuditEvent.toString();
System.out.println("Starting AuditEvent Json: %s".formatted(startAuditEventJson));
System.out.println("Starting AuditEvent String: %s".formatted(startAuditEventString));
jsonMapperManual =
JsonMapper
.builder()
.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION)
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
.build();
}
@Test
void deserializeWithSpringMapper() {
AuditEvent auditEvent =
jsonMapper.readValue(
startAuditEventJson,
AuditEvent.class);
Assertions.assertNotNull(auditEvent);
Assertions.assertEquals("user", auditEvent.getPrincipal());
Assertions.assertEquals("type", auditEvent.getType());
Assertions.assertEquals(2, auditEvent.getData().size());
}
@Test
void deserializeWithLocalMapperDef() {
AuditEvent auditEvent =
jsonMapperManual.readValue(
startAuditEventJson,
AuditEvent.class);
Assertions.assertNotNull(auditEvent);
Assertions.assertEquals("user", auditEvent.getPrincipal());
Assertions.assertEquals("type", auditEvent.getType());
Assertions.assertEquals(2, auditEvent.getData().size());
}
}
```
### Expected behavior
AuditEvent should be constructed as a public constructor for the properties supplied exists.
### Additional context
The defaut builder(which was always enough in Jackson2) fails but I have a Spring config which fails as well.
```java
@Bean
public JsonMapperBuilderCustomizer includeSourceInLocationCustomizer() {
return builder -> {
builder.enable(StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION);
builder.constructorDetector(
ConstructorDetector.USE_PROPERTIES_BASED
.withAllowImplicitWithDefaultConstructor(ConstructorDetector.DEFAULT_ALLOW_IMPLICIT_WITH_DEFAULT_CONSTRUCTOR)
.withRequireAnnotation(false)
.withAllowJDKTypeConstructors(true)
.withSingleArgMode(ConstructorDetector.SingleArgConstructor.PROPERTIES));
};
}
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.