This is an automated email from the ASF dual-hosted git repository.
He-Pin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 033500b6e5 feat: optionally bind ScalaPB messages to the protobuf
serializer (#3651)
033500b6e5 is described below
commit 033500b6e538a049f3d8bc4452dbd4d867cb5c3e
Author: PJ Fanning <[email protected]>
AuthorDate: Fri Oct 23 06:39:02 2026 +0100
feat: optionally bind ScalaPB messages to the protobuf serializer (#3651)
* feat: optionally bind ScalaPB messages to the protobuf serializer
Motivation:
ProtobufSerializer can serialize ScalaPB generated messages and the
protobuf allow list already contains scalapb.GeneratedMessageCompanion,
but users had to add the scalapb.GeneratedMessage serialization binding
themselves.
Modification:
- Add an optional "scalapb.GeneratedMessage" = proto serialization
binding to the remote reference.conf.
- Only use the binding when scalapb.GeneratedMessage can be loaded, in
the same way as the optional com.google.protobuf binding.
- Mention the binding in the serialization docs.
Result:
ScalaPB messages are serialized with ProtobufSerializer out of the box
when ScalaPB is on the classpath. Nothing changes when ScalaPB is absent.
Rolling upgrade consideration: applications that have ScalaPB on the
classpath and relied on another binding for those messages (for example
a binding on a shared marker trait or interface) may now select
ProtobufSerializer instead, since scalapb.GeneratedMessage can be the
more specific match. Nodes running different versions could then
serialize those messages with different serializers during a rolling
upgrade. Such applications can keep their previous behaviour by setting
"scalapb.GeneratedMessage" = none, or an explicit binding, in
pekko.actor.serialization-bindings.
Tests:
- Not run locally - left to CI; scalafmt applied
References:
None - convenience binding for ScalaPB users
* docs: document opting out of the ScalaPB binding in the migration guide
Motivation:
The new default `scalapb.GeneratedMessage` binding can change how ScalaPB
messages are serialized for users upgrading from Pekko 1.x.
Modification:
Add an entry to the serialization bindings section of the 1.x to 2.x
migration guide explaining the change and how to disable the binding with
`"scalapb.GeneratedMessage" = none`.
Result:
Users doing a rolling update know how to keep the Pekko 1.x behavior.
Tests:
- Not run - docs only
References:
Refs #3651
---
.../main/scala/org/apache/pekko/serialization/Serialization.scala | 5 ++++-
docs/src/main/paradox/migration/migration-guide-1.x-2.x.md | 6 ++++++
docs/src/main/paradox/serialization.md | 2 ++
remote/src/main/resources/reference.conf | 2 ++
.../pekko/remote/serialization/ProtobufSerializerSpec.scala | 8 ++++++++
5 files changed, 22 insertions(+), 1 deletion(-)
diff --git
a/actor/src/main/scala/org/apache/pekko/serialization/Serialization.scala
b/actor/src/main/scala/org/apache/pekko/serialization/Serialization.scala
index 6c0d8ec922..6951abd779 100644
--- a/actor/src/main/scala/org/apache/pekko/serialization/Serialization.scala
+++ b/actor/src/main/scala/org/apache/pekko/serialization/Serialization.scala
@@ -462,7 +462,7 @@ class Serialization(val system: ExtendedActorSystem)
extends Extension {
private[pekko] val bindings: Seq[ClassSerializer] = {
val fromConfig = for {
(className: String, alias: String) <- settings.SerializationBindings
- if alias != "none" && checkGoogleProtobuf(className)
+ if alias != "none" && checkGoogleProtobuf(className) &&
checkScalaPb(className)
} yield (system.dynamicAccess.getClassFor[Any](className).get,
serializers(alias))
val fromSettings = serializerDetails.flatMap { detail =>
@@ -500,6 +500,9 @@ class Serialization(val system: ExtendedActorSystem)
extends Extension {
// include "com.google.protobuf.GeneratedMessage" = proto in configured
serialization-bindings.
private def checkGoogleProtobuf(className: String): Boolean =
checkClass("com.google.protobuf", className)
+ // for convenience scalapb.GeneratedMessage is defined in reference.conf,
but might not be included in classpath
+ private def checkScalaPb(className: String): Boolean =
checkClass("scalapb.GeneratedMessage", className)
+
private def checkClass(prefix: String, className: String): Boolean =
!className.startsWith(prefix) ||
system.dynamicAccess.getClassFor[Any](className).isSuccess
diff --git a/docs/src/main/paradox/migration/migration-guide-1.x-2.x.md
b/docs/src/main/paradox/migration/migration-guide-1.x-2.x.md
index c5b6cd97b6..c4bddca295 100644
--- a/docs/src/main/paradox/migration/migration-guide-1.x-2.x.md
+++ b/docs/src/main/paradox/migration/migration-guide-1.x-2.x.md
@@ -221,6 +221,12 @@ these messages as an ordinary `ByteString`.
([PR2924](https://github.com/apache/
instead of `ShardingEnvelope`. `ShardingEnvelope` is serialized as before, but
the serializer also
handles new Sharded Daemon Process messages (its state, and the commands for
getting and changing the
number of processes) that Pekko 1.x nodes cannot deserialize.
([PR2755](https://github.com/apache/pekko/pull/2755))
+* When remoting is enabled and ScalaPB is on the classpath, ScalaPB generated
messages
+(`scalapb.GeneratedMessage`) are now bound to the `proto` serializer by
default. If your ScalaPB
+messages were serialized with another serializer in Pekko 1.x, mixed-version
nodes would disagree
+during a rolling update. To keep the Pekko 1.x behavior, disable the new
binding:
+`pekko.actor.serialization-bindings { "scalapb.GeneratedMessage" = none }`.
+([PR3651](https://github.com/apache/pekko/pull/3651))
* The new `FilteredPayload` placeholder, which is stored in the journal in
place of a filtered event
(for example with events-by-slice queries and projections), has a new
serializer with identifier `34`.
Pekko 1.x cannot deserialize it, so do not run Pekko 1.x against a journal
once such events have been
diff --git a/docs/src/main/paradox/serialization.md
b/docs/src/main/paradox/serialization.md
index 74e9c525b1..4368d4ef36 100644
--- a/docs/src/main/paradox/serialization.md
+++ b/docs/src/main/paradox/serialization.md
@@ -65,6 +65,8 @@ you would need to reference it as `Wrapper$Message` instead
of `Wrapper.Message`
Pekko provides serializers for several primitive types and
[protobuf](https://github.com/protocolbuffers/protobuf)
@javadoc[com.google.protobuf.GeneratedMessage](com.google.protobuf.GeneratedMessage)
by default, so normally you don't need to add
configuration for that if you send raw protobuf messages as actor messages.
+Messages generated by [ScalaPB](https://scalapb.github.io/)
(`scalapb.GeneratedMessage`) are also bound to the
+protobuf serializer by default when ScalaPB is on the classpath.
### Programmatic
diff --git a/remote/src/main/resources/reference.conf
b/remote/src/main/resources/reference.conf
index 4f7702b322..4d752322aa 100644
--- a/remote/src/main/resources/reference.conf
+++ b/remote/src/main/resources/reference.conf
@@ -42,6 +42,8 @@ pekko {
# This com.google.protobuf serialization binding is only used if the
class can be loaded,
# i.e. com.google.protobuf dependency has been added in the application
project.
"com.google.protobuf.GeneratedMessage" = proto
+ # optional binding, only used if the ScalaPB dependency has been added
in the application project
+ "scalapb.GeneratedMessage" = proto
"org.apache.pekko.actor.Identify" = pekko-misc
"org.apache.pekko.actor.ActorIdentity" = pekko-misc
diff --git
a/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
b/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
index 9d79c066bc..1a7146b0b4 100644
---
a/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
+++
b/remote/src/test/scala/org/apache/pekko/remote/serialization/ProtobufSerializerSpec.scala
@@ -91,6 +91,14 @@ class ProtobufSerializerSpec extends PekkoSpec(s"""
ser.serializerFor(classOf[MyMessageV3]).getClass should
===(classOf[ProtobufSerializer])
}
+ "skip the optional ScalaPB binding when ScalaPB is not on the classpath"
in {
+ val scalaPbClassName = "scalapb.GeneratedMessage"
+ ser.settings.SerializationBindings.get(scalaPbClassName) should
===(Some("proto"))
+ val dynamicAccess =
system.asInstanceOf[ExtendedActorSystem].dynamicAccess
+ dynamicAccess.getClassFor[Any](scalaPbClassName).isFailure should
===(true)
+ ser.bindings.exists { case (clazz, _) => clazz.getName ==
scalaPbClassName } should ===(false)
+ }
+
"work for SerializedMessage (just an org.apache.pekko.protobuf message)"
in {
// create a protobuf message
val protobufMessage: SerializedMessage =
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]