scodec turns a binary wire format into a typed Scala value
Scala combinator library for working with binary data
At a glance
- What is it?
- scodec is a Scala combinator library for encoding and decoding binary data, built so that a protocol definition is written once and checked by the compiler. It suits engineers who own a wire format or a file layout, and it deliberately trades raw speed for statically verified structure.
- Who is it for?
- scodec is worth adopting when a wire format is stable, shared between systems and worth describing precisely, and when a failed decode should say what went wrong. It is the wrong tool for byte level hot paths where hand written loops win, and for formats that change shape every week.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 45 days ago.
- What is it written in?
- Mainly Scala, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem is stating a wire format before writing a parser
Binary protocol code tends to arrive in the wrong order. Someone writes a parser that pulls bytes out of an input stream by index, the offsets drift after a while, and the failure surfaces as a mystery three layers up. scodec starts from the opposite position: describe the contract first, in a form the compiler checks, and let the library carry the byte handling. The project describes itself as a Scala combinator library for working with binary data, and it is aimed at engineers who own a wire format, a file header or a packet layout and want the definition to read like the specification. The part that matters is that the contract is a value rather than a class, so a format can be handed to other code, reused across a codebase and tested without opening a socket or a file.
Codec[A] is the whole contract, and it runs on BitVector rather than streams
The primary abstraction is Codec[A], which supports encoding a value of type A to a BitVector and decoding a BitVector back into a value of type A. Everything else in the library hangs off that pair of operations. Decoding returns an Attempt[DecodeResult[A]], so a caller receives either a failure or a decoded value paired with the bits that were left over, which makes it possible to parse several structures in sequence without guessing lengths up front. The codecs objects supply predefined codecs and combinators for the common widths, and the library is explicitly a combinator based DSL rather than a set of annotations or macros. One stated design constraint is that a compiler plugin should not be used, and another that encoding and decoding should be purely functional. That combination rules out a fair amount of runtime magic, but it also means a codec is an ordinary value you can pass around and compose.
Adding scodec-core to a build and decoding your first three integers
The README points at the scodec-core artifact on Maven Central and shows how to pull the right version for the Scala binary in use. Add this to your build definition:
libraryDependencies += "org.scodec" %%% "scodec-core" %
(if (scalaVersion.value.startsWith("2.")) "1.11.9" else "2.1.0")The version choice is baked into the snippet, so pick the branch that matches your Scala version rather than copying it blindly. With the dependency in place, a first decode looks like this:
import scodec.*
import scodec.bits.*
import scodec.codecs.*
// Create a codec for an 8-bit unsigned int followed by an 8-bit unsigned int followed by a 16-bit unsigned int
val firstCodec = uint8 :: uint8 :: uint16
// Decode a bit vector using that codec
val result: Attempt[DecodeResult[(Int, Int, Int)]] = firstCodec.decode(hex"102a03ff".bits)
// Successful(DecodeResult(((16, 42), 1023), BitVector(empty)))Mapping over that result with a function that adds the three integers together gives the total 1081, and the same codec can encode a tuple back into a BitVector, so the round trip is symmetric. What to watch for above is the shape of the result. The decoder consumed the four bytes, returned the three integers as a tuple, and reported an empty remainder, which tells you the codec consumed the whole input.
Two ways to bind a case class, and they do not produce identical bytes
Automatic case class binding is supported through tuples. The codec is built from three 8-bit signed integers and then mapped onto the case class with as:
case class Point(x: Int, y: Int, z: Int)
val pointCodec: Codec[Point] = (int8 :: int8 :: int8).as[Point]
val encoded: Attempt[BitVector] = pointCodec.encode(Point(-5, 10, 1))
// Successful(BitVector(24 bits, 0xfb0a01))
val decoded: Attempt[DecodeResult[Point]] = pointCodec.decode(0xfb0a01)
// Successful(DecodeResult(Point(-5, 10, 1), BitVector(empty)))Codecs can also be derived, which changes the call site:
case class Point(x: Int, y: Int, z: Int) derives Codec
val encoded: Attempt[BitVector] = Codec.encode(Point(-5, 10, 1))
// Successful(BitVector(96 bits, 0x000000fb0000000a00000001))
val decoded: Attempt[DecodeResult[Point]] = Codec.decode[Point](0x000000fb0000000a00000001)
// Successful(DecodeResult(Point(-5, 10, 1), BitVector(empty)))Notice that the comments in these two examples show different encodings for the same value. The tuple based codec produces 24 bits while the derived codec produces 96 bits. If a peer system expects a particular byte layout, check which of the two routes your Scala version actually generates rather than assuming the two are interchangeable. New codecs can also be created by implementing the Codec trait, though the README notes that applying combinators to existing codecs is the usual route.
Performance is the first thing scodec gives up, on purpose
The stated design constraints are worth reading as a shopping list of what was traded away. Binary structure should mirror protocol definitions and be self-evident under casual reading, mapping binary structures to types should be statically verified, encoding and decoding should be purely functional, failures should provide descriptive errors, and no compiler plugin should be used. Performance is considered but yields to those constraints. That is a defensible choice for a protocol library, and it is also the reason scodec is the wrong tool for a hot path that copies megabytes per second through a parser you wrote yourself. The repository root contains a benchmarks directory, so the project does track speed, but it does not claim speed as a design goal. A second trade-off is Scala version churn. Scodec 1.x used Shapeless and was heavily influenced by scala.util.parsing.combinator, while as of 2.x the library only depends on the standard library. Code written against the 1.x line leans on Shapeless, and that dependency is gone.
Protocol codecs ship as examples in the test directory
The fastest way to judge whether the library fits your problem is to read the example codecs that live beside the tests. There are codecs for UDP datagrams, for MPEG packets and for libpcap files, each one in the unitTests tree. The fs2 protocols module is described as holding production quality codecs for those same examples, which tells you the examples are not toys written for a blog post. The wider ecosystem is also listed: FS2, Circe and Refined have support for scodec, the skunk database library uses it to communicate with Postgres, hippo uses it to parse .hprof files, bitcoin-scodec carries a codec for the Bitcoin network protocol, scodec-msgpack provides MessagePack codecs, and scodec-bson implements BSON codecs and combinators. The fs2-http project uses FS2, scodec and shapeless to implement a minimal HTTP client and server. That list is the best evidence available about which kinds of format the library handles comfortably: binary structures with fixed fields, headers and length prefixes.
Upgrading between the 1.x and 2.x lines, and testing your own codecs
scodec is not archived, and the last push was on 2026-09-08. The release history shows v2.3.3 on 2025-07-26, v1.11.11 on 2025-05-08 and v2.3.2 on 2024-10-05, so both lines are still moving and the patch numbers on the 1.x line matter for teams that have not migrated. The README dependency snippet still points at 1.11.9 for Scala 2 while 1.11.11 exists, so pin deliberately rather than pasting the snippet unchanged. The build uses sbt and requires node.js to be installed in order to run the Scala.js tests, and the documented build command is sbt publishLocal. If you are writing your own Codec instances, the project publishes its own test tooling in the scodec-testkit module, which is the piece to reach for before hand rolling property checks. On the licensing side the project is under a 3-clause BSD licence, which is permissive and places no obligation on how you deploy it.
Editorial conclusion
scodec is worth adopting when a wire format is stable, shared between systems and worth describing precisely, and when a failed decode should say what went wrong. It is the wrong tool for byte level hot paths where hand written loops win, and for formats that change shape every week. Before committing, check the exact dependency line for your Scala version in your build, confirm that the failure messages you rely on appear in the scodec-testkit module you plan to use, and verify that the release you pull matches the Scala binary you compile against.
Frequently asked questions
How do I add scodec to a Scala project?
Add the scodec-core artifact from Maven Central to your build definition, using the version line shown in the README, which selects 1.11.9 for Scala 2 and 2.1.0 otherwise. The project publishes to Maven Central and also documents sbt publishLocal for a local build.
Does scodec work with both Scala 2 and Scala 3?
The dependency snippet in the README branches on scalaVersion, using 1.11.9 when the version starts with 2 and 2.1.0 otherwise, so both lines are supported. As of Scodec 2.x the library only depends on the standard library, while 1.x used Shapeless.
Is scodec still being maintained?
The repository is not archived and the last push was on 2026-09-08. Recent releases include v2.3.3 on 2025-07-26 and v1.11.11 on 2025-05-08, so both the 2.x and 1.x lines have seen releases.
How do I test the codecs I write?
The project publishes its own test tooling in the scodec-testkit module for people creating their own Codec instances. The repository also keeps example codecs for UDP datagrams, MPEG packets and libpcap files in the test directory.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/scodec-scodec)