javaspring-bootnullpointerexceptionapplicationcontext

Why ApplicationContextProvider throws NullPointerException on another system


I was using my spring boot 3.2.4 app on Win 10 and Win Server 2019 and it worked fine. I know my architecture of the app is not good, but it worked. After I had to migrate to Ubuntu 24.04 , running the same app throws NullPointerException in ApplicationContextProvider.getApplicationContext(). JDK on all machines is the same - openjdk-18.0.1

I am using a "simple" way to get applicationContext, which i found convenient, found here:

ApplicationContextProvider

@Component
public class ApplicationContextProvider implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        ApplicationContextProvider.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

The class where the error occurs:

@Component
public class MainBuilderUtil {
    MainChannelCredentialsUtil mainChannelCredentialsUtil;

    public final OAuth2Credential credentialMain = new OAuth2Credential("provider",
            ApplicationContextProvider.getApplicationContext().getBean(MainChannelCredentialsUtil.class).getMainToken());

    @Autowired
    private MainBuilderUtil(MainChannelCredentialsUtil mainChannelCredentialsUtil) {
        this.mainChannelCredentialsUtil = mainChannelCredentialsUtil;
    }

    public final Client ClientMain =
            ClientBuilder.builder()
                    .withEnableChat(true)
                    .withChatAccount(credentialMain)
                    .build();

    //getters
}
    

error:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.bot.springbootbot.connections.channels.builder_utils.MainBuilderUtil]: Constructor threw exception
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:221) ~[spring-beans-6.1.5.jar!/:6.1.5]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:111) ~[spring-beans-6.1.5.jar!/:6.1.5]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:315) ~[spring-beans-6.1.5.jar!/:6.1.5]
    ... 24 common frames omitted
Caused by: java.lang.NullPointerException: Cannot invoke "org.springframework.context.ApplicationContext.getBean(java.lang.Class)" because the return value of "com.bot.springbootbot.ApplicationContextProvider.getApplicationContext()" is null
    at com.bot.springbootbot.connections.channels.builder_utils.MainBuilderUtil.<init>(MainBuilderUtil.java:18) ~[!/:0.0.1-SNAPSHOT]
    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[na:na]
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[na:na]
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:208) ~[spring-beans-6.1.5.jar!/:6.1.5]
    ... 26 common frames omitted

Also there is this Warn during tomcat starting before ERROR, basically the same:

WARN 6417 --- [SpringBootBot] [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mainBuilderUtil' defined in URL [jar:nested:/usr/SpringBootBot/target/SpringBootBot-0.0.1-SNAPSHOT.jar/!BOOT-INF/classes/!/com/bot/springbootbot/connections/channels/builder_utils/MainBuilderUtil.class]: Failed to instantiate [com.bot.springbootbot.connections.channels.builder_utils.MainBuilderUtil]: Constructor threw exception

I know this class is not even needed to be a Spring Bean, but i was learning injections and ApplicationContextProvider workaround worked. So before rewriting my whole code i just want to understand why the code works on win10 and not on ubuntu. I tried different jdks, nothing works. Running it with java -jar in all cases. Maybe there is an obvious cause and solution that i can't see. Tried to do steps from here - no effect: applicationContextProvider is not being called


Solution

  • I think the error has to do with the order of component initialization when Spring is building the context.

    There is no guarantee that ApplicationContextProvider is going to be instantiated before MainBuilderUtil. The order in which the beans are built can change based upon many different things and can change seemingly randomly when spring does not know about a required dependency.

    The easy fix for this is to use @DependsOn on MainBuilderUtil to make sure ApplicationContextProvider gets created first.

    The cleaner fix is to recognize that 'MainChannelCredentialsUtil' is already being autowired into the constructor for MainBuilderUtil. The constructor could initialize credentialMain and clientMain and not use ApplicationContextProvider at all.

    In general it is a bad idea to try to use the applicationContext to get beans during component instantiation because it breaks Spring's normal dependency resolution mechanisms and requires using hacks like @DependsOn

    I would also like to note that if MainChannelCredentialsUtil was not being auto wired into MainBuilderUtil, then there would be no guarantee that it would get created before MainBuilderUtil. In that case, it would not be available through ApplicationContextProvider either.