Search code examples
spring-bootjacksonnativelombokgraalvm

Spring Boot 3 Native with lombok immutable (@Value)


I've been trying to create a example project using spring boot 3, graalvm, lombok and jpa. Everything looks perfect when I execute the test using the main method. But I have a problem when I run the native binary.

First I create the binary:

mvn -Pnative native:build

Then I run the project:

./target/demo

The project start fine, but when I try to create a new record using curl:

curl --location --request POST 'localhost:8080' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "test",
    "surname": "surname"
}'

I see this error in console:

2022-12-21T01:51:48.055+01:00  WARN 105751 --- [nio-8080-exec-2] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.example.demo.dto.ExampleDto]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Builder class `com.example.demo.dto.ExampleDto$ExampleDtoBuilder` does not have build method (name: 'build')
2022-12-21T01:51:48.055+01:00  WARN 105751 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content-Type 'application/json;charset=UTF-8' is not supported]

My DTO looks like this:

@Value
@Builder
@Jacksonized
public class ExampleDto {
    Integer id;
    String name;
    String surname;

}

The controller:

@RestController
@RequestMapping("/")
@AllArgsConstructor
@Slf4j
public class ExampleController {

    private final ExampleService exampleService;

    @GetMapping
    public ExampleDto findByName(@RequestParam String name) {
        return exampleService.getByName(name);
    }

    @PostMapping
    public void save(@RequestBody ExampleDto dto) {
        exampleService.save(dto);
    }
}

The problem is pretty clear, there is a problem with the "build" method that is created by lombok. If I inspect the generated source I can see the method in the right place. Apparently GraalVM (or jackson) does not see the method.

I also tried to add some traces in a GET method just to be sure that the controller has access to to builder.build() method and the method exist in runtime. (2nd clue that gives me the idea that there is a configuration problem.

I removed the @Value,@Jacksonized and @Builder and put just @Data and everything works fine.

Any idea? Should I add an extra configuration?

The example project is in github: https://github.com/elysrivero99/spring-boot-3-native-demo


Solution

  • After a while I found the answer. You have to add the hints as:

    @SpringBootApplication
    @ImportRuntimeHints(DemoApplication.DemoApplicationRuntimeHints.class)
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    
        static class DemoApplicationRuntimeHints implements RuntimeHintsRegistrar {
    
            @SneakyThrows
            @Override
            public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
                hints.reflection()
                        .registerConstructor(ExampleDto.ExampleDtoBuilder.class.getDeclaredConstructor(), ExecutableMode.INVOKE)
                        .registerMethod(
                                ExampleDto.ExampleDtoBuilder.class.getMethod("build"), ExecutableMode.INVOKE);
            }
        }
    }