Android 빌드가 빨간 줄을 뱉어요 (Gradle의 늪에서 탈출하기)
1. "코끼리(Gradle)가 움직이지 않아요."
Flutter 프로젝트를 처음 클론받거나 라이브러리를 추가했을 때, 안드로이드 빌드가 멈추는 건 일상입니다.
터미널에는 빨간 글씨로 FAILURE: Build failed with an exception.이라고 뜨는데,
로그가 너무 길어서 뭘 봐야 할지도 모르겠습니다.
StackOverflow를 뒤져서 gradle-wrapper.properties 버전을 요리조리 바꿔보지만 해결되지 않습니다.
안드로이드 빌드 에러의 4대 천왕과, 그들을 잡는 고급 무기(명령어)를 소개합니다.
2. 문제 1 - minSdkVersion 충돌 (Manifest merger failed)
라이브러리를 추가하고 빌드를 돌렸는데 이런 에러가 뜹니다.
Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [...]
해석
"네가 추가한 라이브러리는 안드로이드 19(KitKat) 이상이어야 돌아가는데, 너는 16(Jelly Bean)부터 지원한다고 설정해놨어. 말이 안 되잖아."
해결책
android/app/build.gradle 파일을 열어서 버전을 올려줍니다.
요즘(2025년)은 최소 21(Lollipop)이나 23(Marshmallow)은 되어야 합니다. 대부분의 라이브러리가 21 이상을 요구하기 때문입니다.
defaultConfig {
// minSdkVersion 16 <-- 삭제
minSdkVersion 23 // <-- 상향 조정 (Flutter 3.x 기본값 권장)
targetSdkVersion 34
}
또는 local.properties 파일에 변수를 선언하고 가져다 쓰는 방식이 더 깔끔합니다.
// android/local.properties
flutter.minSdkVersion=23
3. 문제 2 - 메서드가 너무 많아 (Multidex)
앱이 커지고 Firebase, Google Maps 같은 덩치 큰 라이브러리를 쓰다 보면 갑자기 이런 에러가 뜹니다.
Cannot fit requested classes in a single dex file (# methods: 65536 > 65536)
해석
"안드로이드 실행 파일(dex) 하나에 담을 수 있는 함수(method) 개수가 65,536개를 넘었어. 터질 것 같아." (65536은 2의 16승, 즉 2바이트로 표현 가능한 최대 주소값입니다.)
해결책
파일을 여러 개로 쪼개서 담도록(Multi-dex) 설정을 켜주면 됩니다.
android/app/build.gradle:
defaultConfig {
// ...
multiDexEnabled true // 👈 이거 한 줄이면 끝
}
dependencies {
// 만약 에러가 계속되면 이것도 추가 (minSdkVersion 20 이하일 때 필수)
implementation "androidx.multidex:multidex:2.0.1"
}
4. 문제 3 - Namespace vs Package (Gradle 8.0 대격변)
최신 Flutter 프로젝트에서 Gradle 8.0 (AGP 8.0) 이상을 쓸 때 발생하는 에러입니다.
AndroidManifest.xml에 있는 package="com.example.app" 속성과 build.gradle의 설정이 충돌합니다.
해결책
android/app/build.gradle의 android { ... } 블록 안에 namespace를 명시해야 합니다.
android {
namespace "com.example.myapp" // 👈 패키지명을 여기로 이사시킴
compileSdk 34
// ...
}
이제 매니페스트 파일에서는 package 속성을 지워도 됩니다. AGP 8.0부터는 namespace가 R 클래스 생성의 기준이 됩니다.
5. 문제 4 - Java (JDK) 버전 불일치
Unsupported class file major version 61
또는
Execution failed for task ':app:compileReleaseJavaWithJavac'.
> invalid source release: 17
해석
"Java 17로 짜여진 코드를 Java 8 컴파일러로 돌리려고 해?" 또는 그 반대 상황입니다. 최신 Flutter와 안드로이드 스튜디오는 Java 17을 표준으로 씁니다.
해결책 1 - build.gradle 수정
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
해결책 2 - 로컬 환경 확인
Mac 터미널에서 javac -version을 쳤을 때 17 버전이 나오는지 확인하세요.
안드로이드 스튜디오 설정(Settings > Build, Execution, Deployment > Build Tools > Gradle)에서도 Gradle JDK가 17로 잡혀있는지 확인해야 합니다.
6. 의존성 지옥 분석하기 (./gradlew dependencies) 제대로 파보기
가끔 "A 라이브러리가 B 라이브러리 1.0을 쓰고, C 라이브러리는 B 라이브러리 2.0을 써서 충돌"하는 경우가 있습니다. 이럴 때는 Gradle이 어떤 의존성을 가져오는지 눈으로 확인해야 합니다.
cd android
./gradlew app:dependencies
이 명령어를 치면 트리가 쫙 나옵니다.
debugRuntimeClasspath - Runtime classpath of compilation 'debug' (target (androidJvm)).
+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0 -> 1.9.0 (*)
+--- com.google.firebase:firebase-analytics:21.3.0
| +--- com.google.android.gms:play-services-measurement:21.3.0
| | +--- com.google.android.gms:play-services-basement:18.1.0 -> 18.2.0
여기서 -> 1.9.0 (*) 같은 표시를 보면서 버전 충돌이나 불필요한 라이브러리가 포함되는지 감시할 수 있습니다.
7. 최후의 수단 - 캐시 날리기
"어제는 됐는데 오늘은 안 돼요"의 90%는 캐시 문제입니다. Gradle 데몬이 꼬였거나, 다운로드받은 jar 파일이 깨졌을 때 씁니다.
cd android
# 1. 프로젝트 내 빌드 폴더 삭제 (가벼운 청소)
./gradlew clean
# 2. 유저 홈의 Gradle 캐시 삭제 (강력한 청소 - 라이브러리 다시 다 받음)
rm -rf ~/.gradle/caches
# 3. Gradle 데몬 죽이기
./gradlew --stop
이 3콤보를 날리고 다시 빌드하면, 웬만한 "유령 에러"는 사라집니다.
8. 요약
- minSdkVersion: 그냥 21 이상으로 올려라. 마음이 편해진다.
- 65k 에러:
multiDexEnabled true를 켜라. - Gradle 8.0:
build.gradle에namespace를 넣어라. - Java 버전: 17로 통일해라.
- 의존성 트리:
./gradlew app:dependencies로 범인을 찾아라. - 안 되면: 청소해라 (
clean,rm -rf caches).
Gradle은 느리고 복잡하고 짜증 나지만, 안드로이드 생태계를 지탱하는 거대하고 강력한 코끼리입니다. 코끼리를 다루는 법(명령어)을 익혀두면, 개발이 훨씬 쾌적해집니다.
Flutter: Surviving Gradle Build Errors (The Complete Guide)
1. "The Elephant (Gradle) Won't Move."
Cloning a Flutter project or adding a library often breaks Android builds.
The terminal screams FAILURE: Build failed with an exception., and the logs are 500 lines long.
You copy-paste gradle-wrapper.properties versions from StackOverflow, confusingly replacing 7.5 with 8.0, but nothing works.
Let's catch the 4 Bosses of Android Build Errors and equip the Advanced Weapons (Commands) to defeat them.
2. Boss 1: minSdkVersion Mismatch
Manifest merger failed : uses-sdk:minSdkVersion 16 cannot be smaller than version 19 declared in library [...]
Translation
"The library you added demands Android 19+ (KitKat), but your app claims to support Android 16 (Jelly Bean). That's illegal."
Solution
Open android/app/build.gradle and bump the version.
In 2025, 21 (Lollipop) or 23 (Marshmallow) is the de facto standard.
defaultConfig {
// minSdkVersion 16 <-- Delete
minSdkVersion 23 // <-- Upgrade
targetSdkVersion 34
}
If you stick to 16, you can't use 90% of modern libraries (Firebase, Google Maps, etc.).
3. Boss 2: Too Many Methods (Multidex)
Cannot fit requested classes in a single dex file (# methods: 65536 > 65536)
Translation
"Your executable file (dex) is full. It can only hold 65,536 methods (2^16 addresses)." This happens when you add heavy dependencies like Firebase or AWS Amplify.
Solution
Enable Multi-dex (splitting code into multiple files).
android/app/build.gradle:
defaultConfig {
multiDexEnabled true // 👈 The magic line
}
dependencies {
// If you support ancient Android versions (SDK < 21), add this too:
implementation "androidx.multidex:multidex:2.0.1"
}
4. Boss 3: Namespace vs Package (Gradle 8.0+)
With Gradle 8.0 (AGP 8.0), Google changed the rules.
Defining package in AndroidManifest.xml is deprecated/removed for generating R classes.
Solution
Move the package definition to android/app/build.gradle utilizing the namespace property:
android {
namespace "com.example.myapp" // 👈 Define it here
compileSdk 34
// ...
}
If you declare it in both places improperly, Gradle throws a "Namespace not specified" or conflict error.
5. Boss 4: JDK Version Mismatch
Unsupported class file major version 61
Translation
You are mixing Java versions.
- Major version 61 = Java 17
- Major version 55 = Java 11
- Major version 52 = Java 8
You are forcing a Java 17 library to run on Java 8 JVM, or vice versa.
Solution
Align everything to Java 17 (The current standard for Flutter/Android).
- Gradle:
compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = '17' } - IDE: Check
Android Studio > Settings > Build Tools > Gradle > Gradle JDK. - Terminal: Check
java -version.
6. Deep Dive: Dependency Analysis (./gradlew dependencies)
Sometimes, Library A uses Library B (v1.0) and Library C uses Library B (v2.0). Conflict! Gradle tries to resolve it, but sometimes fails. You need to See the Matrix.
cd android
./gradlew app:dependencies
This prints the entire dependency tree.
debugRuntimeClasspath - Runtime classpath of compilation 'debug'.
+--- com.squareup.retrofit2:retrofit:2.9.0
| +--- com.squareup.okhttp3:okhttp:3.14.9 -> 4.9.0 (*)
You can trace exactly which library is pulling in the old version of OkHttp or Kotlin and causing issues.
7. The Nuclear Option: Clear Cache
If it worked yesterday but fails today, it's a corrupted cache. Don't just restart your computer. Nuke the cache.
cd android
# 1. Clean project artifacts
./gradlew clean
# 2. Delete global Gradle cache (forces re-download of all jars)
rm -rf ~/.gradle/caches
# 3. Stop the Gradle Daemon
./gradlew --stop
Running ./gradlew --stop is crucial because the Gradle Daemon (a background process) holds onto file locks and memory. Killing it forces a fresh start.
8. FAQ
Q: usage of kapt vs ksp?
A: kapt is in maintenance mode. Migrate to KSP (Kotlin Symbol Processing) for faster builds. Most libraries (Room, Moshi) support KSP now.
Q: "Duplicate class found" error?
A: This happens when two dependencies include the same class. Run ./gradlew app:dependencies and exclude the transitive dependency:
implementation ('com.example.lib:1.0.0') {
exclude group: 'com.duplicate.group', module: 'duplicate-module'
}
Q: How to speed up Gradle builds?
A:
- Enable Offline Mode in Android Studio (if you have all dependencies).
- Enable Configuration Cache in
gradle.properties:org.gradle.configuration-cache=true. - Allocate more RAM to Gradle Daemon:
org.gradle.jvmargs=-Xmx4g.
Deep Dive Glossary
1. Gradle
An open-source build automation tool that is designed to be flexible enough to build almost any type of software. Android Studio uses a specialized plugin called the Android Gradle Plugin (AGP) to integrate Gradle into the IDE. Unlike Ant or Maven, Gradle uses a Groovy or Kotlin-based DSL (Domain Specific Language).
2. Dex (Dalvik Executable)
Android apps are compiled into .dex files, which are zipped into a single .apk file. The Dalvik Virtual Machine (DVM) or Android Runtime (ART) executes these files. The "65k method limit" refers to the limitation of addressing methods within a single dex file.
3. Multidex
A support library and build configuration that allows an app to be split into multiple .dex files. The primary dex file (classes.dex) contains the code necessary to boot the app, while secondary dex files (classes2.dex, etc.) are loaded dynamically.
4. R8 and ProGuard
ProGuard is a tool for code shrinking, optimization, and obfuscation. R8 is Google's replacement for ProGuard, offering faster builds and better optimization. It removes unused code and resources, making the APK smaller and harder to reverse-engineer.
5. Manifest Merger
The process where Gradle merges all AndroidManifest.xml files from your main app module and all included libraries into a single manifest file for the final APK. Conflicts often arise here (e.g., conflicting minSdkVersion or permissions).
6. Gradle Daemon
A long-lived background process that executes builds much faster than starting a new JVM for every build. It caches data about project structure, files, tasks, and more. Sometimes it consumes too much memory (RAM) and needs to be killed or configured with higher heap size (-Xmx).
7. ART vs Dalvik
Dalvik: The original runtime used by Android (up to KitKat 4.4). It used JIT (Just-In-Time) compilation. ART (Android Runtime): The modern runtime (Lollipop 5.0+). It uses AOT (Ahead-Of-Time) compilation, installing compiled native machine code during app installation, resulting in faster app launch and execution.