skip to Main Content

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

2

Answers


  1. 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.

    Login or Signup to reply.
  2. A class like the ApplicationContextProvider is a hack and should be avoided. The problem is the fact that you are using this in a component that is already spring managed and gets dependencies. To properly fix, rewrite your class to use dependency injection instead of the hack.

    @Component
    public class MainBuilderUtil {
        private MainChannelCredentialsUtil mainChannelCredentialsUtil;
    
        private final OAuth2Credential credentialMain;
        private final Client ClientMain;
    
        @Autowired
        private MainBuilderUtil(MainChannelCredentialsUtil mainChannelCredentialsUtil) {
            this.mainChannelCredentialsUtil = mainChannelCredentialsUtil;
            this.credentialMain = new OAuth2Credential("provider", mainChannelCredentialsUtil.getMainToken();
            this.ClientMain = ClientBuilder.builder()
                        .withEnableChat(true)
                        .withChatAccount(credentialMain)
                        .build();
        }
        //getters
    }
    

    If you are directly referencing those public final fields from other classes, stop doing that and instead inject the MainBuilderUtil in those classes and use accessor methods (getters) to access the properties (or even bvetter make those beans and inject them instead).

    In short using a class like the ApplicationContextProvider is a hack and should be avoided.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search