Skip to content

[Breaking Change] XmlSerializer now includes [Obsolete] members by default; IsError=true triggers exception (compat switch available) #120162

@StephenMolloy

Description

@StephenMolloy

Breaking Change: XmlSerializer now includes [Obsolete] members by default

/cc @dotnet/compat, @mconnew, @mangod9

Summary

XmlSerializer now includes members marked with [Obsolete] during XML serialization and deserialization. Previously, such members were silently skipped, which could lead to silent data loss. This change corrects that behavior. The vast majority of affected scenarios will simply start seeing previously omitted data appear in serialized XML (and be populated during deserialization).

Old versions of XmlSerializer (or the current version with the compat switch enabled) will continue silently drop any 'obsolete' data included in payloads for deserialization.

A secondary change: if a member is marked [Obsolete(..., error: true)], the serializer now throws an InvalidOperationException at serializer creation time instead of silently ignoring the member. This enforces the intent of error-level obsolete usage.

Reason for Change

The old behavior was problematic because it silently omitted [Obsolete]-decorated members, causing data loss or corruption without any warning or error. This was contrary to the intended purpose of the [Obsolete] attribute, which is to provide a warning about API usage, not to affect runtime serialization behavior. The prior behavior treated [Obsolete] like [XmlIgnore], suppressing those members without warning. This was:

  • Undocumented and surprising
  • Contrary to the purpose of [Obsolete] (a compile-time guidance mechanism, not a runtime serialization control)
  • Potentially dangerous due to silent data loss
    Since [XmlIgnore] already exists for intentionally excluding members, overloading [Obsolete] for that purpose was incorrect.

This change aligns serialization behavior with developer expectations and .NET design principles. It prevents accidental data loss and makes the impact of [Obsolete] explicit at runtime instead of silent.

Category (Breaking Change Classification)

Bucket 2: Reasonable Grey Area
A change of behavior that some consumers could have (even if unintentionally) depended on. Despite the old behavior being incorrect, it may have been relied upon in production.

Additionally, in some scenarios, the introduction of an exception when error: true could create unexpected noise when upgrading. Properties that are obsolete with error: true can be explicitly silenced with [XmlIgnore], since that attribute takes precedence over any obsolete checks.

Impact

  • Old Behavior:
    • Members with [Obsolete] were silently excluded from XML output and not populated on deserialization. No warning or exception.
  • New Behavior:
    • Members with [Obsolete] (default error: false) are now serialized and deserialized like any other public serializable members.
    • Members with [Obsolete(..., error: true)] cause XmlSerializer construction to throw an InvalidOperationException, making the incompatibility explicit.
  • Mitigation / Opt-out:
    • Set the AppContext switch to restore legacy omission behavior:
      • Switch.System.Xml.IgnoreObsoleteMembers = true

Risk-Benefit Analysis

  • Benefits:
    • Eliminates a source of silent data loss.
    • Restores clear separation of concerns: [Obsolete] for deprecation signaling, [XmlIgnore] for serialization control.
    • Surfaces issues early in development or deployment.
    • Makes intentional blocking (error: true) explicit via an exception.
  • Risks:
    • Some applications may begin emitting additional XML elements (previously, obsolete members were missing).
    • Code that (incorrectly) relied on omission may need adjustment.
    • Rare cases with error: true now fail early instead of proceeding with partial data.
  • Mitigation:
    • Provided compat switch (AppContext) to re-enable old behavior temporarily while migrating.

Guidance for Affected Developers

  • If you truly intended to exclude a member: replace [Obsolete] with [XmlIgnore] (and optionally keep [Obsolete] alongside if you still want deprecation warnings).
  • If you relied on the omission and need time to adjust: enable the AppContext switch temporarily.
  • If you see an exception for [Obsolete(..., error: true)]: either remove error: true, remove the member from the contract, or explicitly ignore it via [XmlIgnore].
  • Review test baselines or golden XML files that may now include additional elements.

AppContext Switch

Name: Switch.System.Xml.IgnoreObsoleteMembers
Default: false (new correct behavior active)
Set to true to revert to legacy ignoring of [Obsolete] members.

Original Issue

Related PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-Serializationbreaking-changeIssue or PR that represents a breaking API or functional change over a prerelease.needs-breaking-change-doc-createdBreaking changes need an issue opened with https://github.com/dotnet/docs/issues/new?template=dotnet

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions