Skip to content

Commit 3932d4f

Browse files
authored
Expand README (#240)
This update expands documentation to cover: - Comprehensive debugging guide (launch/attach modes with config reference) - Project runtime configuration for targeting different Java versions - JVM memory settings and JDTLS launcher options - Architecture note about the java-lsp-proxy binary - Extension development and testing - Various clarifications and improvements to existing sections
1 parent 47eac07 commit 3932d4f

1 file changed

Lines changed: 255 additions & 7 deletions

File tree

README.md

Lines changed: 255 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,42 @@
11
# Java Extension for Zed
22

3-
This extension adds support for the Java language to [Zed](https://zed.dev). It is using the [Eclipse JDT Language Server](https://projects.eclipse.org/projects/eclipse.jdt.ls) (JDTLS for short) to provide completions, code-actions and diagnostics.
3+
This extension adds support for Java and `.properties` files to [Zed](https://zed.dev). It uses the [Eclipse JDT Language Server](https://projects.eclipse.org/projects/eclipse.jdt.ls) (JDTLS for short) to provide completions, code-actions and diagnostics.
44

55
## Quick Start
66

77
Install the extension via Zeds extension manager. It should work out of the box for most people. However, there are some things to know:
88

9-
- It is generally recommended to open projects with the Zed-project root at the Java project root folder (where you would commonly have your `pom.xml` or `build.gradle` file). The extension will automatically detect Maven and Gradle projects in subdirectories, but opening at the project root provides the best experience.
9+
- It is generally recommended to open projects with the Zed-project root at the Java project root folder (where you would commonly have your `pom.xml` or `build.gradle` file). The extension will automatically detect Maven and Gradle projects in subdirectories, but opening at the project root provides the best experience. If you're working with a non-standard project layout or encounter issues with classpath resolution, see [Advanced Configuration/JDTLS initialization Options](#advanced-configurationjdtls-initialization-options) for fine-tuning.
1010

11-
- By default the extension will download and run the latest official version of JDTLS for you, but this requires Java version 21 to be available on your system via either the `$JAVA_HOME` environment variable or as a `java(.exe)` executable on your `$PATH`. If your project requires a lower Java version in the environment, you can specify a different JDK to use for running JDTLS via the `java_home` configuration option.
11+
- By default the extension will download and run the latest official version of JDTLS for you, but this requires Java version 21 or higher to be available on your system via either the `$JAVA_HOME` environment variable or as a `java(.exe)` executable on your `$PATH`. The `java_home` configuration option allows you to specify a **separate** JDK 21+ installation specifically for running JDTLS — this is useful when your system default Java is a lower version required by your project. Note that `java_home` is also passed as the `JAVA_HOME` environment variable to the JDTLS process; to configure runtimes for your project itself, use `initialization_options.settings.java.configuration.runtimes` (see [Configuring Project Runtimes](#configuring-project-runtimes) below).
1212

13-
- You can provide a **custom launch script for JDTLS**, by adding an executable named `jdtls` (or `jdtls.bat` on Windows) to your `$PATH` environment variable. If this is present, the extension will skip downloading and launching a managed instance and use the one from the environment.
13+
- You can provide a **custom JDTLS binary** through one of these mechanisms (in priority order):
14+
1. The `jdtls_launcher` setting — specify an absolute path to a JDTLS launch script
15+
2. An executable named `jdtls` (or `jdtls.bat` on Windows) on your `$PATH`
16+
17+
When either is found, the extension will skip downloading and launching a managed JDTLS instance and use the provided one instead.
1418

1519
- To support [Lombok](https://projectlombok.org/), the lombok-jar must be downloaded and registered as a Java-Agent when launching JDTLS. By default the extension automatically takes care of that, but in case you don't want that you can set the `lombok_support` configuration-option to `false`.
1620

17-
- The option to let the extension automatically download a version of OpenJDK can be enabled by setting `jdk_auto_download` to `true`. When enabled, the extension will only download a JDK if no valid java_home is provided or if the specified one does not meet the minimum version requirement. User-provided JDKs **always** take precedence.
21+
- The option to let the extension automatically download a JDK can be enabled by setting `jdk_auto_download` to `true`. When enabled, the extension will download [Amazon Corretto](https://aws.amazon.com/corretto/) (an OpenJDK distribution) if no valid `java_home` is provided or if the specified one does not meet the minimum version requirement (Java 21). User-provided JDKs **always** take precedence.
1822

1923
Here is a common `settings.json` including the above mentioned configurations:
2024

2125
```jsonc
2226
"lsp": {
2327
"jdtls": {
2428
"settings": {
29+
// Path to a JDK 21+ used to run JDTLS.
30+
// Also accepts the legacy key "java.home".
2531
"java_home": "/path/to/your/JDK21+",
2632
"lombok_support": true,
2733
"jdk_auto_download": false,
2834

35+
// JVM heap size for JDTLS (maps to -Xms and -Xmx)
36+
// Accepts values like "512m", "1G", "4096m", etc.
37+
"min_memory": "1G", // default: "1G"
38+
"max_memory": "2G", // default: unset (no -Xmx limit)
39+
2940
// Controls when to check for updates for JDTLS, Lombok, and Debugger
3041
// - "always" (default): Always check for and download the latest version
3142
// - "once": Check for updates only if no local installation exists
@@ -55,6 +66,8 @@ JDTLS uses **CamelCase fuzzy matching** for symbol queries. For example, searchi
5566

5667
Debug support is enabled via our [Fork of Java Debug](https://github.com/zed-industries/java-debug), which the extension will automatically download and start for you. Please refer to the [Zed Documentation](https://zed.dev/docs/debugger#getting-started) for general information about how debugging works in Zed.
5768

69+
### Launch Mode
70+
5871
To get started with Java, click the `edit debug.json` button in the Debug menu, and replace the contents of the file with the following:
5972
```jsonc
6073
[
@@ -75,6 +88,55 @@ To get started with Java, click the `edit debug.json` button in the Debug menu,
7588

7689
You should then be able to start a new Debug Session with the "Launch Debugger" scenario from the debug menu.
7790

91+
### Attach Mode
92+
93+
You can attach to a running JVM process that was started with debug options (e.g. `-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005`):
94+
95+
```jsonc
96+
[
97+
{
98+
"label": "Attach to JVM (port 5005)",
99+
"adapter": "Java",
100+
"request": "attach",
101+
"hostName": "localhost",
102+
"port": 5005
103+
}
104+
]
105+
```
106+
107+
### Debug Configuration Reference
108+
109+
The following options are available in `debug.json` for **launch** configurations:
110+
111+
| Option | Type | Description |
112+
|--------|------|-------------|
113+
| `request` | `"launch"` | Required. The request type. |
114+
| `mainClass` | `string` | Fully qualified class name. Auto-resolved if omitted. |
115+
| `projectName` | `string` | Project name to disambiguate when multiple projects exist. |
116+
| `args` | `string \| string[]` | Command line arguments passed to the program. |
117+
| `vmArgs` | `string \| string[]` | Extra JVM options (e.g. `-Xmx2G -Dprop=value`). |
118+
| `classPaths` | `string[]` | Classpaths for the JVM. Special values: `$Auto`, `$Runtime`, `$Test`. |
119+
| `modulePaths` | `string[]` | Module paths for the JVM. Auto-resolved if omitted. |
120+
| `cwd` | `string` | Working directory. Defaults to the worktree root. |
121+
| `env` | `object` | Extra environment variables for the program. |
122+
| `encoding` | `string` | The `file.encoding` setting for the JVM. |
123+
| `stopOnEntry` | `boolean` | Pause the program after launching. |
124+
| `noDebug` | `boolean` | Launch without attaching the debugger (e.g. for profiling). |
125+
| `console` | `string` | Console type: `internalConsole`, `integratedTerminal`, or `externalTerminal`. |
126+
| `shortenCommandLine` | `string` | Shorten long command lines: `none`, `jarmanifest`, or `argfile`. |
127+
| `launcherScript` | `string` | Path to a custom JVM launcher script. |
128+
| `javaExec` | `string` | Path to a specific Java executable. |
129+
130+
For **attach** configurations:
131+
132+
| Option | Type | Description |
133+
|--------|------|-------------|
134+
| `request` | `"attach"` | Required. The request type. |
135+
| `hostName` | `string` | Host name or IP of the remote debuggee. |
136+
| `port` | `integer` | Debug port of the remote debuggee. |
137+
| `timeout` | `integer` | Timeout before reconnecting in milliseconds (default: 30000). |
138+
| `projectName` | `string` | Project name for source resolution. |
139+
78140
### Single-File Debugging
79141

80142
If you're working a lot with single file debugging, you can use the following `debug.json` config instead:
@@ -107,8 +169,68 @@ This extension provides tasks for running your application and tests from within
107169

108170
There is a fairly straightforward fix that you can apply to make it work on Windows by supplying your own task scripts. Please see [this Issue](https://github.com/zed-extensions/java/issues/94) for information on how to do that and read the [Tasks section in Zeds documentation](https://zed.dev/docs/tasks) for more information.
109171

172+
## Configuring Project Runtimes
173+
174+
If your project targets a Java version different from the one running JDTLS, you can register multiple JDK installations via `java.configuration.runtimes`. JDTLS will use these to compile and run your project at the correct language level, while still running itself on JDK 21+.
175+
176+
```jsonc
177+
"lsp": {
178+
"jdtls": {
179+
"settings": {
180+
// JDK 21+ for running JDTLS itself
181+
"java_home": "/usr/lib/jvm/java-21-openjdk"
182+
},
183+
"initialization_options": {
184+
"settings": {
185+
"java": {
186+
"configuration": {
187+
"runtimes": [
188+
{
189+
"name": "JavaSE-1.8",
190+
"path": "/usr/lib/jvm/java-8-openjdk"
191+
},
192+
{
193+
"name": "JavaSE-11",
194+
"path": "/usr/lib/jvm/java-11-openjdk"
195+
},
196+
{
197+
"name": "JavaSE-17",
198+
"path": "/usr/lib/jvm/java-17-openjdk"
199+
},
200+
{
201+
"name": "JavaSE-21",
202+
"path": "/usr/lib/jvm/java-21-openjdk",
203+
"default": true
204+
}
205+
]
206+
}
207+
}
208+
}
209+
}
210+
}
211+
}
212+
```
213+
214+
- `name` must match an [execution environment identifier](https://wiki.eclipse.org/Execution_Environments) (e.g. `JavaSE-1.8`, `JavaSE-11`, `JavaSE-17`, `JavaSE-21`). Note that Java 8 uses the `1.8` naming convention while Java 9+ uses the major version number directly.
215+
- `path` is the absolute path to the JDK installation root (the directory containing `bin/java`).
216+
- `default` (optional) — set to `true` on the runtime JDTLS should use when no project-specific source level is detected.
217+
218+
JDTLS will automatically pick the appropriate runtime based on your project's source level (from `pom.xml`, `build.gradle`, or `.classpath`). For example, a Maven project with `<maven.compiler.source>11</maven.compiler.source>` will use the `JavaSE-11` runtime for compilation and code analysis.
219+
220+
> **macOS paths** typically look like `/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home`
221+
>
222+
> **Windows paths** typically look like `C:\Program Files\Java\jdk-17`
223+
110224
## Advanced Configuration/JDTLS initialization Options
111-
JDTLS provides many configuration options that can be passed via the `initialize` LSP-request. The extension will pass the JSON-object from `lsp.jdtls.initialization_options` in your settings on to JDTLS. Please refer to the [JDTLS Configuration Wiki Page](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request) for the available options and values. Below is an opinionated example configuration for JDTLS with most options enabled:
225+
226+
JDTLS provides many configuration options that can be passed via the `initialize` LSP-request. The extension will pass the JSON-object from `lsp.jdtls.initialization_options` in your settings on to JDTLS. Please refer to the [JDTLS Configuration Wiki Page](https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request) for the available options and values.
227+
228+
The extension automatically injects the following defaults into `initialization_options` (unless you override them):
229+
- `workspaceFolders` — set to the worktree root as a `file://` URI
230+
- `extendedClientCapabilities.classFileContentsSupport``true` (enables decompiled source navigation)
231+
- `extendedClientCapabilities.resolveAdditionalTextEditsSupport``true`
232+
233+
Below is an opinionated example configuration for JDTLS with most options enabled:
112234

113235
```jsonc
114236
"lsp": {
@@ -265,4 +387,130 @@ MyProject/
265387
}
266388
```
267389

268-
If changes are not picked up, clean JDTLS' cache (from a java file run the task `Clear JDTLS cache`) and restart the language server
390+
If changes are not picked up, clean JDTLS' cache (from a java file run the task `Clear JDTLS cache`) and restart the language server.
391+
392+
## Architecture Note
393+
394+
The extension uses a native binary (`java-lsp-proxy`) that wraps the JDTLS process. This proxy enables the extension to communicate with JDTLS for features like debug class resolution and classpath queries. It is automatically downloaded from the [extension repository releases](https://github.com/zed-extensions/java/releases) and requires no user configuration.
395+
396+
## Developing Locally
397+
398+
If you want to contribute to this extension or test local changes, you can install it as a dev extension. Refer to the [Zed documentation on developing extensions](https://zed.dev/docs/extensions/developing-extensions) for full details.
399+
400+
### Prerequisites
401+
402+
- [Rust](https://rustup.rs/) toolchain
403+
- The `wasm32-wasip1` target: `rustup target add wasm32-wasip1`
404+
- [just](https://github.com/casey/just) command runner (optional but recommended)
405+
406+
### Installing as a Dev Extension
407+
408+
1. Clone the repository:
409+
```sh
410+
git clone https://github.com/zed-extensions/java.git
411+
cd java
412+
```
413+
414+
2. Make sure you are on the branch that contains the feature or fix you want to test:
415+
```sh
416+
git branch --show-current
417+
# Switch if needed:
418+
git checkout <feature-branch>
419+
```
420+
421+
3. In Zed, open the extensions panel (`zed: extensions` in the command palette), click the **Install Dev Extension** button, and select the cloned repository folder.
422+
423+
Zed will build the WASM extension automatically and load it. After making changes to the extension source, use **Rebuild Dev Extension** from the command palette to pick them up.
424+
425+
### Using the `justfile`
426+
427+
The project includes a `justfile` with common development tasks:
428+
429+
| Recipe | Description |
430+
|--------|-------------|
431+
| `just proxy-build` | Build the proxy binary in debug mode |
432+
| `just proxy-release` | Build the proxy binary in release mode |
433+
| `just proxy-install` | Build release proxy and copy it to the extension workdir |
434+
| `just ext-build` | Build the WASM extension in release mode |
435+
| `just fmt` | Format all code (Rust + tree-sitter queries) |
436+
| `just clippy` | Run clippy on both crates |
437+
| `just lint` | Format and lint all code |
438+
| `just all` | Lint, build extension, and install proxy |
439+
440+
### Updating the `java-lsp-proxy` Binary
441+
442+
The proxy is a separate native Rust binary (in the `proxy/` directory) that runs alongside the WASM extension. Because it's a native binary, it is **not** rebuilt when you use "Rebuild Dev Extension" — you need to build and install it manually.
443+
444+
> **Important:** When testing a manually built proxy, set `"check_updates": "never"` in your `lsp.jdtls.settings` to prevent the extension from downloading a release binary and overwriting your local build.
445+
446+
```sh
447+
# Build the proxy in release mode and copy it to the extension workdir
448+
just proxy-install
449+
```
450+
451+
This compiles the proxy for your native target and copies it to the appropriate Zed extension working directory:
452+
- **macOS**: `~/Library/Application Support/Zed/extensions/work/java/proxy-bin/`
453+
- **Linux**: `~/.local/share/zed/extensions/work/java/proxy-bin/`
454+
- **Windows**: `%LOCALAPPDATA%/Zed/extensions/work/java/proxy-bin/`
455+
456+
After installing the proxy, restart the language server in Zed for the changes to take effect.
457+
458+
If you prefer not to use `just`, you can build and copy manually:
459+
460+
```sh
461+
cd proxy
462+
cargo build --release --target $(rustc -vV | grep host | awk '{print $2}')
463+
# Then copy the binary from target/<your-target>/release/java-lsp-proxy
464+
# to the appropriate extension workdir shown above
465+
```
466+
467+
### Remote Development (SSH)
468+
469+
When using [Zed's remote development](https://zed.dev/docs/remote-development) over SSH, extensions installed locally are automatically propagated to the remote server. The language server and the proxy binary run on the **remote host**, not your local machine.
470+
471+
For standard use, the proxy binary is auto-downloaded from GitHub releases for the remote server's platform — no action is needed.
472+
473+
However, if you're **testing local proxy changes** against a remote host, you need to get the binary onto the remote server yourself. The key thing to be aware of is that on remote hosts, extensions are stored under a **different path** than on your local machine — typically:
474+
475+
```
476+
~/.local/share/zed/remote_extensions/work/java/proxy-bin/
477+
```
478+
479+
> **Tip:** If you're unsure of the exact path, SSH into the remote and look for it:
480+
> ```sh
481+
> find ~/.local/share/zed -type d -name "proxy-bin" 2>/dev/null
482+
> ```
483+
484+
#### Option A: Build on the remote directly
485+
486+
If you have Rust installed on the remote server, you can clone the repo there and build natively:
487+
488+
```sh
489+
# On the remote host
490+
git clone https://github.com/zed-extensions/java.git
491+
cd java/proxy
492+
cargo build --release
493+
494+
# Copy to the remote extensions workdir
495+
mkdir -p ~/.local/share/zed/remote_extensions/work/java/proxy-bin
496+
cp target/release/java-lsp-proxy ~/.local/share/zed/remote_extensions/work/java/proxy-bin/
497+
```
498+
499+
#### Option B: Cross-compile locally and copy
500+
501+
If you prefer to build on your local machine:
502+
503+
1. Cross-compile the proxy for the remote target (typically Linux x86_64 or aarch64):
504+
```sh
505+
cd proxy
506+
cargo build --release --target x86_64-unknown-linux-gnu
507+
```
508+
> You may need to install the target first: `rustup target add x86_64-unknown-linux-gnu` and configure a linker in `.cargo/config.toml`.
509+
510+
2. Copy the binary to the remote server:
511+
```sh
512+
scp target/x86_64-unknown-linux-gnu/release/java-lsp-proxy \
513+
user@remote:~/.local/share/zed/remote_extensions/work/java/proxy-bin/java-lsp-proxy
514+
```
515+
516+
After either option, restart the language server in Zed for the changes to take effect.

0 commit comments

Comments
 (0)