Backend Development 7 min read

How to Decrypt Spring Boot Properties with a Custom EnvironmentPostProcessor

This guide shows how to implement a custom EnvironmentPostProcessor in Spring Boot to decrypt sensitive configuration values such as datasource passwords, explains registration via META-INF/spring.factories, and details the execution order and related Spring Boot internals.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Decrypt Spring Boot Properties with a Custom EnvironmentPostProcessor

Introduction

Spring Boot does not provide built‑in encryption for property values, but it offers hook points such as

EnvironmentPostProcessor

that allow you to manipulate the

Environment

before the application starts. An alternative for secure credential storage is Spring Cloud Vault.

Custom Extension Point

Implement a custom

EnvironmentPostProcessor

to decrypt encrypted properties (e.g., passwords) at startup.

<code>public class EncryptEnvironmentPostProcessor implements EnvironmentPostProcessor {
    private static final String PWD_PREFIX = "enc(";
    private static final String PWD_SUFFIX = ")";
    private static final String DS_KEY = "spring.datasource.password";

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        System.out.println("准备处理加解密数据");
        String originPassword = environment.getProperty(DS_KEY);
        System.out.println("原始密码: " + originPassword);
        if (originPassword != null && originPassword.startsWith(PWD_PREFIX) && originPassword.endsWith(PWD_SUFFIX)) {
            String encryptPassword = originPassword.substring(originPassword.indexOf('(') + 1, originPassword.length() - 1);
            String decryptPassword = decrypt(encryptPassword);
            System.out.println("解密后的密码:" + decryptPassword);
            Map<String, Object> kv = new HashMap<>(2);
            kv.put(DS_KEY, decryptPassword);
            PropertySource<Map<String, Object>> source = new MapPropertySource("ds-pwd", kv);
            environment.getPropertySources().addFirst(source);
        }
        System.out.println("加解密数据处理完成");
    }

    private static String decrypt(String source) {
        byte[] bs = source.getBytes(Charset.forName("UTF-8"));
        for (int i = 0; i < bs.length; i++) {
            bs[i] = (byte) (bs[i] ^ 0x01);
        }
        return new String(bs, Charset.forName("UTF-8"));
    }
}
</code>

Register the processor in

META-INF/spring.factories

:

<code>org.springframework.boot.env.EnvironmentPostProcessor=com.pack.envs.EncryptEnvironmentPostProcessor
</code>

Test Configuration

<code>spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testjpa?serverTimezone=GMT%2B8
    username: root
    password: enc(032032)
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 10
      maximumPoolSize: 200
      autoCommit: true
      idleTimeout: 30000
      poolName: MasterDatabookHikariCP
      maxLifetime: 1800000
      connectionTimeout: 30000
      connectionTestQuery: SELECT 1
</code>

When the application starts, the custom processor runs before the IOC container is created, decrypting the password and injecting the clear value into the environment.

Source Code Analysis

Execution Entry Point

<code>public class SpringApplication {
    public ConfigurableApplicationContext run(String... args) {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // Pre‑process Environment
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        configureIgnoreBeanInfo(environment);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // Refresh context (initialize IOC container)
        refreshContext(context);
        return context;
    }
    // ... other methods including prepareEnvironment ...
}
</code>

Event Handling

<code>public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationEnvironmentPreparedEvent) {
            onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
        }
        if (event instanceof ApplicationPreparedEvent) {
            onApplicationPreparedEvent(event);
        }
    }

    private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
        List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
        postProcessors.add(this);
        AnnotationAwareOrderComparator.sort(postProcessors);
        for (EnvironmentPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
        }
    }

    List<EnvironmentPostProcessor> loadPostProcessors() {
        // Load processors configured in META-INF/spring.factories
        return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
    }
}
</code>

The whole execution chain is illustrated below:

Ordering Note

ConfigFileApplicationListener

implements

Ordered

with order

Ordered.HIGHEST_PRECEDENCE + 10

. If a custom

EnvironmentPostProcessor

implements

Ordered

with a lower value (higher priority), it may run before the environment is fully prepared, causing

NullPointerException

because required properties are not yet available.

Configuration Locations

<code>private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
</code>

These locations are scanned from right to left, with later entries having higher precedence.

End of tutorial.

backendJavaconfigurationSpring BootEncryptionEnvironmentPostProcessor
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.