`resetBeanToDefaults` transiently nulls nested `@ConfigurationProperties` objects, NPEing concurrent request threads
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 48/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- java, spring, spring-boot
Direzione di ricerca
Inizia con resetBeanToDefaults/resetProperties e il percorso di rebind attraverso ConfigurationPropertiesBindingPostProcessor; esegui la riproduzione allegata con le versioni di spring-cloud-context elencate. Esamina come ConfigurationPropertiesBeans e ContextRefresher partecipano all’aggiornamento, quindi definisci un comportamento sicuro per le proprietà annidate durante il rebinding. Il lavoro è completato quando i test deterministici e dei lettori concorrenti passano senza NPEs e il rebinding rimane supportato.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
Since spring-cloud-context 5.0.2 (resetBeanToDefaults, #1680), a rebind empties the live
@ConfigurationProperties singleton before repopulating it. For a bean with nested property
objects, the observable intermediate state is not merely a stale or new scalar — it is null, so a
concurrent reader does not read a wrong value, it throws NullPointerException.
This is the same window PR #1709 reported and #1721 partially addressed (writer/writer only). #1709
was closed with the observation acknowledged; this report adds the failure mode it did not cover — a
hard failure rather than a wrong value — and evidence of how often it is hit in a framework where
every service has nested properties read on request threads.
Environment
- spring-cloud-context 5.0.2 and 5.0.3 (trains 2025.1.2, 2025.1.3); Spring Boot 4.0.8. Not present in
4.3.x/4.2.x.
What happens
@Bean
@ConfigurationProperties(prefix = "my.service")
MyProperties myProperties() { return new MyProperties(); }
public class MyProperties {
private Timeout timeout = null; // populated only by binding
public Timeout getTimeout() { return timeout; }
// ...
}
resetProperties walks the BeanWrapper descriptors and, for every writable property, does
target.setPropertyValue(name, defaultsWrapper.getPropertyValue(name)). For timeout that is
setTimeout(null). Between resetBeanToDefaults(bean) and the binding inside
initializeBean(bean, name), every reader of myProperties.getTimeout().getX() throws NPE.
spring.cloud.refresh.never-reset-nested-types does not help: it controls recursion into a non-null
nested value, not the top level setTimeout(null).
Reproduction
rebind-reset-repro.zip is a self-contained project — Spring Boot 4.0.8 with
spring-cloud-context as the only other dependency, no Spring Cloud Config, no Consul, no
@RefreshScope. The test is also attached on its own as RebindEmptiesNestedPropertiesTest.java.txt,
and the three runs below as test-results.txt.
mvn test # 5.0.3 (default) - both tests fail
mvn test -Dspring-cloud-context.version=5.0.2 # both tests fail
mvn test -Dspring-cloud-context.version=5.0.1 # both tests pass
spring-cloud-context |
nestedPropertyIsReadableThroughoutARebind |
concurrentReadersSurviveRepeatedRebinds |
|---|---|---|
| 5.0.1 | passes | passes, 0 failed reads |
| 5.0.2 | fails, NPE | fails, 10,942,734 of 19,659,911 reads |
| 5.0.3 | fails, NPE | fails, 11,072,765 of 20,753,865 reads |
The first test is deterministic rather than a race: a PriorityOrdered BeanPostProcessor reads the bean
from inside the re-bind, ahead of ConfigurationPropertiesBindingPostProcessor, which is where a request
thread lands.
NullPointerException: Cannot invoke "...MyProperties$Timeout.getConnect()"
because the return value of "...MyProperties.getTimeout()" is null
The second is one reader thread against 200 re-binds. A tight reader loop overstates the proportion
compared with real traffic, but it shows the window is wide rather than a few instructions.
Impact and frequency
Measured in a framework used across a fleet of services, where a Consul config watch fires
ContextRefresher.refresh() on a running service:
- In a framework integration test, 5,363 of 2,647,116 reads failed with NPE during a single
ContextRefresher.refresh()- 4 reader threads resolving an HTTP connect timeout through the
properties bean, exactly as request threads do for every outbound call. - In production this is a burst of failed outbound calls or 5xx per config change, not a sustained
outage — but it is one burst per config change, per service, unattended. - It is aggravated by
ContextRefresher.refresh()also callingscope.refreshAll():@RefreshScope
beans that read the properties bean are rebuilt at exactly the moment the properties they read are
reset. An application with an asyncApplicationEventMulticastermakes it near deterministic,
because/refreshreturns before the rebind has finished.
Why @RefreshScope is only a partial answer
@RefreshScope on the properties bean does work — ConfigurationPropertiesBeans skips refresh scoped
beans, so they are replaced rather than rebound — and that is the mitigation adopted here. But it has
to be applied bean by bean, by whoever owns each bean. A framework can scope its own properties beans;
it cannot scope the ones its consuming applications declare. Every application that upgrades to a
2025.1.2+ train silently acquires this exposure in its own configuration classes, with no warning at
build or start time, and the symptom surfaces as an unexplained NPE burst minutes after a config change.
Request
- An open issue tracking the reader visible window (#1709 was a closed PR; #750 predates the reset and
is about a different symptom), so the state of it is discoverable. - Consider making the reset opt-in — or at least opt-out per bean without losing rebinding — on 5.0.x,
rather than only in the next major. The reset restores removed properties to defaults, which is
valuable, but it turns a previously non-failing operation into a failing one for any bean with
nested properties, and #1680 changed the behavior for non-proxied beans that #1662 had deliberately
left alone for backwards compatibility.
References
- #1680 introduced the reset (5.0.2); #1662 was the superseded variant that kept non-proxied beans safe.
- #1709 reported the racy window with a repro; closed 2026-08-10, with the reporter noting on 2026-08-17
that #1721 does not fix it. - #1721 added per-bean-name rebind locks (5.0.3) — serializes rebinders, not readers.
- #1716 the permanent
@Valuenulling variant, fixed by #1720 in 5.0.3. - #750 open since 2020, "there never was a guarantee of thread safety refreshing
@ConfigurationProperties".
- Lingua principale
- Java
- Stelle
- 751
- Fork
- 746
- Merge medio
- 16h 35m
- PR unite (30g)
- 23
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di spring-cloud/spring-cloud-commons
-
waiting-for-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
spring-cloud/spring-cloud-commons#1708 ·
I maintainer di solito rispondono entro 1 giorno
-
waiting-for-triage
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 65/100
spring-cloud/spring-cloud-commons#1543 ·
I maintainer di solito rispondono entro 1 giorno
-
HealthCheckServiceInstanceListSupplier docs say all instances are returned when none are healthy, but code returns an empty listForse già presa @bhosale-mahesh l’ha presa 3 giorni fa. Apertadocumentation
Difficoltà 2/5 1-3 ore Idoneità per principianti 50/100
spring-cloud/spring-cloud-commons#1771 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement for team discussion
Difficoltà 5/5 Più di una settimana Idoneità per principianti 42/100
spring-cloud/spring-cloud-commons#1676 ·
I maintainer di solito rispondono entro 1 giorno
-
waiting-for-triage
Difficoltà 5/5 Più di una settimana Idoneità per principianti 38/100
spring-cloud/spring-cloud-commons#1675 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di spring-cloud/spring-cloud-commons
Issue simili
-
account bug communication lecture
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
-
Consumer progress rows interleave topics with equal queue IDsForse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
apache/rocketmq-dashboard#7250 ·
I maintainer di solito rispondono entro 3 giorni
-
Bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
ignatov/intellij-erlang#1093 ·
-
[Bug] QoS count command ignores the times argument after cdForse già presa @CCCLL l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
apache/dubbo#16530 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno