Library / SDK
softwaremill/macwire avatar
softwaremill/macwire

MacWire, dependency injection that runs at compile time and leaves no trace at runtime

Zero-cost, compile-time, type-safe dependency injection library.

1,315 stars77 forksScalaApache-2.0

At a glance

What is it?
MacWire is a Scala dependency injection library built on macros, so the wiring code is generated while the project compiles and the resulting program has no container and no runtime lookup. Your classes stay ordinary classes, because the mechanism for defining dependencies is plain constructors or apply methods in companion objects, with no annotations and no conventions to follow.
Who is it for?
Adopt MacWire if you are on Scala and want dependency injection that costs nothing at runtime and keeps your classes free of annotations, and skip it if you are not on Scala 2, since the whole approach depends on macro expansion.
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 9 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 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Compile-time wiring, and what that buys you

The core of MacWire is a set of macros, which is code that runs at compile time. Those macros generate the new instance creation code automatically, which lets you avoid a ton of boilerplate while maintaining type safety. The consequence that matters is stated plainly: the core library has no dependencies and incurs no run-time overhead. There is no container object, no lookup at startup, no reflection, and no configuration file describing your graph. A conventional DI framework produces an application whose wiring is decided at runtime and whose errors show up when the application starts. MacWire produces a program where the wiring was decided when it compiled, so a missing dependency is a compile error in the place where you wrote the class. The other half of the design is that it does not impact the code of your classes in any way: there are no annotations that you have to use and no conventions to follow.

code
The core library has no dependencies, and incurs no run-time overhead.

Five macros, and the line between them

The wiring variants are the API surface, and they divide on a single question: does the result depend on where you call it. autowire creates an instance of the given type using the provided dependencies, and any missing dependencies are created using constructors or apply methods. wire creates an instance of the given type using dependencies from the context within which it is called, and those dependencies are looked up in the enclosing trait, class or object and its parents via inheritance. wireRec is a variant of wire which creates missing dependencies using constructors. wireSet collects all instances of the given type from the context and returns them as a Set, and wireList does the same but returns a List preserving order. In other words, autowire is context-free, while the wire family of macros is context-dependent. That distinction decides which one you reach for: a service whose dependencies are all constructible is an autowire case, and a plugin or handler that needs the surrounding application's objects is a wire case.

scala
val userStatusReader = autowire[UserStatusReader]()

What the generated code looks like

The example that explains the library best is a small class graph and the code it produces. Given a DatabaseAccess, a SecurityFilter, a UserFinder that takes both, and a UserStatusReader that takes the finder, one call to autowire expands to a block of local vals: the database access, the security filter, the user finder constructed from the two, the status reader constructed from the finder, and then the status reader as the result. Nothing about the classes changed. The public primary constructor is used, or if one is absent the apply method from the companion object, and dependencies are created recursively. What you get at the call site is one line and what the compiler gives you is the whole graph, with the same type safety you would have had writing it by hand and none of the lines.

scala
val userStatusReader =
  val wiredDatabaseAccess   = new DatabaseAccess()
  val wiredSecurityFilter   = new SecurityFilter()
  val wiredUserFinder       = new UserFinder(wiredDatabaseAccess, wiredSecurityFilter)
  val wiredUserStatusReader = new UserStatusReader(wiredUserFinder)
  wiredUserStatusReader

When you need a hand-built dependency or a factory

autowire accepts an arbitrary number of parameters, which specify how to look up or create dependencies instead of the default construct or apply mechanism. Each parameter might be an instance to use, a function to create an instance, a class to instantiate to provide a dependency for the types it implements given as classOf[SomeType], or an autowireMembersOf(instance) call to use the members of the given instance as dependencies. The first case is the common one. When a dependency has to be initialised with configuration or has a managed lifecycle, such as a DataSource that needs a JDBC connection string, you construct it yourself and pass it in, and it is used whenever a value of the given type is needed. The factory case is for when you want to control how a type is built but still let the library resolve that factory's own parameters. The result is a design where the library does the mechanical part and you stay in charge of the parts with a life-cycle.

scala
Using.resource(DataSource("jdbc:h2:~/test")): ds =>
  autowire[UserStatusReader](ds)

Scopes, composition and the places it stops

The wire family brings features that autowire does not have, because wire has a context to work with. The documentation covers how wiring works, factories and factory methods, the difference between a lazy val and a val when holding a wired instance, recursive wiring, composing modules, scopes, and accessing wired instances dynamically. Recursive wiring is also listed under limitations, alongside a separate limitations section for the wire family, and the table of contents shows a section on errors, on limitations of autowire, and on interceptors and qualifiers for when two implementations of the same type need to be distinguished. There is an Akka integration section too, and the repository has separate modules for it, for Pekko, and for cats-effect autowiring, each with its own test module, so the framework-specific behaviour is isolated rather than bolted into the core.

code
`autowire` create an instance of the given type, using the provided dependencies.

Cross-platform, and the version caveats

MacWire is available for Scala 2.12, 2.13 and 3 on the JVM, JS and Native platforms, and the documentation is careful that not all functionalities are available for each Scala version. The repository confirms the split in its directory names, with separate modules for the core macros, the Akka integration, the Pekko integration, the cats-effect autowiring, and test modules for each, plus a proxy and a util module, and separate test directories for the two Scala 3 lines. The examples directory carries a Scala 2.4 play example, a Scala.js example and a Scalatra example. The dependency is added with the macros artifact marked as provided, which is the correct scope for a compile-time library, and the version in the example matches the current release. The licence is Apache-2.0 and the recent releases are v2.6.7, v2.6.6 and v2.6.5, with the last push on 2026-10-14 on the master branch.

scala
// sbt
"com.softwaremill.macwire" %% "macros" % "2.6.7" % "provided"

// scala-cli
//> using dep com.softwaremill.macwire::macros:2.6.7

What it is like to use it day to day

The practical picture is that you keep writing ordinary Scala classes, and the only place the library appears is the one line where you ask for a graph. The documentation even has a section on how wiring works and on debugging, and a section on future development where you can vote, which is the sort of thing a project that has been around for a decade tends to have. There is a Discourse board linked for ideas, suggestions, problems and questions, and the project sits under SoftwareMill, which also offers commercial support. The copyright notice in the repository and the presence of a notes/ directory and a banner image are the small traces of a project that takes its housekeeping seriously. The honest assessment is that MacWire is a good fit for a Scala codebase that wants dependency injection without a container, and a poor fit for a team that wants runtime reconfiguration, because nothing about the wiring can be changed after compilation, and that is the trade the design makes deliberately.

code
MacWire doesn't impact the code of your classes in any way, there are no
annotations that you have to use or conventions to follow.

Editorial conclusion

Adopt MacWire if you are on Scala and want dependency injection that costs nothing at runtime and keeps your classes free of annotations, and skip it if you are not on Scala 2, since the whole approach depends on macro expansion. Verify three things before you commit: which Scala version you are on, because the project states it is available for 2.12, 2.13 and 3 on the JVM, JS and Native but that not all functionalities are available for each version; whether you need the wire family rather than autowire, since wire depends on an enclosing context and pulls in trait and inheritance lookup that autowire does not need; and the limitation on recursive wiring, which the documentation lists as a known limit of both macro families and is the classic failure case in a container with cycles.

Frequently asked questions

What is MacWire and how does it do dependency injection?

It is a zero-cost, compile-time, type-safe dependency injection library for Scala. The core is a set of macros that run at compile time and generate the instance creation code, so the core library has no dependencies and incurs no run-time overhead, and there is no container at runtime.

What is the difference between autowire and wire in MacWire?

autowire is context-free: it creates an instance of the given type using the provided dependencies, creating any missing ones from constructors or apply methods. wire is context-dependent: it looks up dependencies in the enclosing trait, class or object and its parents via inheritance. wireRec, wireSet and wireList are further variants of the wire family.

Do I have to annotate my classes to use MacWire?

No. MacWire does not impact the code of your classes in any way, with no annotations to use and no conventions to follow. The main mechanism of defining dependencies of a class is plain constructors or apply methods in companion objects, and the macros generate the wiring code.

How do I provide a dependency that needs configuration or a managed lifecycle?

autowire accepts extra parameters that specify how to look up or create dependencies. You can pass an instance you built yourself, which is then used whenever a value of that type is needed, or a factory function whose own parameters are resolved recursively, or a class to instantiate, or autowireMembersOf(instance) to use the members of an existing instance.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. softwaremill/macwire 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-macwire.svg)](https://hysenlabs.com/projects/softwaremill-macwire)

Was this useful?