Java 개발을 하다 보면 클래스를 찾지 못해 프로그램이 실행되지 않는 오류를 겪을 수 있습니다. 대표적으로 ClassNotFoundException 예외와 NoClassDefFoundError 오류가 이러한 경우에 해당합니다. 이번 글에서는 두 오류의 발생 원인과 대표적인 사례를 살펴보고, 문제를 해결하는 방법을 단계별로 정리해보겠습니다.
- 에러 로그
아래는 각 오류가 발생했을 때 콘솔에 출력되는 예외 메시지 예시입니다.
ClassNotFoundException 발생 시:
java.lang.ClassNotFoundException: com.example.MyClass
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
... (생략)
NoClassDefFoundError 발생 시:
Exception in thread "main" java.lang.NoClassDefFoundError: com/example/YourClass
at package.MyApp.main(MyApp.java:10)
Caused by: java.lang.ClassNotFoundException: com.example.YourClass
... (생략)
위 예에서 볼 수 있듯이 ClassNotFoundException과 NoClassDefFoundError 모두 특정 클래스를 로드하지 못해 발생하는 오류입니다. 그러나 둘은 발생 맥락과 의미에 차이가 있습니다.
- 원인
ClassNotFoundException은 프로그램 실행 중 애플리케이션이 문자열로 지정한 클래스를 로드하려 했을 때 해당 클래스를 찾지 못하면 발생하는 예외입니다. 예를 들어 Class.forName("com.example.MyClass")와 같이 동적으로 클래스 로드를 시도하거나, 특정 클래스 로더를 통해 클래스를 읽어올 때 그 클래스파일이 클래스패스(classpath)에 없으면 ClassNotFoundException이 발생합니다. 이 예외는 Exception을 상속하므로 **체크 예외(Checked Exception)**이며, 발생 시 적절한 조치를 취하거나 예외를 처리해야 합니다.
NoClassDefFoundError는 컴파일 시에는 존재했던 클래스를 런타임에 찾지 못할 때 발생하는 오류(Error)입니다. JVM이 내부의 클래스 정의 데이터 구조에서 해당 클래스 정보를 찾지 못했음을 의미하는데, 보통 이미 한 번 로드 시도가 실패했던 클래스를 다시 사용할 때 발생합니다. 예를 들어, 이전에 해당 클래스를 로드하려고 시도했다가 실패한 적이 있다면(JVM이 그 실패를 기억하고 있기 때문에), 다음에 다시 그 클래스를 사용할 때 JVM은 로드를 재시도하지 않고 즉시 NoClassDefFoundError를 던집니다. 이때 "이전의 로드 실패"는 앞서 언급한 ClassNotFoundException으로 클래스를 찾지 못한 경우일 수도 있고, 또는 클래스 초기화 중에 예외(ExceptionInInitializerError)가 발생한 경우 등 여러 원인이 있을 수 있습니다. NoClassDefFoundError는 Error를 상속하는 런타임 오류이므로 일반적으로 코드에서 잡지 않고, 발생 자체를 예방하는 쪽으로 접근해야 합니다.
요약하면 두 오류 모두 "클래스를 로드할 수 없음"을 나타내지만, ClassNotFoundException은 애플리케이션에서 클래스를 찾지 못한 경우 발생하는 예외이고, NoClassDefFoundError는 JVM 레벨에서 클래스 로딩에 문제가 생긴 경우(이전에 로드 실패 이력이 있거나 런타임에 클래스가 없어진 경우 등) 발생하는 오류입니다.
- 대표적인 발생 상황
두 오류가 발생하는 구체적인 상황을 몇 가지 사례로 나누어 정리하면 다음과 같습니다.
- ClassNotFoundException이 발생하는 경우:
- 동적 클래스 로딩 실패: Class.forName()이나 사용자 정의 ClassLoader를 사용하여 런타임에 클래스를 로드하려 할 때, 해당 클래스가 클래스패스에 없어 로드되지 않을 경우. (예: JDBC 드라이버를 Class.forName("org.postgresql.Driver")로 로드하려 했는데 해당 드라이버 JAR이 누락된 경우)
- 잘못된 클래스패스 설정: 애플리케이션 실행 시 클래스패스에 필요한 경로나 JAR이 포함되지 않아 클래스를 찾지 못하는 경우. 이로 인해 *"Error: Could not find or load main class ..."*와 같은 오류 메시지가 나타나기도 합니다. 예를 들어, .class 파일이 있는 디렉터리를 클래스패스에 추가하지 않거나, 대소문자 오타로 클래스 이름을 잘못 지정한 경우에도 발생할 수 있습니다.
- NoClassDefFoundError가 발생하는 경우:
- 컴파일엔 있었지만 런타임에 누락된 클래스: A 클래스가 B 클래스를 사용하도록 컴파일되었는데, 정작 실행 시에 B 클래스의 정의를 찾을 수 없는 경우입니다. 예를 들어, 개발 시엔 존재하던 라이브러리(JAR)를 빌드 후 배포 단계에서 누락한 경우나, 클래스 파일을 실수로 삭제한 경우입니다. (이 경우 컴파일 시엔 문제없지만 실행하면 해당 클래스에 대해 NoClassDefFoundError가 발생합니다.)
- 클래스 초기화 실패: 클래스 로딩 과정에서 static 초기화 블록이나 static 필드 초기화 중에 에러가 발생하여 초기화에 실패하면, 이후 그 클래스를 사용할 때 NoClassDefFoundError가 발생합니다. 예를 들어, 어떤 클래스의 static 초기화 블록에서 예외가 던져지면 첫 로드 시 ExceptionInInitializerError가 발생하고, JVM은 그 이후로 해당 클래스의 로딩을 시도할 때 NoClassDefFoundError를 발생시킵니다.
- 의존성 또는 클래스로더 문제: 라이브러리 간 버전 충돌로 클래스 로딩에 문제가 생기는 경우나, 애플리케이션 서버 환경에서 클래스로더 격리로 인해 필요한 클래스를 못 찾는 경우입니다. 예를 들어, 서로 다른 버전의 라이브러리가 동일한 클래스를 가지고 있어 충돌하거나, 웹 어플리케이션에서 필요한 클래스를 WEB-INF/lib에 넣지 않고 실행해서 해당 클래스가 로드되지 않는 경우 등이 있습니다. 이 경우 원인은 클래스패스보다는 로드 순서나 영역의 문제이므로 오류 메시지를 통해 어떤 클래스가 문제인지 확인해야 합니다.
- 해결 방법
ClassNotFoundException과 NoClassDefFoundError를 해결하려면, 원인에 따라 클래스패스 설정을 조정하거나 누락된 라이브러리를 추가하고, 경우에 따라 코드나 환경을 수정해야 합니다. 아래에 주요 해결 방법을 단계별로 설명합니다.
- 클래스패스(Classpath) 설정 확인 및 수정: 우선 클래스패스에 문제가 없는지 점검합니다. Java 애플리케이션을 실행할 때 -cp (또는 -classpath) 옵션으로 올바른 경로와 JAR 파일들이 지정되어 있는지 확인하세요. 특정 클래스나 패키지가 클래스패스에 빠져있다면 해당 경로를 추가해야 합니다. CLASSPATH 환경 변수를 사용 중인 경우에도 거기에 필요한 경로가 모두 포함되어 있는지 확인합니다. (예를 들어, 현재 디렉터리의 클래스를 찾지 못하는 경우 -cp . 옵션을 누락한 것은 아닌지 점검해보세요.) 최신 Java 9+ 환경에서는 모듈을 사용하고 있다면 모듈패스(module path) 설정도 확인해야 합니다. 필요한 모듈이 --module-path에 포함되어 있거나 module-info.java에 제대로 선언되어 있는지 점검합니다.
- JAR 등 의존 라이브러리 추가: 오류 메시지에 찾을 수 없다고 나온 클래스가 어떤 라이브러리에 속하는지 파악합니다. 그리고 그 라이브러리가 실행 환경에 포함되어 있는지 확인하세요. 만약 누락되었다면 해당 JAR 파일을 클래스패스에 추가하거나, 빌드 도구(Maven/Gradle 사용 시 pom.xml 또는 build.gradle에 의존성 추가)를 통해 포함시킵니다. 예를 들어, Maven 프로젝트에서 NoClassDefFoundError가 발생했다면 pom.xml에 누락된 의존성이 있는지 또는 스코프(scope)가 runtime으로 포함되는지 확인하고, mvn install 후 재배포하여 문제가 해결되는지 확인합니다. 라이브러리를 수동으로 관리한다면, 배포 폴더(예: WEB-INF/lib 등)에 해당 JAR을 복사하는 것을 잊지 않도록 합니다.
- 프로젝트 빌드/컴파일 문제 해결: 개발 환경에서 **클린 빌드(Clean & Rebuild)**를 수행해봅니다. 간혹 이전 빌드의 산출물이 남아있거나, 클래스파일이 손상/유실되어 발생하는 경우도 있으므로, 이클립스(Eclipse)나 인텔리J와 같은 IDE를 사용 중이라면 프로젝트를 Refresh/Clean 하여 다시 컴파일합니다. 컴파일 오류로 클래스가 생성되지 않았는데 런타임에 그 클래스를 호출한 경우도 ClassNotFoundException으로 이어질 수 있으므로, 먼저 컴파일 시 경고나 누락된 클래스가 없는지 확인합니다. 또한, 빌드 결과물(JAR 또는 .class 파일)이 실제 실행되는 환경에 제대로 배포되었는지도 확인해야 합니다.
- 클래스 로더 문제 해결: 애플리케이션 서버나 플러그인처럼 여러 계층의 클래스로더가 존재하는 환경에서는 필요한 클래스가 적절한 클래스로더에서 로드되고 있는지 확인해야 합니다. 예를 들어, 웹 어플리케이션에서 공용 라이브러리를 서버에 설치하지 않고 어플리케이션 내에 포함했을 때, 해당 라이브러리를 서버의 클래스로더가 로드하지 못해 ClassNotFoundException이 발생할 수 있습니다. 이런 경우 라이브러리를 올바른 위치(예: 컨테이너가 제공하는 공유 라이브러리 디렉터리 또는 각 어플리케이션의 전용 lib 폴더)에 배치해야 합니다. 반대로, 특정 라이브러리를 WAR에 포함했는데 이미 서버에 같은 라이브러리가 있어서 충돌하는 경우에도 오류가 날 수 있으니, 중복 포함 여부와 버전 충돌도 점검합니다. 필요하다면 클래스패스나 모듈 경로상에서 우선순위를 조정하거나, OSGi 같은 환경에서는 번들 의존성을 수정하는 등의 조치를 취합니다.
- 코드상의 오류 수정: 만약 NoClassDefFoundError가 static 초기화 블록이나 초기화 시점 오류 때문에 발생한 경우라면, 해당 클래스의 코드를 수정하는 것이 근본 해결책입니다. 오류 메시지에서 "Caused by: ..." 부분을 확인하여 어떤 예외가 근본 원인인지 파악한 뒤, 그 예외가 발생하지 않도록 코드를 변경하거나 예외 처리를 추가합니다. 예를 들어, static 블록에서 발생한 ExceptionInInitializerError가 원인이라면 static 블록 내 로직을 수정하거나 예외 상황을 방지하도록 해야 합니다. 코드 수정 후에는 재배포/재실행하여 더 이상 오류가 발생하지 않는지 확인합니다.
상황별 권장 해결책 요약:
- 클래스패스 누락으로 인한 오류 → 클래스패스 설정을 수정하고 필요한 경로/JAR를 추가합니다.
- JAR 라이브러리 누락으로 인한 오류 → 해당 라이브러리를 빌드 및 실행 경로에 포함시킵니다. (예: 프로젝트 설정에 JAR 추가 또는 Maven 의존성 추가)
- 클래스 로더/라이브러리 충돌로 인한 오류 → 라이브러리 배치와 로딩 구조를 조정하고 버전 충돌을 해소합니다.
- static 초기화 등 코드 오류로 인한 오류 → 코드의 버그를 수정하여 초기화 시 예외가 발생하지 않도록 합니다.
以上의 단계를 따라 문제를 확인하고 조치하면, 대부분의 ClassNotFoundException 및 NoClassDefFoundError를 해결할 수 있습니다. 다음으로는 간단한 예제 코드를 통해 이러한 오류를 직접 재현하고 해결하는 방법을 실습해보겠습니다.
- 실습 예제 (ClassNotFoundException)
먼저, ClassNotFoundException을 강제로 발생시켜 원인을 이해해보겠습니다. 아래와 같이 존재하지 않는 클래스를 로드하는 코드를 작성합니다.
- 코드 작성: 임의의 클래스를 Class.forName으로 로드하는 간단한 자바 프로그램을 만듭니다 (예: CNFEDemo.java).
- 컴파일 및 실행: 작성한 코드를 컴파일(javac CNFEDemo.java)하고 실행(java CNFEDemo)합니다.
- 결과 확인: 프로그램 실행 시 ClassNotFoundException이 발생하며, 스택 트레이스를 콘솔에서 확인할 수 있습니다. 예외 메시지에 나타난 클래스 이름이 우리가 의도적으로 로드한 이름과 일치하는 것을 볼 수 있습니다.
// CNFEDemo.java
public class CNFEDemo {
public static void main(String[] args) {
try {
// 존재하지 않는 클래스 'com.example.MissingClass' 로드 시도
Class.forName("com.example.MissingClass");
} catch (ClassNotFoundException e) {
// 예외 발생: e.printStackTrace()로 출력
e.printStackTrace();
}
}
}
위 코드에서는 com.example.MissingClass라는 실제로 존재하지 않는 클래스를 로드하도록 시도했습니다. 이러한 경우 ClassNotFoundException이 발생하여 e.printStackTrace()를 통해 예외 내용이 출력됩니다. 이 예제를 통해 클래스패스에 존재하지 않는 클래스를 로드하려 할 때 ClassNotFoundException이 발생함을 직접 확인할 수 있습니다. (실행 환경에 com.example.MissingClass가 없으므로 당연한 결과입니다.)
- 실습 예제 (NoClassDefFoundError)
이번에는 NoClassDefFoundError를 재현해보겠습니다. 컴파일 시에는 문제가 없지만 실행 시에만 오류가 나는 상황을 만들어볼 것입니다. 두 개의 클래스를 만들어 하나가 다른 하나를 참조하도록 한 뒤, 실행 시 일부러 한 클래스를 제거하여 NoClassDefFoundError를 발생시켜 보겠습니다.
- 코드 작성: 서로 다른 파일로 클래스 A와 클래스 B를 작성합니다. A 클래스는 메인 메서드에서 B 클래스를 생성하고 사용하는 간단한 코드로 작성합니다. 예시 코드는 아래와 같습니다. (A.java와 B.java 파일 생성)
- 컴파일: 두 소스를 모두 컴파일합니다. (javac A.java B.java) - 컴파일 시에는 두 클래스 모두 존재하므로 에러 없이 성공합니다.
- 정상 실행 확인: 일단 컴파일된 상태에서 java A로 프로그램을 한 번 실행하면, 클래스 B의 메서드가 호출되어 정상적으로 동작합니다 (B의 hello() 메서드가 "Hello from B" 메시지를 출력한다고 가정).
- 클래스 파일 제거: 이제 B.class 파일을 삭제하거나 다른 곳으로 옮겨서 런타임에 B 클래스가 없어진 상황을 만듭니다. (A.class는 남겨둡니다.)
- 재실행: 다시 java A로 A 클래스를 실행하면, 이번에는 실행 도중 NoClassDefFoundError가 발생하게 됩니다. A 클래스가 B를 사용하려고 시도하지만 B 클래스 정의를 찾을 수 없기 때문입니다.
// B.java
public class B {
public void hello() {
System.out.println("Hello from B");
}
}
// A.java
public class A {
public static void main(String[] args) {
B b = new B(); // B 클래스 인스턴스 생성
b.hello(); // B 클래스의 메서드 호출
}
}
위의 A, B 클래스는 모두 존재할 때는 문제없이 동작합니다. 하지만 컴파일 후 B.class를 삭제하고 A를 실행하면, 다음과 같은 오류가 발생합니다.
Exception in thread "main" java.lang.NoClassDefFoundError: B
at A.main(A.java:5)
이것이 바로 NoClassDefFoundError입니다. A입장에서는 컴파일 때 B를 참조했기 때문에 문제가 없었지만, 런타임에 B가 사라지자 JVM이 B를 로드하지 못해 오류를 내는 것입니다. 이 실습을 통해 컴파일 시존재했던 클래스가 실행 시 누락되면 NoClassDefFoundError가 발생한다는 것을 직접 확인할 수 있습니다.
위에서 살펴본 것처럼, ClassNotFoundException과 NoClassDefFoundError는 주로 클래스패스 설정이나 라이브러리 누락 문제로 발생하지만, 각각 발생하는 맥락이 조금씩 다릅니다. 문제 발생 시에는 우선 오류 메시지에 표시된 클래스 이름을 확인하고, 해당 클래스가 실행 환경에서 로드 가능한 경로에 존재하는지를 점검해야 합니다. 그리고 앞서 제시한 해결 방법에 따라 클래스패스를 수정하거나 누락된 라이브러리를 추가하고, 필요하다면 코드의 오류를 수정함으로써 문제를 해결할 수 있습니다. 올바른 클래스 경로 설정과 의존성 관리만 신경 쓰면 최신 자바 버전에서도 이러한 클래스 로드 오류를 예방하고 빠르게 해결할 수 있을 것입니다.
'IT > Java' 카테고리의 다른 글
ConcurrentModificationException (반복 중 컬렉션 변경 오류) 발생 원인과 해결 방법 (0) | 2025.03.14 |
---|---|
Java OutOfMemoryError (메모리 부족 오류) 원인과 해결 방법소개 (1) | 2025.03.10 |
Java 인증서 추가 방법 ( SunCertPathBuilderException 해결 ) (0) | 2021.10.27 |
Quartz 스케줄러 사용하기 (0) | 2020.06.15 |
댓글