Spring – How to Autowire bean in a static class

Spring – How to Autowire bean in a static class

There are situations in witch you need to access Spring ApplicationContext or any other Spring bean from a static method.

At the beginning I would like to emphasise, that wiring a bean into a static class is strongly discouraged. This article is for those who insist on taking going ahead with it and are aware of what they are doing.

The problem

We have a static utility class called StaticUtils which looks like below:

import com.bettercoding.spring.config.MyConfig;
import org.springframework.beans.factory.annotation.Autowired;

public final class StaticUtils {

    @Autowired
    private static MyConfig myConfig;

    private StaticUtils() {
    }

    public static void printFoo() {
        System.out.println("Foo:" + myConfig.getFoo());
    }
}

As you can see there is an @Autowired annotation in the 6th line and we expect that Spring inject a proper bean here. But pay attention to that the wired field is static.

Below is an example MyConfig class used in the utility class. It requires to pass foo property.

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyConfig {
    @Value("${foo}")
    private String foo;

    public String getFoo() {
        return foo;
    }
}

Let’s write an application class and to test it. Remember to pass -Dfoo=bar property.

import com.bettercoding.spring.utils.StaticUtils;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }

    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                System.out.println("Hello World");
                StaticUtils.printFoo();
            }
        };
    }
}

The result is NullPointerException.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.2.RELEASE)

2018-06-02 13:45:47.435  INFO 8077 --- [           main] com.bettercoding.spring.MyApp            : Starting MyApp on bc-vbox with PID 8077 (/home/better-coding/Workspace/static-initializer/out/production/classes started by better-coding in /home/better-coding/Workspace/static-initializer)
2018-06-02 13:45:47.442  INFO 8077 --- [           main] com.bettercoding.spring.MyApp            : No active profile set, falling back to default profiles: default
2018-06-02 13:45:47.539  INFO 8077 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@1b68b9a4: startup date [Sat Jun 02 13:45:47 CEST 2018]; root of context hierarchy
2018-06-02 13:45:48.495  INFO 8077 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-06-02 13:45:48.516  INFO 8077 --- [           main] com.bettercoding.spring.MyApp            : Started MyApp in 1.689 seconds (JVM running for 2.339)
Hello World
2018-06-02 13:45:48.519  INFO 8077 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-06-02 13:45:48.524 ERROR 8077 --- [           main] o.s.boot.SpringApplication               : Application run failed

java.lang.IllegalStateException: Failed to execute CommandLineRunner
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:781) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	at com.bettercoding.spring.MyApp.main(MyApp.java:13) [classes/:na]
Caused by: java.lang.NullPointerException: null
	at com.bettercoding.spring.utils.StaticUtils.printFoo(StaticUtils.java:19) ~[classes/:na]
	at com.bettercoding.spring.MyApp$1.run(MyApp.java:22) ~[classes/:na]
	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:797) [spring-boot-2.0.2.RELEASE.jar:2.0.2.RELEASE]
	... 5 common frames omitted

2018-06-02 13:45:48.525  INFO 8077 --- [           main] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1b68b9a4: startup date [Sat Jun 02 13:45:47 CEST 2018]; root of context hierarchy
2018-06-02 13:45:48.527  INFO 8077 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

Process finished with exit code 1

As you can see Spring is not able to inject beans directly to static class and the NullPointerException occurred.

The solution

There are several ways to achieve it, but some of them may fail when you run an application it in different conditions like passing application properties from command line instead of application properties file.

Using StaticContextInitializer class

The most elegant and quite safe method I’ve known is what I called StaticContextInitializer. The idea is to create component class called StaticContextInitializer, which is responsible to initialize static classes in its @PostConstruct annotated method. Let’s see it in practice.

Step 1 – change utility class

Edit StaticUtils class, remove the @Autowired annotation and add setter method for myConfig field. The class after changes should looks like below.

import com.bettercoding.spring.config.MyConfig;

public final class StaticUtils {

    private static MyConfig myConfig;

    public static void setMyConfig(MyConfig myConfig) {
        StaticUtils.myConfig = myConfig;
    }

    private StaticUtils() {
    }

    public static void printFoo() {
        System.out.println("Foo:" + myConfig.getFoo());
    }
}

Step 2  – create StaticContextInitializer compoment

Create class called StaticContextInitializer with init method annotated by @PostConstruct. Pass myConfig to the StaticUtils class.

import com.bettercoding.spring.config.MyConfig;
import com.bettercoding.spring.utils.StaticUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class StaticContextInitializer {

    @Autowired
    private MyConfig myConfig;

    @Autowired
    private ApplicationContext context;

    @PostConstruct
    public void init() {
        StaticUtils.setMyConfig(myConfig);
    }
}

Step 3 – test

Run the application once again and you will see the expected result.

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.2.RELEASE)

2018-06-02 14:04:21.993  INFO 8556 --- [           main] com.bettercoding.spring.MyApp            : Starting MyApp on bc-vbox with PID 8556 (/home/better-coding/Workspace/static-initializer/out/production/classes started by better-coding in /home/better-coding/Workspace/static-initializer)
2018-06-02 14:04:21.998  INFO 8556 --- [           main] com.bettercoding.spring.MyApp            : No active profile set, falling back to default profiles: default
2018-06-02 14:04:22.141  INFO 8556 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d36e4cd: startup date [Sat Jun 02 14:04:22 CEST 2018]; root of context hierarchy
2018-06-02 14:04:23.078  INFO 8556 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-06-02 14:04:23.093  INFO 8556 --- [           main] com.bettercoding.spring.MyApp            : Started MyApp in 1.679 seconds (JVM running for 2.253)
Hello World
Foo:bar
2018-06-02 14:04:23.101  INFO 8556 --- [       Thread-2] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@3d36e4cd: startup date [Sat Jun 02 14:04:22 CEST 2018]; root of context hierarchy
2018-06-02 14:04:23.106  INFO 8556 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

Process finished with exit code 0
At the end… May I ask you for something?

If I helped you solve your problem, please share this post. Thanks to this, I will have the opportunity to reach a wider group of readers. Thank You

4.3 6 votes
Article Rating
Subscribe
Notify of
guest
14 Comments
oldest
newest most voted
Inline Feedbacks
View all comments
Mike Hazelwood
Mike Hazelwood
2 years ago

Nice article Łukasz. Thanks for putting this together and sharing it. It’s just what I needed.

Rafael Lima
Rafael Lima
1 year ago

Well done. Thanks

Annonymous
Annonymous
1 year ago

This article is just plain wrong. There are very little circumstances in which this approach should be taken. It is prone to errors, and leads to bad design overall. Please don’t encourage people to do this.

xike
xike
1 year ago
Reply to  lukasz.ciesla

how to do when MyConfig class include many static variable, further more, these variables have been referenced in some where

Raiden
Raiden
1 year ago

You saved my day! Our old project used a static configuration file to specify the environment(Different environment server has different static configuration file but has the same name). I am trying to convert it to Spring Boot so the environment has to be injected according to active profile. This approach solves this issue.

Binh Thanh Nguyen
Binh Thanh Nguyen
1 year ago

Thanks, you saved me

Peter
Peter
1 year ago

Not sure how this is all working but I still get a null pointer exception. Seems the init() method of the StaticContextInitializer never gets called? I put a System.out.println to prove it. Is there some magic to getting this working not documented here? Perhaps someone can point me in the right direction. Thanks in advance.

TJay
TJay
1 year ago

Please don’t do this. This only relies on the fact the Spring context is built up before the main application is run.
If that’s the case there’s almost no reason why the config is not wired to a non-static field. Spring beans have scope singleton by default, so there is also so need for a global static variable (considering singleton an anti-pattern anyway).

When dealing with static methods from generated code or “external” libraries, this is the last resort. Static methods should be context-free, so injecting a spring bean is almost EVER a bad idea and refactoring is strongly advised.

mart
mart
9 months ago

nevermind, I misread the problem. Sorry for confusion!

Kevin Walker
Kevin Walker
9 months ago

Thanks – this solved my problem

vijayakumar
vijayakumar
16 days ago

Thanks You .. This is working..