Skip to content

Instantly share code, notes, and snippets.

@longcao
Last active December 4, 2025 23:33
Show Gist options
  • Select an option

  • Save longcao/a56d28fb8f98aebb3706ed27dab5c201 to your computer and use it in GitHub Desktop.

Select an option

Save longcao/a56d28fb8f98aebb3706ed27dab5c201 to your computer and use it in GitHub Desktop.

ScalaPB Breaking Changes: v0.9.8 to v0.10.11

This report documents all public methods that were removed or changed in a source/binary incompatible way between ScalaPB v0.9.8 and v0.10.11.

Summary Table

Method/Change Location Change Type Source Compat Binary Compat Deprecated Since Can Pre-Migrate in 0.9.8? Replacement/Migration
valueDescriptor GeneratedEnum Removed ❌ No ❌ No 0.5.47 ✅ Yes Use javaValueDescriptor
descriptor GeneratedEnumCompanion Removed ❌ No ❌ No 0.5.47 ✅ Yes Use javaDescriptor
getField(JavaDescriptor) GeneratedMessage Removed ❌ No ❌ No 0.6.0 ✅ Yes Use getField with ScalaPB descriptor
getAllFields GeneratedMessage Removed ❌ No ❌ No 0.6.0 ✅ Yes Use toPMessage
fromFieldsMap GeneratedMessageCompanion Removed ❌ No ❌ No 0.6.0 ✅ Yes Use messageReads
descriptor GeneratedMessageCompanion Removed ❌ No ❌ No 0.5.47 ✅ Yes Use javaDescriptor
mergeFrom(input) Generated messages (instance) Removed ❌ No ❌ No N/A (not deprecated) ⚠️ Partial Use parseFrom (available); merge only in 0.10+
of(..., unknownFields) Generated companions Signature changed ⚠️ Partial ❌ No N/A ❌ No Remove unknownFields param; use .withUnknownFields()
Message[T] trait Generated messages Removed from extends ✅ Yes ❌ No N/A ✅ Yes Use GeneratedMessage in type bounds
Enum base type Generated enums traitclass ✅ Yes ❌ No N/A ✅ N/A Recompile; no code changes needed
Oneof parameter order Generated messages Position changed ❌ No ❌ No N/A ✅ Yes Use named parameters (already works in 0.9.8)
unknownFields parameter Generated case classes Added to constructor ⚠️ Partial ❌ No N/A ❌ No Update pattern matches; use @ pattern

Legend

  • Yes: Compatible / Can pre-migrate
  • No: Incompatible / Cannot pre-migrate
  • ⚠️ Partial: May be compatible depending on usage / Partial migration possible
  • N/A: Not applicable

Impact Summary by Category

Category Count Severity
Removed deprecated methods 6 🟡 Medium (long deprecated)
Removed non-deprecated methods 1 🔴 High (mergeFrom)
Signature changes 4 🔴 High (of(), Message[T], merge, enum base)
Structural changes 2 🟠 Medium-High (oneof order, unknown fields)
Total breaking changes 13 -

1. Previously Deprecated Public Methods That Were Removed

From commit f50436f4956d832f2e0f30be4376fa8628eddb22 (November 28, 2019), these deprecated methods were removed from the runtime:

In scalapb.GeneratedEnum trait:

  • def valueDescriptor: JavaDescriptors.EnumValueDescriptor
    • Deprecated since: ScalaPB 0.5.47
    • Replacement: Use javaValueDescriptor instead
    • Location: scalapb-runtime/shared/src/main/scala/scalapb/GeneratedMessageCompanion.scala

In scalapb.GeneratedEnumCompanion trait:

  • def descriptor: com.google.protobuf.Descriptors.EnumDescriptor
    • Deprecated since: ScalaPB 0.5.47
    • Replacement: Use javaDescriptor instead (note: in future versions this may refer to scalaDescriptor)
    • Location: scalapb-runtime/shared/src/main/scala/scalapb/GeneratedMessageCompanion.scala

In scalapb.GeneratedMessage trait:

  • def getField(field: com.google.protobuf.Descriptors.FieldDescriptor): Any

    • Deprecated since: 0.6.0
    • Replacement: Use getField that accepts a ScalaPB descriptor and returns PValue
    • Location: scalapb-runtime/shared/src/main/scala/scalapb/GeneratedMessageCompanion.scala
  • def getAllFields: Map[JavaDescriptors.FieldDescriptor, Any]

    • Deprecated since: 0.6.0
    • Replacement: Use toPMessage
    • Location: scalapb-runtime/shared/src/main/scala/scalapb/GeneratedMessageCompanion.scala

In scalapb.GeneratedMessageCompanion trait:

  • def fromFieldsMap(fields: Map[JavaDescriptors.FieldDescriptor, Any]): A

    • Deprecated since: 0.6.0
    • Replacement: Use messageReads
    • Location: scalapb-runtime/shared/src/main/scala/scalapb/GeneratedMessageCompanion.scala
  • def descriptor: com.google.protobuf.Descriptors.Descriptor

    • Deprecated since: ScalaPB 0.5.47
    • Replacement: Use javaDescriptor instead (note: in future versions this may refer to scalaDescriptor)
    • Location: scalapb-runtime/shared/src/main/scala/scalapb/GeneratedMessageCompanion.scala

2. Other Public Methods That Were Removed (Not Previously Deprecated)

Instance method moved to companion:

  • def mergeFrom(_input__: CodedInputStream): A
    • Removed from: Generated message classes (as instance method)
    • Replaced with: Companion object method def merge(_message__: A, _input__: CodedInputStream): A
    • Commit: c5eda09b16ef954a0ec091b281b7a1947b6ee1fb (November 30, 2019)
    • Impact: Source and binary incompatible
    • Migration: Instead of message.mergeFrom(input), use MessageCompanion.merge(message, input) or use the generated parseFrom methods
    • Location: Generated code in compiler-plugin/src/main/scala/scalapb/compiler/ProtobufGenerator.scala

3. Public Methods with Signature Changes (Source/Binary Incompatible)

Major signature changes:

3.1. of() method signature change

Commit: a0d25705e74774a214d129cfad248fc7d3efb2f (March 10, 2020)

Change: The unknownFields parameter was removed from the of() method in companion objects

Before (v0.9.8):

def of(
  field1: T1,
  field2: T2,
  // ... more fields
  unknownFields: _root_.scalapb.UnknownFieldSet
): MessageType

After (v0.10.11):

def of(
  field1: T1,
  field2: T2
  // ... more fields (no unknownFields parameter)
): MessageType

Impact: Binary incompatible (method signature changed). May be source compatible in some cases if unknownFields was always passed explicitly.

Migration: Remove the unknownFields parameter from of() calls. If you need to set unknown fields, use the case class constructor directly or use .withUnknownFields() after construction.

Example:

// Before:
val msg = MyMessage.of(field1 = "value", unknownFields = UnknownFieldSet.empty)

// After:
val msg = MyMessage.of(field1 = "value")
// Or if you need unknown fields:
val msg = MyMessage.of(field1 = "value").withUnknownFields(unknownFields)

3.2. Generated message extends clause change

Commit: c5eda09b16ef954a0ec091b281b7a1947b6ee1fb (November 30, 2019)

Change: Message[T] trait removed from generated message extends clause

Before (v0.9.8):

final case class MyMessage(...)
  extends scalapb.GeneratedMessage
  with scalapb.Message[MyMessage]
  with scalapb.lenses.Updatable[MyMessage]

After (v0.10.11):

final case class MyMessage(...)
  extends scalapb.GeneratedMessage
  with scalapb.lenses.Updatable[MyMessage]

Note: In 0.10.x, Message[T] became a type alias to Any for source compatibility. It is fully deprecated.

Impact: Binary incompatible but source compatible for most code.

Migration: Remove explicit references to Message[T] in type bounds and extends clauses. Use GeneratedMessage instead.

Example:

// Before:
def process[T <: GeneratedMessage with Message[T]](msg: T): Unit = ???

// After:
def process[T <: GeneratedMessage](msg: T): Unit = ???

3.3. mergeFrom instance method → merge companion method

Commit: c5eda09b16ef954a0ec091b281b7a1947b6ee1fb (November 30, 2019)

Change: The mergeFrom method moved from instance to companion and changed signature

Before (v0.9.8):

// Instance method
def mergeFrom(_input__: CodedInputStream): MyMessage

After (v0.10.11):

// Companion object method
def merge(_message__: MyMessage, _input__: CodedInputStream): MyMessage

Impact: Source and binary incompatible

Migration:

  • If you were calling mergeFrom directly, switch to using the companion merge method
  • However, most users should use the parseFrom methods instead, which remain unchanged
  • The merge method is typically used internally by ScalaPB's parsing logic

Example:

// Before:
val updated = existingMessage.mergeFrom(codedInputStream)

// After:
val updated = MyMessage.merge(existingMessage, codedInputStream)

// Better: Use parseFrom for most cases
val message = MyMessage.parseFrom(inputStream)

3.4. Enum base type change

Commit: Part of 0.10.0 release (commit c5eda09b and related)

Change: Enum case objects changed from extending a sealed trait to extending a sealed abstract base class

Before (v0.9.8):

sealed trait MyEnum extends GeneratedEnum { ... }
case object Value1 extends MyEnum
case object Value2 extends MyEnum

After (v0.10.11):

sealed abstract class MyEnum extends GeneratedEnum { ... }
case object Value1 extends MyEnum
case object Value2 extends MyEnum

Impact: Binary incompatible but source compatible for most code. This was done for improved performance.

Migration: No source code changes needed. Recompile all code that depends on the generated enums.

4. Additional Breaking Changes

4.1. Constructor parameter order for oneofs

BREAKING CHANGE: In earlier versions, constructor parameters for oneofs were always generated after all the regular fields. From 0.10.x, oneofs are generated in the position that matches their index in the proto file.

Impact: Source and binary incompatible if you were relying on parameter positions

Migration: Use named parameters when constructing messages with oneofs

4.2. Unknown fields preservation

BREAKING CHANGE: All messages now preserve unknown fields by default, which adds an additional unknownFields: UnknownFieldSet parameter to case classes.

Impact: Source incompatible if using pattern matching with specific parameter counts, binary incompatible

Migration:

  • Update pattern matches to handle the additional parameter or use @ pattern
  • To disable this feature: set preserve_unknown_fields to false at the file or package level

Example:

// Before (v0.9.8):
case class MyMessage(field1: String, field2: Int)

// After (v0.10.11):
case class MyMessage(
  field1: String,
  field2: Int,
  unknownFields: UnknownFieldSet = UnknownFieldSet.empty
)

Summary

The most impactful breaking changes between v0.9.8 and v0.10.11 are:

  1. Removal of long-deprecated methods (0.5.47, 0.6.0): fromFieldsMap, getAllFields, getField, descriptor, valueDescriptor
  2. mergeFrommerge: Instance method removed, replaced with companion static method
  3. of() signature change: unknownFields parameter removed
  4. Message[T] removal: Generated messages no longer extend Message[T]
  5. Unknown fields parameter: Added to all case classes by default
  6. Oneof parameter order: Now matches proto file order instead of always being last
  7. Enum base type: Changed from sealed trait to sealed abstract class

Most users should focus on:

  • Removing unknownFields from of() calls
  • Updating type bounds to remove Message[T]
  • Handling the additional unknownFields parameter in pattern matches
  • Using named parameters for messages with oneofs

References

  • Commit f50436f4956d832f2e0f30be4376fa8628eddb22: Remove deprecated methods
  • Commit a0d25705e74774a214d129cfad248fc7d3efb2f: Remove unknownFields parameter from of()
  • Commit c5eda09b16ef954a0ec091b281b7a1947b6ee1fb: Deprecate Message[T] and move mergeFrom
  • ScalaPB CHANGELOG
@longcao
Copy link
Author

longcao commented Dec 4, 2025

Claude Code prompt, executed from the root of a locally cloned ScalaPB repo:

Between v0.9.8 and v0.10.11, find me all previously deprecated public methods that were removed.

Then, find me any other public methods that were removed too.

Finally, find me any public methods that changed signature significantly in a source or binary incompatible way. 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment