avro4s turns Scala case classes into Avro schemas at compile time
Avro schema generation and serialization / deserialization for Scala
At a glance
- What is it?
- avro4s is a Scala library that generates Avro schemas from case classes with macros rather than reflection, which means the schema and the serializers exist before the program runs and no boilerplate conversions are needed in either direction.
- Who is it for?
- avro4s suits a Scala team that already uses case classes as its domain model and wants Avro schemas to be derived from those classes rather than maintained in parallel, and it suits anyone who wants to avoid the runtime cost of reflection in a hot serialization path. It is the wrong tool for a project whose schema is the contract and must exist before the code, since a schema-first workflow inverts the direction this library works.
- Can I use it commercially?
- Yes. Apache-2.0 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 154 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 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The macro approach is the whole point
Avro4s is a schema and class generation and serializing and deserializing library for Avro written in Scala, and the stated objective is to allow seamless use with Scala without the need to write boilerplate conversions yourself and without the runtime overhead of reflection. That second half is what separates this from the Java-based alternatives, and it is why the library is macro based and generates code for use with Avro at compile time. The features are three. Schema generation from classes at compile time, boilerplate free serialization of Scala types into Avro types, and boilerplate free deserialization of Avro types to Scala types. The repository is described as a community project where pull requests are accepted and releases published by the maintainer, and the licence is Apache-2.0 with the artifacts published to Maven Central for Scala 2.12, 2.13 and 3.
Two branches, and only one of them is still moving
The versioning section is the first thing to read, because it tells you which line to depend on. The master branch contains version 5.0.x, which is designed for Scala 3, and pull requests are welcome. That version may have minor breaking changes compared to the previous major release, in order to support the new features of Scala 3. The previous major version is 4.0.x, located at the release/4.0.x branch and released for Scala 2.12 and 2.13, and it is in support mode only: bug reports are welcome and bug fixes will be released, but no new features will be added. The contribution convention is branch naming, with pull requests raised using scala2/* and scala3/* prefixes depending on the Scala version the work targets. That split is a reasonable compromise, and it is also a signal about the maintenance bandwidth of a community project, since the 4.0.x line will get fixes but not features for as long as the maintainer is willing to maintain it.
One type parameter produces the schema
The generation story is short. You define your case classes.
case class Ingredient(name: String, sugar: Double, fat: Double)
case class Pizza(name: String, ingredients: Seq[Ingredient], vegetarian: Boolean, vegan: Boolean, calories: Int)Then to generate an Avro schema you use the AvroSchema object passing in the target type as a type parameter, which returns an org.apache.avro.Schema instance.
import com.sksamuel.avro4s.AvroSchema
val schema = AvroSchema[Pizza]The generated schema handles nested case classes, sequences and primitives, and the readme notes that the full list of supported object types is in a table later in the document. The motivation is stated before any of this, in the schemas section, and it is worth repeating because it explains the design. Unlike JSON, Avro is a schema based format, so you will find yourself wanting to generate schemas frequently, and writing them by hand or through the Java based SchemaBuilder classes can be tedious for complex domain models. Generating directly from case classes at compile time gives both the convenience of generated code without the annoyance of a separate code generation step, and it avoids the performance penalty of reflection based code.
AvroName and AvroNamespace override what the compiler would infer
Avro schemas for complex types, which the readme calls RECORDS, contain a name and a namespace, and by default those are the class name and the enclosing package name. Both can be customised with annotations.
package com.sksamuel
@AvroName("Wibble")
@AvroNamespace("com.other")
case class Foo(a: String)The generated schema then carries the name Wibble and the namespace com.other. The readme notes that it is possible but not necessary to use both annotations, and that you can use either one if you wish. The same AvroName annotation overrides field names, which the readme says is useful when the record instances you are generating or reading need to have field names different from the Scala case classes, for example when you are reading data generated by another system or another language.
package com.sksamuel
case class Foo(a: String, @AvroName("z") b : String)There is a distinction here that is easy to get wrong. The readme is explicit that AvroName does not add an alternative name for the field but an override, and that if you want alternatives you should use AvroAlias instead. Overriding a field name is right when one side of your system is fixed and the other is not, and it is wrong if you expect both names to resolve.
Documentation and properties travel into the schema
Avro allows a doc field and arbitrary key and value pairs to be added to generated schemas, and avro4s supports this through the AvroDoc and AvroProp annotations, which work on either complex or simple types, in other words on both fields and classes.
package com.sksamuel
@AvroDoc("hello, is it me you're looking for?")
case class Foo(@AvroDoc("I am a string") str: String, @AvroDoc("I am a long") long: Long, int: Int)The resulting schema carries the doc string at the record level and the field level. It is a small feature and it is worth more than it looks, because a schema with embedded documentation travels with the data, so a consumer reading the schema gets the field descriptions without a separate document to keep in sync. The AvroProp annotation is the general case of the same idea, letting arbitrary key and value pairs into the generated schema.
Where a schema from code is the wrong direction
The main limit is a question of authority, and it is worth answering before you adopt the library. This library treats the Scala case class as the source of truth and derives the schema from it. Many integration contracts run the other way, where a schema is agreed with a consumer, often in another organisation or another language, and the code has to conform to it, and a schema-first workflow and a code-first workflow are opposites. The annotations help here, since AvroName, AvroNamespace and AvroAlias let you bend the generated schema toward an external contract, but the friction of expressing an agreed schema as annotations on case classes is real, and the readme's own example of reading data generated by another system is framed as the exception rather than the rule. The second limit is the maintenance split already described, with a Scala 2 line that takes fixes but no features. The third is the scope of what the macro understands, which the readme defers to a table of supported object types further down, so a domain model with an unusual type is where you will find out whether the library is usable for your case.
Editorial conclusion
avro4s suits a Scala team that already uses case classes as its domain model and wants Avro schemas to be derived from those classes rather than maintained in parallel, and it suits anyone who wants to avoid the runtime cost of reflection in a hot serialization path. It is the wrong tool for a project whose schema is the contract and must exist before the code, since a schema-first workflow inverts the direction this library works. It is also the wrong choice for a team on Scala 2.13 who wants new features, because that line is in support mode only. Before adopting it, decide whether the schema or the class is the source of truth, since the answer determines whether AvroName and AvroNamespace overrides are what you want or a workaround. The last push was on 2026-05-22 and the current release is v5.0.14 from 2024-12-02.
Frequently asked questions
How do I generate an Avro schema from a Scala case class?
Use the AvroSchema object with the target type as a type parameter, for example import com.sksamuel.avro4s.AvroSchema and val schema = AvroSchema[Pizza]. It returns an org.apache.avro.Schema instance, generated at compile time by a macro rather than by reflection.
Which Scala versions does avro4s support?
The master branch holds version 5.0.x for Scala 3, while the previous major version 4.0.x is on the release/4.0.x branch for Scala 2.12 and 2.13 and is in support mode only, receiving bug fixes but no new features. Pull requests should use scala2/* or scala3/* branch names.
How do I change the name or namespace of a generated Avro record?
Use the AvroName and AvroNamespace annotations on the case class, as in @AvroName("Wibble") and @AvroNamespace("com.other"). The same AvroName annotation overrides a field name, while AvroAlias is what you want when a field needs an alternative name rather than a replacement.
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/sksamuel-avro4s)Was this useful?