As an architect, how do you decide between @RefreshScope runtime refresh, plain @ConfigurationProperties rebind, and a full restart/redeploy for a config change — and what are the tradeoffs and risks?
answer
- least-powerful mechanism that works
- values -> @ConfigurationProperties rebind
- must rebuild resource -> @RefreshScope
- structural/safety/audit -> redeploy
- secure the POST endpoint; refresh is per-instance
basics
~20 sUse @ConfigurationProperties rebind for simple tunables that change in place; add @RefreshScope only when a bean must fully rebuild on config change. Prefer a restart/redeploy for structural or safety-critical changes, since runtime refresh has weaker guarantees and audit trail.
solid answer
~50 sRuntime refresh is a tradeoff between speed and guarantees. For pure value tunables (timeouts, flags, sampling), @ConfigurationProperties rebind is the cleanest: type-safe, validated, updated in place with no proxy or re-creation. Reserve @RefreshScope for beans that must fully re-initialize on change — re-open a datasource, rebuild a client — accepting the cost and the non-atomic, two-instances-briefly behavior. Avoid runtime refresh for structural changes (bean wiring, security config, things read only at startup) and for anything where a bad value must never go live silently — there a versioned redeploy gives you review, canary, rollback, and a clean audit trail. Risks to weigh: the /actuator/refresh endpoint is a state-changing POST that must be authenticated and authorized; partial application if a listener throws; per-instance-only effect (multi-instance propagation is a separate broadcast concern); and refreshed-in memory state loss. Choose refresh for fast, low-blast-radius operational tuning; choose redeploy when you need review and rollback.
go deeper
Know the three options exist: rebind, @RefreshScope, redeploy.
Match each option to value-only vs re-init vs structural changes.
Discuss endpoint security, partial-application risk, and consistency windows.
Set org policy: least-powerful mechanism, gate safety-critical changes behind reviewed redeploys, secure/observe the endpoint, and account for per-instance blast radius.
**Frame it as guarantees vs latency-to-change.** **Option A — @ConfigurationProperties rebind (no @RefreshScope).** Best default for refreshable config. On `/actuator/refresh`, `ConfigurationPropertiesRebinder` updates fields in place on `EnvironmentChangeEvent`. Type-safe, supports `@Validated` (bad values can be rejected on rebind), no proxy, no re-creation, bean identity preserved. Ideal for numeric/boolean/string tunables consumed by reading the property object. Limitation: only updates the fields; it won't re-run initialization logic that derived something from the property at startup. **Option B — @RefreshScope.** Use when a config change must trigger *full re-initialization*: rebuild an HTTP client, re-open a `DataSource`/pool with new credentials, recompute derived immutable state in the constructor. Costs: lazy re-creation, `@PreDestroy`+`@PostConstruct` re-run, two instances can briefly coexist, first post-refresh caller pays construction latency, in-memory state is discarded. Necessary for `@Value`-driven beans (which otherwise never update). **Option C — restart / redeploy.** Choose for: structural changes (bean graph, conditional wiring, security filter chain, connection topology) that Spring reads only at boot and refresh can't safely swap; safety-critical values where an incorrect setting must be gated by code review, canary, and easy rollback; and anywhere you need an immutable, auditable record of *what* changed and *when* (a git commit + pipeline run beats an ad-hoc POST). Redeploy gives atomic, all-or-nothing rollout per instance and a clean version history at the cost of higher latency-to-change and a process restart. **Decision heuristics.** - Value-only, frequently tuned, low blast radius, reversible → @ConfigurationProperties rebind (add @RefreshScope only if init must re-run). - Change must rebuild a resource/client → @RefreshScope. - Structural / security / must-be-reviewed / needs rollback + audit → redeploy. **Cross-cutting risks / considerations.** - **Security of the endpoint.** `POST /actuator/refresh` mutates running state. It must be exposed deliberately (`exposure.include`) and protected (authn + authz, network isolation). An unprotected refresh endpoint is an operational and security hazard. - **Partial application.** Listeners run synchronously on the refresh thread; if one throws (e.g., `@Validated` rebind fails), some listeners may have already applied changes while others didn't — the system can end in a mixed state. Design listeners to be resilient and validate aggressively. - **Observability / audit.** Refresh has a weak audit trail compared to a deploy. Log refreshes, emit metrics/events (`RefreshScopeRefreshedEvent`), and record who triggered them. - **Consistency window.** Non-atomic re-creation means brief inconsistency; for per-request correctness, snapshot values once. - **Blast radius across instances.** A single `/actuator/refresh` affects only *that* instance. Fleet-wide propagation is a distinct concern (broadcast via a message bus) — out of scope for this decision but part of the operational picture: if you refresh instances one-by-one, they run mixed config transiently. - **State loss.** Refresh discards in-memory caches/counters on refresh-scoped beans; account for cold-start. **Bottom line.** Prefer the least-powerful mechanism that meets the need: @ConfigurationProperties for values, @RefreshScope for re-initialization, redeploy for structural/safety-critical changes with review and rollback.
- Why might you deliberately NOT expose /actuator/refresh in production?It's a state-changing POST with a weak audit trail that can put an instance into a mixed/partial state if a listener fails. For safety-critical config, a reviewed, canaried, rollback-able redeploy gives stronger guarantees, so some teams disable runtime refresh.
- What happens if a listener throws during refresh?Listeners run synchronously on the refresh thread; an exception (e.g., a failed @Validated rebind) can leave some changes applied and others not — a partial/mixed state. The HTTP call returns an error, but the system may already be inconsistent.
saying these in an interview costs you the question
- Treating runtime refresh as a full replacement for deploys, including for structural/security changes
- Ignoring authentication/authorization on the refresh endpoint
- Assuming refresh is atomic and always all-or-nothing
- Forgetting that a single refresh only affects one instance