java.lang.UnsupportedClassVersionError: class file version 65.0

Quick answer

  • The class was compiled for a newer JDK than the runtime running it. The JVM won't load a format it doesn't understand.
  • Decode the numbers: major version = JDK + 44. So 65.0 = Java 21, 61.0 = Java 17, 52.0 = Java 8.
  • Fix by running newer: point JAVA_HOME/PATH at a JDK ≥ the compile target.
  • Or compile older: javac --release 8 / maven.compiler.release / a Gradle toolchain, so bytecode matches the runtime you must deploy to.

The exact error string

The modern wording (Java 8 and later) names two version numbers — the one the class was built for, and the highest one your runtime understands:

Exception in thread "main" java.lang.UnsupportedClassVersionError:
  com/example/App has been compiled by a more recent version of the
  Java Runtime (class file version 65.0), this version of the Java
  Runtime only recognizes class file versions up to 52.0

Java 7 and earlier printed the same error with different words — if you searched that phrasing, this is your page too:

Exception in thread "main" java.lang.UnsupportedClassVersionError:
  com/example/App : Unsupported major.minor version 52.0

Both mean the same thing: a .class file was produced by a newer compiler than the JVM trying to load it, and the JVM refuses because it can't read a class-file format from the future. Every JDK release bumps the class-file format version by one and will not load anything higher than its own.

Anatomy of the message — read the two numbers

This error tells you exactly what's wrong; you just have to decode it. There are only two moving parts, and the fix falls out of them:

class file version 65.0 → the class was compiled for JDK 21  (65 − 44 = 21)

recognizes ... up to 52.0 → your runtime is JDK 8  (52 − 44 = 8)

∴ A Java 21 class is being run on a Java 8 JVM. The runtime is too old — raise it, or lower the compile target.

The rule is major version = JDK feature release + 44, so you never have to guess. The trailing .0 is the minor version and is almost always 0; the one exception is a class compiled with preview features enabled, which sets the minor version to 65535 (0xFFFF) — if you see 65.65535, the class targets Java 21 and was built with --enable-preview, which additionally requires an exactly-matching runtime with --enable-preview too.

Class-file version ↔ JDK lookup table

Match the number in your message to the JDK on both sides:

Class file versionJDK releaseNotes
52.0Java 8Very long-lived LTS — still the runtime on many servers
53.0Java 9Module system introduced
54.0Java 10
55.0Java 11LTS — common baseline
56.0 – 60.0Java 12 – 16Non-LTS feature releases
61.0Java 17LTS — a very common compile target
62.0 – 64.0Java 18 – 20Non-LTS feature releases
65.0Java 21LTS
66.0Java 22
67.0Java 23
68.0Java 24

If your message shows an even higher number, keep applying version − 44. This is a mismatch of the same shape as ClassNotFoundException / NoClassDefFoundError in that both stop a class from loading — but that one is about a class that can't be found, whereas this one found the class fine and rejected its format.

Confirm which side is which

Before changing anything, verify the runtime version and the class's compiled version so you know which one to move. Check the runtime first:

java -version
# openjdk version "1.8.0_402"   <- runtime is Java 8 (the "up to 52.0" side)

javac -version
# javac 21.0.2                  <- compiler is Java 21 (the "65.0" side)

Then read the version stamped into the actual class file — this is the ground truth, independent of which javac is on your PATH right now:

javap -verbose com/example/App.class | grep -E "major|minor"
# minor version: 0
# major version: 65            <- 65 - 44 = Java 21

# on any Unix box, without a JDK:
file com/example/App.class
# App.class: compiled Java class data, version 65.0 (Java 21)

Now you know both numbers came from your own machine, not just from the error text, and you can pick the fix with confidence.

Fix 1: run on a newer Java runtime (raise the runtime)

The direct fix is to run the class on a JDK at least as new as its class-file version. A version 65.0 class needs Java 21 or newer. Install one and point JAVA_HOME and PATH at it:

# example on Linux/macOS after installing a JDK 21 build
export JAVA_HOME=/usr/lib/jvm/temurin-21-jdk
export PATH="$JAVA_HOME/bin:$PATH"

java -version   # confirm it now reports 21.x before re-running
java -cp . com.example.App

If you manage several JDKs, a version manager (sdkman, jenv, or your OS package manager's alternatives system) makes switching the active runtime a one-liner. This is the right fix when the newer bytecode is intentional — you want Java 21 features and simply need the deploy target to catch up.

Fix 2: recompile for the older runtime (lower the target)

When you can't change the runtime — a server stuck on Java 8, a customer environment, a locked base image — compile the bytecode down to that runtime instead. Use --release, which both emits older bytecode and restricts you to APIs that existed in that release (unlike the older -source/-target pair, which could let you call APIs the target JDK doesn't have):

# javac --release requires JDK 9+; targets the given release's bytecode + API
javac --release 8 -d out com/example/App.java

javap -verbose out/com/example/App.class | grep major
# major version: 52            <- now a Java 8 class, runs on a Java 8 JVM

You obviously can't use language or library features newer than the target — --release 8 will reject a record or a var in a lambda. That rejection at compile time is the point: it stops you from shipping a class that would only blow up at runtime on the old JVM.

Fix 3: set the release level in Maven or Gradle

Most real projects don't call javac directly. Set the target once in the build tool so every module is consistent. In Maven, prefer the single release property over separate source/target:

<!-- pom.xml -->
<properties>
  <maven.compiler.release>17</maven.compiler.release>
</properties>

In Gradle, a toolchain is the robust choice because it pins the exact JDK used to compile regardless of which java launched Gradle:

// build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
// (or, to only lower bytecode without pinning the JDK:)
// tasks.withType(JavaCompile).configureEach { options.release = 17 }

Set the number to the oldest runtime you must support. Building with a newer JDK is fine — --release 17 under a JDK 21 build produces Java 17 bytecode that runs everywhere from Java 17 up.

Why it usually happens: build JDK ≠ runtime JDK

Almost every occurrence is one machine (or image, or IDE) compiling with a newer JDK than a different machine runs. The build succeeds because the compiler is new; the failure only shows up when the class meets the older runtime. The usual culprits:

Debugging checklist

Frequently Asked Questions

What JDK is class file version 65.0?

Class file version 65.0 is Java 21. The major version equals the JDK feature release plus 44, so 65 minus 44 is 21. The .0 minor version just means no preview features were enabled. Common values: 52 is Java 8, 55 is Java 11, 61 is Java 17, 65 is Java 21.

What does UnsupportedClassVersionError mean?

A .class file was compiled by a newer JDK than the Java runtime you are running it on. The JVM refuses to load a class whose format is newer than it understands. The message names both numbers: the class file version it found and the highest version this runtime recognizes. Fix it by running on a runtime at least as new as the compile target, or by recompiling for the older runtime.

How do I fix 'has been compiled by a more recent version of the Java Runtime'?

Either upgrade the runtime or lower the compile target. To upgrade, install a JDK at least as new as the class file version (65.0 needs Java 21+) and point JAVA_HOME and PATH at it. To lower the target, recompile with javac --release N (or set maven.compiler.release / Gradle's toolchain) so the bytecode matches the runtime you must deploy to.

What is 'Unsupported major.minor version 52.0'?

It is the same error, printed by Java 7 and earlier. Older JVMs used the wording Unsupported major.minor version 52.0 instead of the modern class file version 52.0. The number decodes the same way: 52 is Java 8, so a Java 8 class was run on a Java 7 or older runtime. Upgrade the runtime or recompile for the older one.

How do I check what version a .class or .jar was compiled for?

Run javap -verbose YourClass.class and read the major version: line, or run file YourClass.class. For a jar, extract one class and inspect it. A major version of 61 means the class targets Java 17. Compare that against java -version for your runtime to confirm which side is too old.

Why does Maven or Gradle build fine but fail at runtime?

The JDK that compiled the code is newer than the JRE that runs it. This is common in CI, IDEs, and containers where JAVA_HOME for the build differs from the java on the deploy machine or in the base image. Set the compiler's release level to the runtime you deploy to (maven.compiler.release or a Gradle toolchain), or make the deploy runtime match the build JDK.

More Java & backend errors

Browse the full reference for Java, Node.js, Python, and database errors — exact message, cause, and fix.

All Error References Java: ClassNotFoundException HTTP Status Codes
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.