JDK Resolution¶
codelens uses Java at two independent levels, and resolves each separately:
- Server JVM — the JDK that runs
codelens-server-all.jar(codelens itself). - Project JVM — a JDK used to run the target project's Gradle when its Gradle version can't run on the server JVM.
┌────────────────────────────────────────────────────────────┐
│ codelens CLI │
│ │
│ Server JVM → runs codelens-server-all.jar │
│ (JDK 21–25, newest available) │
│ │
│ Project JVM → passed to the server as --project-java-home │
│ (the target's own JDK, when needed) │
└────────────────────────────────────────────────────────────┘
Server JVM¶
The server is built for Java 21 and is verified through Java 25. The CLI runs it on the newest installed JDK whose major version is in the range 21–25, discovered across SDKMAN, mise, Homebrew, and macOS/Linux system JDK locations.
Resolution order¶
| Priority | Source | Notes |
|---|---|---|
| 1 | CODELENS_JAVA_HOME |
Explicit override; used as-is if it has bin/java |
| 2 | SDKMAN + mise + Homebrew + JavaVMs (in range) | Highest installed JDK with major in [21, 25]; tie-break order is SDKMAN > mise > Homebrew > JavaVMs |
| 3 | JAVA_HOME |
Fallback if nothing in range was found |
| 4 | java on PATH |
Last resort |
Sources scanned:
- SDKMAN:
~/.sdkman/candidates/java/* - mise:
~/.local/share/mise/installs/java/*(or$MISE_DATA_DIR/installs/java/*) - Homebrew kegs
openjdk@21…openjdk@25under the standard prefixes (/opt/homebrew,/usr/local,/home/linuxbrew/.linuxbrew) - JavaVMs / jvm dirs —
/Library/Java/JavaVirtualMachines/*and~/Library/Java/JavaVirtualMachines/*on macOS (catches Homebrew casks, Oracle/Zulu/Liberica DMG installers, and manual installs);/usr/lib/jvm/*on Linux (deduped by real path sodefault-javasymlinks aren't double-counted)
Floor and ceiling¶
- Floor (21) is the server's build target — the minimum JVM that can launch it.
- Ceiling (25) is the newest JDK the server stack (Kotlin/Ktor/Netty, the Gradle Tooling API, and ClassGraph) is verified on.
The CLI picks the newest JDK in range rather than pinning an exact version, because a server JVM must be greater than or equal to the bytecode version of the project it analyzes. Running on the newest in-range JDK maximizes the set of target projects codelens can read without rebuilding the server.
Target newer than the server¶
If a target project targets a Java version newer than the server JVM, codelens prints a warning at startup, for example:
warning: server is running Java 21 but /path/to/project targets Java 24;
install a JDK >= 24 (<= 25) via `sdk install java 24...` or
`brew install openjdk@24` so codelens can analyze it.
Install an in-range JDK that is at least the target's version and codelens will prefer it on the next start.
Project JVM¶
To resolve a target project's classpath, the server runs the project's Gradle.
That Gradle daemon must run on a JDK the project's Gradle version supports — often
an older JVM than the server's. codelens runs it on the project's
declared JDK, passed as --project-java-home, decoupled from the server JVM.
A declared project JDK is required
codelens does not guess the project's JDK. Every analyzed project must
declare one (or you pass --project-java). If none is declared — or the
declared version isn't installed — codelens prints an actionable error and
stops, rather than running the project's Gradle on the wrong JVM.
How the project JDK is declared¶
Declare it with any one of these (checked in order); the first that specifies a Java version wins:
| Priority | Source | Example |
|---|---|---|
| 1 | .sdkmanrc |
java=11.0.28-tem |
| 2 | .java-version |
11.0.28-tem or 11 |
| 3 | gradle.properties |
org.gradle.java.home=/abs/path/to/jdk |
| 4 | mise | .mise.toml ([tools] java = "21") or .tool-versions (java temurin-21.0.9) |
A .sdkmanrc is the simplest. See Target Project Setup.
How the declared version is resolved¶
The declared version is located in order: SDKMAN (~/.sdkman/candidates/java,
exact then major-prefix), Homebrew (openjdk@<major>), JavaVMs
(/Library/Java/JavaVirtualMachines/* on macOS, /usr/lib/jvm/* on Linux),
then mise (~/.local/share/mise/installs/java). An absolute
org.gradle.java.home path that has bin/java is used directly as a last
resort when none of the above match.
Same-major fallback¶
When the exact declared version isn't installed but another JDK with the same major version IS, codelens uses it and prints a one-line informational note to stderr:
This is intentional. For bytecode scanning the patch version and vendor don't affect class-file compatibility — only the major matters. The note is discoverable so unexpected JDK choices are never silent.
Cross-major substitution is never performed (declaring Java 21 will not
silently use Java 17). If you need a specific patch or vendor, install it
or pass --project-java explicitly.
If nothing resolves at all, codelens stops with guidance to install the JDK
(sdk install java <v> / brew install openjdk@<major> / mise install java@<v>).
The error names what was declared AND lists what IS installed so you
can pick a working version without guessing:
project /path declares Java 8.0.392-amzn but it isn't installed;
install it (`sdk install java 8.0.392-amzn`, `brew install openjdk@8`, or
`mise install java@8.0.392-amzn`) or pass --project-java;
installed JDKs: 17.0.13-tem (SDKMAN ~/.sdkman/...), 21.0.9-amzn (SDKMAN ~/.sdkman/...)
Explicit escape hatch (bypasses declaration/resolution):
Because the daemon runs on the declared JDK, a project on an older Gradle works even when the server runs a newer JDK — and a server on Java 25 can still read the project's (older) bytecode via ClassGraph.
Environment variables and flags¶
| Variable / flag | Applies to | Purpose |
|---|---|---|
CODELENS_JAVA_HOME |
Server JVM | Force the JDK that runs the server |
JAVA_HOME |
Server JVM | Fallback when nothing in range is found |
CODELENS_JAVA_OPTS |
Server JVM | Extra JVM options (e.g. -Xmx4g), whitespace-split |
CODELENS_SERVER_JAR |
Server JAR | Force the path to codelens-server-all.jar |
CODELENS_REPO_PATH |
Server JAR | Hint the codelens repo root for server/app/build/libs/... |
CODELENS_JAVA_VM_DIRS |
Discovery | Comma-separated override of the JavaVMs / jvm search dirs (test/diagnostic use only) |
HOMEBREW_PREFIX |
Discovery | Override Homebrew prefix (read by Homebrew's shellenv too) |
MISE_DATA_DIR / XDG_DATA_HOME |
Discovery | Override mise installs root |
--project-java |
Project JVM | Java home for the target project's Gradle |
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
UnsupportedClassVersionError at startup |
Server JVM older than Java 21 | Install a JDK 21+ (SDKMAN or brew install openjdk@21), or set CODELENS_JAVA_HOME |
| Warning that the target needs a newer Java | Target bytecode newer than the server JVM | Install an in-range JDK ≥ the target version |
no JDK declared for project … |
Project doesn't declare a JDK | Add a .sdkmanrc / .java-version / mise config, or pass --project-java |
project … declares Java X but it isn't installed; installed JDKs: … |
Declared major isn't installed; the error lists what IS | Install the missing major (sdk install java <X>, brew install openjdk@<major>, or mise install java@<X>), or update the declaration to one of the listed JDKs |
project … declares org.gradle.java.home=<P> but <P>/bin/java doesn't exist |
gradle.properties points at a deleted or moved JDK | Update the path in gradle.properties or install the JDK at that location |
note: project declares Java X; using installed Y (Source) as a same-major substitute |
Not an error — your declared version isn't installed but a same-major JDK was found and used | None required. Install the exact version if vendor/patch matters to you |
| Server starts but shows 0 classes | Project not compiled | Build the target first (./gradlew build -x test) |
To see what the server logged: