Skip to content
Advertisement

How to print out environment variables, when starting a Micronaut application

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:

  1. 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.)
  2. We react to io.micronaut.context.event.StartupEvent – an event fired once startup is complete.
  3. 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;
    }
}
Advertisement