Skip to content

[Breaking change]: ConfigurationBinder silently skips array elements that fail type conversion instead of preserving null placeholders #51753

@tarekgh

Description

@tarekgh

Description

Starting in .NET 8, ConfigurationBinder.Get<T>() / ConfigurationBinder.Bind() silently skips array or list elements whose values fail type conversion, resulting in a shorter collection. Previously in .NET 6/7, failed elements were preserved as null placeholders, maintaining the original array length.

This behavioral change affects any code that relies on array indices aligning with configuration indices, or that checks for null entries to detect binding failures.

Related runtime issue: dotnet/runtime#124169

The root cause is that the internal binding logic changed from pre-allocating the array and binding in-place (leaving null on failure) to first collecting successfully-bound elements into a list and then materializing the final array, which drops failed entries entirely.

Previous behavior

When binding an array/list property via IConfiguration.Get<T>() (with default BinderOptions), if an element contained a value that could not be converted to the target type (e.g., "a" for an int property), the element was preserved as a null placeholder in the resulting array. The array length matched the number of elements in configuration.

// appsettings.json
// "List": [
//   { "Name": "A", "Interval": 10 },
//   { "Name": "B", "Interval": "a" }   <-- invalid int
// ]

var settings = configuration.Get<SampleSettings>();
// .NET 6/7 result:
// settings.List.Length == 2
// settings.List[0] = { Name = "A", Interval = 10 }
// settings.List[1] = null  (conversion failed, but placeholder preserved)

New behavior

Starting in .NET 8, elements that fail type conversion are silently skipped. The resulting array only contains successfully-bound elements, and its length is shorter than the number of configuration entries.

var settings = configuration.Get<SampleSettings>();
// .NET 8+ result:
// settings.List.Length == 1
// settings.List[0] = { Name = "A", Interval = 10 }
// List[1] is gone entirely — no null placeholder

This behavior persists in .NET 9 and .NET 10 as well.

Type of breaking change

  • Behavioral change: Existing binaries might behave differently at run time.

Reason for change

The internal implementation of ConfigurationBinder was refactored in .NET 8. The binder changed from pre-allocating the target array and binding elements in-place (which left null on conversion failure) to collecting only successfully-bound elements into a temporary list before materializing the final array. This causes elements with conversion errors to be dropped instead of preserved. The previous behavior was also incorrect when binding to arrays of value types, such as int[]. For example, it could store 0 when encountering an invalid configuration value, and it would also store 0 when the actual configuration value was 0. This made it confusing and difficult to distinguish between a legitimate value and an error.

Recommended action

  1. Enable ErrorOnUnknownConfiguration during development to catch invalid configuration values early:

    var settings = configuration.Get<SampleSettings>(options =>
        options.ErrorOnUnknownConfiguration = true);

    This will throw an InvalidOperationException when a value cannot be converted, making misconfiguration immediately visible rather than silently dropping elements. It is strongly recommended to enable this option at least during development and testing.

  2. Validate array lengths after binding if your code depends on the number of elements matching the configuration source.

  3. Fix invalid configuration values — ensure all values in your configuration files match the expected types (e.g., don't use "a" where an int is expected).

  4. Use string properties with manual parsing if you need to gracefully handle unconvertible values and preserve all array entries.

Feature area

Extensions

Affected APIs

  • Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>(IConfiguration)
  • Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>(IConfiguration, Action<BinderOptions>)
  • Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(IConfiguration, object)
  • Microsoft.Extensions.Configuration.ConfigurationBinder.Bind(IConfiguration, object, Action<BinderOptions>)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions