Protocol Buffers works excellently with Scala, especially for big data processing, Akka applications, and microservices. Scala developers use ScalaPB, which generates idiomatic Scala case classes with immutability and type safety built in.
This guide walks you through using Protocol Buffers in Scala. We'll use simple examples and focus on practical implementation. Learn more about protobuf best practices for production apps.
What You'll Need
- •Scala: Version 2.12+ or 3.x (check with
scala -version) - •sbt: Scala build tool version 1.5+
- •ScalaPB: We'll add this as an sbt plugin
Step 1: Set Up Your Project
Create a new Scala project:
sbt new scala/scala-seed.g8 cd scala-protobuf-demo mkdir -p src/main/protobuf
Add ScalaPB to your project/plugins.sbt:
addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.6")
libraryDependencies += "com.thesamet.scalapb" %% "compilerplugin" % "0.11.13"Update your build.sbt:
name := "scala-protobuf-demo" version := "0.1.0" scalaVersion := "2.13.12" // Enable ScalaPB Compile / PB.targets := Seq( scalapb.gen() -> (Compile / sourceManaged).value / "scalapb" ) // Add ScalaPB runtime library libraryDependencies ++= Seq( "com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf" )
Step 2: Create Your .proto File
Create src/main/protobuf/subscriber.proto:
syntax = "proto3";
package telecom;
// Mobile subscriber information
message Subscriber {
string msisdn = 1; // Mobile number
string name = 2; // Subscriber name
string email = 3; // Email address
SubscriptionType type = 4; // Plan type
bool active = 5; // Account status
repeated string services = 6; // Active services
}
enum SubscriptionType {
PREPAID = 0;
POSTPAID = 1;
CORPORATE = 2;
}Step 3: Generate Scala Code
Compile the proto file to generate Scala classes:
sbt compile
ScalaPB automatically generates Scala case classes in target/scala-*/src_managed/main/scalapb/
Step 4: Use Protocol Buffers in Scala
Create src/main/scala/Main.scala:
import telecom.subscriber._
object Main extends App {
// Create a subscriber using case class syntax
val subscriber = Subscriber(
msisdn = "+91-9876543210",
name = "Telecom Customer",
email = "[email protected]",
type = SubscriptionType.POSTPAID,
active = true,
services = Seq("Voice", "Data", "4G LTE")
)
println(s"Created subscriber: ${subscriber.name}")
println(s"MSISDN: ${subscriber.msisdn}")
println(s"Type: ${subscriber.type}")
// Serialize to binary
val bytes = subscriber.toByteArray
println(s"Serialized to ${bytes.length} bytes\n")
// Deserialize from binary
val decoded = Subscriber.parseFrom(bytes)
println(s"Deserialized subscriber: ${decoded.name}")
println(s"Email: ${decoded.email}")
println(s"Active: ${decoded.active}")
// Access repeated fields (services)
println("\nActive services:")
decoded.services.foreach(service => println(s" - $service"))
}Step 5: Run Your Application
Run the application:
sbt run
You should see output like:
Created subscriber: Telecom Customer MSISDN: +91-9876543210 Type: POSTPAID Serialized to 68 bytes Deserialized subscriber: Telecom Customer Email: [email protected] Active: true Active services: - Voice - Data - 4G LTE
Immutable Updates with Copy
ScalaPB generates case classes, so you can use Scala's copy method:
val subscriber = Subscriber(
msisdn = "+91-9876543210",
name = "Original Name",
type = SubscriptionType.PREPAID
)
// Update fields immutably
val updated = subscriber.copy(
name = "Updated Name",
type = SubscriptionType.POSTPAID,
active = true
)
println(s"Original: ${subscriber.name}, ${subscriber.type}")
println(s"Updated: ${updated.name}, ${updated.type}")
// Add services
val withServices = updated.copy(
services = updated.services :+ "5G"
)
println(s"Services: ${withServices.services.mkString(", ")}")Saving and Loading from Files
Save and load protobuf data:
import java.nio.file.{Files, Paths}
import telecom.subscriber._
object FileExample extends App {
val subscriber = Subscriber(
msisdn = "+91-9123456789",
name = "Network Admin",
email = "[email protected]",
type = SubscriptionType.CORPORATE,
active = true
)
// Save to file
val bytes = subscriber.toByteArray
Files.write(Paths.get("subscriber.bin"), bytes)
println("Saved to file")
// Load from file
val loadedBytes = Files.readAllBytes(Paths.get("subscriber.bin"))
val loaded = Subscriber.parseFrom(loadedBytes)
println(s"Loaded: ${loaded.name}")
println(s"MSISDN: ${loaded.msisdn}")
}JSON Conversion
ScalaPB supports JSON conversion. Add the JSON dependency to build.sbt:
libraryDependencies += "com.thesamet.scalapb" %% "scalapb-json4s" % "0.12.0"
Use it in your code:
import scalapb.json4s.JsonFormat
import telecom.subscriber._
val subscriber = Subscriber(
msisdn = "+91-9876543210",
name = "Mobile User",
type = SubscriptionType.PREPAID,
active = true
)
// Convert to JSON
val jsonString = JsonFormat.toJsonString(subscriber)
println("JSON output:")
println(jsonString)
// Parse from JSON
val jsonData = """
{
"msisdn": "+91-9111111111",
"name": "New Subscriber",
"type": "POSTPAID",
"active": true
}
"""
val parsed = JsonFormat.fromJsonString[Subscriber](jsonData)
println(s"\nParsed from JSON: ${parsed.name}")
println(s"Type: ${parsed.type}")Using Protobuf with Akka
Protobuf works great with Akka actors for message passing:
import akka.actor.{Actor, ActorSystem, Props}
import telecom.subscriber._
class SubscriberActor extends Actor {
def receive = {
case subscriber: Subscriber =>
println(s"Received subscriber: ${subscriber.name}")
println(s"MSISDN: ${subscriber.msisdn}")
println(s"Type: ${subscriber.type}")
// Process the subscriber
val updated = subscriber.copy(active = true)
sender() ! updated
}
}
object AkkaExample extends App {
val system = ActorSystem("TelecomSystem")
val actor = system.actorOf(Props[SubscriberActor], "subscriber")
val subscriber = Subscriber(
msisdn = "+91-9876543210",
name = "Akka User",
type = SubscriptionType.POSTPAID
)
actor ! subscriber
Thread.sleep(1000)
system.terminate()
}Best Practices for Scala
Use Case Classes Naturally
ScalaPB generates immutable case classes. Use pattern matching, copy methods, and functional transformations just like regular Scala code.
Leverage Type Safety
Scala's type system catches many protobuf errors at compile time. Take advantage of sealed traits for enums and Option types for optional fields.
Use Functional Operations
Repeated fields are Scala Seq, so usemap,filter, and other collection operations.
Keep Proto Files Simple
ScalaPB generates more code than Java. Keep your proto files focused and avoid overly complex nested structures.
Common Issues
Issue: Generated classes not found
Solution: Make sure to run sbt compile first. ScalaPB generates code during compilation. In IntelliJ, you may need to reimport the project.
Issue: Version conflicts
Solution: Make sure your ScalaPB plugin version matches the runtime library version. Check the official ScalaPB docs for compatible versions.
Issue: sbt compilation slow
Solution: ScalaPB generates a lot of code. Use sbt ~compile for incremental compilation during development.
Related Tools
Additional Resources
Official Documentation & References
- Official ScalaPB Documentation - Complete ScalaPB guide
- ScalaPB on GitHub - Source code and examples
- Protocol Buffers Official Site - Proto language reference
Conclusion
Protocol Buffers and Scala are a natural fit. ScalaPB generates idiomatic, type-safe code that feels like native Scala. Whether you're building Spark jobs, Akka systems, or microservices, protobuf offers excellent performance with Scala's functional programming benefits.
Start with these examples and integrate protobuf into your Scala applications. The combination of protobuf's efficiency and Scala's expressiveness creates powerful, maintainable systems.