Repository navigation
Serialization: built-in serializers declare their ids in code (#8676, part 1) - #8694
Merged
Aaronontheweb merged 3 commits intoSep 30, 2026
Merged
Conversation
…tnet#8676, part 1) Every built-in legacy serializer now declares its wire id with public override int Identifier => N, instead of reading it lazily from akka.actor.serialization-identifiers via SerializerIdentifierHelper. This removes the last reason a legacy serializer needs HOCON to work; wire ids are unchanged. Remote: ProtobufSerializer 2, DaemonMsgCreateSerializer 3, MessageContainerSerializer 6, MiscMessageSerializer 16, PrimitiveSerializers 17, SystemMessageSerializer 22. Cluster: ClusterMessageSerializer 5, ReliableDeliverySerializer 36. Cluster.Tools: DistributedPubSubMessageSerializer 9, ClusterSingletonMessageSerializer 14, ClusterClientMessageSerializer 15. Sharding: ClusterShardingMessageSerializer 13. DistributedData: ReplicatedDataSerializer 11, ReplicatorMessageSerializer 12. Metrics: ClusterMetricsMessageSerializer 10. Persistence: PersistenceMessageSerializer 7, PersistenceSnapshotSerializer 8. Streams: StreamRefSerializer 30. Core: ByteArraySerializer 4, NewtonSoftJsonSerializer 1. ArteryControlMessageSerializer is out of scope: its id already comes from the [AkkaSerializer] V2 generator attribute. Hyperion (src/contrib/serializers) is a third-party serializer and stays on HOCON/SerializationSetup. Adds a BuiltInSerializerIdentifierSpec per module test project asserting that each serializer's code Identifier equals the value in that module's shipped serialization-identifiers HOCON, plus a D1 spec in Akka.Remote.Tests proving a HOCON override of a built-in id is now ignored. Updates the Akka.API.Tests approval files with the added Identifier override lines.
Guard the Identifier override on every public, unsealed built-in serializer with a GetType() check so a user subclass keeps resolving its own id from HOCON instead of inheriting the built-in's hardcoded constant: ProtobufSerializer, DaemonMsgCreateSerializer, MessageContainerSerializer, ClusterMessageSerializer, DistributedPubSubMessageSerializer, ClusterSingletonMessageSerializer, ClusterClientMessageSerializer, ClusterShardingMessageSerializer, ClusterMetricsMessageSerializer, PersistenceSnapshotSerializer, ByteArraySerializer and NewtonSoftJsonSerializer. Sealed and internal serializers keep a plain constant, since they can't be subclassed. Adds a regression spec in Akka.Tests: a NewtonSoftJsonSerializer subclass registered under its own HOCON id keeps that id, and the built-in still reports 1. Replaces the 9 per-module BuiltInSerializerIdentifierSpec files and the Akka.Remote.Tests D1 spec with one Fact in Akka.API.Tests, the only project that references all 9 modules. It merges every module's default config, and for each akka.actor.serialization-identifiers row, builds the serializer with RuntimeHelpers.GetUninitializedObject (skipping the constructor) and asserts Identifier matches. A serializer whose id still resolved lazily from HOCON would throw on the null actor-system field, so this also proves D1. Reverts two whitespace-only changes picked up incidentally while adding the Identifier overrides to ClusterMessageSerializer.cs and ClusterMetricsMessageSerializer.cs.
Aaronontheweb
added a commit
that referenced
this pull request
Oct 1, 2026
…8698 (#8700) * Audit BREAKING_CHANGES_V1.6.md against v1.5.71 and catch up through #8698 Restate the definition (binary / source / behavioral compatibility against the last stable v1.5 release), drop rows that are not breaking under it (Artery-only, DynamicTypeLoading-off-only, fixes, dev-only comparisons), fold the TargetInvocationException and trim-annotation rows into one row each, shorten the rest, mark every row Merged with its PR, and add the breaking changes from #8694 and #8698. * Add pre-ledger breaking changes to BREAKING_CHANGES_V1.6.md Cover changes on dev since the v1.5 line diverged that landed before the ledger existed: net10.0-only targeting and ByteString removal (#8132), App.config HOCON loading removal (#7456), and the AddOrSet removal (#7622). * Apply review feedback to BREAKING_CHANGES_V1.6.md Add rows for #8222 (Serializer overloads, ByteArraySerializer manifest), #8465 (explicit TLS hostname check), #8132 (TCP connects bypass akka.io.dns) and #7557 (object handler without predicate blocks later Receive calls). State net10.0-only once, folding in #8594 and the dependency bumps; fix the ByteString row's write types; narrow the #8698 row to module serializers; drop the .Internal AddOrSet row and say why in the preamble; shorten rows; use asterisk list markers. * Drop the #7557 ReceiveActor row from BREAKING_CHANGES_V1.6.md The Receive(typeof(object), Func<object, bool>) regression is being fixed in a separate PR instead of documented.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Part 1 of the plan to move first-party serializer registration into C# (#8676). Every built-in legacy serializer now declares its wire id in code with
public override int Identifier => N;(or aGetType()-guarded version of it, see below), instead of reading it lazily fromakka.actor.serialization-identifiersviaSerializerIdentifierHelper.GetSerializerIdentifierFromConfig. That HOCON lookup is lazy on first access toIdentifier- it is never called from theSerializerconstructor - so nothing about serializer construction changes. No wire bytes change: every id below is exactly what each module's.confalready ships.Serializers and ids
ProtobufSerializer2,DaemonMsgCreateSerializer3,MessageContainerSerializer6,MiscMessageSerializer16,PrimitiveSerializers17,SystemMessageSerializer22ClusterMessageSerializer5,ReliableDeliverySerializer36 (internal)DistributedPubSubMessageSerializer9,ClusterSingletonMessageSerializer14,ClusterClientMessageSerializer15ClusterShardingMessageSerializer13ReplicatedDataSerializer11,ReplicatorMessageSerializer12ClusterMetricsMessageSerializer10PersistenceMessageSerializer7,PersistenceSnapshotSerializer8StreamRefSerializer30ByteArraySerializer4,NewtonSoftJsonSerializer1NullSerializeralready overrodeIdentifier, so it's untouched.ArteryControlMessageSerializeris skipped: its id already comes from the[AkkaSerializer<T>]V2 source-generator attribute, not from HOCON. Hyperion (src/contrib/serializers) is a third-party serializer and is out of scope; it stays on HOCON/SerializationSetup.The GetType() guard on public, unsealed serializers
Serializer.Identifierresolves byGetType(), not by the declaring type. A plainpublic override int Identifier => N;on a serializer that a user can subclass would reportNfor the subclass too, even if the subclass is registered under its own HOCON id - silently taking overGetSerializerById(N)for messages the subclass never wrote. So every serializer that ispublicand unsealed guards the constant on an exact type check and falls back to the base (HOCON) lookup otherwise:The 12 affected classes:
ProtobufSerializer,DaemonMsgCreateSerializer,MessageContainerSerializer,ClusterMessageSerializer,DistributedPubSubMessageSerializer,ClusterSingletonMessageSerializer,ClusterClientMessageSerializer,ClusterShardingMessageSerializer,ClusterMetricsMessageSerializer,PersistenceSnapshotSerializer,ByteArraySerializer,NewtonSoftJsonSerializer.The other 8 (
MiscMessageSerializer,PrimitiveSerializers,SystemMessageSerializer,ReplicatedDataSerializer,ReplicatorMessageSerializer,PersistenceMessageSerializer,StreamRefSerializer- allsealed- andReliableDeliverySerializer, which isinternal sealed) can't be subclassed, so they keep a plain constant.No wire change
Ids come from code now but are identical to what HOCON already assigned. Existing serialization and remote round-trip specs pass unchanged.
Breaking changes
A built-in serializer's id can no longer be overridden via
akka.actor.serialization-identifiers. Before this change, an application that set, for example,"Akka.Remote.Serialization.MiscMessageSerializer, Akka.Remote" = 99in its own HOCON would make that node write id 99 on the wire. Now the code id (16, in that example) always wins, because it's a hardcodedIdentifieroverride rather than a config lookup. A user subclass of a built-in serializer keeps resolving its own id from HOCON - only the built-in type itself is pinned.serialization-identifiersoverride of a built-in Akka.NET serializer from your HOCON; it no longer has any effect.This is expected to affect very few, if any, real applications, since overriding a built-in serializer's id would already have broken wire compatibility with any node that didn't carry the same override.
Tests
[Fact]inAkka.API.Tests- the only project that references all 9 affected modules (Remote, Cluster, Cluster.Tools, Cluster.Sharding, DistributedData, Cluster.Metrics, Persistence, Streams, core) - merges every module's default config and, for every row inakka.actor.serialization-identifiers, builds the serializer withRuntimeHelpers.GetUninitializedObject(skipping the constructor, leaving the actor-system field null) and assertsIdentifiermatches the row. A serializer whose id still resolved lazily from HOCON would touch that null field and throw, so this test also proves D1 for every built-in serializer in one place.Akka.Tests: aNewtonSoftJsonSerializersubclass registered under its own HOCON id keeps that id, and the built-inNewtonSoftJsonSerializerstill reports 1.Akka.Tests,Akka.Remote.Tests,Akka.Cluster.Tests,Akka.Persistence.TestsandAkka.Streams.Tests(all green), plus the serializer specs inAkka.Cluster.Tools.Tests,Akka.Cluster.Sharding.Tests,Akka.DistributedData.TestsandAkka.Cluster.Metrics.Tests.dotnet test src/core/Akka.API.Tests -c Release -f net10.0: adding theIdentifieroverrides changes the public API surface for 9 assemblies (the property signature is unchanged, only its body), so both theDotNetandNetapproval files were updated with just the addedIdentifieroverride lines.Next steps
PR 2 moves alias and binding registration into the modules'
ModuleSerializerstables, so the C# tables carry everything HOCON carries today (behavior unchanged, verified by parity specs). Later PRs load those tables from code and delete the built-in HOCON rows.