47deg / pbdirect

Read/Write Scala objects directly to Protobuf with no .proto file definitions


Build Status codecov.io Maven Central Latest version License Join the chat at https://gitter.im/47deg/pbdirect GitHub Issues


Read/Write Scala objects directly to Protobuf with no .proto file definitions


Protobuf is a fast and efficient way to serialize data. While .proto files are great to share schema definitions between components, it is sometimes much simpler and straightforward to directly encode Scala object without using a .proto schema definition file.

PBDirect aims just that: Make it easier to serialize/deserialize into Protobuf.


In order to use PBDirect you need to add the following lines to your build.sbt:

libraryDependencies += "com.47deg" %% "pbdirect" % "0.2.1"


PBDirect depends on:

  • protobuf-java the Protobuf java library (maintained by Google)
  • shapeless for the generation of type-class instances
  • cats to deal with optional and repeated fields


In order to use PBDirect you need to import the following:

import cats.instances.list._
import cats.instances.option._
import pbdirect._

Note: It's not recommended to use import cats.instances.all._ as it may cause issues with implicit resolution.


Schema definition

PBDirect serialises case classes into protobuf and there is no need for a .proto schema definition file.

case class MyMessage(
  id: Option[Int], 
  text: Option[String], 
  numbers: List[Int]

is equivalent to the following protobuf definition:

message MyMessage {
   optional int32  id      = 1;
   optional string text    = 2;
   repeated int32  numbers = 3;

The field numbers correspond to the order of the fields inside the case class.


You only need to call the toPB method on your case class. This method is implicitly added with import pbdirect._.

val message = MyMessage(
  id = Some(123),
  text = Some("Hello"),
  numbers = List(1, 2, 3, 4)
val bytes = message.toPB


Deserializing bytes into a case class is also straight forward. You only need to call the pbTo[A] method on the byte array containing the protobuf encoded data. This method is added implicitly on all Array[Byte] by importing pbdirect._.

val bytes: Array[Byte] = Array[Byte](8, 123, 18, 5, 72, 101, 108, 108, 111, 24, 1, 32, 2, 40, 3, 48, 4)
val message = bytes.pbTo[MyMessage]


You might want to define your own formats for unsupported types. E.g. to add a format to write java.time.Instant you can do:

import java.time.Instant
import cats.syntax.invariant._

implicit val instantFormat: PBFormat[Instant] =

If you only need a reader you can map over an existing PBReader

import java.time.Instant
import cats.syntax.functor._

implicit val instantReader: PBReader[Instant] =

And for a writer you simply contramap over it:

import java.time.Instant
import cats.syntax.contravariant._

implicit val instantWriter: PBWriter[Instant] =


pbdirect is designed and developed by 47 Degrees

Copyright (C) 2019 47 Degrees. http://47deg.com