I’m using Spring Boot 3.5.3 with GraalVM Native Image via 'org.graalvm.buildtools.native' version '0.11.1' plugin.
I also use Jackson for polymorphic deserialization using @JsonSubTypes.
Example setup:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),
@JsonSubTypes.Type(value = Cat.class, name = "Cat")
})
public interface Animal {
String getName();
}
@Getter
@Setter
public class Dog implements Animal {
private String name;
}
@Getter
@Setter
public class Cat implements Animal {
private String name;
}
@Getter
@Setter
public class Zoo {
private Animal animal;
}
And a simple Spring REST controller:
@RestController
public class ZooController {
@PostMapping("/zoo")
public void createZoo(@RequestBody Zoo zoo) {
System.out.println(zoo.getAnimal().getName());
}
}
When I build a native image via gradle clean nativeCompile, I see that subtypes Dog and Cat are not included in the build/generated/aotResources/META-INF/native-image/project-path/reflect-config.json, whereas Zoo and Animal are included.
And If I try to call the /zoo endpoint with this request body:
{
"animal": {
"@type": "Dog",
"name": "dog_name"
}
}
the following exception naturally occurs:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.example.model.Dog: cannot deserialize from Object value (no delegate- or property-based Creator): this appears to be a native image, in which case you may need to configure reflection for the class that is to be deserialized
Question:
Is it possible to set up the org.graalvm.buildtools.native plugin to automatically add @JsonSubTypes implementations to reflect-config.json?
Or should I really add manually each subtype to reflection-config.json (or via RuntimeHintsRegistrar)?
The part that is responsible for exporting the reflection configuration for GraalVM is the BindingReflectionHintsRegistrar.
If you look at the implementation there is a section that iterates the annotations from Jackson.
private void registerHintsForClassAttributes(ReflectionHints hints, MergedAnnotation<Annotation> annotation) {
annotation.getRoot().asMap().forEach((attributeName, value) -> {
if (value instanceof Class<?> classValue && value != Void.class) {
if (attributeName.equals("builder")) {
hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS);
}
else {
hints.registerType(classValue, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}});
}
It will by default export the information of the value attribute and there is special handling of the builder attribute for the @JsonDeserialize annotation. However as @JsonSubTypes has its value attribute reference another annotation it doesn't loop back into the @JsonSubTypes.Type annotation and hence appears not to include the specific types (or binding).
To fix you could add your own RuntieHintsRegistrar for these cases but I would also register an issue with the Spring Framework to fix this, what I think is an, omission.