빌드나 실행 중에 다음이 난다.
class file has wrong version 55.0, should be 52.0
비슷한 형태로 다음도 같은 원인이다.
java.lang.UnsupportedClassVersionError: com/example/App has been compiled by a more
recent version of the Java Runtime (class file version 61.0), this version of the
Java Runtime only recognizes class file versions up to 52.0
클래스 파일에는 메이저 버전 번호가 박혀 있고, 그 번호가 자바 버전과 일대일로 대응한다.
| 클래스 파일 버전 | 자바 |
|---|---|
| 52.0 | 8 |
| 53.0 | 9 |
| 55.0 | 11 |
| 59.0 | 15 |
| 61.0 | 17 |
| 65.0 | 21 |
55.0 should be 52.0 은 "자바 11 로 컴파일된 클래스를 만났는데 지금 도구는 자바 8 기준으로 동작한다" 는 뜻이다. 자바는 상위 호환이 없다. 낮은 런타임은 높은 버전의 클래스를 읽지 못한다.
실행 중에 나면 런타임이 낮은 것이고, 컴파일 중에 나면 의존 라이브러리가 높은 것이다.
먼저 지금 무엇이 도는지 본다.
java -version
javac -version
echo "$JAVA_HOME"
which java javac
문제의 jar 이 어느 버전으로 빌드됐는지 직접 확인한다.
unzip -p app.jar 'com/example/App.class' | od -An -tu1 -j6 -N2
# 앞이 마이너, 뒤가 메이저. 0 55 면 자바 11
jar 전체를 훑을 수도 있다.
javap -verbose -cp app.jar com.example.App | grep -i 'major'
Maven 프로젝트라면 어느 의존성이 끌려오는지 본다.
mvn dependency:tree
가장 깨끗한 방법이다. 서버에 여러 JDK 가 깔려 있으면 전환한다.
sudo alternatives --config java
sudo alternatives --config javac
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk
export PATH=$JAVA_HOME/bin:$PATH
이클립스라면 프로젝트의 JRE System Library 와 컴파일러 준수 수준을 함께 바꿔야 한다. 둘 중 하나만 바꾸면 같은 오류가 남는다.
런타임을 못 올리는 경우다. --release 를 쓰면 문법과 API 를 모두 그 버전으로 제한하므로, -source·-target 만 주는 것보다 안전하다.
javac --release 8 -d out src/com/example/App.java
Maven 은 다음과 같다.
<properties>
<maven.compiler.release>8</maven.compiler.release>
</properties>
Gradle 은 다음과 같다.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
}
}
-source 8 -target 8 만 주면 자바 11 의 API 를 참조한 코드가 컴파일을 통과한 뒤 실행 시점에 NoSuchMethodError 로 터진다. --release 는 이것을 컴파일 단계에서 잡는다.
의존 라이브러리가 자바 11 이상만 지원하면 런타임을 올리는 수밖에 없다. 그럴 수 없다면 그 라이브러리의 마지막 자바 8 지원 버전을 찾아 고정한다.
클러스터 쪽 클라이언트 jar 은 오랫동안 자바 8 기준으로 배포됐다. 애플리케이션을 자바 11 로 빌드해 놓고 자바 8 로 도는 YARN 컨테이너에 올리면 이 오류가 난다. 제출 측과 실행 측의 JDK 를 맞춘다.
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk
hadoop classpath | tr ':' '\n' | head
한 프로젝트 안에 컴파일 버전이 다른 클래스가 섞여도 실행 시점까지 드러나지 않는다. 그 클래스를 처음 적재할 때 비로소 터지므로, 테스트에서 지나쳐 운영에서 나오는 일이 잦다.
멀티 릴리스 jar(META-INF/versions/)은 예외다. 런타임이 자기 버전에 맞는 클래스를 골라 쓴다. 이런 jar 을 열어 보고 최상위 클래스의 버전만 보면 오판할 수 있다.