jj-language-server
v0.2.0
Published
Java Language Server implemented in TypeScript/JavaScript (no JVM required)
Readme
jj-language-server
[!WARNING] Experimental project — Developed by Julien Dubois. This software is provided "as is", without warranty of any kind, express or implied. See the Apache-2.0 License for details.
A Java Language Server implemented in TypeScript/JavaScript — no JVM required.
Installable via npm, works with any editor that supports the Language Server Protocol.
Installing
npm install -g jj-language-serverRunning
jj-language-server --stdioSee the LSP Configuration Guide for detailed setup instructions for VS Code, Neovim, Sublime Text, Emacs, Helix, and GitHub Copilot CLI (Windows/macOS/Linux).
CLI Options
Usage: jj-language-server [options]
Options:
-V, --version output the version number
--stdio use stdio (required)
--log-level <logLevel> Log level: 4=log, 3=info, 2=warn, 1=error (default: 3)
-h, --help display help for commandHow It Works
Unlike the Eclipse JDT Language Server which requires a JVM, jj-language-server parses Java source code using java-parser (a Chevrotain-based parser written in pure JavaScript) and implements the LSP protocol using vscode-languageserver.
Performance Benchmarks
Benchmarked against Eclipse JDTLS 1.56 on Spring PetClinic (30 Java files), measured via JSON-RPC over stdio. JDTLS is configured with Maven import (Gradle disabled due to Java toolchain requirements). Both Node.js and Bun runtimes are tested for jj-language-server:
| Metric | jj (Node.js) | jj (Bun) | Eclipse JDTLS | |---|---|---|---| | Startup time | ~295 ms | ~2,200 ms | ~2,350 ms (8×) | | Memory after init (RSS) | 220 MB | 202 MB | 2,427 MB | | Memory final (RSS) | 345 MB | 254 MB | 2,516 MB | | Bulk open (30 files) | ~1,500 ms | ~1,500 ms | ~5,000 ms |
Operation Latency (avg of 3 runs, largest file)
| Operation | jj (Node.js) | jj (Bun) | Eclipse JDTLS |
|---|---|---|---|
| hover | 3.2 ms | 2.2 ms | 1.9 ms |
| completion | 1.2 ms | 3.9 ms | 36.1 ms |
| documentSymbol | 0.6 ms | 1.3 ms | 1.1 ms |
| definition | 1.5 ms (∅) | 2.1 ms (∅) | 1.5 ms (∅) |
| references | 11 ms | 5.2 ms | 42.1 ms |
| formatting | 7.5 ms | 24.3 ms | 7 ms |
| codeAction | 1.3 ms | 1.3 ms | 31.6 ms (∅) |
| foldingRange | 1.2 ms | 2.0 ms | 1.1 ms |
| semanticTokens | 2.9 ms | 3.3 ms | 1.5 ms |
(∅) = response was null or empty (not yet supported for cross-file definition)
Key takeaway: jj-language-server is 30× faster at completion and 8× faster at references than JDTLS, while using 7× less memory (~345 MB vs ~2.5 GB). Bun offers the lowest memory footprint (254 MB final). JDTLS excels at semantic tokens and folding ranges thanks to full type resolution. For cold-start scenarios (opening a project for the first time, CI environments, quick edits), jj-language-server is dramatically faster.
Run benchmarks locally:
# Download JDTLS, then:
npm run benchmark # all three (Node.js + Bun + JDTLS)
npm run benchmark:jj # jj-language-server only (Node.js)
node benchmarks/lsp-benchmark.mjs --bun-only # jj (Bun) only
node benchmarks/lsp-benchmark.mjs --no-jdtls # Node.js + Bun, skip JDTLSDevelopment
Build
npm install
npm run buildTest
npm testThere are 503 tests across 35 test files. Integration tests use the Spring PetClinic project as a realistic fixture. The pinned commit is tracked in test-fixtures/spring-petclinic-sha.txt.
CI / CD
| Workflow | Trigger | Description |
|----------|---------|-------------|
| CI (.github/workflows/ci.yml) | PR / push to main | Lint, build, test (Node 22 & 24) with Spring PetClinic integration |
| Update Spring PetClinic (.github/workflows/update-spring-petclinic.yml) | Weekly (Monday) / manual | Checks for new PetClinic commits, runs tests, opens a PR |
| Dependabot (.github/dependabot.yml) | Weekly (Monday) | Updates npm dependencies and GitHub Action versions |
Features
✅ Fully Implemented
All features are wired into the LSP server and fully functional:
- Document symbols — hierarchical: classes, methods, fields, constructors, enums, records, interfaces, annotations
- Workspace symbols — search across all indexed Java files
- Code folding — blocks, methods, imports, comments, annotations
- Code formatting — via Prettier Java, with on-type formatting (auto-indent, auto-close braces)
- Selection range — smart expand/shrink via CST hierarchy
- Code lens — reference counts above declarations
- Source generation — generate constructors, getters/setters, toString, equals/hashCode (real TextEdit insertions)
- Diagnostics — parse errors, duplicate declarations, unused imports, missing return statements, unreachable code, unresolved type references, deprecated API warnings (
@Deprecated), unresolved method detection, access control violations, missing@Overridehints - Maven/Gradle parsing — extracts project metadata, dependencies, source directories, Java version
- Javadoc — parses
/** */comments, renders in hover & completion - Type hierarchy — real supertypes/subtypes using symbol table
- File watcher —
workspace/didChangeWatchedFilesto re-index on external changes - Workspace configuration — settings for formatter, Java version, classpath hints
- Import resolution — resolves
importstatements, maps unqualified names throughout the file - Type inference — local variable types, expression types (method calls, field access, literals, operators), type resolver for symbols, members, method return types, field types
- JDK API model — 238 built-in JDK types covering core packages (java.lang, java.util, java.io, java.nio, java.net, java.time, java.sql, java.math, java.text, java.util.concurrent, java.util.stream, java.util.function, java.util.regex, java.security)
- Hover — symbol signatures + JDK type info + Javadoc from source comments + dependency JAR type info via source JARs
- Code completion — scope-aware symbols, JDK types with auto-import, keywords, snippets, overload-aware (each overload shown separately with argument-count ranking)
- Signature help — parameter hints for methods in scope, shows all overloads with best-match selection based on argument count
- Go-to-definition — local + cross-file via workspace index, import-resolved, source JAR navigation for dependency types
- Go-to-implementation — finds subclasses/implementors across workspace
- Go-to-type-definition — navigates to type declaration of a variable
- Find references / Document highlight — finds matching identifiers, cross-file search
- Rename — cross-file rename via workspace index
- Semantic tokens — classifies identifiers via symbol table (20 token types, 10 modifiers), generic type parameter classification
- Code actions — organize imports, extract variable, extract method, extract constant, inline variable, surround with try-catch, add import for JDK types, move class to package, change method signature
- Inlay hints — parameter name hints on method calls,
vartype inference hints (shows inferred type forvardeclarations) - Call hierarchy — incoming/outgoing calls, cross-file via workspace index
- Classpath resolution — automatically runs
mvn dependency:build-classpathor Gradle to resolve all transitive dependencies (including downloads); falls back to scanning~/.m2/repositoryand~/.gradle/cachesif Maven/Gradle CLI is unavailable; detects JDK path and version - Java class file reader — parses
.classfiles from JARs to extract type metadata (classes, methods, fields, access flags) without a JVM - JAR type index — builds a searchable type index from resolved dependency JARs (ZIP parsing, class extraction, type search)
- Annotation processing — Lombok support (
@Data,@Getter,@Setter,@Builder,@Value,@Slf4j,@NoArgsConstructor,@AllArgsConstructor,@RequiredArgsConstructor) and Spring annotations (@Component,@Service,@RestController,@GetMapping, etc.); integrated into symbol table - Document cache — version-aware caching with debounced reparsing for performance
- Multi-root workspace — supports multiple workspace folders, each with its own index; handles
workspace/didChangeWorkspaceFolders - Linked editing ranges — synchronized editing of all occurrences of an identifier
- Generics support — type parameters extracted from classes, interfaces, methods; classified in semantic tokens; bounded type parameter tracking
- Source JAR navigation — go-to-definition into dependency source JARs with virtual URI scheme, Javadoc from source JARs in hover
Roadmap
| Phase | Focus | Status |
|-------|-------|--------|
| 1. Semantic Diagnostics | Parse-level semantic checks | ✅ Done |
| 2. Source Generation | Constructor, getters/setters, toString, equals/hashCode | ✅ Done |
| 3. Enhanced Features | Real code action edits, semantic tokens, cross-file rename | ✅ Done |
| 4. Navigation | Go-to-implementation, type definition, improved cross-file def | ✅ Done |
| 5. Quick Wins | Javadoc extraction, type hierarchy wiring, file watcher, workspace config | ✅ Done |
| 6. Type Resolution | Import resolution, local type inference, expression types, type resolver | ✅ Done |
| 7. Advanced Diagnostics | Deprecated warnings, unresolved methods, access control, missing @Override | ✅ Done |
| 8. Advanced Refactoring | Extract method, extract constant, inline variable | ✅ Done |
| 9. Classpath Resolution | Classpath resolver, class file reader, JDK model, annotation processing | ✅ Done |
| 10. Performance & Polish | Document cache, multi-root workspace, linked editing, document links | ✅ Done |
| 11. Quick Wins | var inlay hints, cross-file call hierarchy | ✅ Done |
| 12. Generics Foundation | Type parameters in symbol table, semantic token classification | ✅ Done |
| 13. Overload Resolution | Overload-aware completion, best-match signature help | ✅ Done |
| 14. Advanced Refactoring | Move class to package, change method signature | ✅ Done |
| 15. Source JAR Navigation | Source JAR extraction/caching, go-to-definition into JARs | ✅ Done |
