컴파일은 됐는데 실행 시점에 NoClassDefFoundError가 난 이유
문제 발생
로컬에서는 멀쩡히 빌드되고 실행되던 애플리케이션이, 특정 배포 환경에서만 NoClassDefFoundError를 던지며 시작조차 되지 않았습니다.
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/util/JsonMapper원인 분석
NoClassDefFoundError는 ClassNotFoundException과 혼동하기 쉽지만 성격이 다릅니다.
ClassNotFoundException(checked exception):Class.forName()같은 명시적 클래스 로딩 시도가 실패했을 때 발생합니다 — "애초에 그런 클래스를 찾을 수 없었다"는 뜻입니다.NoClassDefFoundError(Error): 컴파일 시점에는 그 클래스가 분명히 존재해서 정상적으로 컴파일됐지만, 런타임에 JVM이 그 클래스를 클래스패스에서 찾지 못한 경우 발생합니다. "있었는데 지금은 없다"는 상황입니다.
흔한 원인은 다음과 같습니다.
- 빌드 시점의 클래스패스와 실행 시점의 클래스패스가 다릅니다 — 빌드 환경에는 있던 의존성 jar가 배포 환경(Docker 이미지, 서버)에는 빠져 있는 경우.
- 정적 초기화 블록(
static { ... })이나 static 필드 초기화 중 예외가 발생하면, 그 클래스는 로딩에 실패한 것으로 JVM이 기록합니다 — 이후 그 클래스를 다시 참조하는 모든 곳에서 원래 예외가 아니라NoClassDefFoundError가 발생합니다(초기화가 이미 실패했다는 걸 JVM이 기억하고 있기 때문입니다). - 버전이 다른 두 개의 같은 의존성 jar가 클래스패스에 함께 있어서, 특정 메서드/클래스가 있는 버전과 없는 버전이 충돌하는 경우.
해결 방안
- 에러 메시지의 원인을 정확히 구분합니다 — 그냥 클래스패스 누락이라면 (1)번, 정적 초기화 실패라면 그 클래스의 첫 로딩 시점 로그(보통 애플리케이션 로그의 가장 이른 시점)에서 진짜 원인 예외를 찾아야 합니다.
- 빌드 산출물(fat jar, Docker 이미지)이 실제로 필요한 모든 의존성을 포함하는지
jar tf your-app.jar | grep JsonMapper같은 명령으로 직접 확인합니다. - 의존성 버전 충돌이 의심되면 빌드 도구(Maven
dependency:tree, Gradledependencies)로 실제 해석된 버전과 충돌 여부를 확인합니다. - 정적 초기화 블록에서는 예외를 던질 수 있는 로직(파일 읽기, 네트워크 호출 등)을 최소화합니다 — 클래스 로딩이라는, 디버깅하기 까다로운 타이밍에 실패가 얽히는 것을 피할 수 있습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.