안드로이드 NoSuchMethodError 완벽 해결: 원인 진단부터 의존성 충돌 정리까지

Prompt Architect 편집팀 · 2026-06-18 · 7분

TL;DR — 컴파일은 통과했는데 실행하면 터지는 NoSuchMethodError, 대부분 라이브러리 버전 충돌이 범인입니다. 원인을 진단하고 단계별로 잡아내는 실전 방법을 정리했습니다.

분명히 빌드는 됐는데 실행하면 죽는다

안드로이드 개발을 하다 보면 가장 당황스러운 순간 중 하나가 있습니다. 컴파일은 아무 문제 없이 통과했고, APK도 정상적으로 만들어졌습니다. 그런데 막상 앱을 실행하거나 특정 화면으로 진입하는 순간 다음과 같은 로그를 남기며 앱이 강제 종료됩니다.

java.lang.NoSuchMethodError: No virtual method
  setContentColor(I)V in class Lcom/example/widget/Card;
  or its super classes

NoSuchMethodError는 컴파일타임 오류가 아니라 런타임 오류라는 점이 핵심입니다. 즉, 컴파일러는 그 메서드가 존재한다고 믿고 통과시켰지만, 정작 실행 시점에 로드된 클래스에는 그 메서드가 없는 상황입니다. 이름에 "Error"가 붙은 것에서 알 수 있듯, 이건 잡아서 복구하라고 만든 예외가 아니라 클래스 로딩 자체가 어긋났다는 신호입니다.

이 글에서는 이 오류가 왜 발생하는지 구조적으로 짚고, 실무에서 실제로 통하는 진단 순서와 해결 방법을 단계별로 정리합니다.

왜 컴파일은 되는데 런타임에 터질까

NoSuchMethodError의 본질을 한 문장으로 요약하면 이렇습니다.

컴파일에 사용된 클래스 정의와, 실행 시점에 실제로 로드된 클래스 정의가 다르다.

자바/코틀린에서 메서드 호출은 컴파일 단계에서 "어떤 클래스의 어떤 시그니처를 부른다"는 형태로 바이트코드에 기록됩니다. 그런데 이 호출 대상의 실제 구현은 런타임에 클래스로더가 찾아서 연결합니다. 만약 컴파일에 쓴 버전과 실행에 쓴 버전이 다르면, 시그니처가 안 맞아 연결에 실패하고 NoSuchMethodError가 터집니다.

가장 흔한 원인을 정리하면 다음과 같습니다.

  • 의존성 버전 불일치: A 라이브러리는 gson:2.10을 요구하는데, B 라이브러리가 gson:2.8을 끌어와 결국 낮은 버전이 빌드에 들어가는 경우. 2.10에서 추가된 메서드를 호출하면 런타임에서 사라집니다.
  • 중복 의존성(다이아몬드 충돌): 여러 라이브러리가 같은 라이브러리의 서로 다른 버전을 요구할 때 Gradle이 하나로 해소하면서 일부 코드가 기대한 메서드가 빠집니다.
  • 캐시·증분 빌드 꼬임: 오래된 클래스 파일이 캐시에 남아 새 시그니처와 섞이는 경우.
  • API 레벨/디슈가링 문제: 낮은 안드로이드 버전에서 최신 자바 표준 메서드(예: String.isBlank())를 호출하는 경우.

핵심은 거의 대부분 버전 충돌로 수렴한다는 점입니다.

단계별 해결 절차

1단계 — 오류 메시지에서 정확한 시그니처 읽기

가장 먼저 할 일은 로그를 정확히 읽는 것입니다. 메시지에는 어떤 클래스의 어떤 메서드가 없는지가 명시되어 있습니다.

NoSuchMethodError: No static method
  intToColor(I)Landroidx/compose/ui/graphics/Color;
  in class Landroidx/compose/ui/graphics/ColorKt;

여기서 androidx.compose.ui.graphics.ColorKt가 범인 클래스이고, intToColor가 사라진 메서드입니다. 이 정보로 "어떤 라이브러리에 속한 클래스인지"를 특정할 수 있습니다.

2단계 — 의존성 트리로 버전 충돌 찾기

가장 강력한 진단 도구는 Gradle의 의존성 트리 출력입니다. 문제가 되는 모듈에서 다음을 실행합니다.

# app 모듈의 릴리스 런타임 의존성 트리를 출력
./gradlew :app:dependencies --configuration releaseRuntimeClasspath

특정 라이브러리만 추적하고 싶다면 더 좁혀서 봅니다.

# gson 의존성이 어디서 어떤 버전으로 들어오는지만 추적
./gradlew :app:dependencyInsight \
  --dependency gson \
  --configuration releaseRuntimeClasspath

출력에서 화살표 표기(2.8.6 -> 2.10.1)는 Gradle이 버전을 끌어올렸다는 뜻이고, 여러 버전이 동시에 보이면 충돌 후보입니다. 런타임에서 실제로 쓰이는 버전이 내 코드가 컴파일된 버전과 같은지 확인하세요.

3단계 — 버전 정렬 또는 강제 지정

원인 버전을 찾았다면 한 버전으로 통일합니다. BOM(Bill of Materials)을 쓰면 가장 깔끔합니다.

// build.gradle.kts (app 모듈)
dependencies {
    // BOM이 호환되는 버전 집합을 한 번에 고정
    implementation(platform("androidx.compose:compose-bom:2024.09.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
}

BOM이 없는 라이브러리라면 충돌 버전을 직접 강제할 수 있습니다.

configurations.all {
    resolutionStrategy {
        // gson을 무조건 이 버전으로 통일해 충돌 제거
        force("com.google.code.gson:gson:2.10.1")
    }
}

특정 라이브러리가 원치 않는 하위 의존성을 끌고 온다면 제외할 수도 있습니다.

implementation("com.example:networking:1.4.0") {
    // 이 라이브러리가 가져오는 낡은 gson은 빼고
    exclude(group = "com.google.code.gson", module = "gson")
}

4단계 — 캐시 정리 후 클린 빌드

버전을 정리했는데도 증상이 남는다면 캐시 꼬임을 의심합니다.

# 이전 빌드 산출물 제거
./gradlew clean

# Gradle 데몬과 캐시까지 새로고침하며 빌드
./gradlew assembleDebug --refresh-dependencies

Android Studio에서는 File > Invalidate Caches / Restart로 IDE 캐시까지 초기화하면 IDE가 보여주는 자동완성과 실제 빌드 결과의 괴리를 없앨 수 있습니다.

5단계 — API 레벨/디슈가링 점검

라이브러리 충돌이 아니라 자바 표준 API가 원인일 수도 있습니다. 낮은 minSdk에서 신규 메서드를 쓰려면 디슈가링을 켜야 합니다.

android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}
dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.2")
}

흔한 실수와 엣지케이스

  • implementationapi의 혼동: 멀티모듈 프로젝트에서 라이브러리를 implementation으로 두면 상위 모듈에 전파되지 않아, 상위 모듈이 다른 버전을 끌어와 충돌이 생기기 쉽습니다. 전파가 필요하면 api를 의도적으로 사용하세요.
  • debug만 보고 release를 놓침: ProGuard/R8이 적용된 릴리스 빌드에서만 터지는 경우가 있습니다. 난독화 규칙으로 메서드가 제거되거나 시그니처가 바뀐 것이므로 release 의존성 트리와 mapping.txt를 함께 확인해야 합니다.
  • 캐시된 의존성 잔존: ~/.gradle/caches에 손상된 아티팩트가 남는 드문 경우, 해당 라이브러리 캐시 폴더를 지우고 다시 받으면 해결됩니다.
  • 로컬 AAR/JAR 수동 추가: libs/ 폴더에 직접 넣은 라이브러리는 Gradle 충돌 해소 대상에서 빠질 수 있어, 원격 의존성과 시그니처가 어긋나기 쉽습니다.
  • 트랜지티브 의존성 가정: "예전엔 됐는데"라며 버전만 올리면, 그 라이브러리가 새로 끌어온 하위 의존성이 또 다른 충돌을 만들 수 있습니다. 항상 의존성 트리를 다시 확인하세요.

요약

NoSuchMethodError는 "컴파일 정의 ≠ 런타임 정의"가 본질이며, 실무에서는 거의 대부분 의존성 버전 충돌입니다. 진단 순서는 다음과 같습니다.

  1. 오류 로그에서 사라진 클래스/메서드 시그니처를 정확히 읽는다.
  2. ./gradlew :app:dependencyInsight로 버전 충돌을 찾는다.
  3. BOM·force·exclude로 버전을 한 줄로 정렬한다.
  4. clean + --refresh-dependencies로 캐시 꼬임을 제거한다.
  5. API 레벨 문제면 코어 라이브러리 디슈가링을 켠다.

무작정 버전을 올리거나 clean만 반복하기보다, 의존성 트리로 원인을 특정하는 것이 가장 빠른 길입니다.

AI에게 물어볼 때 (프롬프트 팁)

같은 오류라도 AI에게 맥락을 충분히 주면 훨씬 정확한 답을 받습니다. Prompt Architect가 권장하는 구체 프롬프트 예시입니다.

1) 로그와 함께 원인 좁히기

안드로이드에서 아래 NoSuchMethodError가 release 빌드에서만 발생한다.
스택트레이스 전문과 app 모듈의 build.gradle.kts 의존성 블록을 첨부했다.
(1) 사라진 메서드가 속한 라이브러리를 특정하고
(2) 버전 충돌 가능성이 높은 의존성 3개를 우선순위로 제시하고
(3) dependencyInsight 명령을 내 모듈명에 맞게 만들어줘.

2) 의존성 충돌 해소 전략 비교

compose-bom과 직접 force(resolutionStrategy) 방식 중
내 상황(멀티모듈, minSdk 24, R8 적용)에 더 안전한 충돌 해소법을 비교해줘.
각 방식의 부작용과 롤백 난이도까지 표로 정리해줘.

3) 재발 방지 점검 리스트

NoSuchMethodError 재발을 막기 위한 CI 단계 체크리스트를 만들어줘.
의존성 락(version catalog), dependencyInsight 자동 검사,
release 빌드 스모크 테스트를 포함해 실행 가능한 Gradle/CI 명령으로 제시해줘.

이렇게 오류 로그 전문 + 빌드 설정 + 원하는 출력 형식을 한 번에 주면, AI가 추측 대신 근거 기반으로 답하게 만들 수 있습니다.