Back to Articles

Android Build Types Explained: Debug vs Release, Suffixes & BuildConfig Beginner’s Guide

A simple way to think about it is… build types are “outfits” for your app—debug for development and release for production—controlling how it’s compiled, labeled, and signed. The guide shows how to add applicationId and versionName suffixes, tweak launcher labels with manifestPlaceholders, and ex...

You’ll see build types early in any Android project. Here’s the short answer: build types control how your app is compiled and packaged for different moments—coding, testing, and shipping.

A simple way to think about it is… you wear different “outfits” for different occasions: sneakers for practice debug, formal shoes for a wedding release. Same person, different outfit, different rules.

Why this matters in one minute

  • You can install debug and release builds side‑by‑side without clashing.
  • You can point the app to different servers like staging vs production.
  • You can hide dev‑only tools from real users.
  • Your QA team can tell builds apart quickly.

We should avoid thinking that build types were just labels. In real projects, they’re guardrails that keep experiments out of production.

Prerequisites light - For Beginners who don't know what we are alking about

  • You’ve built a basic Android app in Kotlin or Java.
  • You can open and edit your build.gradle.kts file.

Term check plain English:

  • Build type: A named set of rules for compiling/packaging your app e.g., debug, release.
  • Application ID: The unique package name used on a device/store e.g., com.example.app. Two apps can live side‑by‑side if their IDs differ.
  • BuildConfig: A generated class with constants you can read in code like DEBUG, APPLICATION_ID.

If you’re a beginner, this is the part that unlocks a lot of “how do they do that?” moments when you see teams switch between environments. So let's get started.

The starting point Gradle snippet

Typically this how a starting build.gradle.kts file looks like.

android 
    namespace = "com.codetutor.varientdemo"
    compileSdk = 36

    defaultConfig 
        applicationId = "com.codetutor.varientdemo"
        minSdk = 24
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"
    

    buildTypes 
        release 
            isMinifyEnabled = false
            proguardFiles
                getDefaultProguardFile"proguard-android-optimize.txt",
                "proguard-rules.pro"
            
        
    

    buildFeatures 
        buildConfig = true
    

What this gives you

  • A release build type defined.
  • BuildConfig generation is on, so you can use constants in code.

What I’ve noticed is folks forget buildFeatures buildConfig = false and then wonder why BuildConfig isn’t available. On paper this looks simple, but… one missing line leads to cryptic compile errors.

Enable and use BuildConfig

Turn it on already shown above

buildFeatures 
    buildConfig = true

Read values in code

Text"APPLICATION_ID: BuildConfig.APPLICATION_ID"
Text"VERSION_NAME: BuildConfig.VERSION_NAME"
Text"DEBUG: BuildConfig.DEBUG"

Why this is useful: You can branch behavior e.g., show a debug banner and display environment info for quick checks.

Tell debug and release apart clearly

This is where most people get stuck… The app icons look the same, and you can’t tell which build is which on a device. Let’s fix that with suffixes and labels.

1 Add suffixes for debug

buildTypes 
    debug 
        applicationIdSuffix = ".debug"
        versionNameSuffix = "-debug"
        isMinifyEnabled = false
    

    release 
        isMinifyEnabled = false
        signingConfig = signingConfigs.getByName"debug"
        proguardFiles
            getDefaultProguardFile"proguard-android-optimize.txt",
            "proguard-rules.pro"
        
    
  • Debug app ID becomes: com.codetutor.varientdemo.debug
  • Release app ID stays: com.codetutor.varientdemo

2 Change the launcher label per build

Use manifestPlaceholders so the app name shows which build you opened.

buildTypes 
    debug 
        manifestPlaceholders["appLabel"] = "Varient Demo dbg"
        applicationIdSuffix = ".debug"
        versionNameSuffix = "-debug"
        isMinifyEnabled = false
    

    release 
        manifestPlaceholders["appLabel"] = "Varient Demo"
        isMinifyEnabled = false
        signingConfig = signingConfigs.getByName"debug"
    

Now you’ll see the difference on your phone right away.

Add build‑specific config like different APIs

Point debug to staging and release to production:

buildTypes 
    debug 
        manifestPlaceholders["appLabel"] = "Varient Demo dbg"
        applicationIdSuffix = ".debug"
        versionNameSuffix = "-debug"
        buildConfigField"String", "BASE_URL", ""https://staging.api.example.com""
    

    release 
        manifestPlaceholders["appLabel"] = "Varient Demo"
        buildConfigField"String", "BASE_URL", ""https://api.example.com""
    

Read it in code:

KeyValue"BASE_URL", BuildConfig.BASE_URL

In real projects, this small switch avoids “why is my local build hitting production?” chaos.

Debug‑only features safe by default

You can guard tools and experiments behind BuildConfig.DEBUG so they never leak to real users.

@Composable
fun ConfigScreen 
    Surfacemodifier = Modifier.fillMaxSize 
        Column 
            // Show debug indicator only in debug builds
            if BuildConfig.DEBUG 
                AssistChip
            

            Text"Varients Demo", style = MaterialTheme.typography.headlineSmall
            
            KeyValue"APPLICATION_ID", BuildConfig.APPLICATION_ID
            KeyValue"VERSION_NAME", BuildConfig.VERSION_NAME ?: "—"
            KeyValue"DEBUG flag", BuildConfig.DEBUG.toString
            KeyValue"BASE_URL", BuildConfig.BASE_URL

            // Debug-only tools
            if BuildConfig.DEBUG 
                val activity = LocalContext.current as Activity
                ButtononClick =  
                    ActivityLeaker.leakactivity as MainActivity 
                 
                    Text"Trigger Activity Leak"
                
            
        
    

Why this helps: You keep power tools for developers without risking the production app.

Signing the quick mental model

  • Debug builds: auto‑signed with a debug keystore so you can install and test right away.
  • Release builds: must be signed with your release keystore before store upload.

For local testing only, you can temporarily reuse the debug signing for release:

release 
    signingConfig = signingConfigs.getByName"debug"

Important: For production, always use a dedicated, secure release keystore.

Common pitfalls and fixes

  • Identical app names/icons → Use manifestPlaceholders["appLabel"] to label debug builds.
  • Same applicationId → Add applicationIdSuffix = ".debug" so both builds install together.
  • Leaky dev tools → Wrap them with if BuildConfig.DEBUG checks.
  • Wrong server calls → Set buildConfigField"String", "BASE_URL", "..." per build type.

According to me, these four cover 80% of the “oops” moments for teams.

Takeaway

  • Build types aren’t fluff; they’re practical switches for safer development.
  • Start with clear labels, ID suffixes, and separate BASE_URLs.
  • Keep debug‑only tools behind BuildConfig.DEBUG.

Next step: When you’re ready, explore Product Flavors to handle multiple environments QA, UAT, Production or brand variants—that’s the more advanced layer on top of build types.