Open-source project
softwaremill/magnolia avatar
softwaremill/magnolia

Magnolia: typeclass instances derived from the shape of your data

Easy, fast, transparent generic derivation of typeclass instances

800 stars127 forksScalaApache-2.0

At a glance

What is it?
A Scala 3 macro that materialises typeclass instances for algebraic data types, so a Print or Codec for a case class is written once as a derivation rather than by hand, with a two method surface, join and split, and a strict major version policy.
Who is it for?
Adopt Magnolia if you write Scala and keep hand writing the same typeclass instance for every case class, because a derivation replaces that boilerplate with a join and a split method. Do not adopt it if your data types are not algebraic, or if you need instances that cannot be expressed structurally, since Magnolia derives from the shape of the type and has no way to consult a service or a registry.
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 4 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

Two methods, join and split, cover every algebraic data type

Magnolia is described as a generic macro for automatic materialization of typeclass instances for datatypes composed from product types such as case classes and coproduct types such as enums. It supports recursively-defined datatypes out of the box and, in the project's words, incurs no significant time-penalty during compilation.

The surface area is unusually small. Creating a generic derivation requires implementing two methods on `magnolia1.Derivation`: `join()` to create typeclasses for case classes, which are product types, and `split()` to create typeclasses for sealed traits and enums, which are sum types.

That is the whole abstraction. A product type is a value with named fields, so `join` receives the fields and combines their instances. A sum type is a choice between alternatives, so `split` receives a way to select the alternative matching a given value and to cast it.

The feature list adds what this covers in practice: derivation for case classes, case objects and sealed traits, a lightweight syntax for writing derivations without needing to understand complex parts of Scala, building upon Scala 3's built-in generic derivation, support for recursive and mutually-recursive definitions, support for parameterized ADTs including GADTs and those in recursive types, and typeclasses whose generic type parameter appears in either covariant or contravariant positions.

The audience is Scala developers who keep writing the same instance boilerplate. The licence is Apache 2.0 and the default branch is `scala3`, with a separate `scala2` branch for the older language.

Deriving a Print instance in a dozen lines

The getting started example is worth reading in full, because it shows the shape of a derivation rather than just its result.

The typeclass itself uses Scala's extension syntax for a single abstract method type:

scala
import magnolia1.*

trait Print[T] {
  extension (x: T) def print: String
}

object Print extends AutoDerivation[Print]:
  def join[T](ctx: CaseClass[Print, T]): Print[T] = value =>
    ctx.params.map { param =>
      param.typeclass.print(param.deref(value))
    }.mkString(s"${ctx.typeInfo.short}(", ",", ")")

  override def split[T](ctx: SealedTrait[Print, T]): Print[T] = value =>
    ctx.choose(value) { sub => sub.typeclass.print(sub.cast(value)) }
  
  given Print[Int] = _.toString

`join` maps over the case class parameters, asks each parameter's typeclass to print its dereferenced value, and joins the results with the short type name. `split` uses `ctx.choose` to find the alternative that matches the value and delegates to that alternative's typeclass. The base instance for `Int` is a single line.

What you get at the use site is an instance for the whole tree. Given an ADT such as a `Tree` enum with a `Branch` and a `Leaf` case, and with a Magnolia derivation for `Print` in scope, the instance for `Print[Tree[Int]]` is derived on demand, so calling `.print` on a nested tree works without any instance being written for that tree type.

The documentation notes that typeclass authors may provide Magnolia derivations in the typeclass's companion object, but that it is easy to create your own. The companion object is described as the obvious choice if you control the typeclass, since generic derivations there are automatically available during contextual search.

Auto derivation versus semi-auto derivation

The `AutoDerivation` trait provides a given `autoDerived` method that attempts to construct the corresponding typeclass instance for the type passed to it. Importing `Print.autoDerived` makes generic derivation for `Print` available in the scope of that import.

If you would rather not make the automatic derivation available in the given scope, the documentation points at the `Derivation` trait, which provides semi-auto derivation with a `derived` method, but which also brings additional limitations. The distinction is a scoping decision: automatic derivation is a wildcard that participates in implicit search wherever it is in scope, while semi-auto derivation is invoked at a specific site.

That trade matters more than it first appears. With automatic derivation in scope, the compiler will try to build an instance for any type it cannot otherwise find, which is convenient when the derivation is correct and frustrating when it is not, because a wrong derivation produces a compile error somewhere far from the import. Semi-auto derivation makes the intent explicit at the call site, at the cost of writing the `given` yourself.

The documentation also explains why the companion object is the right place for a derivation. While any object may be used to define one, putting it in the companion object of the typeclass means the generic derivations for that typeclass are automatically available for consideration during contextual search, which removes the need for an import at every use site.

The compiler flags and the recursion caveat are the practical limits

The limitations section is short, and both items in it are things you will hit.

The first is default values. For accessing default values for case class parameters, the documentation recommends compiling with `-Yretain-trees` on. This is a compiler flag that keeps the trees around for reflection-like access at the cost of some compile time and memory, and without it a derived instance cannot see a parameter's default.

The second is recursion. For recursive structures it is required to assign the derived value to an implicit variable, and the example given is:

scala
given instance: SemiPrint[Recursive] = SemiPrint.derived

The reason is structural. A recursive type has no finite derivation; the instance for the recursive type refers to itself. Assigning the derived value to a named given breaks the cycle, because the name can be referenced inside the derivation while it is being built.

Neither of these is a design flaw so much as the cost of doing the work at compile time in a way that stays transparent. The alternative approaches to deriving typeclass instances in Scala, such as hand writing them, avoid both of these constraints and pay for it in boilerplate.

There is a third constraint that the documentation does not spell out but that follows from the design. Because a derivation is structural, it cannot consult anything outside the type. An instance that needs to look up a service, read configuration or depend on a value that is not part of the data cannot be expressed this way, and a typeclass whose instances vary by context rather than by structure is outside what Magnolia is for.

Adding it to a build is one dependency line

For Scala 3 the artifact is published under the standard sbt cross-version naming:

scala
val magnolia = "com.softwaremill.magnolia1_3" %% "magnolia" % "1.3.24"

For Scala 2 there is a separate branch of the repository, and the documentation sends you there.

The naming scheme deserves a note because it explains several decisions. The group id is `com.softwaremill.magnolia[major version]_[scala major version]`, and the Scala major version suffix is there so versions for Scala 2 and Scala 3 can be published and evolved independently. The Magnolia major version is included for the same reason, so that a future major release can sit alongside the current one.

The main package is `magnolia1`. The documentation is explicit that this exists so magnolia 1.x can be used alongside magnolia 0.17, which the two are binary incompatible with, and that future major releases of Magnolia can change the package name for the same reason. That is a deliberate versioning design rather than an accident, and it is what allows an upgrade to be staged rather than all at once.

The project is a standard sbt build, with build.sbt at the root and the usual supporting directories: `core/`, `examples/`, `test/`, `project/`, plus `.scalafmt.conf` for formatting and `.scala-steward.conf` for dependency updates, and a `.git-blame-ignore-revs` file for the same bulk-commit blame problem that shows up in large Scala codebases.

Where Magnolia is the wrong tool

Three cases are clear.

The first is when your data is not algebraic. Magnolia derives from the shape of the type, so a typeclass instance over something that is not a case class or a sealed trait has nothing to derive from. A typeclass over a primitive, or over an open class hierarchy, is out of scope.

The second is when the instance depends on context rather than structure. A derivation cannot reach a registry, a configuration or a service. For those cases, the instance has to be written by hand, and the question then becomes whether the rest of your typeclasses are worth deriving.

The third is the alternative approach, and it is worth naming precisely because Magnolia is not the only way to get generic derivation in Scala 3. Scala 3 has built-in generic derivation, and Magnolia builds upon it. The difference in approach is that the built-in mechanism derives instances from `given` instances you already have in scope for the type parameters, while Magnolia gives you the `join` and `split` callbacks so you can write the combining logic yourself. Where the built-in derivation is enough, using it removes a dependency and the compiler flags and the recursion caveat that come with Magnolia. Where you need to control how fields are combined, how alternatives are labelled, or how a type parameter is threaded through, Magnolia's callbacks are the point.

There is also the option of not deriving at all. For a typeclass with a handful of instances, writing them by hand is often clearer than introducing a macro into your build, and macros are a dependency you inherit at every compile. The trade is boilerplate against a compile-time dependency and its sharp edges.

Editorial conclusion

Adopt Magnolia if you write Scala and keep hand writing the same typeclass instance for every case class, because a derivation replaces that boilerplate with a join and a split method. Do not adopt it if your data types are not algebraic, or if you need instances that cannot be expressed structurally, since Magnolia derives from the shape of the type and has no way to consult a service or a registry. Verify first that your build can enable the -Yretain-trees compiler option if you rely on default parameter values, and that recursive structures get their derived value assigned to an explicit given as the documentation requires.

Frequently asked questions

What is Magnolia in Scala?

It is a generic macro for automatic materialization of typeclass instances for datatypes composed from product types such as case classes and coproduct types such as enums. It supports recursively-defined datatypes out of the box and builds upon Scala 3's built-in generic derivation.

How do I write a derivation with Magnolia?

Implement two methods on magnolia1.Derivation: join() to create instances for case classes and split() for sealed traits and enums. Extending AutoDerivation gives you the autoDerived given method, and importing it into scope makes generic derivation available for that typeclass.

Which artifact do I add to a Scala 3 build?

The dependency is `"com.softwaremill.magnolia1_3" %% "magnolia" % "1.3.24"`. Scala 2 users are directed to the scala2 branch of the repository.

What compiler flag does Magnolia need for default parameter values?

For accessing default values for case class parameters the documentation recommends compiling with -Yretain-trees on. For recursive structures the derived value also has to be assigned to an implicit variable, for example given instance: SemiPrint[Recursive] = SemiPrint.derived.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. softwaremill/magnolia on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/softwaremill-magnolia.svg)](https://hysenlabs.com/projects/softwaremill-magnolia)

Was this useful?