2

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

lmk
  • 654
  • 5
  • 21
  • The autowired field cannot be `null`. If autowiring fails the application will not even start. So this means you are creating an instance of that service yourself instead of using a spring managed instance. – M. Deinum Oct 22 '20 at 09:49
  • @M.Deinum i have updated the question with a snippet of the `@Service` class where i am autowiring.. – lmk Oct 22 '20 at 10:10
  • I don't think application will fail to start if autowired field is null...are you sure? – lmk Oct 22 '20 at 10:14
  • 1
    I'm not pretty sure, I'm 100% sure, however in your case it is different, due the the `@Lazy` (also do you really need `@EnableSpringConfigured`, which is only useful if you use AspectJ with loadtime or compile time weaving). Due to the `@Lazy` you get a proxy, because your method is `private` it will be invoked on the proxy, which has nothing injected. Make the method `public` or remove the `@Lazy`. See https://deinum.biz/2020-07-03-Autowired-Field-Null/#aop for a detailed explanation. – M. Deinum Oct 22 '20 at 10:19
  • @M.Deinum, thank you, as u noted `@EnableSpringConfigured` need not be there its a remnant from some earlier code which i missed removing my bad..similarly i missed the private modifier thanks for catching that but `@Lazy` i only added towards the end. I reverted & also changed modifier to public, but still the same result :-( – lmk Oct 22 '20 at 10:42
  • In my snippet above, `threadPoolTaskScheduler` which is `protected` is getting injected fine from my bean method – lmk Oct 22 '20 at 10:45
  • 1
    The fact that it is `protected` is weird and feels like you are setting it yourself instead of letting Spring manage it, please add your configuration of the bean as well. The whole behavior, still acts and feels like you are constructing an instance yourself instead of letting spring handle it. The only other reason I can see is that it is very early instantiated which leaves you with a partially instantiated object. This might be due to the `@EventListener` you are probably better of implementing `ApplicationListener`. – M. Deinum Oct 22 '20 at 11:54
  • Hi @M.Deinum thank you, your latter point was indeed the problem, it appears that the instantiation is not done at the time the`ApplicationReadyEvent` event handler ( `@EventListener(ApplicationReadyEvent.class)`) gets executed. Once i move the logger statement to an `@Async` method that i call from the same event handler i see its injected & the object reference getting logged!.Which raises more questions for me, i thought as mentioned in the docs for `ApplicationReadyEvent` it will be "published as late as conceivably possible to indicate that the application is ready to service requests"? – lmk Oct 23 '20 at 03:12
  • But that's not whats happening in the case of ObjectMapper injection where at the time the `ApplicationReadyEvent` is fired the injection of ObjectMapper is NOT done. You mention i would need to implement `ApplicationListener`, how is that different from `@EventListener(ApplicationReadyEvent.class)` annotated method? I thought the latter that i am doing is the more newer of doing the same thing..is it not? – lmk Oct 23 '20 at 03:18
  • Your ObjectMapper property is private, you should add getter and setter or make it public (as it is in the working class) otherwise Spring is not able to assign the field. That's why it is null. In my opinion better adding getter and setter – Massimo Petrus Oct 23 '20 at 05:23
  • 1
    I'm no Spring expert. I'm just interested in this issue because I've had some problems that were similar. In your case, the one thing that worries me is seeing `@Lazy`. Have you tried removing that just to see if the autowiring then always happens before `initOnAppReady` is called? I figure this shouldn't be an issue because while `@Lazy` might cause the creation of `SampleServiceSingletonService` to be delayed, you'd think Spring would still fully construct and wire it before calling a method on it. - btw, I use ApplicationListener for waiting for initialization. – CryptoFool Oct 23 '20 at 05:49
  • Because an `ApplicationListener` and `@EventListener` althoug looking the same aren't and are treated very differently. The annotation one is processed by a postprocessor to create a proxy to be invoked when needed, the interface one doesn't need that. – M. Deinum Oct 23 '20 at 08:04
  • 1
    Also you made what `public`? The field or the method? As I was mentioning the method **not** the field. – M. Deinum Oct 23 '20 at 08:05
  • @Steve , thanks for that thought, i will try removing `@Lazy` to see if that makes a difference, just for curiosity, although as you mention one would expect (as i did) that Spring would still fully construct and wire it before calling the `ApplicationReadyEvent` handler on it.. – lmk Oct 23 '20 at 21:19
  • @M.Deinum , thanks once again..I am sorry my bad, i missed your point about the method. I remember reading the behavior you mention w.r.t the `@Lazy` and proxy somewhere & forgot about it as a culprit...i too think that should be it...lemme try that & update it shortly. – lmk Oct 23 '20 at 21:29
  • @M.Deinum YEP, that was it! Please add it as answer so i can accept that to make it easy for others if they make the same mistake i made..thanks once again! – lmk Oct 23 '20 at 21:46
  • BTW, little bit of history on why i had that lingering `@EnableSpringConfigured` which i missed cleaning, i also had (still have) issues with spring aop injections on non-managed objects after i set it up (been using it a lot before java 11 to which we are upgrading now) and there also i was trying to log it in this (`private`) even handler method & seeing that the injected fields were null. But the difference here was that it was still null even when done in the `@Asyc` method. Given other issues with aspectj in java 11 i think its a separate issue, will put that as separate question on SO.. – lmk Oct 23 '20 at 21:59

2 Answers2

1

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 for a more detailed explanation.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
-2

You should add setter and getter to your

     private ObjectMapper objectMapper;

so Spring is able to assign it a value

Massimo Petrus
  • 1,881
  • 2
  • 13
  • 26
  • Hi @Massimo Petrus i tried changing it to public & still had the issue also i had other private & protected members that get properly injected aswell..the problem was that the instantiation and/or injection doesn't seem to happen at the time the `ApplicationReadyEvent` was fired.. – lmk Oct 23 '20 at 05:33
  • Sorry, i'm not sure of what you say. Are you sure that SprigBoot will autowire an objectmapper as 'it is' and without any configuration ? See this question https://stackoverflow.com/questions/30060006/how-do-i-obtain-the-jackson-objectmapper-in-use-by-spring-4-1 – Massimo Petrus Oct 23 '20 at 05:44
  • Adding a getter/stter won\t help. – M. Deinum Oct 23 '20 at 08:05
  • Yes, @MassimoPetrus please see the update i made on the question, Yes it will autowire as long as jackson is on classpath, please see [here](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-customize-the-jackson-objectmapper). it appears that the reference is null only at the time the event handler is invoked not after... – lmk Oct 23 '20 at 21:14
  • Tnx. Very strange the topic of autowiring coming after ApplicationEvent firing – Massimo Petrus Oct 24 '20 at 09:45