How We Made the Brownfield Gradle Plugin 22% Faster

Build performance work is rarely about one silver bullet. More often, it comes from removing unnecessary work, moving expensive operations to the right phase, and making the build pipeline easier for Gradle to reason about.

That is what we did in the latest Brownfield Gradle Plugin optimization.

In PR https://github.com/callstack/react-native-brownfield/pull/237, we reworked the plugin internals around a simple goal: stop doing too much too early. The result is a cleaner, more variant-aware Gradle pipeline and a measured 22% improvement in clean release builds for the Brownfield Android Modules consuming the brownfield-gradle-plugin.

The problem: configuration-time work was costing us

The Brownfield Gradle Plugin helps package React Native code and its Android dependencies into a form that can be consumed by an existing native Android application. In brownfield setups, this matters a lot: teams are usually integrating React Native incrementally, inside an already complex native build.

Before this optimization, the plugin did more work during Gradle’s configuration phase than it needed to. In particular, artifact resolution and variant processing were tightly coupled. A central VariantProcessor coordinated multiple responsibilities: exploding AARs, merging manifests, wiring resources and assets, handling JNI libraries, merging ProGuard rules, and preparing data binding-related pieces.

That design worked, but it made the plugin heavier than necessary. It also meant some expensive operations were triggered before Gradle had enough context to execute them efficiently.

The core idea behind the refactor was to split the pipeline into smaller, task-driven pieces and defer expensive work until execution time.

The fix: move from eager resolution to a task-driven pipeline

The new implementation introduces a lightweight dependency model called UnresolvedArtifactInfo.

Instead of resolving and processing artifacts too early, the plugin first collects lightweight metadata about what needs to be included. That metadata is then passed into variant-specific tasks, where the actual work happens at the right time.

In practice, this means the plugin now follows a clearer sequence:

  1. Collect lightweight dependency metadata.
  2. Register variant-specific tasks.
  3. Resolve and explode AARs during task execution.
  4. Wire outputs into manifest, resources, assets, JNI, ProGuard, data binding, and class transformation steps.

This is a big architectural improvement because it separates “what should be packaged?” from “when should we do the packaging work?”

Example: AAR exploding became a real task

One of the most important changes is the introduction of a dedicated ExplodeAarTask.

Previously, AAR handling was part of a broader variant-processing flow. Now, exploding AARs has a dedicated task with explicit inputs such as the variant name, minification state, and artifact metadata.

That gives the build a more natural shape:

  • Gradle configures the task.
  • The task receives unresolved artifact metadata.
  • At execution time, the task validates that each AAR file exists.
  • It unzips each AAR into the expected output directory.
  • It runs the class merge steps for the variant.

This makes the lifecycle easier to understand and easier to optimize. It also produces better failure messages. For example, if an artifact file is missing, the task now fails with a clear error explaining which artifact and variant failed, instead of hiding that failure inside a larger processing step.

Example: variant processing is now composed, not monolithic

Another key improvement is how each Android variant is handled.

The plugin now configures work per LibraryVariant, but instead of delegating everything to one large processor, it wires dedicated processors and task providers:

  • ExplodeTaskProvider handles AAR explosion.
  • ManifestTaskProcessor handles manifest merging.
  • ResourceTaskProcessor wires generated resources.
  • AssetTaskProcessor wires exploded AAR assets.
  • JNILibsProcessor handles native libraries.
  • ProguardProcessor handles consumer and generated ProGuard files.
  • VariantTaskProvider wires pre-build, bundle, and data binding tasks.

This makes the pipeline more explicit. Each processor has a narrower responsibility, and each variant gets the tasks it needs without carrying unnecessary coupling from unrelated steps.

For maintainers, this matters as much as the speedup. A plugin that is easier to reason about is easier to keep fast.

Example: Expo dependencies are handled more deliberately

The PR also improves the Expo integration path.

Expo dependencies can be linked in more than one way: as local project dependencies or as locally published Maven artifacts. The new resolver accounts for both. It reads Expo’s api configuration, adds relevant dependencies to the Brownfield consumer project, and records lightweight unresolved artifact metadata for later processing.

The important detail is that Expo-specific dependency handling no longer forces the rest of the pipeline into eager artifact resolution. It becomes part of the same metadata-first model as the default dependency flow.

That makes Expo support fit the optimized architecture instead of being a special case bolted onto it.

Laying The Foundation: Adopting AGP v9

This optimization and architectural revamp represents a critical milestone in our transition to Android Gradle Plugin (AGP) v9. Our previous implementation relied heavily on the com.android.build.gradle.api.LibraryVariant API, which has been completely removed in the v9 release in favor of com.android.build.api.variant.LibraryVariant.

React Native core currently runs on AGP v8, so our brownfield plugin still functions correctly today. But once the wider community migrates, the plugin would inevitably break without this work. Anticipating this shift, we initiated this overhaul well ahead of the ecosystem's adoption curve.

Eliminating our dependency on the deprecated LibraryVariant is a significant step forward. Today, only a few legacy instances remain in our codebase, and their complete removal is scheduled for the upcoming weeks.

React Native’s official transition to AGP v9 is currently underway. You can track the progress here.

Measuring the improvement

To make the result reproducible, we added a Gradle Profiler setup to the react-native app which applies the brownfield-gradle-plugin .

The benchmark scenario runs:

:brownfield:assembleRelease

with

clean
--no-build-cache
--no-configuration-cache

‍It uses two warm-up builds and five measured builds. This is intentionally strict: by disabling build cache and configuration cache, the benchmark focuses on the raw clean-build cost of the plugin changes.

The measured result:

Version Average clean build time
Before 98,900.67 ms
After 76,922.76 ms
Improvement 21,977.91 ms faster
Speedup 22.22%

In practical terms, the optimized plugin saves almost 22 seconds on this benchmarked clean release build.

That is a meaningful improvement for local development, CI, and any workflow where brownfield libraries are rebuilt frequently.

Final thoughts

The 22% improvement is the headline, but the deeper win is architectural: the plugin now does less during configuration, gives Gradle more explicit tasks to work with, and separates dependency discovery from artifact processing. The best build performance improvements often come from respecting the build system’s lifecycle.

In this effort, we did exactly that. We replaced eager, tightly coupled variant processing with a task-driven pipeline built around lightweight artifact metadata. We split responsibilities across focused processors. We improved Expo integration. And we added benchmark tooling so future changes can be measured instead of guessed.

The outcome is a Brownfield Gradle Plugin that is not only 22% faster, but also easier to maintain, easier to debug, and better aligned with modern Gradle and Android build patterns.

For teams adopting React Native incrementally inside existing native apps, that means less waiting, more predictable builds, and a smoother path to brownfield integration.

If you’re interested in following the work we do for Brownfield, find out more here and leave a star ⭐

Insights

Learn more about Brownfield

cover

Aug 27Article

React Native in .NET MAUI: A Practical Brownfield Integration Guide

Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes.

cover

Aug 3Article

Migration to React Native in 2026 Starts With a Delivery Question

Choosing between brownfield and greenfield React Native migration? Learn where risk sits, when to use a checkpoint, and what to prove before committing.

cover

Jul 8Article

How we made Hybrid iOS apps compile 39% faster with React Native Brownfield

See how React Native Brownfield uses iOS prebuilts to cut packaging times by up to 39% for hybrid apps, with details on setup, internals, and benchmark results.

cover

Jun 3Article

AI-Supported Brownfield Migration to React Native

See how AI-supported workflows cut brownfield React Native evaluation from weeks to days by speeding up setup, planning, implementation, and QA.

cover

May 18Article

Unlocking Expo Updates in an Isolated Brownfield Architecture with SDK 55

Learn how to enable Expo Updates in an Isolated Brownfield setup with Expo SDK 55, including iOS packaging, Android patching, and OTA testing.

cover

Apr 23Talk

State of Brownfield in React Native

A practical look at Brownfield React Native adoption in mature native apps, architecture, tooling, and real-world tradeoffs.

cover

Apr 2Webinar

AI-Assisted Migrations to React Native: From Months to Days

Reduce migration time from months to days by using AI in real React Native workflows. Learn when to migrate incrementally and when starting fresh is the better option.

cover

Mar 31Article

Brownie: A Type-Safe Shared-State Workflow for Brownfield React Native

Brownie brings stable, cross-platform shared state to brownfield React Native apps with typed models for Swift and Kotlin.

cover

Nov 17Talk

Vega Developer Meetup

Learn how to integrate React Native into Android TV apps at the Vega Developer Meetup. Workshop, roadmap, and community talks, live in Munich.

cover

May 16Podcast

You Don’t Need a Rewrite, You Need React Native Brownfield

Discover how modern React Native Brownfield integration enables cross-platform power in native apps, without rewriting from scratch.

cover

Jan 13Podcast

Lessons from Shopify's Migration to React Native with Mustafa Ali

Learn how Shopify's migration to React Native led to 86% code unification, reduced build times, and seamless feature parity across platforms.

cover

Jun 14Podcast

Migration to React Native

The 13th episode of React Universe On Air (formerly known as The React Native Show) is fully focused on migrating from different technologies to React Native, the two most popular approaches to this process (greenfield and brownfield software development), and our state-of-the-art brownfield approach.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.