Skip to content

Commit 4ed48f3

Browse files
committed
WIP
Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
1 parent 64cf119 commit 4ed48f3

File tree

11 files changed

+364
-1
lines changed

11 files changed

+364
-1
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "spring-boot",
3+
"description": "Spring Boot Language Server — real-time diagnostics, completions, and code navigation for Spring Boot projects. Operates standalone without requiring JDT Language Server.",
4+
"version": "1.0.0",
5+
"author": {
6+
"name": "Spring Tools"
7+
},
8+
"homepage": "https://spring.io/tools",
9+
"repository": "https://github.com/spring-attic/sts4"
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore the built jar — populate with build.sh
2+
language-server/*.jar
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"spring-boot": {
3+
"command": "java",
4+
"args": [
5+
"-Xmx1024m",
6+
"-Djdk.util.zip.disableZip64ExtraFieldValidation=true",
7+
"-Dspring.config.location=classpath:/application.properties",
8+
"-jar",
9+
"${CLAUDE_PLUGIN_ROOT}/language-server/spring-boot-language-server-standalone-exec.jar"
10+
],
11+
"extensionToLanguage": {
12+
".java": "java",
13+
".properties": "spring-boot-properties",
14+
".yml": "spring-boot-properties-yaml",
15+
".yaml": "spring-boot-properties-yaml",
16+
".xml": "xml",
17+
".factories": "spring-factories"
18+
},
19+
"transport": "stdio",
20+
"startupTimeout": 60000,
21+
"restartOnCrash": true
22+
}
23+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Spring Boot Language Server — Claude Code Plugin
2+
3+
A [Claude Code](https://code.claude.com) plugin that contributes the Spring Boot Language Server, providing real-time diagnostics, completions, and navigation for Spring Boot projects.
4+
5+
Unlike the VS Code extension, this plugin uses the **standalone** variant of the language server which operates **without** JDT Language Server. Project classpath is computed directly via Maven and Gradle tooling; type indexing uses Jandex.
6+
7+
## Requirements
8+
9+
- Java 21 or higher on `PATH`
10+
- Maven or Gradle projects in your workspace
11+
12+
## Build
13+
14+
Run the build script once to compile the language server and install it into this plugin:
15+
16+
```bash
17+
./build.sh
18+
```
19+
20+
This builds `spring-boot-language-server-standalone` from source and copies the resulting fat jar to `language-server/`.
21+
22+
## Usage
23+
24+
### During development / testing
25+
26+
```bash
27+
claude --plugin-dir ./claude-extensions/spring-boot
28+
```
29+
30+
### Install permanently
31+
32+
Follow the [Claude Code plugin installation guide](https://code.claude.com/docs/en/discover-plugins) to install from a local directory or marketplace.
33+
34+
Once installed, the language server starts automatically when you open `.java`, `.properties`, `.yml`, or `.xml` files in a Spring Boot project. No configuration required.
35+
36+
## What the language server provides
37+
38+
- **Diagnostics** — Spring-specific warnings and quick fixes (missing annotations, incorrect bean wiring, etc.)
39+
- **Completions** — Spring Boot properties (`application.properties` / `application.yml`), annotation values, bean references
40+
- **Navigation** — Go to definition for Spring beans, `@Value` expressions, request mappings
41+
- **Inlay hints** — Cron expressions, JPA/JPQL queries, SpEL expressions
42+
43+
## Plugin structure
44+
45+
```
46+
spring-boot/
47+
├── .claude-plugin/
48+
│ └── plugin.json # Plugin manifest
49+
├── .lsp.json # LSP server configuration (uses java directly — cross-platform)
50+
├── language-server/ # Populated by build.sh (gitignored)
51+
│ └── spring-boot-language-server-standalone-exec.jar
52+
├── build.sh # Build script
53+
└── README.md
54+
```
55+
56+
## How it works
57+
58+
The plugin registers a Language Server Protocol server for Java, Spring Boot properties, and XML files. Claude Code launches the server process when any of these file types are opened and communicates over `stdio`.
59+
60+
The standalone LS uses `MavenProjectCache` and `GradleProjectCache` to scan the workspace for `pom.xml` and `build.gradle` files, discovering projects without JDT LS. All type indexing is done locally using [Jandex](https://smallrye.io/jandex/).
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# Builds the standalone Spring Boot Language Server jar and installs it into
3+
# this plugin's language-server/ directory.
4+
#
5+
# Usage: ./build.sh
6+
#
7+
# Requirements:
8+
# - Java 21+
9+
# - Maven wrapper (mvnw) available at the repo root
10+
set -e
11+
12+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13+
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
14+
HEADLESS_SERVICES="${REPO_ROOT}/headless-services"
15+
OUTPUT_DIR="${SCRIPT_DIR}/language-server"
16+
17+
echo "Building spring-boot-language-server-standalone..."
18+
cd "${HEADLESS_SERVICES}"
19+
./mvnw \
20+
-f pom.xml \
21+
-pl spring-boot-language-server-standalone \
22+
-am \
23+
-DskipTests \
24+
clean package
25+
26+
echo "Copying jar to plugin language-server/ directory..."
27+
mkdir -p "${OUTPUT_DIR}"
28+
cp "${HEADLESS_SERVICES}/spring-boot-language-server-standalone/target/"*-standalone-exec.jar \
29+
"${OUTPUT_DIR}/spring-boot-language-server-standalone-exec.jar"
30+
31+
echo "Done. Jar installed at: ${OUTPUT_DIR}/spring-boot-language-server-standalone-exec.jar"

headless-services/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<module>manifest-yaml-language-server</module>
1616
<module>concourse-language-server</module>
1717
<module>spring-boot-language-server</module>
18+
<module>spring-boot-language-server-standalone</module>
1819
<module>bosh-language-server</module>
1920
<module>jdt-ls-extension</module>
2021
<module>xml-ls-extension</module>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<project xmlns="https://maven.apache.org/POM/4.0.0"
2+
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
4+
5+
<modelVersion>4.0.0</modelVersion>
6+
<artifactId>spring-boot-language-server-standalone</artifactId>
7+
<packaging>jar</packaging>
8+
<name>spring-boot-language-server-standalone</name>
9+
<description>Standalone Spring Boot Language Server — operates without JDT Language Server, using Maven/Gradle project caches and Jandex for type indexing.</description>
10+
11+
<parent>
12+
<groupId>org.springframework.ide.vscode</groupId>
13+
<artifactId>commons-parent</artifactId>
14+
<version>2.2.0-SNAPSHOT</version>
15+
<relativePath>../commons/pom.xml</relativePath>
16+
</parent>
17+
18+
<dependencies>
19+
20+
<!-- Main Boot LS — provides BootLanguageServerBootApp, JavaProjectsService, all Spring wiring.
21+
The thin jar (without -exec classifier) is used here as a library. -->
22+
<dependency>
23+
<groupId>org.springframework.ide.vscode</groupId>
24+
<artifactId>spring-boot-language-server</artifactId>
25+
<version>${project.version}</version>
26+
</dependency>
27+
28+
<!-- Legacy Maven project cache — scans pom.xml files to discover projects -->
29+
<dependency>
30+
<groupId>org.springframework.ide.vscode</groupId>
31+
<artifactId>commons-maven</artifactId>
32+
<version>${project.version}</version>
33+
</dependency>
34+
35+
<!-- Legacy Gradle project cache — scans build.gradle files to discover projects -->
36+
<dependency>
37+
<groupId>org.springframework.ide.vscode</groupId>
38+
<artifactId>commons-gradle</artifactId>
39+
<version>${project.version}</version>
40+
</dependency>
41+
42+
</dependencies>
43+
44+
<build>
45+
<plugins>
46+
<!-- Produce a self-contained executable fat jar with classifier standalone-exec -->
47+
<plugin>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-maven-plugin</artifactId>
50+
<configuration>
51+
<layout>ZIP</layout>
52+
<classifier>standalone-exec</classifier>
53+
<mainClass>org.springframework.ide.vscode.boot.app.StandaloneBootApp</mainClass>
54+
</configuration>
55+
<executions>
56+
<execution>
57+
<goals>
58+
<goal>repackage</goal>
59+
</goals>
60+
</execution>
61+
</executions>
62+
</plugin>
63+
</plugins>
64+
</build>
65+
66+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025, 2026 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.app;
12+
13+
import java.net.URI;
14+
import java.util.Arrays;
15+
import java.util.Collection;
16+
import java.util.Optional;
17+
18+
import org.eclipse.lsp4j.TextDocumentIdentifier;
19+
import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService;
20+
import org.springframework.ide.vscode.commons.gradle.GradleCore;
21+
import org.springframework.ide.vscode.commons.gradle.GradleProjectCache;
22+
import org.springframework.ide.vscode.commons.gradle.GradleProjectFinder;
23+
import org.springframework.ide.vscode.commons.java.IJavaProject;
24+
import org.springframework.ide.vscode.commons.java.IJavadocProvider;
25+
import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders;
26+
import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder;
27+
import org.springframework.ide.vscode.commons.languageserver.java.CompositeProjectOvserver;
28+
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
29+
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
30+
import org.springframework.ide.vscode.commons.maven.MavenCore;
31+
import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache;
32+
import org.springframework.ide.vscode.commons.maven.java.MavenProjectFinder;
33+
import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE;
34+
35+
/**
36+
* Implementation of {@link JavaProjectsService} for standalone mode (no JDT Language Server).
37+
* Uses Maven and Gradle project caches to discover projects directly from the workspace,
38+
* and Jandex for type indexing (via {@code LegacyJavaProject}).
39+
*
40+
* <p>This class mirrors the setup in {@code BootLanguageServerHarness.createTestDefault()}.
41+
* Its presence on the classpath causes {@link BootLanguageServerBootApp} to skip creating
42+
* the JDT-LS-backed {@code JavaProjectsService} bean.
43+
*/
44+
public class LegacyJavaProjectsService implements JavaProjectsService {
45+
46+
private final CompositeJavaProjectFinder projectFinder;
47+
private final CompositeProjectOvserver projectObserver;
48+
49+
public LegacyJavaProjectsService(SimpleLanguageServer server) {
50+
MavenProjectCache mavenProjectCache = new MavenProjectCache(server, MavenCore.getDefault(), false, null,
51+
(uri, cpe) -> JavaDocProviders.createFor(cpe));
52+
mavenProjectCache.setAlwaysFireEventOnFileChanged(true);
53+
54+
GradleProjectCache gradleProjectCache = new GradleProjectCache(server, GradleCore.getDefault(), false, null,
55+
(uri, cpe) -> JavaDocProviders.createFor(cpe));
56+
gradleProjectCache.setAlwaysFireEventOnFileChanged(true);
57+
58+
this.projectFinder = new CompositeJavaProjectFinder();
59+
projectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache));
60+
projectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache));
61+
62+
this.projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache));
63+
}
64+
65+
@Override
66+
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
67+
return projectFinder.find(doc);
68+
}
69+
70+
@Override
71+
public Collection<? extends IJavaProject> all() {
72+
return projectFinder.all();
73+
}
74+
75+
@Override
76+
public void addListener(ProjectObserver.Listener listener) {
77+
projectObserver.addListener(listener);
78+
}
79+
80+
@Override
81+
public void removeListener(ProjectObserver.Listener listener) {
82+
projectObserver.removeListener(listener);
83+
}
84+
85+
@Override
86+
public boolean isSupported() {
87+
return projectObserver.isSupported();
88+
}
89+
90+
@Override
91+
public IJavadocProvider javadocProvider(URI projectUri, CPE classpathEntry) {
92+
return JavaDocProviders.createFor(classpathEntry);
93+
}
94+
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.app;
12+
13+
import org.springframework.boot.SpringApplication;
14+
import org.springframework.ide.vscode.commons.languageserver.LanguageServerRunner;
15+
16+
import reactor.core.publisher.Hooks;
17+
18+
/**
19+
* Entry point for the standalone Spring Boot Language Server.
20+
*
21+
* <p>Runs without a JDT Language Server by registering two Spring configuration
22+
* sources: the standard {@link BootLanguageServerBootApp} (all language server beans)
23+
* plus {@link StandaloneProjectServiceConfig} (the Maven/Gradle-backed
24+
* {@code JavaProjectsService}). The latter's presence on the classpath also suppresses
25+
* the JDT-LS-backed bean in {@link BootLanguageServerBootApp} via
26+
* {@code @ConditionalOnMissingClass}.
27+
*/
28+
public class StandaloneBootApp {
29+
30+
public static void main(String[] args) throws Exception {
31+
Hooks.onOperatorDebug();
32+
System.setProperty(LanguageServerRunner.SYSPROP_LANGUAGESERVER_NAME, "boot-language-server"); //makes it easy to recognize language server processes - and set this as early as possible
33+
new SpringApplication(BootLanguageServerBootApp.class, StandaloneProjectServiceConfig.class).run(args);
34+
}
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025, 2026 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.boot.app;
12+
13+
import org.springframework.context.annotation.Bean;
14+
import org.springframework.context.annotation.Configuration;
15+
import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService;
16+
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
17+
18+
/**
19+
* Spring configuration that provides a {@link JavaProjectsService} backed by Maven and
20+
* Gradle project caches, requiring no JDT Language Server.
21+
*
22+
* <p>Registered explicitly as a second Spring source in {@link StandaloneBootApp#main},
23+
* so it is discovered regardless of classpath scanning boundaries. The presence of
24+
* {@link LegacyJavaProjectsService} on the classpath causes
25+
* {@link BootLanguageServerBootApp} to skip its JDT-LS-backed bean via
26+
* {@code @ConditionalOnMissingClass}.
27+
*/
28+
@Configuration(proxyBeanMethods = false)
29+
public class StandaloneProjectServiceConfig {
30+
31+
@Bean
32+
JavaProjectsService javaProjectsService(SimpleLanguageServer server) {
33+
return new LegacyJavaProjectsService(server);
34+
}
35+
36+
}

0 commit comments

Comments
 (0)