How do you mark a @ConfigurationProperties property as deprecated in the metadata, and what do the deprecation levels mean?
answer
- @DeprecatedConfigurationProperty on the getter
- reason + replacement -> level warning
- Manual metadata can set level error + since
- warning = still works; error = removed
- Metadata only, does not stop binding
basics
~20 sAnnotate the getter with @DeprecatedConfigurationProperty(reason, replacement) and the processor emits a deprecation entry, so the IDE warns users. You can also declare it manually in additional-spring-configuration-metadata.json with a deprecation block having level warning or error.
solid answer
~40 sThere are two ways. The clean way is to put @DeprecatedConfigurationProperty on the property's getter, giving a reason and the replacement key; the processor then records a deprecation with level 'warning' in the metadata, and IDEs flag any use of the old key with a strike-through and a message. The manual way is to add a deprecation block in additional-spring-configuration-metadata.json under that property, with fields like level ('warning' or 'error'), reason, replacement, and since. Level 'warning' means the property still works but is discouraged; level 'error' means it is no longer supported (removed) and should not be used. Deprecating properties is the polite way to evolve configuration: you keep binding working while steering users to the new key. Note @DeprecatedConfigurationProperty affects only metadata, not runtime binding behavior.
code
java · 20 lines@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
private String smtpHost;
private String endpoint;
/**
* Legacy SMTP host.
* @deprecated use {@code app.mail.endpoint}
*/
@Deprecated
@DeprecatedConfigurationProperty(
reason = "Superseded by the pooled endpoint.",
replacement = "app.mail.endpoint")
public String getSmtpHost() { return smtpHost; }
public void setSmtpHost(String smtpHost) { this.smtpHost = smtpHost; }
public String getEndpoint() { return endpoint; }
public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
}go deeper
Know an annotation exists to mark a property deprecated for the IDE.
Explain @DeprecatedConfigurationProperty on the getter, reason/replacement, and warning vs error levels.
Cover manual metadata for level error/since and that deprecation is metadata-only, separate from runtime binding.
Treat config as a versioned API; design migration windows (warning -> error) and combine with property-migration for smooth upgrades.
## Why deprecate properties Configuration is an API. When you rename or retire a property you want to guide users, not silently break them. Metadata deprecations let the IDE surface the change (strike-through, warning message, suggested replacement) while the old key can still bind during a transition window. ## Approach 1 — annotation (preferred) Annotate the **getter** of the deprecated property with `@DeprecatedConfigurationProperty`: ```java @Deprecated public String getHost() { return host; } @DeprecatedConfigurationProperty( reason = "Use the pooled endpoint instead.", replacement = "app.mail.endpoint") public String getSmtpHost() { return smtpHost; } ``` The processor reads this and emits, inside that property's metadata entry: ```json "deprecation": { "level": "warning", "reason": "Use the pooled endpoint instead.", "replacement": "app.mail.endpoint" } ``` Annotation-driven deprecations are always **level `warning`** — the property is discouraged but still bindable. ## Approach 2 — manual metadata In `additional-spring-configuration-metadata.json` you can attach a `deprecation` block to a property, and here you control the **level**: ```json { "properties": [ { "name": "app.mail.legacy-timeout", "type": "java.lang.Integer", "deprecation": { "level": "error", "reason": "Removed; timeouts are now automatic.", "replacement": "app.mail.timeout", "since": "3.2.0" } } ] } ``` ## Deprecation levels - **`warning`** (default) — the property is **still supported** and bound at runtime, but its use is discouraged. IDEs show a soft warning. Use during a migration window. - **`error`** — the property is **no longer supported / removed**. It will not be bound (or is meaningless). IDEs flag it more strongly. Use once the property has actually been retired. ## Other fields - **`reason`** — human explanation of why it changed. - **`replacement`** — the new property key to use instead. - **`since`** — the version in which it was deprecated (manual metadata). ## Gotchas - `@DeprecatedConfigurationProperty` goes on the **getter**, not the field or class, and it produces **only metadata** — it does not stop the property from binding. If you want the old key to also *functionally* map to the new one, you handle that in code or via property migration mechanisms; the annotation alone just documents. - Annotation-based deprecations are always `warning`; to express `error` you must use the manual metadata file. - Pair it with the standard Java `@Deprecated` on the accessor for compiler-level signaling, but the metadata deprecation is what drives the *config* IDE experience. - Deprecations are merged like any other metadata, so a rebuild is needed for the IDE to reflect them. ## When to use `warning` while both old and new keys coexist during migration; switch to `error` (manual metadata) once the property is gone. Always supply a `replacement` so users know where to go.
- What is the difference between deprecation level 'warning' and 'error'?'warning' means the property is discouraged but still supported and bound at runtime — used during migration. 'error' means it is removed/no longer supported and should not be used at all. Annotation-based deprecations are always 'warning'; 'error' requires manual metadata.
- Does @DeprecatedConfigurationProperty stop the old key from binding?No. It only produces metadata that drives IDE warnings and documents a replacement. Runtime binding of the old key is unchanged; if you want the old key to functionally redirect to the new one you must handle it in code or a property-migration mechanism.
saying these in an interview costs you the question
- Placing @DeprecatedConfigurationProperty on the field or class instead of the getter
- Thinking the annotation can produce level 'error'
- Believing the annotation stops the property from binding at runtime
- Confusing java @Deprecated with the config metadata deprecation (they are different signals)