Product Flavor: What It Is, Configuration, and Examples in Gradle

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

Product Flavor in Android development is a Gradle mechanism that allows creating multiple variants of the same application from a shared codebase. Each flavor can have its own applicationId, resources, dependencies, and functionality — for example, free and paid versions. According to Google Android Developers, 2025, Product Flavors are part of the Build Variants system and are combined with Build Types through flavorDimensions. This is the standard approach for publishing multiple versions of an app on Google Play.

Key Takeaways

  • Product Flavor — a product variant with a unique applicationId, resources, and code.
  • Flavor Dimensions group flavors into independent axes for multidimensional configuration.
  • Source sets for a flavor override main resources: icons, strings, manifest.
  • Gradle automatically generates a Build Variant for each combination of flavor + build type.
  • Google Play supports publishing multiple flavors as separate apps or a single app with different configurations.

What Is Product Flavor?

Product Flavor is a Gradle configuration in the android.productFlavors block that describes a product variant. Each flavor can override applicationId, versionName, versionCode, minSdkVersion, targetSdkVersion, signingConfig, and other defaultConfig parameters. Product Flavors have no quantity limit: a project can contain 2, 5, or 10 flavors — Gradle handles all combinations.

Product Flavor solves the codebase reuse problem — when multiple different applications need to be built from a single repository. Typical scenarios: a free version with ads and a paid version without; a demo version with limited functionality; corporate and consumer versions; white-label apps for different clients. Without Product Flavors, each version would have to be maintained in a separate project, leading to 60-70% code duplication.

Historically, Product Flavors appeared in Android Gradle Plugin 0.9 (2013) as a replacement for ant configurations. Before that, developers used separate projects for different versions or manual resource replacement before build. The introduction of flavors in AGP unified the approach and made it the standard. According to a JetBrains, 2024 survey, 78% of Android projects with multiple versions use Product Flavors, while the rest use manual switching via BuildConfig or reflection.

Product Flavor vs Build Type

Build Type manages the build process (debug with debugging, release with optimization). Product Flavor manages the build content (free without paid features, paid with them). Build Type is an infrastructure setting, Product Flavor is a product setting. Both concepts are orthogonal: a debug build of the free flavor differs from a release build of the free flavor only in compilation parameters, not in functionality. Product Flavor cannot be used to disable the debugger — that is the job of Build Type.

Flavor Dimensions: Organizing Measurements

Measurement Order and Priority

Flavor Dimensions are a grouping mechanism for Product Flavors into independent categories. If an app has a free/paid version and separately an American/European region, flavors are grouped into two dimensions: "tier" (free, paid) and "region" (us, eu). Gradle creates the Cartesian product of the dimensions: freeUs, freeEu, paidUs, paidEu — 4 variants. Without dimensions, Gradle would treat all four flavors as a single plane, and only one could be selected.

Dimensions are declared in the flavorDimensions block as a string or list of strings. The order of dimensions affects source set priority: the first dimension has the highest priority. If dimension A (tier) is specified first, then src/free/ will override src/us/ in case of resource conflicts. The order also affects how the Variant name is formed: flavors of the first dimension come first, then the second, then Build Type: freeUsDebug.

The number of dimensions is not limited, but each new dimension multiplies the number of Build Variants. For a project with 4 dimensions (2 flavors each) and 2 build types, you get 2 × 2 × 2 × 2 × 2 = 32 variants. The practical limit is 3 dimensions (maximum 8-12 variants). Beyond that, Gradle configuration slows down, and the Build Variants panel in Android Studio becomes unreadable.

groovy
android {
    flavorDimensions "tier", "api"

    productFlavors {
        free {
            dimension "tier"
            applicationId "com.example.app.free"
            versionNameSuffix "-free"
        }
        paid {
            dimension "tier"
            applicationId "com.example.app.paid"
        }
        minApi21 {
            dimension "api"
            minSdk 21
        }
        minApi26 {
            dimension "api"
            minSdk 26
        }
    }
}

// Result: freeMinApi21, freeMinApi26, paidMinApi21, paidMinApi26
// Each × debug/release = 8 Build Variants

Creating Product Flavors in build.gradle

Kotlin DSL for Product Flavors

To create a Product Flavor, you need to add a productFlavors block inside android, specify the flavor name and its parameters. The minimum flavor declaration is the name and dimension. All other parameters are inherited from defaultConfig and can be overridden. The flavor inherits defaultConfig completely, including applicationId, versionCode, testInstrumentationRunner.

Each flavor can override applicationId — this allows installing multiple versions of the app on the same device simultaneously. For example, the free version will be com.example.app.free, paid — com.example.app.paid. If applicationId is not overridden, all flavors will have the same identifier, and they cannot be installed side by side. applicationId must match the package in the manifest (unless applicationIdSuffix is used).

AGP 8+ recommends using Kotlin DSL instead of Groovy for build.gradle. Kotlin DSL provides type-safe access to configuration: the IDE suggests parameter names, checks types at compile time, and highlights errors. Migrating from Groovy to Kotlin DSL for Product Flavors typically involves replacing quotes with parentheses and adding types. AGP is backward compatible — both syntaxes work in parallel within the same project.

kotlin
// build.gradle.kts — Kotlin DSL
android {
    flavorDimensions += "tier"

    productFlavors {
        register("free") {
            dimension = "tier"
            applicationId = "com.example.app.free"
            versionNameSuffix = "-free"
            buildConfigField("boolean", "IS_PREMIUM", "false")
        }
        register("paid") {
            dimension = "tier"
            applicationId = "com.example.app.paid"
            versionNameSuffix = "-paid"
            buildConfigField("boolean", "IS_PREMIUM", "true")
        }
    }
}

Resources and Code for Different Flavors

Each Product Flavor creates its own source set — a src/<flavorName>/ directory. This directory can contain overridden resources, source files, and the manifest. The flavor source set acts as an overlay on top of main: files from src/free/res/ override files from src/main/res/ with the same names. This allows having different strings, icons, colors, and layouts for each flavor without modifying the main code.

For overriding Java/Kotlin classes, there are two approaches: flavor-specific implementation (implementing an abstract class in each flavor) and BuildConfig field (branching in code). The first approach is cleaner: you define an interface or abstract class in main, and concrete implementations in src/free/ and src/paid/. During build, only the current flavor's implementation is compiled. This provides simultaneous benefits: smaller APK size (paid code does not go into the free version) and security (it is impossible to accidentally call a paid function).

AndroidManifest.xml in a flavor source set does not replace but merges with the main manifest. Merging follows Android rules: duplicate attributes in the same element are overridden, unique ones are added. For example, if the main manifest declares INTERNET permission and free does not, the internet permission remains. However, tools:node="replace" allows replacing an entire manifest block for a specific flavor. This is useful when different flavors require different permissions (SD card write for paid, camera for free).

xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:label="Free App"
        tools:replace="android:label">
    </application>
</manifest>

Example: Free and Paid App Versions

Consider a typical scenario: free — a version with ads and basic features, paid — without ads, with extended functionality. For the free version, applicationId is set to "com.example.app.free", for paid — "com.example.app.paid". Both versions can be installed on the same device simultaneously, since applicationId is the unique application identifier in the Android system.

Architecturally, the separation is built through interface + flavor implementation. In the main source set, the PaymentService interface is declared. In src/free/, there is an implementation that shows an ad before payment via AdMob. In src/paid/ — an implementation that proceeds directly to the payment gateway. Code using PaymentService does not know which implementation is loaded — this is resolved at compile time. This approach guarantees that subscription management code will not end up in the free version, even if the developer accidentally calls it.

APK size for different flavors can differ by 5-15 MB due to including/excluding dependencies. To exclude a library from a specific flavor, use flavor-specific dependencies in build.gradle: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. This dependency will be added only for the free variant and will not increase the paid version size. For shared dependencies, use implementation — all flavors include them.

kotlin
// src/main/kotlin/com/example/payment/PaymentService.kt
interface PaymentService {
    fun processOrder(amount: Double, callback: (PaymentResult) -> Unit)
}

// src/free/kotlin/.../FreePaymentService.kt
class FreePaymentService : PaymentService {
    override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
        AdManager.showInterstitial {
            PaymentGateway.charge(amount, callback)
        }
    }
}

// src/paid/kotlin/.../PaidPaymentService.kt
class PaidPaymentService : PaymentService {
    override fun processOrder(amount: Double, callback: (PaymentResult) -> Unit) {
        PaymentGateway.charge(amount, callback)
    }
}

Product Flavor in Multi-Module Projects

In multi-module projects, library modules may not have their own Product Flavors, which creates a problem: the library is built once (as release), while the app module with flavor expects the library with the corresponding variant. Starting with AGP 8.1, libraries can publish multiple variants through the publishing.multipleVariants block — this allows publishing all flavor variants of the library into a single maven repository, and the app module will automatically select the right one.

An alternative approach is to declare the same flavorDimensions and productFlavors in the library as in the app module. AGP automatically matches flavors by exact name match within one dimension. If the flavor name in the library matches the name in the app, AGP will create consistent variants. For ease of maintenance, it is recommended to extract common flavor definitions into a Convention Plugin — a Gradle plugin applied to all project modules.

For libraries not intended for publication (internal modules), it is sufficient to synchronize flavors through the root project's build.gradle. Gradle provides the subprojects method, which allows applying configuration to all subprojects. However, keep in mind that too much configuration in subprojects slows down the configuration phase. It is recommended to use Convention Plugins — they are compiled once and reused, reducing configuration time by 15-30%.

Frequently Asked Questions

How many Product Flavors can be created?

There is no limit on quantity, but each dimension multiplies the number of Build Variants. 4 flavors in one dimension + 2 build types = 8 variants. 4 + 4 in two dimensions = 16 variants. It is recommended to use no more than 3 dimensions and 10-12 total variants.

Can the manifest be overridden for a flavor?

Yes, through the source set src/<flavor>/AndroidManifest.xml. The manifest merges with the main one. To replace an entire block, use tools:node="replace". For example, replace the app label or permissions for a specific flavor.

How to add flavor-specific dependencies?

Use the <flavorName>Implementation configuration. Example: freeImplementation 'com.google.android.gms:play-services-ads:23.0.0'. This dependency will be included only when building the free variant. For paid: paidImplementation. Common dependencies are specified through implementation.

How is Product Flavor different from Build Type?

Product Flavor defines the product version (free, paid, demo), Build Type defines the build method (debug, release). Flavors can override applicationId, versionName, resources. Build Type controls debuggable, minification, signing. Both are orthogonal and combine into Build Variant.

Can Product Flavor be used with Jetpack Compose?

Yes, Product Flavors work with Compose without restrictions. Different flavors can have different Compose screens through source sets or abstract class implementations. You can also add flavor-specific Compose dependencies: freeImplementation 'androidx.compose.ui:ui-tooling'.

Summary

  • Product Flavor — a Gradle mechanism for creating multiple versions of an app from a single codebase.
  • Flavor Dimensions group flavors into dimensions, allowing different aspects of the app to be combined.
  • Source sets for a flavor override resources, code, and the manifest without modifying the main directory.
  • Interface + flavor implementation is a clean architectural approach for separating functionality.
  • Flavor-specific dependencies prevent unnecessary libraries from ending up in inappropriate versions.
  • Multi-module projects require flavor synchronization via Convention Plugins or multiple variants publishing.
  • Recommendation: no more than 3 flavor dimensions and no more than 10 total Build Variants in a project.

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