Series MapLesson 39 / 112
Focus mode active/Press Alt+Shift+R to toggle/Esc to exit
Build CoreOrdered learning track

Configuration and Precedence

Configuration Profiles Properties and Precedence

Konfigurasi service enterprise lintas environment: source, profile, precedence, validation, runtime reload, drift, dan safe defaults

8 min read1420 words
PrevNext
Lesson 39112 lesson track22–61 Build Core
#configuration#profiles#properties#precedence+5 more

Part 039 — Configuration, Profiles, Properties, and Precedence

Fokus part ini: memahami configuration sebagai runtime contract. Di production, banyak incident bukan karena algoritma salah, tetapi karena konfigurasi salah, precedence tidak jelas, default terlalu permissive, config drift, atau secret/config tertukar.

Dalam service enterprise, konfigurasi bukan sekadar file .properties.

Konfigurasi menentukan:

- service berjalan di environment mana
- dependency mana yang dipanggil
- timeout berapa
- retry aktif atau tidak
- database schema mana yang dipakai
- Kafka topic apa yang dipublish/consume
- feature mana yang enable
- tenant mana memakai rule/catalog tertentu
- observability dikirim ke mana
- security policy apa yang enforce

Senior engineer harus bisa menjawab:

For a given running pod/process, what exact configuration is active, from which source, with what precedence, and how can we prove it safely?

1. Configuration Is Runtime Behavior

Code biasanya sama di semua environment.

Behavior berbeda karena config.

same artifact + different config = different system behavior

Contoh:

quote.order.timeout.ms=3000
quote.order.retry.maxAttempts=2
quote.order.kafka.topic=quote-order-events-v2
quote.order.db.pool.maxSize=30

Empat property ini dapat mengubah:

- latency
- throughput
- retry amplification
- event compatibility
- database pressure
- incident blast radius

Karena itu config harus diperlakukan sebagai bagian dari architecture.

Bukan detail deployment semata.


2. Configuration vs Secret vs Runtime State

Jangan campur tiga hal ini.

CategoryExampleShould be stored asMain risk
Configurationtimeout, endpoint URL, pool sizeconfig sourcewrong behavior
Secretpassword, private key, tokensecret managerleak/compromise
Runtime statecurrent offset, last processed IDdatabase/cache/Kafkadata corruption

Bad smell:

db.password=plain-text-password
lastProcessedOrderId=ORD-123

Yang pertama adalah secret. Yang kedua adalah state. Keduanya tidak seharusnya menjadi normal config property.

Senior rule:

Configuration should describe desired behavior, not hold confidential material or mutable business state.

3. Common Configuration Sources

Dalam Java/JAX-RS/Jersey/Kubernetes service, config bisa datang dari banyak tempat.

Source examples:
- hardcoded default
- Java system property: -Dkey=value
- environment variable
- application.properties
- YAML file
- profile-specific property file
- Kubernetes ConfigMap
- Kubernetes Secret
- mounted file
- Helm values
- GitOps manifest
- cloud parameter store
- cloud app configuration service
- feature flag platform
- database-backed tenant configuration

Masalahnya bukan banyak source. Masalahnya adalah jika precedence tidak jelas.


4. Configuration Precedence

Configuration precedence adalah aturan siapa menang ketika property yang sama muncul di banyak source.

Contoh property:

quote.http.client.timeoutMs

Muncul di:

- default code value: 1000
- application.properties: 2000
- application-prod.properties: 3000
- environment variable: 5000
- Kubernetes ConfigMap: 4000
- command line system property: 7000

Pertanyaannya:

value final yang dipakai berapa?

Tanpa precedence yang eksplisit, debugging menjadi tebak-tebakan.

Contoh precedence yang umum, bukan aturan universal:

1. hardcoded safe default
2. base properties file
3. profile-specific properties file
4. environment variables / mounted files
5. system properties / startup override
6. emergency runtime override / feature flag platform

Tetapi jangan menganggap urutan ini benar di codebase internal. Harus diverifikasi.


5. Profile Is Not Tenant

Profile menjawab:

service berjalan di environment atau mode apa?

Tenant menjawab:

request/data/config ini milik tenant siapa?

Jangan mencampur keduanya.

Bad pattern:

profile=prod-customerA
profile=prod-customerB

Ini membuat environment dan tenancy bercampur. Lebih sehat:

profile=prod
region=ap-southeast-1
tenant=resolved per request or per job context

Dalam CPQ/order management system, tenant-specific catalog/pricing/rules mungkin ada. Tetapi itu bukan berarti application profile harus per tenant.


6. Environment Profile Model

Profile umum:

local
unit-test
integration-test
dev
qa
staging
preprod
prod

Tetapi real enterprise sering punya dimensi tambahan:

region
cloud provider
customer segment
deployment mode
on-prem vs cloud
regulated vs non-regulated environment

Hindari profile explosion:

prod-aws-us-east-customerA-onprem-like-special-case

Gunakan dimensi eksplisit:

app.profile=prod
app.region=us-east-1
app.cloudProvider=aws
app.deploymentMode=cloud

Atau:

app:
  profile: prod
  region: us-east-1
  cloudProvider: aws
  deploymentMode: cloud

7. Build-Time vs Deploy-Time vs Runtime Configuration

Pemisahan penting:

TimingExampleChange requires rebuild?Risk
Build-timeJava version, dependency, generated codeyesartifact mismatch
Deploy-timeenv var, ConfigMap, JVM flagno rebuild, yes redeploy/restartrollout drift
Runtime dynamicfeature flag, dynamic confignoinconsistent behavior
Request-timetenant config, identity, localenocross-request leak

Senior review harus bertanya:

If this value changes, do we need rebuild, redeploy, restart, or just dynamic propagation?

8. Jersey/JAX-RS Configuration Layers

Ada beberapa level config yang mudah tercampur.

8.1 JAX-RS Application Configuration

Standar JAX-RS punya Application sebagai application boundary.

@ApplicationPath("/api")
public class QuoteApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        return Set.of(QuoteResource.class);
    }
}

Ini lebih tentang resource/provider registration. Bukan full enterprise config system.

8.2 Jersey ResourceConfig Properties

Jersey punya ResourceConfig.

public class QuoteResourceConfig extends ResourceConfig {
    public QuoteResourceConfig() {
        packages("com.example.quote.api");
        property("jersey.config.server.tracing.type", "OFF");
    }
}

property(...) di sini adalah Jersey-specific configuration. Jangan otomatis disamakan dengan application business config.

8.3 Application Business Configuration

Contoh business/runtime config:

public record QuoteServiceConfig(
    Duration quoteCalculationTimeout,
    int maxLineItems,
    boolean enableExperimentalPricing,
    String kafkaTopic
) {}

Ini sebaiknya di-load, divalidasi, dan di-inject sebagai typed object.


9. Typed Configuration Object

Config yang tersebar sebagai string raw membuat system rapuh.

Bad pattern:

String timeout = System.getenv("QUOTE_TIMEOUT_MS");
client.setTimeout(Integer.parseInt(timeout));

Lebih baik:

public record HttpClientConfig(
    Duration connectTimeout,
    Duration readTimeout,
    int maxConnections,
    boolean retryEnabled
) {
    public HttpClientConfig {
        if (connectTimeout == null || connectTimeout.isNegative() || connectTimeout.isZero()) {
            throw new IllegalArgumentException("connectTimeout must be positive");
        }
        if (readTimeout == null || readTimeout.isNegative() || readTimeout.isZero()) {
            throw new IllegalArgumentException("readTimeout must be positive");
        }
        if (maxConnections <= 0) {
            throw new IllegalArgumentException("maxConnections must be positive");
        }
    }
}

Benefits:

- config has type
- validation is centralized
- startup can fail fast
- tests can construct explicit config
- PR review becomes easier

10. Fail Fast on Invalid Configuration

Configuration error should be detected at startup whenever possible.

Bad behavior:

service starts successfully
first production request reaches code path
missing config causes 500

Better behavior:

service refuses to start
deployment fails early
bad config never receives production traffic

Example validator:

public final class ConfigValidator {
    public static void validate(QuoteServiceConfig config) {
        requirePositive(config.maxLineItems(), "maxLineItems");
        requirePresent(config.kafkaTopic(), "kafkaTopic");
        requirePositiveDuration(config.quoteCalculationTimeout(), "quoteCalculationTimeout");
    }

    private static void requirePresent(String value, String name) {
        if (value == null || value.isBlank()) {
            throw new IllegalStateException("Missing required config: " + name);
        }
    }

    private static void requirePositive(int value, String name) {
        if (value <= 0) {
            throw new IllegalStateException("Invalid config: " + name + " must be positive");
        }
    }

    private static void requirePositiveDuration(Duration value, String name) {
        if (value == null || value.isZero() || value.isNegative()) {
            throw new IllegalStateException("Invalid config: " + name + " must be positive");
        }
    }
}

Senior rule:

A service should not become ready if required configuration is invalid.

11. Safe Defaults

Default is dangerous if it hides missing config.

Example unsafe default:

boolean authEnabled = getBoolean("auth.enabled", false);

In production, missing config disables auth.

Better:

boolean authEnabled = requireBoolean("auth.enabled");

Or fail closed:

boolean authEnabled = getBoolean("auth.enabled", true);

But even true default may hide intent. For sensitive controls, prefer explicit config.

Safe default principle:

Default should reduce blast radius, not maximize convenience.

Examples:

ConfigSafer default
auth enabledtrue or required
debug endpointfalse
retry max attemptslow or disabled until explicit
max page sizebounded
file upload sizesmall bounded default
cache TTLshort or required
allow cross-originfalse
tenant fallbackno fallback

12. Configuration Naming

Good config names are stable contracts.

Bad:

timeout=5000
url=http://service
flag=true

Better:

quote.pricing.http.connectTimeoutMs=500
quote.pricing.http.readTimeoutMs=3000
quote.pricing.http.baseUrl=https://pricing.internal
quote.pricing.feature.enableNewDiscountEngine=false

Naming guidelines:

- namespace by domain/component
- include dependency name
- include unit in name or use typed duration format
- avoid vague names
- avoid overloaded generic keys
- distinguish connect/read/request/idle timeout

13. Units Must Be Explicit

This is a real production failure source.

Bad:

quote.timeout=5

What is 5?

5 ms?
5 seconds?
5 minutes?

Better:

quote.timeoutMs=5000

Or typed duration:

quote.timeout=5s

But typed duration requires a parser and convention.

Senior rule:

If a config value has a unit, the unit must be unambiguous at the declaration site.

14. Runtime Reload

Runtime reload means config can change without restarting service.

This is powerful and dangerous.

Good use cases:

- feature flag
- emergency kill switch
- traffic percentage
- non-critical threshold
- temporary routing override

Risky use cases:

- DB connection URL
- schema compatibility behavior
- security policy
- object mapper behavior
- Kafka topic name
- tenant isolation mode

Runtime reload hazards:

- half of instances use old value
- in-flight request sees inconsistent values
- dependency clients are not rebuilt safely
- caches are not invalidated
- observability does not show config version
- rollback is unclear

If dynamic config exists, it needs:

- version
- audit trail
- owner
- validation
- propagation model
- rollback model
- observability

15. Immutable Config Snapshot Per Request

When config can change at runtime, avoid reading global config repeatedly during one operation.

Bad:

public QuoteResponse calculate(QuoteRequest request) {
    if (config.flags().newPricing()) {
        // path A
    }

    Money price = pricingEngine.calculate(request, config.pricingRules());

    if (config.flags().newPricing()) {
        // path B
    }
}

If config reloads between reads, path A and path B may disagree.

Better:

public QuoteResponse calculate(QuoteRequest request) {
    QuoteRuntimeConfig snapshot = configProvider.currentSnapshot();

    if (snapshot.flags().newPricing()) {
        // path A
    }

    Money price = pricingEngine.calculate(request, snapshot.pricingRules());

    if (snapshot.flags().newPricing()) {
        // path B
    }
}

Rule:

Use a stable config snapshot for one business operation.

16. Config Version and Observability

Dynamic config should have identity.

Expose safely:

- app version
- config version/hash
- active profile
- region
- feature flag version
- schema version

Do not expose:

- secret values
- credentials
- tenant sensitive settings
- customer-specific pricing rules unless authorized

Possible safe health metadata:

{
  "appVersion": "1.42.3",
  "profile": "prod",
  "region": "ap-southeast-1",
  "configVersion": "2026-07-10T08:30:00Z-7f3a",
  "runtime": "jaxrs-jersey",
  "status": "UP"
}

Internal verification:

Can operators identify which config version a failing pod used?

If not, incident debugging becomes expensive.


17. Configuration Drift

Configuration drift means environments or instances that should be equivalent are no longer equivalent.

Examples:

- pod A has timeout 3s, pod B has timeout 10s
- staging uses Kafka topic v1, prod uses v2
- one region has feature enabled, another not
- local default differs from deployment default
- database migration assumes config that only some pods have

Drift causes non-deterministic bugs.

Detection techniques:

- GitOps desired state
- config hash in logs/metrics
- startup config summary
- environment diff tool
- deployment manifest review
- config conformance test

18. Config and Kubernetes

In Kubernetes, config usually appears as:

- environment variables
- ConfigMap mounted as env
- ConfigMap mounted as file
- Secret mounted as env
- Secret mounted as file
- projected volume
- external secrets operator
- Helm values rendered to manifest

Key difference:

Env var changes usually require pod restart.
Mounted files may update, but application may not reload them safely.

Do not assume changing ConfigMap updates running behavior. Verify:

- does pod restart on config change?
- is checksum annotation used?
- does app watch mounted file?
- is reload atomic?
- what happens to in-flight requests?

19. Config and Readiness

A pod should not be ready if required config/dependency config is invalid.

Readiness should fail for:

- missing required config
- invalid config value
- malformed URL
- unsupported profile
- invalid tenant config baseline
- incompatible feature flag state

But readiness should not perform heavy operations forever.

Balance:

startup validation = deep config validation
readiness = current ability to serve
liveness = process health, not dependency health

20. Config for HTTP Client Resilience

Outbound client config should not be vague.

Bad:

pricing.timeout=3000
pricing.retry=true

Better:

pricing.http.connectTimeoutMs=500
pricing.http.readTimeoutMs=3000
pricing.http.maxConnections=100
pricing.resilience.retry.maxAttempts=2
pricing.resilience.retry.backoffMs=100
pricing.resilience.circuitBreaker.failureRateThreshold=50
pricing.resilience.circuitBreaker.openStateWaitMs=30000

Review questions:

- is timeout shorter than caller timeout?
- is retry budget bounded?
- does retry only happen for safe/idempotent operations?
- does circuit breaker protect the downstream?
- is fallback safe?

21. Config for Database

Database config must be reviewed with workload in mind.

Examples:

db.pool.maxSize=30
db.pool.minIdle=5
db.pool.connectionTimeoutMs=1000
db.pool.idleTimeoutMs=600000
db.statement.timeoutMs=5000
db.transaction.defaultIsolation=READ_COMMITTED

Bad values can create incidents:

- pool too large overloads DB
- pool too small causes app queueing
- statement timeout too high holds locks too long
- isolation too strict reduces throughput
- missing transaction timeout causes stuck transactions

22. Config for Kafka

Kafka config affects correctness.

Examples:

quote.events.topic=quote-events-v2
quote.events.producer.acks=all
quote.events.producer.enableIdempotence=true
quote.events.consumer.groupId=quote-order-service
quote.events.consumer.maxPollRecords=100
quote.events.consumer.autoOffsetReset=earliest

Review questions:

- is topic compatible with schema version?
- is group ID stable?
- is max poll size aligned with processing time?
- is auto offset reset appropriate?
- is idempotency enabled where needed?

23. Config for Logging and Telemetry

Logging config must balance visibility and risk.

Examples:

logging.level.root=INFO
logging.level.com.example.quote=INFO
logging.redaction.enabled=true
telemetry.tracing.enabled=true
telemetry.sampling.ratio=0.1
metrics.highCardinalityLabels.disabled=true

Bad config:

- DEBUG enabled globally in production
- request/response body logging enabled with PII
- trace sampling too low during incident
- high-cardinality labels include quoteId/orderId/customerId

24. Configuration Anti-Patterns

24.1 Hardcoded Production URL

private static final String PRICING_URL = "https://pricing.prod.internal";

Problems:

- impossible to test safely
- breaks environment parity
- hidden dependency

24.2 Boolean Flag Without Owner

enableNewFlow=true

Questions:

- who owns it?
- when is it removed?
- is it tenant-specific?
- what is default?
- is rollback tested?

24.3 Silent Fallback

String topic = get("topic", "default-topic");

If topic is missing in production, service silently publishes to wrong topic.

24.4 Config Read Everywhere

System.getenv("...")

Scattered config reads make precedence and validation impossible.

24.5 Secrets in Config Dump

Operators need visibility, but not secret values.

Use redacted summaries.


25. Debugging Configuration Problems

When behavior differs across environments, debug config first.

Checklist:

1. identify running artifact version
2. identify active profile
3. identify active config version/hash
4. inspect startup config summary
5. compare desired manifest vs running pod env/mounts
6. compare pod A vs pod B
7. check feature flag state
8. check tenant-specific override
9. check cloud parameter/app config version
10. check last deployment/config change timeline

Useful commands in Kubernetes context:

kubectl describe pod <pod>
kubectl exec <pod> -- printenv | sort
kubectl get configmap <name> -o yaml
kubectl get secret <name> -o yaml # do not decode casually in shared logs
kubectl rollout history deployment <name>

Do not paste secret values into tickets or chat.


26. Example Configuration Loader Boundary

A simple pattern:

public interface ConfigSource {
    Optional<String> get(String key);
}

public final class CompositeConfigSource implements ConfigSource {
    private final List<ConfigSource> sourcesInPriorityOrder;

    public CompositeConfigSource(List<ConfigSource> sourcesInPriorityOrder) {
        this.sourcesInPriorityOrder = List.copyOf(sourcesInPriorityOrder);
    }

    @Override
    public Optional<String> get(String key) {
        for (ConfigSource source : sourcesInPriorityOrder) {
            Optional<String> value = source.get(key);
            if (value.isPresent()) {
                return value;
            }
        }
        return Optional.empty();
    }
}

Then build typed config:

public final class QuoteConfigFactory {
    private final ConfigSource source;

    public QuoteConfigFactory(ConfigSource source) {
        this.source = source;
    }

    public QuoteServiceConfig load() {
        QuoteServiceConfig config = new QuoteServiceConfig(
            requireDuration("quote.calculation.timeout"),
            requireInt("quote.lineItems.max"),
            requireBoolean("quote.pricing.experimental.enabled"),
            requireString("quote.events.topic")
        );
        ConfigValidator.validate(config);
        return config;
    }

    private String requireString(String key) {
        return source.get(key)
            .filter(value -> !value.isBlank())
            .orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
    }

    private int requireInt(String key) {
        try {
            return Integer.parseInt(requireString(key));
        } catch (NumberFormatException e) {
            throw new IllegalStateException("Invalid integer config: " + key, e);
        }
    }

    private boolean requireBoolean(String key) {
        String value = requireString(key);
        if (value.equalsIgnoreCase("true")) return true;
        if (value.equalsIgnoreCase("false")) return false;
        throw new IllegalStateException("Invalid boolean config: " + key);
    }

    private Duration requireDuration(String key) {
        return Duration.parse(requireString(key));
    }
}

This is illustrative, not a mandate. In real enterprise systems, you may use MicroProfile Config, Spring-like config infrastructure, custom platform config, cloud config service, or internal libraries.

Internal verification matters more than guessing.


27. Configuration and PR Review

When reviewing PR that adds config, ask:

- what is the default?
- is the default safe?
- is the config required?
- what is the unit?
- where is it documented?
- who owns it?
- can it differ per environment?
- can it differ per tenant?
- does it require restart?
- is it secret?
- is it logged safely?
- is it validated at startup?
- what happens if it is missing?
- what happens if it is malformed?
- how is it rolled back?

A config PR is not trivial. It changes runtime behavior.


28. Failure Modes

Failure modeSymptomRoot causeDetection
Missing required configstartup failure or 500property absentstartup validation
Wrong precedenceunexpected value activesource order unclearconfig summary/hash
Unsafe defaultsecurity/performance incidentmissing value hiddenconfig review
Drift between podsintermittent behaviorinconsistent configpod diff/config hash
Unit mismatchtimeout too high/lowms vs sec confusiontyped config/unit naming
Secret leakcredential exposedconfig dump/logredaction scan
Runtime reload inconsistencysplit behaviorpartial propagationconfig version metrics
Tenant config leakwrong tenant behaviorfallback/lookup bugtenant-aware audit

29. Internal Verification Checklist

For CSG Quote & Order or any internal enterprise service, verify from evidence:

Repository and code:
- where configuration is loaded
- whether config is typed or raw string based
- whether MicroProfile Config, custom config, Jersey property, or another system is used
- whether `ResourceConfig.property(...)` is used for Jersey-specific behavior
- whether config validation runs at startup
- whether default values exist and where
- whether units are explicit

Runtime and deployment:
- active profile naming convention
- config source order and precedence
- Kubernetes ConfigMap/Secret usage
- Helm/GitOps values source
- whether config change triggers pod restart
- whether config hash is exposed in logs/metrics
- whether environment drift is detectable

Security:
- whether secrets are stored separately from config
- whether config dumps are redacted
- whether secret rotation is documented
- whether emergency overrides are audited

Operations:
- how operators inspect active config safely
- how config changes are reviewed
- how rollback is performed
- how config drift is detected
- how tenant-specific config is governed

Do not assume the internal stack. Prove it from code, deployment manifests, pipeline, docs, and team discussion.


30. Senior Mental Model

Think of configuration as a distributed control plane.

code defines possible behavior
configuration selects active behavior
runtime applies behavior
observability proves behavior
governance controls behavior changes

A senior engineer does not merely ask:

What value should I put here?

A senior engineer asks:

Who owns this value?
Where does it come from?
What wins if it conflicts?
How is it validated?
How is it observed?
How is it rolled back?
What is the failure mode if it is wrong?

31. Practical Exercises

  1. Pick one existing service and list all config sources.
  2. Trace one config key from repository to running pod.
  3. Identify the exact precedence order.
  4. Find one config that has no explicit unit.
  5. Find one config whose default may be unsafe.
  6. Find one config that should be secret but is not treated as secret.
  7. Find how active config version is visible during an incident.
  8. Check whether config drift between pods can be detected.

32. Part Summary

Configuration is not support material. It is runtime architecture.

You should now be able to reason about:

- config vs secret vs state
- source and precedence
- profile vs tenant
- build-time/deploy-time/runtime/request-time config
- typed configuration
- validation and fail-fast startup
- safe defaults
- runtime reload risk
- config versioning and observability
- drift detection
- Kubernetes config behavior
- PR review for config changes

Next part: multi-tenancy and enterprise configuration — where configuration becomes tenant-aware and directly affects catalog, pricing, routing, logging, and data isolation.

Lesson Recap

You just completed lesson 39 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.