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:
@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
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 beforeMainBuilderUtil
. 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 sureApplicationContextProvider
gets created first.The cleaner fix is to recognize that ‘MainChannelCredentialsUtil’ is already being autowired into the constructor for
MainBuilderUtil
. The constructor could initializecredentialMain
andclientMain
and not useApplicationContextProvider
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 intoMainBuilderUtil
, then there would be no guarantee that it would get created beforeMainBuilderUtil
. In that case, it would not be available throughApplicationContextProvider
either.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.If you are directly referencing those
public final
fields from other classes, stop doing that and instead inject theMainBuilderUtil
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.