skip to content

Why did the Dart team cancel macros, and what does that mean for code generation with build_runner in a Dart 3.13 project?

level: middleimportance: should knowfreq 30%

answer

  1. January 2025 announcement
  2. deep semantic introspection at compile time
  3. analysis and hot reload got slower
  4. invest in build_runner instead
  5. augmentations pursued separately

basics

~20 s

Macros needed deep semantic introspection at compile time, which slowed static analysis, code completion and the incremental compile behind hot reload, so the Dart team stopped work in January 2025. build_runner generators remain the way to generate code.

solid answer

~40 s

Macros were an experimental Dart feature for compile-time code generation, previewed with a `JsonCodable` macro. In January 2025 the Dart team announced they had stopped work: their design gave macros deep **semantic** introspection of the program, and that cost too much at development time, regressing both editing (analysis, completion) and incremental compilation, the first step of a hot reload. They said they would instead pursue better data support through dedicated language features, improve build times and the code generation experience in `build_runner`, and still aim to ship **augmentations** on their own. So in a Dart 3.13 or Flutter 3.47 project, `json_serializable`, `freezed`, `mockito` and `riverpod_generator` running under `build_runner` remain the answer, and build_runner has since become faster, for example with AOT-compiled builders by default in 2.14.

go deeper

for a junior

Recall that macros never shipped and that build_runner generators are still how Dart generates JSON and data-class code.

for a middle

Explain the reason: semantic introspection at compile time slowed analysis and the incremental compile behind hot reload.

for a senior

Show you track the replacements that did land, such as faster AOT builders, --only-check, sealed classes and primary constructors, and adapt the codebase to them.

for a principal

Judge how much to invest in codegen-heavy architecture given the language is moving boilerplate into targeted features instead.

## What macros were meant to be Dart has long relied on **code generation** for boilerplate: JSON mapping, value equality, `copyWith`, mocks. Flutter apps cannot use run-time reflection (`dart:mirrors` is disabled in Flutter to allow tree shaking), so the generator runs ahead of time through `build_runner` and writes `.g.dart` files you check or regenerate. **Macros** were an experimental language feature meant to replace much of that: a macro would be applied like an annotation, run **inside the compiler and analyzer**, inspect the program, and add declarations directly, with no separate build step and no generated files on disk. A `JsonCodable` macro was previewed around Dart 3.4 and Flutter 3.22 as the showcase. ## Why the work stopped The Dart team's post of **29 January 2025** explains the decision: - Dart's design goals are **AOT performance** and a **fast development loop**, especially stateful hot reload. - Runtime reflection hurts the first (it defeats tree shaking); macros were meant to be the static alternative. - The team chose **deep semantic introspection** rather than purely syntactic macros, so a macro could ask about types and members across the program. - Semantic introspection turned out to add **large compile-time costs**. The implementation regressed **editing** (static analysis, code completion) and **incremental compilation**, the first step of a hot reload. - Each time a major technical hurdle was solved, new ones appeared, and the team concluded macros were not converging on something they would ship. They stopped, citing the opportunity cost. dart.dev removed its experimental macros page, and the team was explicit that macros will not ship in the foreseeable future. ## What the team said it would do instead 1. **Better support for data** in Dart, through bespoke language features rather than general metaprogramming, since data classes and serialization were the main motivation for macros. 2. **Faster builds and a better code generation experience**, with identified improvements to `build_runner`. 3. **Augmentations**, a language feature prototyped as part of macros, to be shipped independently because it would improve existing generators. As of the Dart 3.13 changelog it had not been released. ## What changed in build_runner since The build_runner changelog shows that follow-through: | Version | Change | |---|---| | 2.7 | no more interactive prompts; `-d` ignored, conflicting outputs always deleted | | 2.10 | optional AOT compilation of builders | | 2.13 | 1.4x to 4x faster builds, largest gains on incremental builds | | 2.14 | AOT builders by default, `stop` command, `--workspace` no longer experimental | | 2.16 | `--only-check` for CI, generated files fixed by default | ## Practical consequences in 2026 - To generate serialization, data classes or mocks, you still add a generator package and run `dart run build_runner build` or `watch`. - Tutorials or code that use `@JsonCodable()` or enable a `macros` experiment are describing a feature that never shipped; treat them as history. - Some boilerplate has shrunk through ordinary language features instead: **records** (Dart 3.0) give structural equality for small tuples, **sealed classes** and patterns replace generated `when`/`map` helpers (freezed 3 removed them), and Dart 3.13's **primary constructors** shorten class declarations, which freezed 4 supports. - Interviewers asking "will macros replace build_runner?" expect you to know the answer is no, and why. ## A balanced view Codegen has real costs: an extra build step, generated files to review or regenerate, and stale output if someone forgets. The macros cancellation does not remove those costs; it means the ecosystem addresses them with tooling (faster, AOT builders; CI checks such as `--only-check`) and targeted language features rather than one general mechanism. ## How to answer in an interview A strong answer has three parts: 1. **The fact**: macros were an experiment and were cancelled in January 2025; they never shipped. 2. **The reason**: the design relied on deep semantic introspection at compile time, and that made analysis, code completion and the incremental compile behind hot reload too slow. 3. **The consequence**: code generation through `build_runner` is still the standard, it has become faster since, and some boilerplate has moved into ordinary language features. Adding one concrete detail, such as AOT-compiled builders becoming the default in build_runner 2.14, shows you follow the ecosystem rather than repeating a 2024 blog post about `@JsonCodable`.

  • Why can't a Flutter app use dart:mirrors instead of code generation?
    Flutter disables `dart:mirrors` so the compiler can tree-shake unused code aggressively and keep release binaries small; run-time reflection would force it to keep code that might be looked up by name. dart.dev lists `dart:mirrors` as native JIT only, not Flutter.
  • What did the Dart team say it would build in place of general macros?
    Better data support through dedicated language features, improvements to `build_runner` build times and the codegen experience, and augmentations shipped as a standalone feature. It kept general metaprogramming as a long-term interest only.

Macros were like fitting a translator inside every word processor: convenient, but it slowed every keystroke. The team kept the separate print shop (build_runner) and made it faster instead.

saying these in an interview costs you the question

  • Believes macros shipped as a stable Dart 3 feature
  • Recommends @JsonCodable for new production code
  • Thinks build_runner is deprecated in favour of macros
  • Claims macros were cancelled only for security reasons
  • Suggests dart:mirrors as the Flutter alternative to codegen