I am working on a Micronaut project, where I would like to see if the environment variables from the application.yml are being correctly assigned using the @Value annotation, when the app starts locally. But every time the app is starting it shows me that the variables are not being assigned to the environment variables from the application.yml file.
That is my code:
public class Application { private static String localTestString = "I am the local String"; @Value("${aws.secretkeyid}") public static String applicationYmlTestString; @Value("${aws.keyid}") private static int keyId; public static void main(String[] args) { Micronaut.run(Application.class); } static{ log.warn("Local Test String is: " + localTestString); log.warn("Application Yml Test String is: " + applicationYmlTestString); log.warn("Key ID: " + keyId); } }
This is my application.yml
aws: keyid: 123 secretkeyid: "abcdesdasdsddddd" region: "europe-1"
Output:
Local Test String is: I am the local String Application Yml Test String is: null Key ID: 0
As we see the two variables applicationYmlTestString and keyId are not being assigned to the environment variables. Is there a way to solve this problem and to get:
Application Yml Test String is: abcdesdasdsddddd Key ID: 123
Thank you in advance!
Advertisement
Answer
There are two issues with the example you have shown. Firstly, Micronaut does not inject values to static fields annotated with @Value
annotation. (It’s not weird, Spring does not support it as well.) Secondly, after injecting values to non-static fields, you won’t be able to read their values using the class’ static constructor. The whole application context must be ready to read such values, so you need to use an event listener that reacts to the application startup event.
Here is the simplest way to achieve it based on your example:
package micronaut.hello.world; import io.micronaut.context.annotation.Value; import io.micronaut.context.event.StartupEvent; import io.micronaut.runtime.Micronaut; import io.micronaut.runtime.event.annotation.EventListener; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class Application { private static final Logger log = LoggerFactory.getLogger(Application.class); @Value("${aws.secretkeyid}") private String applicationYmlTestString; @Value("${aws.keyid}") private int keyId; public static void main(String[] args) { Micronaut.run(Application.class, args); } @EventListener void onStartup(StartupEvent event) { log.warn("Application Yml Test String is: " + applicationYmlTestString); log.warn("Key ID: " + keyId); } public String getApplicationYmlTestString() { return applicationYmlTestString; } public void setApplicationYmlTestString(String applicationYmlTestString) { this.applicationYmlTestString = applicationYmlTestString; } public int getKeyId() { return keyId; } public void setKeyId(int keyId) { this.keyId = keyId; } }
There are three things worth mentioning:
- The above example uses
@EventListener
annotation that makes the given method “event-aware”, and this method will be triggered when the specific event is published by the application (or framework.) - We react to
io.micronaut.context.event.StartupEvent
– an event fired once startup is complete. - Keep in mind that to make this
@EventListener
annotation work, we need to annotate the application class with@Singleton
to make this class a proper Micronaut bean.
Alternatively, if making an application class a singleton bean does not look good to you, you can implement the ApplicationEventListener
interface and create a dedicated bean that will react to the same startup event. In this example, I use a static inner class, but that’s just to make this example simple:
package micronaut.hello.world; import io.micronaut.context.annotation.Value; import io.micronaut.context.event.ApplicationEventListener; import io.micronaut.context.event.StartupEvent; import io.micronaut.runtime.Micronaut; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Application { private static final Logger log = LoggerFactory.getLogger(Application.class); public static void main(String[] args) { Micronaut.run(Application.class, args); } @Singleton static class OnStartupEventListener implements ApplicationEventListener<StartupEvent> { @Value("${aws.secretkeyid}") private String applicationYmlTestString; @Value("${aws.keyid}") private int keyId; @Override public void onApplicationEvent(StartupEvent event) { log.warn("Application Yml Test String is: " + applicationYmlTestString); log.warn("Key ID: " + keyId); } public String getApplicationYmlTestString() { return applicationYmlTestString; } public void setApplicationYmlTestString(String applicationYmlTestString) { this.applicationYmlTestString = applicationYmlTestString; } public int getKeyId() { return keyId; } public void setKeyId(int keyId) { this.keyId = keyId; } } }
But eventually, you should consider implementing a configuration class and use it instead of injecting values with the @Value
annotation. However, whatever option you choose, the same thing applies – the configuration class can be injected to a non-static field and can be checked using an event listener mechanism.
And as Tim mentioned in the comment below, “Be careful logging environment variables though… They have a habit of being secrets, and logging them out tends to end up with them being in plain text in loads of different systems 😉”. If you really need to log such information to double-check if the expected configuration is injected, try doing it in the controlled dev environment only. Assuming that you use the dev
profile for the local env, you could use @Requires
annotation to limit specific event listener to only that dev
environment:
@Singleton @Requires(env = "dev") class OnStartupEventListener implements ApplicationEventListener<StartupEvent> { @Value("${aws.secretkeyid}") private String applicationYmlTestString; @Value("${aws.keyid}") private int keyId; @Override public void onApplicationEvent(StartupEvent event) { log.warn("Application Yml Test String is: " + applicationYmlTestString); log.warn("Key ID: " + keyId); } public String getApplicationYmlTestString() { return applicationYmlTestString; } public void setApplicationYmlTestString(String applicationYmlTestString) { this.applicationYmlTestString = applicationYmlTestString; } public int getKeyId() { return keyId; } public void setKeyId(int keyId) { this.keyId = keyId; } }