Starting with .NET 9, we no longer include an implementation of
BinaryFormatter
in the runtime (.NET Framework remains unchanged). The APIs
are still present, but their implementation always throws an exception,
regardless of project type. Hence, setting the existing backwards compatibility
flag is no longer sufficient to use BinaryFormatter
.
In this blog post, I’ll explain why this change was made and what options you have to move forward.
TL;DR: What should I do?
You have two options to address the removal of BinaryFormatter
‘s
implementation:
- Migrate away from BinaryFormatter. We strongly recommend that you
investigate options to stop using
BinaryFormatter
due to the associated security risks. The BinaryFormatter migration guide lists several options. - Keep using BinaryFormatter. If you need to continue using
BinaryFormatter
in .NET 9, you need to depend on the unsupported System.Runtime.Serialization.Formatters NuGet package, which restores the unsafe legacy functionality and replaces the throwing implementation.
Note
Please note that .NET Framework is unaffected by this change and continues to include an implementation ofBinaryFormatter
. However, we still strongly recommend to stop using BinaryFormatter
from .NET Framework too, for the same reasons.What’s the risk in using BinaryFormatter?
Any deserializer, binary or text, that allows its input to carry information
about the objects to be created is a security problem waiting to happen. There
is a common weakness enumeration (CWE) that describes the issue: CWE-502
“Deserialization of Untrusted Data”. BinaryFormatter
, included in the
the initial release of .NET Framework in 2002, is such a deserializer. We also
cover this in the BinaryFormatter security guide.
Why we removed BinaryFormatter
We strongly believe that .NET should make it easy for customers to do the right thing and hard, if not impossible, to do the wrong thing. We generally refer to this as the “pit of success”.
Shipping a technology that is widely regarded as unsafe is counter to this goal. At the same time, we also have a responsibility to ensure customers can support and move their existing code forward. We can’t just remove widely used components from a .NET release, even when communicated long in advance. We also need a migration plan and stop gap options.
This removal was not a sudden change. Due to the known risks of using
BinaryFormatter
, we excluded it from .NET Core 1.0. But without a clear
migration path to using something safer, customer demand led to
BinaryFormatter
being included in .NET Core 2.0.
Since then, we have been on the path to removing BinaryFormatter
, slowly
turning it off by default in multiple project types but letting consumers opt-in
via flags if still needed for backward compatibility:
- 2020: BinaryFormatter Obsoletion Plan
- 2023: .NET 8 Update: Implementation throws by default
- 2024: .NET 9 Update: Announced intention of removal early in the release cycle
- 2024: .NET 9 Update: Removal completed
- 2024: .NET 9 Breaking Change: In-box BinaryFormatter implementation removed and always throws
In .NET 9 we removed all remaining in-box dependencies on BinaryFormatter
and
replaced the implementation with one that always throws.
Options to move forward
New code should not take a dependency on BinaryFormatter
. For existing code,
you should first investigate alternatives to BinaryFormatter
. In case you
don’t control the serializer but only perform deserialization, you can consider
only reading the BinaryFormatter
payload, without performing any
deserialization. And if none of this works for you can bring the implementation
back by depending on an (unsupported) compatibility package.
I’ll explore these options in more detail below.
Migrate-Away
You should first investigate whether you can replace BinaryFormatter
with
another serializer. We have four recommendations:
- Text-based. If a binary serialization format is not a requirement, you can consider using JSON or XML serialization formats. These serializers are included in .NET and are supported by us.
- Binary If a compact binary representation is important for your scenarios, the following serialization formats and open-source serializers are recommended:
Since DataContractSerializer
honors the same attribute and interface as
BinaryFormatter
(namely [Serializable]
and ISerializable
), it’s probably
the easiest one to migrate to. If your migration goals include adopting a
modern and performant serializer or a format with better cross-platform
interoperability, the other options should be considered.
Read BinaryFormatter Payloads
If your code doesn’t control the serialization but only the deserialization,
use the new NrbfDecoder
to read BinaryFormatter
payloads. This allows you to read the encoded data without any
deserialization. It’s the equivalent of using a JSON/XML reader without the
deserializer:
using System.Formats.Nrbf;
void Read(Stream payload)
{
SerializationRecord rootObject = NrbfDecoder.Decode(payload);
if (rootObject is PrimitiveTypeRecord primitiveRecord)
{
Console.WriteLine($"It was a primitive value: '{primitiveRecord.Value}'");
}
else if (rootObject is ClassRecord classRecord)
{
Console.WriteLine($"It was a class record of '{classRecord.TypeName.AssemblyQualifiedName}' type name.");
}
else if (rootObject is SZArrayRecord<byte> arrayOfBytes)
{
Console.WriteLine($"It was an array of `{arrayOfBytes.Length}`-many bytes.");
}
}
For more details, check out the Nrbf documentation.
BinaryFormatter compatibility package
If you have explored the options and determined you can’t migrate away from
BinaryFormatter
, you can also install the unsupported
System.Runtime.Serialization.Formatters NuGet package and set the
compatibility switch to true:
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.Serialization.Formatters" Version="9.0.0" />
</ItemGroup>
The package replaces the in-box implementation of BinaryFormatter
with a
functioning one, including its vulnerabilities and risks. It’s meant as a
stopgap if you can’t wait with migrating to .NET 9 while not having replaced the
usages of BinaryFormatter
yet.
Since the BinaryFormatter
API still exists and this package only replaces the
in-box implementation you only need to reference it from application projects.
Existing code that is compiled against BinaryFormatter
will continue to work.
Caution
The compatibility package is not supported and unsafe. We strongly recommend against taking a dependency on this package and to instead migrate away fromBinaryFormatter
.Summary
Since the start of .NET Core we have been on a path of deprecating
BinaryFormatter
, due to its security risks.
Starting with .NET 9, we no longer ship an implementation with the runtime. We recommend that you migrate away from BinaryFormatter. If that doesn’t work for you can either start reading the binary payloads without deserializing or you can take a dependency on the unsupported compatibility package.
How is the PowerShell team going to resovle this?
To this day they have no strategy posted…
https://github.com/PowerShell/PowerShell/issues/14054
We had replaced our use of BinaryFormatter with JSON serialization using STJ but only as a stopgap. I believe we will be adopting MessagePack given the huge difference in payload size.
This is why I keep my software developments centered on .NET 3.5
I have begun to use XML Serialization on many projects, but that’s so I can better debug things, and occasionally hand-edit my XML File over some issue, which I can’t conveniently do with a binary serialized file. However, the XML Serialization is extremely in efficient: my new editor, which keeps track of many text and document attributes, storage for a 1K test-file can easily exceed 64-K with all the attributes being XML-serialized.
Eliminating binary serialization is just yet another mistake in C# management in a long string of C# management.
You might want to read this:
https://devblogs.microsoft.com/dotnet/binaryformatter-removed-from-dotnet-9/?commentid=21102#comment-21102
and this:
https://devblogs.microsoft.com/dotnet/binaryformatter-removed-from-dotnet-9/?commentid=21103#comment-21103
I can’t tell if this is satire. It must be….right?
For our code the greatest impact this has had is with custom exceptions. Since the beginning of .NET the guidelines for custom exceptions have always been to mark it
Serializable
(which still works), implement a protected constructor that acceptsSerializationinfo
andStreamingContext
and overrideGetObjectData
so your exception data can be properly serialized across boundaries.With the removal of the formatter then these should now be removed from custom exception types. However that is a breaking change if you are a library writer, your exception class isn’t sealed and somebody created a derived class. While you could remove
GetObjectData
and it derived classes would work, the protected constructor cannot be removed. This leaves you with obsolete code you cannot remove, that won’t ever be called (in theory) and is probably broken anyway.Exceptions have been my pain point too. I use IPC to call code in other processes and BinaryFormatter has been the go-to method of propagating exceptions back to the caller process. I don’t really know what to replace it with.
That’s a good point. I’m planning on updating our documentation with guidance, specifically around exceptions.
I don’t think you need to actually remove the code. Those APIs should still be present and not cause compile-time issues. They only throw when actually used at run time.
Keeping the code also allows you to serve library consumers who actually have to install the legacy fallback package and use this technology.
They do cause a compile time issue. They cause compiler warnings because they are marked obsolete. If you compile warnings as errors then it doesn’t work. The fix is to annotate your own code as well but now you also have code marked as obsolete that you really can’t get rid of. As I mentioned, it complicates things.
That is a wrong, very wrong decision. What about GRPC or other serialization scenarios?
Depreciation of BinarySerializaton will affect the performance of our solutions.
Stop doing that; mark it with a high-level warning and keep stuff that worked fine.
It seems there is some confusion around what this post is saying:
BinaryFormatter
, which is part of the .NET remoting infrastructure.BinaryFormatter
is indeed marked with a high-level warning via obsoletion as well as the fact that by default it will throw. However, if you insist,BinaryFormatter
is still available via the package.Binary serialization IS NOT deprecated. Nowhere in this post does it say that. In fact it identifies 2 binary serialization formats that you can use. What is removed is
BinaryFormatter
which was the original binary serialization format that .NET used for things like remoting and whatnot. It has not been recommended for use for quite a while because: a) it is a security vulnerability, and b) there are better options.You can use binary serialization in your code, and you likely already are. You just don’t want to use
BinaryFormatter
as the serialization protocol anymore.gRPC uses protobuf which is one of the links in the post to use as an alternative format. Other serialization scenarios will require you to eval your code. If you are communicating with a legacy NF project that uses
BinaryFormatter
on its side then you’ll need to pull in the new NuGet package for now. If you have control over the format then switch to a binary serializer that was mentioned in the post or another one that you have found.BinaryFormatter
here means a specific class that serializes to the insecure NRBF format. It’s not about deprecating all binary serialization. In fact, protobuf, employed by gRPC you mentioned, is even recommended as a migration path in the article.As discussed in many threads, binary serializer is NOT performant.
Other binary binary serialization formats are unrelated to the .NET binary serialization format, which is fundamentally dangerous.
@Irakli Lomidize
BinaryFormatter
is not equivalent to amemcpy
. It has to walk the object graph, has to keep track of unique instances, handle cycles, call via interfaces, and use reflection to read attributes/fields in order to determine the state that needs to be written out. Deserialization is even more expensive, it has to load and resolve types, use reflection to call constructors, set fields, and so on.If performance is important then
BinaryFormatter
isn’t the best choice.System.Text.Json
support serialization via a static code generator which has likely a better performance profile because it doesn’t need reflection. However, that doesn’t mean the payload is smaller, which is where binary formats usually shine.The post recommends two binary serializers, both of which perform better than `BinaryFormatter`, namley MessagePack and protobuf-net.
How a String parser could have the same performance as memcpy ?
What’s the implications for WinForms? Don’t a lot of the components like ImageList etc. use BinaryFormatter behind the scenes?
Indeed. What we did for WinForms is implement a `BinaryFormatter` playload (formally called NRBF) reader and writer that is being used to write the binary representation for the most common resource types (such as strings and images). While the format being read and produced is NRBF, it’s not using
BinaryFormatter
.We have productized the payload reader and made it available as a package as well.
Anther change WinForms did relates to the handling of clipboard. The migration guide has a specific section on WinForms.
Oh, wow, gotta mark this date in my calendar! On this day, the .NET team actually decided to deprecate something! 🤣
We do deprecate / obsolete APIs a lot more in .NET Core, mostly because we have better tooling support and a better side-by-side story. What is rare for us is making breaking changes to the extent of actually removing components from the core libraries. There are very few cases where such a massive disruption is worth the monumental effort for the ecosystem. We believe binary formatter is one of those very rare instances because there is decades of security research that shows how much trouble this thing causes.