javaspringspring-bootjacksonjackson-databind

Spring Boot 2.x Jackson ObjectMapper instance does not get injected in service class - tried both boot default & with bean method from config class


I am seeing a weird behavior with injecting Jackson ObjectMapper with Spring Boot 2.3.4.RELEASE.

I have both spring-boot-starter-web & spring-boot-starter-json in my maven dependencies. Yet when i auto-wire ObjectMapper in one of my @Service classes, it does not get injected & the reference is null.

I then tried creating & returning one manually in an @Primary @Bean bean method of a @Configurationclass but still with the same result.

I know the @Configuration is working fine since other bean methods in it are used to inject other objects correctly. In addition i also added a log statement inside the bean method that returns the ObjectMapper instance which is also getting logged, yet the reference on the @Service class is STILL null? (i also tried adding @AutoConfigureAfter(JacksonAutoConfiguration.class) in vain)

Anyone else faced this or knows what's going on here? please throw light..

Thanks

EDIT (10/22): Below is a snippet of the @Service class,

@Lazy
@Service
@EnableSpringConfigured
public class SampleServiceSingletonService {

  private static final Logger LOGGER = LoggerFactory.getLogger(SampleServiceSingletonService.class);

  @Autowired
  protected ThreadPoolTaskScheduler threadPoolTaskScheduler;
  
  @Autowired
  private ObjectMapper objectMapper;
  
  @EventListener({ApplicationReadyEvent.class})
  private void initOnAppReady() {
      LOGGER.info("No need to create ObjectMapper instances, most customizations can be set/overriden in application.properties, look at the one here for reference");
      LOGGER.info("Injected ObjectMapper: {}", objectMapper);
      LOGGER.info("Init on app ready complete..");
      //...
  }

EDIT 2 (10/22): For others who face this,

The problem appears to be (thanks to @M. Deinum below) that the instantiation and/or injection doesn't seem to happen at the time the ApplicationReadyEvent is fired & its event handlers are invoked. That seems strange to me for two reasons, one is as per the docs, the event is "published as late as conceivably possible to indicate that the application is ready to service requests...since all initialization steps will have been completed by then" and second i have not seen this behavior with other injections of other objects till now so i never suspected this as a cause. Below is a snippet where i see the injection working,

@Lazy
@Service
public class SampleServiceSingletonService {

  private static final Logger LOGGER = LoggerFactory.getLogger(SampleServiceSingletonService.class);

  @Autowired
  public ThreadPoolTaskScheduler threadPoolTaskScheduler;
  
  @Autowired
  public ThreadPoolTaskExecutor threadPoolTaskExecutor;
  
  @Autowired
  public ObjectMapper objectMapper;
  
  @EventListener(ApplicationReadyEvent.class)
  private void initOnAppReady() {
      // Its null here..
      //LOGGER.info("The Injected ObjectMapper: {}", objectMapper);
      LOGGER.info("Init on app ready complete..");
      runAsync();
  }
  
  @Async
  public void runAsync() {
      LOGGER.info("This is run asynchronously.");
      // Its no longer null here
      LOGGER.info("The Injected ObjectMapper: {}", objectMapper);
  }

}

Thanks


Solution

  • You have marked your bean with @Lazy. When put on a type what will happen is that a lazy proxy will be created for the object. In this case a class based proxy will be created (no interface on the class) and for this a subclass will be created to add the lazy behavior.

    However due to the fact that your method is private there is no way to override this method in the dynamic created class and thus it will be called on the proxy instead or relaying it through to the actual object. The proxy object will not have any dependencies injected, at least not the private fields.

    So to fix, make your method protected or public so it can be properly subclasses and overridden. Or remove the @Lazy if you don't need it.

    See this blog (written by me) for a more detailed explanation.