Migrating to AWS SDK for .NET v4: Key Breaking Changes You Need to Know
AWS SDK for .NET v4 brings performance improvements and cleaner APIs, but it also introduces breaking changes. Let's explore the key changes when migrating from v3 to v4 and how to handle them in your .NET applications.
AWS SDK for .NET v4 brings performance improvements and cleaner APIs, making it an attractive upgrade for .NET developers working with AWS services.
However, like any major version upgrade, it comes with breaking changes that you need to be aware of when migrating your existing applications.
In this post, let's explore the key breaking changes in AWS SDK v4 and see how to migrate smoothly from v3.
Thanks to AWS for sponsoring this article in my .NET on AWS Series.
AWS SDK .NET V4: An Evolutionary Upgrade
The SDK v4 is designed as an evolutionary major version change with minimal breaking changes, making it relatively straightforward to upgrade from v3 to v4.
However, "minimal" doesn't mean zero - there are still quite a few changes to be aware of.
AWS maintains a GitHub tracker that lists all the breaking changes introduced in v4. There's also an official migration guide in the AWS documentation.
Let's walk through the key changes that are worth calling out.
Key Breaking Changes
Value Types: Nullable by Default
One of the most significant changes is how value types are handled in request and response objects.
In v3, all value type properties used non-nullable types (like bool, int, etc.). This created confusion because you couldn't tell if a value was actually returned from the server or if it was just the default value for that type.
In v4, all value type properties now use nullable types (like bool?, int?, etc.).
Let's look at an example using the S3 client to retrieve a document:
// V3var response = await s3Client.GetObjectAsync(request);var bucketKeyEnabled = response.BucketKeyEnabled; // bool - returns false even if not set// V4var response = await s3Client.GetObjectAsync(request);var bucketKeyEnabled = response.BucketKeyEnabled; // bool? - returns null if not set
When running the same code in both versions against an S3 bucket where BucketKeyEnabled was never configured:
- V3 returns:
false(the default value for bool) - V4 returns:
null(indicating the value was not set)
This change is more accurate and addresses a long-standing issue. However, it can have significant impacts on your application:
- If you're using these properties in C# code, you'll get compiler errors if you're not handling nullability
- If you're passing these values to UI or other systems, the behavior might change from
falsetonull
Make sure to review all value type properties in your code and handle them appropriately.
Collections: No More Empty Initializations
Another major change is how collections are returned from SDK responses.
In v3, empty collections were automatically initialized, meaning you'd get an empty list instead of null even when there were no items.
While this follows C# best practices (allowing you to use LINQ methods without null checks), it had significant performance implications for AWS SDK.
In v4, collections are now returned as null instead of being initialized as empty collections.
This is especially noticeable with services like DynamoDB, but it affects other services too, including S3.
S3 Example
Let's look at a simple example listing S3 objects with a specific prefix:
var response = await s3Client.ListObjectsV2Async(new ListObjectsV2Request{BucketName = "my-bucket",Prefix = "weather-documents/"});var keyNames = response.S3Objects.Select(x => x.Key).ToList();
When there are no objects matching the prefix:
- V3:
response.S3Objectsreturns an empty list - code works fine - V4:
response.S3Objectsreturnsnull- throwsNullReferenceException
To fix this, you need to add null checks:
var keyNames = response.S3Objects?.Select(x => x.Key).ToList() ?? new List<string>();
DynamoDB: The Performance Impact
The performance improvement is most visible with DynamoDB.
When querying DynamoDB, each item returned contains multiple AttributeValue objects. Each AttributeValue has properties for different data types (strings, numbers, lists, maps, etc.).
In v3, all these collection properties were initialized to empty collections, even when not used. This meant:
- Significant memory overhead for large result sets
- Extra work for the garbage collector to clean up these unused collections
Let's look at what happens when we query for movies:
var response = await dynamoDbClient.QueryAsync(new QueryRequest{TableName = "Movies",KeyConditionExpression = "Year = :year",ExpressionAttributeValues = new Dictionary<string, AttributeValue>{{":year", new AttributeValue { N = "2020" }}}});// Each item has multiple AttributeValue properties// In V3: All collection properties (BS, L, M, NS, etc.) are initialized as empty collections// In V4: These are null, saving memory and GC pressure
For a query returning 5 items with multiple attributes each, v3 would initialize dozens of empty collections. V4 only creates the collections that actually have data.
Reverting to V3 Behavior (If Needed)
If you want to restore the v3 behavior of initializing collections, you can set a global configuration flag:
// In your application startupAmazon.AWSConfigs.InitializeCollections = true;
With this setting, v4 will behave like v3 and initialize empty collections.
However, for best performance, it's recommended to keep this as false (the default in v4) and update your code to handle null collections.
You can also try out the v4 behavior in v3 before upgrading:
// Try v4 behavior in v3Amazon.AWSConfigs.InitializeCollections = false;
This lets you test how your application behaves with null collections before committing to the v4 upgrade.
AWS SDK Extensions for .NET Core Setup
The AWSSDK.Extensions.NETCore.Setup NuGet package has also been updated in v4.
The main enhancement is native AOT support, which is useful if you're building applications that use native ahead-of-time compilation.
This package provides the AddAWSService<T>() extension methods for dependency injection:
builder.Services.AddAWSService<IAmazonS3>();builder.Services.AddAWSService<IAmazonDynamoDB>();
The Right Way to Register AWS SDK Clients in .NET Dependency Injection

DynamoDB: UTC DateTime by Default
DynamoDB SDK in v4 has introduced an important change in how DateTime values are handled.
In v4, the RetrieveDateTimeInUtc setting now defaults to true, meaning all DateTime values are treated as UTC.
Let's see what this means with an example of retrieving customer order data:
// Assume we have an Order with OrderDateTime propertyvar customer = await context.LoadAsync<Customer>(customerId);// V3: OrderDateTime is in local time (e.g., 2025-11-04T10:30:00+10:00)// V4: OrderDateTime is in UTC (e.g., 2025-11-04T00:30:00Z)
The same datetime value stored in DynamoDB will be returned differently:
- V3: Depends on the server's timezone setting
- V4: Always UTC, regardless of server timezone
This is a more consistent approach and makes your application behavior predictable across different deployment environments.
However, if you need to revert to the old behavior, you can configure it when setting up your DynamoDB context:
builder.Services.AddSingleton<IDynamoDBContext>(sp =>{var client = sp.GetRequiredService<IAmazonDynamoDB>();var config = new DynamoDBContextConfig{RetrieveDateTimeInUtc = false // Set to false for v3 behavior};return new DynamoDBContext(client, config);});
I highly recommend keeping the default v4 behavior as it provides consistent results independent of the server configuration.