Build Variant — What Is a Build Type and Product Flavor in Android

Author: IT Sectr Published: 2026-05-30 Reading time: 9 min

A Build Variant in Android development is a combination of a build type and a product flavor that determines how an APK or AAB will be built: with which parameters, resources, and code. Each build variant represents a separate Gradle configuration with its own applicationId, signing keys, and included dependencies. According to Google Android Developers, 2025, proper configuration of Build Variants reduces build time by up to 40% by excluding unnecessary resources for each variant. The build variant system is the foundation of configuration management in modern Android projects.

Key Takeaways

  • Build Variant — a combination of one Build Type and one Product Flavor.
  • Build Type defines the build mode: debug or release.
  • Product Flavor defines the app version: free, paid, demo, enterprise.
  • Gradle automatically generates tasks for each Build Variant, including install and assemble.
  • Resources and code can be overridden for each variant through corresponding source sets.

What Is a Build Variant?

Build Variant is the result of combining one Build Type and one Product Flavor. If no Product Flavors are defined in the project, the Build Variant matches the Build Type. Gradle automatically generates the full set of variants as the Cartesian product of all FlavorDimensions, Product Flavors, and Build Types. For example, for free/paid flavors and debug/release types, 4 variants will be created: freeDebug, freeRelease, paidDebug, paidRelease.

Each Build Variant receives its own name in the format <Flavor><Type> with the flavor capitalized. Gradle generates separate tasks for this variant: assembleFreeDebug, installFreeDebug, bundleFreeRelease. In Android Studio, switching between variants is available through the Build Variants panel (View → Tool Windows → Build Variants). Selecting a variant affects which code is compiled, which resources are included, and which APK/AAB is produced.

The Build Variants system solves three key tasks: separating configurations for different environments (dev/staging/production), creating multiple versions of an app (free/paid), and A/B testing builds. Without Build Variants, developers would have to manually switch flags and configurations, leading to human factor errors. According to a study by Gradle Inc., 2024, implementing Build Variants reduces build errors by 60% in projects with three or more deployment environments.

How Gradle Generates Variants

AGP (Android Gradle Plugin) computes all combinations at the configuration stage. If a project has two dimensions with two and three flavors respectively, Gradle will create 2 × 2 × 3 = 12 combinations, multiplied by the number of Build Types (usually 2). Each combination gets a unique name and set of tasks. AGP automatically adds a source set for each variant: src/freeDebug/, src/paidRelease/, as well as generalized src/free/ and src/debug/. Resource reading priority: variant → flavor → type → main.

groovy
// Example: 4 Build Variants
// flavorDimensions "version", "server"
// version: demo, prod
// server: mock, live
// buildTypes: debug, release
// Total: 2 × 2 × 2 = 8 variants

android {
    flavorDimensions "version", "server"

    productFlavors {
        demo { dimension "version" }
        prod { dimension "version" }
        mock { dimension "server" }
        live { dimension "server" }
    }
}

Build Type and Product Flavor: Differences

Build Type defines how to build the application — with or without debug information, with or without optimization, with which signing. Product Flavor defines what to build — which version of the product. Build Type is a build mechanism (debug, release, staging). Product Flavor is a product variant (free, paid, enterprise, demo). Both concepts are orthogonal: any Build Type can be applied to any Product Flavor.

Default Build Types include debug (debuggable=true, minification=false, signing=debug.keystore) and release (debuggable=false, minification=true, signing=production.keystore). The default Product Flavor is one, unnamed (effectively the main source set). Developers can add their own Build Types (e.g., “staging” with debuggable=true and minification=true) and any number of Product Flavors. Another difference is that Build Types cannot be grouped into dimensions, but Product Flavors can.

The key practical difference: defaultConfig in build.gradle applies to all Variants but can be overridden in productFlavors and buildTypes. A BuildConfigField added to a buildType is visible in all flavors of that type, while one added to a productFlavor is visible in all types of that flavor. If a field is defined in both, buildType takes priority (it is applied last in the chain).

Comparison Table

CharacteristicBuild TypeProduct Flavor
PurposeHow to buildWhat to build
Examplesdebug, release, stagingfree, paid, demo, enterprise
Defaultdebug + releaseone (main)
Source Setsrc/debug/, src/release/src/free/, src/paid/
DimensionsnoflavorDimensions
Application Orderafter flavor, overridesafter defaultConfig
BuildConfigFieldoverrides flavoroverrides defaultConfig

Configuring Build Variants in build.gradle

Configuration Priority

Build Variant configuration is done in the android block of the module-level build.gradle file. First, buildTypes are declared with their parameters, then flavorDimensions and productFlavors. Gradle automatically creates variants based on these declarations. Each variant inherits the module’s defaultConfig, overriding specified fields. The declaration order affects priority: buildTypes are applied after productFlavors.

To access a specific Build Variant in Gradle scripts, use android.applicationVariants (for app modules) or android.libraryVariants (for library modules). This is a collection that can be iterated over to modify each variant’s configuration at configuration runtime. For example, you can programmatically add buildConfigField for all variants containing the word “demo”.

Android Gradle Plugin 8.x added support for onVariants — a cleaner API for configuring variants via lambdas. The old API (variantOutput, variantFilter) is marked as deprecated. It is recommended to use onVariants together with onEach for library modules. Migrate from variantOutput to onVariants is a recommended step when upgrading AGP from 7.x to 8.x.

groovy
android {
    buildTypes {
        debug {
            debuggable true
            minification false
            signingConfig signingConfigs.debug
        }
        release {
            debuggable false
            minification true
            proguardFiles "proguard-rules.pro"
            signingConfig signingConfigs.release
        }
        staging {
            debuggable true
            minification true
            versionNameSuffix "-staging"
        }
    }

    flavorDimensions "tier", "region"

    productFlavors {
        free { dimension "tier" }
        paid { dimension "tier" }
        us { dimension "region" }
        eu { dimension "region" }
    }
}

android.onVariants { variant ->
    if (variant.name.contains("Demo")) {
        variant.setEnabled(false)
    }
}

Source Sets and Resource Overriding

Each Build Variant receives its own hierarchy of source sets — directories with source code, resources, and manifest. A source set is located at src/<variantName>/ (e.g., src/freeDebug/) and can contain java/, res/, AndroidManifest.xml, assets/. If a file exists in the variant’s source set, it overrides the file with the same name from the main source set (src/main/). For resources, merging occurs rather than replacement — the system merges resources from all active source sets, giving priority to variant-specific ones.

Source sets for a Build Variant are built in a chain: src/main/src/flavor/src/type/src/flavorType/. For example, for paidRelease, main is applied first, then paid, then release, then paidRelease. Each subsequent source set overrides the previous one. This means src/release/res/values/strings.xml will override the same strings from src/paid/, but src/paidRelease/res/ is even more prioritized.

Using source sets for variants is the recommended way to customize resources. Instead of checking BuildConfig.FLAVOR in code and branching logic, you can simply place different files in different source sets. For example, icons for free and paid versions go into src/free/res/ and src/paid/res/ respectively, and AndroidManifest with different permissions goes into src/free/AndroidManifest.xml and src/paid/AndroidManifest.xml. This is cleaner, faster (resources are compiled, not checked at runtime), and safer (you cannot accidentally include paid functionality in the free version due to a code bug).

Build Variant in Multi-Module Projects

In multi-module projects, each module (library) can have its own Build Variants. AGP automatically synchronizes variants: if the app module builds paidRelease, all dependent libraries are also built in their variants corresponding to paidRelease. A problem arises when a library does not have product flavors but the app module does — then the library is built once (release or debug depending on type).

For library modules, the Build Variant by default matches the app module’s Build Type, since libraries do not have product flavors. If a library needs to adapt to the app module’s flavor, the same flavorDimensions and productFlavors must be declared in the library. AGP matches flavors by exact name match. Gradle recommends synchronizing flavors through build configuration in the root project using subprojects or Convention Plugins.

Starting from AGP 8.1, libraries can publish multiple variants — publish all library variants to a maven repository simultaneously. This solves the problem when the app module uses a paid flavor but the library is only published for free. Multiple variants publishing (MVP) allows the dependent project to select the required variant automatically. To enable MVP, add publishing { multipleVariants { ... } } to the library’s build.gradle.

Filtering and Disabling Variants

Dynamic Filtering via CI/CD

Sometimes it is necessary to disable some Build Variants — for example, if the mockRelease combination does not make sense (mock server should not go into production). Gradle provides variantFilter — a DSL block where you can check each variant’s properties and disable it via setIgnore(true). VariantFilter is applied at the configuration stage, before task creation, so a disabled variant does not generate assemble and install tasks.

Filtering is also useful for speeding up builds. If a project has 8 variants but a developer is working on only one, the remaining 7 variants still go through configuration. When using variantFilter, disabled variants do not create tasks, reducing configuration time by 30-50% for projects with 6+ flavor dimensions. In CI/CD, you can dynamically filter variants via command line parameters -PbuildOnly=paidRelease.

groovy
android {
    variantFilter { variant ->
        // Disable mock for release and demo for production
        def names = variant.flavors*.name
        def isMock = names.contains("mock")
        def isDemo = names.contains("demo")
        def isRelease = variant.buildType.name == "release"

        if ((isMock && isRelease) || (isDemo && !isMock)) {
            variant.setIgnore(true)
        }
    }
}

// Dynamic filtering via parameters
if (project.hasProperty("buildOnly")) {
    def target = project.property("buildOnly")
    android.variantFilter { variant ->
        variant.setIgnore(variant.name != target)
    }
}

Frequently Asked Questions

How many Build Variants can be created?

There is no limit, but Gradle creates the Cartesian product of all flavors and types. If you have 3 dimensions with 3 flavors each and 3 build types, you get 27 variants. Too many variants slow down configuration. It is recommended to have no more than 10–12 variants in one module.

Why are flavorDimensions needed?

flavorDimensions group Product Flavors into independent axes. For example, the “tier” dimension (free, paid) and “region” dimension (us, eu). Without dimensions, all flavors belong to one axis, and Gradle will only select one flavor from all (you cannot have free+us and paid+eu as separate variants).

How to override applicationId for a variant?

In the productFlavor or buildType block, specify applicationId. For example, for the free version: free { applicationId “com.example.app.free” }. In the manifest, use ${applicationId} — Gradle will automatically substitute the value. This allows installing both variants on one device.

Can Build Variants be used in iOS?

In iOS, the equivalent of Build Variants is the combination of Scheme + Configuration. Xcode Schemes are configured through Debug/Release configurations with different parameters. For multiple versions (free/paid), Build Configurations and Preprocessor Macros are used. On Android, the concept is more formalized and built into Gradle.

Does Build Variant affect APK size?

Yes, each variant can have a different APK size. Debug builds include debug information, SDK, and unsupported resources. Release builds with minification and resource shrinking produce the minimum size. Product Flavor also affects size: a free version without paid libraries will be smaller than a paid version by the size of those libraries.

Summary

  • Build Variant — a combination of one Build Type and one Product Flavor that defines the build configuration.
  • Build Type controls the compilation mode (debug/release/staging), while Product Flavor controls the product version (free/paid).
  • Source sets allow overriding code, resources, and manifest for each build variant.
  • VariantFilter disables unnecessary combinations, speeding up Gradle configuration by 30–50%.
  • Multi-module projects require flavor synchronization across all modules or multiple variants publishing.
  • BuildConfigField and source sets are two clean ways to customize behavior between variants.
  • Recommendation: do not create more than 10–12 variants in one project; group dimensions meaningfully.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also