Kotlin language support for Eclipse JDT Language Server (jdtls) via JDT Core's DerivedSourceSearchParticipant extension point.
This project provides cross-language code intelligence between Java and Kotlin source files: type hierarchy, call hierarchy, find references, hover, and go-to-definition all work bidirectionally across the Java/Kotlin boundary.
The plugin registers a KotlinSearchParticipant for .kt/.kts files via a new org.eclipse.jdt.core.derivedSourceSearchParticipant extension point in JDT Core. When JDT Core's IndexManager discovers Kotlin files in source folders, it routes them to the participant for indexing. The participant parses Kotlin source, emits index entries (type declarations, method references, supertype references, etc.), and JDT's search infrastructure automatically includes Kotlin results in all cross-language queries.
No AspectJ weaving, no light classes, no IntelliJ platform dependency.
The system spans three repositories with changes at two integration boundaries:
JDT Core's indexing pipeline is extended to discover javaDerivedSource files (.kt, .kts) alongside Java files and route them to contributed DerivedSourceSearchParticipant implementations:
- Extension point:
org.eclipse.jdt.core.derivedSourceSearchParticipant— maps file extensions toDerivedSourceSearchParticipantclasses DerivedSourceSearchParticipantRegistry: lazily loads contributed participants, maps each file extension to a singleton participant instance- File discovery:
IndexAllProject,AddFolderToIndex,DeltaProcessor— addisJavaDerivedFileName()checks alongside existingisJavaLikeFileName()checks - Index routing:
IndexManager.addDerivedSource()— routes discovered files to the registered participant viascheduleDocumentIndexing() - Search API:
SearchEngine.getSearchParticipants()— returns[default Java participant, ...contributed participants]
The javaDerivedSource content type (base-type org.eclipse.core.runtime.text, NOT a subtype of javaSource) ensures the Java builder ignores .kt files while the indexer processes them.
Five jdtls handler call sites are updated from:
new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }to:
SearchEngine.getSearchParticipants()This ensures Kotlin index results are included in all search operations:
| Handler | LSP Method |
|---|---|
ReferencesHandler |
textDocument/references |
HoverInfoProvider |
textDocument/hover |
CodeLensHandler |
textDocument/codeLens |
ImplementationCollector |
textDocument/implementation |
WorkspaceSymbolHandler |
workspace/symbol |
WorkspaceSymbolHandler uses searchAllTypeNames() which only queries the default Java participant's indexes. A supplementary search() call through contributed participants finds non-Java types with the same match rules (camelcase, wildcard).
The OSGi bundle registers with JDT Core via plugin.xml:
<!-- Register .kt/.kts under javaDerivedSource content type -->
<extension point="org.eclipse.core.contenttype.contentTypes">
<file-association content-type="org.eclipse.jdt.core.javaDerivedSource"
file-extensions="kt,kts"/>
</extension>
<!-- Register KotlinSearchParticipant for .kt/.kts files -->
<extension point="org.eclipse.jdt.core.derivedSourceSearchParticipant">
<derivedSourceSearchParticipant
id="co.karellen.jdtls.kotlin.derivedSourceSearchParticipant"
class="co.karellen.jdtls.kotlin.search.KotlinSearchParticipant"
fileExtensions="kt,kts"/>
</extension>The participant uses an ANTLR4-based parser (official Kotlin grammar from kotlin-spec, supporting v1.4 through v1.9) and implements:
indexDocument()— parses.ktfiles via ANTLR4, extracts declarations and expression references, emits index entries (TYPE_DECL,SUPER_REF,METHOD_DECL,FIELD_DECL,CONSTRUCTOR_DECL,REF,METHOD_REF,CONSTRUCTOR_REF)locateMatches()— pattern-aware match reporting withIJavaElementresolution: dispatches onTypeDeclarationPattern,SuperTypeReferencePattern,TypeReferencePattern,MethodPattern,FieldPattern; returnsKotlinElementinstances backed byKotlinCompilationUnitwith proper parent chain; includes receiver type verification via scope chain, import resolution, and subtype checkingselectIndexes()— returns index file paths for this participant's entries
The parser pipeline includes a symbol table, scope-walking type resolver, overload resolver, and lambda type propagation for ~80-85% call site coverage without full Kotlin type inference.
.kt file created/modified in workspace
→ JDT Core DeltaProcessor detects change
→ isJavaDerivedFileName() matches .kt
→ DerivedSourceSearchParticipantRegistry looks up participant for "kt"
→ IndexManager.addDerivedSource() schedules indexing
→ KotlinSearchParticipant.indexDocument() parses and emits entries
LSP client requests textDocument/references
→ jdtls ReferencesHandler calls SearchEngine.search()
→ SearchEngine.getSearchParticipants() returns [Java, Kotlin]
→ Index queried for both participants
→ KotlinSearchParticipant.locateMatches() maps hits to .kt source
→ Results merged and returned to client
| Module | Description |
|---|---|
co.karellen.jdtls.kotlin |
OSGi plugin bundle with ANTLR4 parser and KotlinSearchParticipant |
co.karellen.jdtls.kotlin.tests |
Integration tests (200 JUnit 5 tests) |
co.karellen.jdtls.kotlin.coverage |
JaCoCo code coverage aggregation |
co.karellen.jdtls.kotlin.product |
Self-contained product distribution with native launcher |
co.karellen.jdtls.kotlin.target |
Target platform definition |
This project depends on two upstream forks that must be built and installed locally:
-
JDT Core fork — adds
org.eclipse.jdt.core.derivedSourceSearchParticipantextension point and wires the indexing pipeline forjavaDerivedSourcefiles- Repository:
eclipse.jdt.core, branchsearch-participant-extension-point
- Repository:
-
jdtls fork — updates search call sites to use
SearchEngine.getSearchParticipants()instead of hardcoding the default Java participant- Repository:
eclipse.jdt.ls, branchfeature/search-participant-extension-point - PR: #3772
- Repository:
Requires JDK 21+ and Maven 3.9+.
# Step 1: Build and install JDT Core fork
cd path/to/eclipse.jdt.core
mvn clean install -DskipTests -Dtycho.baseline.skip=true
# Step 2: Build and install jdtls fork
cd path/to/eclipse.jdt.ls
mvn clean install -DskipTests
# Step 3: Build karellen-jdtls-kotlin (plugin + tests + product)
cd path/to/karellen-jdtls-kotlin
mvn clean verifyThe root pom.xml sets tycho.localArtifacts=consider, which makes Tycho prefer the locally-installed fork bundles (higher SNAPSHOT versions) over the P2 repository versions.
- Test results: 366 integration tests, all passing — extension point discovery, indexing pipeline, search pipeline, lifecycle, cross-language type discovery (v1.4-v1.9), hover, implementation search, find references, code lens, call hierarchy (incoming/outgoing), receiver type verification, local variable resolution, field references, type aliases, document symbols, workspace symbols, code select
- Distribution archive:
co.karellen.jdtls.kotlin.product/distro/karellen-jdtls-kotlin-<timestamp>.tar.gz(~48MB) - Materialized products: platform-specific directories under
co.karellen.jdtls.kotlin.product/target/products/
The distribution contains ~128 OSGi bundles including jdtls core (with PR #2 changes), JDT Core (with PR #1 extension point), and the Kotlin plugin, plus platform-specific OSGi configuration directories.
# Extract the distribution
tar xzf karellen-jdtls-kotlin-<timestamp>.tar.gz -C /path/to/install
cd /path/to/install
# Run with platform-appropriate config
./jdtls \
-configuration ./config_linux \
-data /path/to/workspace \
--add-modules=ALL-SYSTEM \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMEDPlatform config directories: config_linux, config_linux_arm, config_mac, config_mac_arm, config_win.
cd co.karellen.jdtls.kotlin.product/target/products/karellen-jdtls-kotlin.product/linux/gtk/x86_64/
./jdtls \
-configuration ./configuration \
-data /path/to/workspace \
--add-modules=ALL-SYSTEM \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMEDThe launcher speaks the Language Server Protocol over stdio. Point any LSP client (VS Code, Neovim, Emacs, etc.) at the jdtls binary:
- Extract the distribution archive to a permanent location
- Configure your LSP client to launch
jdtlsas the Java language server, passing:-configuration <path>/config_<platform>— platform-specific OSGi config-data <path>/workspace— jdtls workspace data directory (per-project)--add-modules=ALL-SYSTEMand--add-opensflags for JDK module access
- Open a project containing both
.javaand.ktfiles in source folders - Verify cross-language features:
- Find references on a Java type should include usages from
.ktfiles - Type hierarchy should show Kotlin classes extending Java interfaces
- Hover on a Kotlin type reference should show type information
- Find references on a Java type should include usages from
The jdtls binary reads jdtls.ini for default VM arguments. The product uses org.eclipse.jdt.ls.core.id1 as its OSGi application, which is the standard jdtls language server entry point.
The plugin has a working ANTLR4-based Kotlin parser with an 8-phase pipeline (declaration extraction, symbol table, scope-walking type resolution, overload resolution, lambda propagation, smart cast narrowing, index emission, IJavaElement resolution). All cross-language search features work bidirectionally across the Java/Kotlin boundary: type hierarchy, call hierarchy (incoming and outgoing), find references, hover, go-to-definition, implementation search, code lens, workspace symbols, and document symbols. 366 integration tests pass with 87% instruction / 68% branch coverage.
Key capabilities:
- Receiver type verification filters false positives by resolving receiver expressions to types via scope chain (file, class, function, and local variable scopes), import resolution, and subtype hierarchy checking with JDT delegation for Java types
KotlinElementhierarchy implementsIType,IMethod,IFieldwith full type metadata, parameter types/names, return types, and JDT modifier flag mappingKotlinCompilationUnitprovides a populated model (getTypes(),getChildren(),codeSelect(),getElementAt()) for document symbol and navigation features- Call hierarchy via
locateCallees()for outgoing calls and element-basedMethodPatternsearch for incoming calls
Apache License 2.0. Copyright 2026 Karellen, Inc.