Skip to Content
Course content

158: Protocol Buffer Code Generation in sbt

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've noticed a recurring pattern when engineers first start integrating Protocol Buffers into their Scala projects: they treat the protoc compiler as a separate, manual chore. You'll see them writing bash scripts or adding "Pre-build" steps in their IDE to run the compiler, then committing the generated Java/Scala source files directly into version control.

The "Manual Compilation" Trap

The misconception here is that Protobuf generation is an external dependency that happens before the build. If you're doing this, your workflow probably looks like this: you change a field in user.proto, you remember to run protoc --scala_out=..., and then you run sbt compile. If you forget that first step, you spend ten minutes scratching your head wondering why your Scala code is referencing a version of the message that doesn't exist yet.

Committing generated code is even worse. It litters your pull requests with thousands of lines of machine-generated boilerplate that no human wants to review, and it inevitably leads to merge conflicts when two people update the schema simultaneously. We want the build tool to handle this, not the developer.

Letting sbt Own the Generation Lifecycle

In a professional Scala setup, we use the sbt-protoc plugin. This integrates the Protocol Buffer compiler directly into the sbt task graph. When you run compile, sbt checks if your .proto files have changed. If they have, it triggers the generator and adds the output directory to your project's source path—all before the Scala compiler even starts looking at your code.

Let's look at a concrete example. Imagine we're building a User Profile service. First, we define our schema in src/main/protobuf/user_profile.proto:

syntax = "proto3";

option java_package = "com.example.user";
option java_outer_classname = "UserProfileProto";

message UserProfile {
  int32 id = 1;
  string email = 2;
  string display_name = 3;
  
  enum Status {
    ACTIVE = 0;
    SUSPENDED = 1;
    DELETED = 2;
  }
  Status status = 4;
}

To make this work automatically, you first need to add the plugin to your project/plugins.sbt file. I usually stick to the latest stable version:

addSbtPlugin("com.github.sbt" % "sbt-protoc" % "1.0.6")

Now, in your main build.sbt, you don't need to write any complex shell commands. You just need to tell sbt which Protobuf library you're using (usually protobuf-java or scala-pb). If you're using scala-pb for idiomatic Scala code, your configuration looks like this:

libraryDependencies ++= Seq(
  "com.thesamet.scalapb" %% "scalapb-runtime" % "0.11.0"
)

// The plugin handles the generation automatically based on 
// the presence of files in src/main/protobuf

I'll be honest: the first time you run this, it feels like magic. You run sbt compile, and suddenly the UserProfile class is available in your IDE via autocomplete, even though you never wrote a single line of Scala for it. If you change display_name to full_name in the .proto file and hit compile again, the Scala class updates instantly. No scripts, no manual triggers, and absolutely no generated code in your git history.




📋 Practical Task

Automating the Order Tracking Schema Generation

You are tasked with adding a new OrderTracking schema to an existing sbt project. Instead of generating the code manually, you must configure the project so that the Scala classes are produced automatically during the compilation phase.

  • Step 1: Create a file at src/main/protobuf/order.proto. Define a message called OrderUpdate with the following fields: order_id (string), timestamp (int64), and status (an enum containing PLACED, SHIPPED, and DELIVERED).
  • Step 2: Add the sbt-protoc plugin to project/plugins.sbt.
  • Step 3: Add the scalapb-runtime dependency to your build.sbt.
  • Step 4: Run sbt compile from your terminal.
  • Step 5: Create a small Scala object in src/main/scala/Main.scala that instantiates an OrderUpdate message to verify that the generated class is accessible to the Scala compiler.

Success Criteria: The project must compile successfully using only the sbt compile command, without the use of any external protoc shell commands or manually copied source files.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.