DEV Community

Solon Framework
Solon Framework

Posted on

Solon Multi-Environment Config: Six Loading Tiers, an In-File Segment Trick, and No Profiles API

If you've spent time with Spring Boot, you know the spring.profiles.active routine — one property, multiple application-{profile}.properties files, and a creeping suspicion that somewhere a prod config is silently falling back to dev defaults.

Solon's answer is solon.env. Same concept, different design choices — and one pattern (solon.env.on in-file segments) that Spring has no direct equivalent for.

Here's how it works.

The Single Key: solon.env

Everything in Solon's multi-environment system pivots on one property:

# app.yml
solon.env: dev
Enter fullscreen mode Exit fullscreen mode

Set this, and Solon automatically loads app-dev.yml alongside app.yml. Remove it, and only app.yml loads. No profiles API, no @ActiveProfiles, no annotation scanning — just a key.

Environment-Specific Files

The naming convention is app-{env}.yml (or .properties):

app.yml          ← always loaded
app-dev.yml      ← loaded when solon.env=dev
app-pro.yml      ← loaded when solon.env=pro
app-test.yml     ← loaded when solon.env=test
Enter fullscreen mode Exit fullscreen mode

Keys in app-{env}.yml override the same keys in app.yml. Everything else in app.yml is inherited.

One constraint worth knowing: app-{env}.yml cannot itself declare another solon.env. The env chain is intentionally one level deep.

Four Ways to Activate an Environment

You don't have to hardcode solon.env in the file. You can set it at any of four levels, with higher numbers winning:

Priority Method Example
1 (lowest) In app.yml solon.env: dev
2 JVM system property -Dsolon.env=pro
3 Launch argument --env=pro
4 (highest) OS environment variable export solon.env=pro

In practice: set solon.env: dev in app.yml as a safe default for local development, then override at deploy time:

# Jar deployment
java -jar demo.jar --env=pro

# Docker run
docker run -e 'solon.env=pro' demo_image

# Kubernetes pod spec
env:
  - name: solon.env
    value: pro
Enter fullscreen mode Exit fullscreen mode

All three launch forms below are equivalent:

java -Dsolon.env=pro -jar demo.jar
java -jar demo.jar --solon.env=pro
java -jar demo.jar --env=pro
Enter fullscreen mode Exit fullscreen mode

Six Loading Tiers

solon.env controls which env file loads, but that's just the first two tiers of a six-tier config loading stack. Later tiers override earlier ones:

Tier Source Notes
1 app.yml + app-{env}.yml Base config, inside the jar
2 solon.config.load classpath entries v2.2.7+, supports ${solon.env} variables; wildcard * from v2.7.6+
3 solon.config.add external files Files placed beside the jar at runtime
4 JVM props / env vars / launch args Runtime overrides
5 app.cfg().loadAdd() in startup code Programmatic additions
6 Solon Cloud Config (e.g., Nacos) Remote config center

The pattern: static defaults live inside the jar, runtime overrides live outside it, remote config sits at the top.

Loading More Configs: solon.config.load

For apps with per-module configs, solon.config.load lets you declare additional classpath resources with variable substitution and wildcard support (v2.7.6+):

# app.yml
solon.env: dev

solon.config.load:
  - "classpath:${solon.env}/jdbc.yml"        # resolves to classpath:dev/jdbc.yml
  - "classpath:${solon.env}/*.yml"           # all yml in dev/ folder (v2.7.6+)
  - "classpath:app-ds-${solon.env}.yml"      # e.g., app-ds-dev.yml
  - "classpath:common/base.yml"              # environment-agnostic shared config
Enter fullscreen mode Exit fullscreen mode

This lets you split config by concern (database, cache, auth) rather than cramming everything into one file per environment.

For external files placed beside the jar, use solon.config.add:

solon.config.add: "./local-overrides.yml"
Enter fullscreen mode Exit fullscreen mode

Or at launch time:

java -jar demo.jar --solon.config.add=./local-overrides.yml
Enter fullscreen mode Exit fullscreen mode

The In-File Segment Trick (v2.5.5+)

This is the feature that has no Spring Boot equivalent. Solon lets you embed multiple environment-specific segments inside a single app.yml, separated by --- and guarded by solon.env.on:

solon.env: pro

---
solon.env.on: pro
demo.auth:
  user: root
  password: "${AUTH_PASSWORD}"

---
solon.env.on: dev|test
demo.auth:
  user: demo
  password: "demo1234"
Enter fullscreen mode Exit fullscreen mode

Only the segment matching the current solon.env is applied. The | syntax lets one segment cover multiple environments.

This pattern suits small services where maintaining separate env files feels heavy, but you still want clear boundaries in the source.

Programmatic Loading

Sometimes you need to load config based on conditions known only at startup. Two clean hooks:

@SolonMain
public class App {
    public static void main(String[] args) {
        Solon.start(App.class, args, app -> {
            // Tier 5: added before beans initialize, merged into the config stack
            app.cfg().loadAdd("app-jdbc-" + app.cfg().env() + ".yml");
            app.cfg().loadAdd("app-cache-" + app.cfg().env() + ".yml");
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Or via @Import on the startup class:

@Import(profiles = "classpath:module-defaults.yml")
@SolonMain
public class App {
    public static void main(String[] args) {
        Solon.start(App.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

For Docker/Kubernetes, loadEnv() pulls in environment variables by prefix:

Solon.start(App.class, args, app -> {
    app.cfg().loadEnv("demo."); // pulls in all env vars starting with "demo."
});
Enter fullscreen mode Exit fullscreen mode

Note: Solon automatically syncs solon.* environment variables into the config store, so solon.env=pro as a container env var works without loadEnv().

Injecting Config Values

@Inject with ${} syntax wires config values into beans:

@Component
public class DataSourceConfig {

    @Inject("${db.url}")
    private String dbUrl;

    @Inject("${db.username}")
    private String username;
}
Enter fullscreen mode Exit fullscreen mode

For structured config objects, @BindProps binds an entire prefix:

@BindProps(prefix = "app.mail")
@Component
public class MailProperties {
    public String host;
    public int port;
    public String username;
}
Enter fullscreen mode Exit fullscreen mode

Variable references also work inside YAML values themselves:

db.server: "10.0.0.1"
db.name: "orders"
db.url: "jdbc:mysql://${db.server}/${db.name}"  # composed from other keys
Enter fullscreen mode Exit fullscreen mode

A Realistic Project Layout

src/main/resources/
├── app.yml                   # base config + solon.env: dev
├── app-dev.yml               # dev overrides
├── app-pro.yml               # prod overrides
└── config/
    ├── common/
    │   └── base.yml          # shared constants (env-agnostic)
    ├── dev/
    │   └── jdbc.yml          # dev DB connection
    └── pro/
        └── jdbc.yml          # prod DB connection
Enter fullscreen mode Exit fullscreen mode

app.yml:

solon.env: dev

solon.config.load:
  - "classpath:config/common/base.yml"
  - "classpath:config/${solon.env}/jdbc.yml"

solon.app.name: "order-service"
server.port: 8080
Enter fullscreen mode Exit fullscreen mode

app-dev.yml:

solon.logging.level: debug
solon.debug: 1
Enter fullscreen mode Exit fullscreen mode

app-pro.yml:

solon.logging.level: warn
server.port: 80
Enter fullscreen mode Exit fullscreen mode

Deploy to prod:

java -jar order-service.jar --env=pro
Enter fullscreen mode Exit fullscreen mode

One flag flips the entire config stack.

Migration Reference (from Spring Boot)

Spring Boot Solon Notes
spring.profiles.active solon.env Same concept, different key
application-{profile}.properties app-{env}.yml Same convention
@Profile("dev") on beans solon.env.on: dev in YAML Config-layer isolation vs code-layer
@PropertySource solon.config.load in YAML Declarative import
@ConfigurationProperties(prefix="x") @BindProps(prefix="x") Prefix binding
@Value("${x}") @Inject("${x}") Value injection

The mental model is nearly identical. The key difference: Solon keeps environment logic in config (via solon.env.on segments or separate files) rather than scattering @Profile annotations across bean definitions.

Key Takeaways

  • solon.env is the single activation key — set it in the file, at the JVM, or as a container env var
  • app-{env}.yml is loaded automatically; no registration step required
  • Six loading tiers let you layer static defaults with runtime overrides cleanly
  • solon.config.load with ${solon.env} handles per-module, per-environment config splitting
  • In-file --- segments with solon.env.on (v2.5.5+) are unique to Solon — useful for small services
  • Config injection uses @Inject("${...}") and @BindProps — both standard Solon IoC

The loading order is deterministic and documented. Deploy with confidence.

Top comments (0)