Spring Boot Runner(免插件)— VS Code 扩展
一个在命令行直接运行未打包的 Spring Boot(Maven)项目的 VS Code 扩展:通过 它是
运行器是纯 Node(ESM)实现,因此无需安装 Python —— 只需要 PATH 上有 Maven 和一个 JDK。 源码仓库Gitee:
为什么要做这个Spring Boot Dashboard「运行」应用的方式是:解析出 命令
输出在一个可复用的集成终端里,终端名为 Spring Boot Runner。 运行/调试配置 ——
|
| 字段 | 默认值 | 含义 |
|---|---|---|
module |
"" |
构建模块目录名(Maven 的 pom.xml 或 Gradle 的 build.gradle(.kts),留空自动探测)。 |
mainClass |
"" |
全限定主类名(留空自动探测 @SpringBootApplication;面板里是带候选的下拉框,可直接选)。 |
programArgs |
"" |
程序实参,每行一条;不换行时仍按空格分隔(兼容旧写法)。 |
vmArgs |
"" |
额外的 JVM 参数,每行一条;不换行时仍按空格分隔(兼容旧写法)。 |
extraClasspath |
[] |
额外的 classpath 条目,可以写多个。 JSON 数组(在 launch.json 里一行一条)或单个字符串、用换行/; 分隔。追加在 Maven 解析出的类路径之后,因此只做补充、不打乱既有顺序,可用于补上缺失的 jar 或另一个模块的 target/classes。相对路径按项目根解析,支持 ${workspaceFolder} 等变量。不参与构建缓存 —— 改完下次运行即生效。 |
encodingArgs |
UTF-8 一组 | 控制台编码参数。 默认就是一组 UTF-8(file.encoding、sun.jnu.encoding、sun.stdout.encoding、sun.stderr.encoding、console.encoding)—— 缺省即生效、不写进 launch.json;显式写空数组 [] 表示「不设置」。面板里是「控制台编码方式」下拉框(UTF-8 / GBK / 不设置 / 自定义),选中后启动时会先切换控制台代码页(Windows 上 chcp 65001 / 936)再注入参数。列表格式同 extraClasspath。参数同样追加到 Maven 构建的 MAVEN_OPTS;vmArgs 里手写的同名 -D<key> 始终优先。JDK 19+ 的标准名(stdout.encoding / stderr.encoding)在缺省时自动补上。 |
profiles |
"" |
spring.profiles.active。 |
port |
"" |
覆盖 server.port。 |
workingDir |
"" |
java 进程的工作目录(留空 = 模块目录)。 |
env |
"" |
额外的 KEY=VALUE 环境变量,每行一条;不换行时仍按分号分隔(兼容旧写法)。 |
envFile |
"" |
要加载的 .env 文件路径。 |
javaHome |
"" |
Maven 构建与应用启动共用的 JDK 目录(留空用 PATH 上的 java)。 |
debugPort |
"5005" |
调试时使用的 JDWP 端口。 |
suspend |
true |
调试时挂起 JVM,等待调试器附加。 |
jrebel |
false |
自动探测本机安装的 JRebel 扩展并注入 -agentpath:…/jrebel64.dll。 |
force |
false |
即使没有改动也重新构建(忽略构建缓存)。 |
clean |
false |
构建前先执行 mvn clean。 |
skipTests |
true |
构建时跳过测试。 |
offline |
false |
Maven 离线模式(-o)。 |
额外 classpath(数量不限)
面板里填 额外 classpath 即可,一行一条:
"extraClasspath": [
"${workspaceFolder}/libs/fastjson.jar",
"${workspaceFolder}/other-module/target/classes"
]
每一条都会追加到运行器从 Maven 解析出的类路径(应用 classes + reactor 兄弟模块 + 已解析依赖) 之后。不存在的条目只在终端里给出警告,不会中断运行。
Gradle 项目
构建系统自动判定:根目录(或子目录)有 pom.xml 走 Maven,只有 build.gradle(.kts)
走 Gradle —— 纯 Gradle 项目不会因为 PATH 上没有 mvn 而失败。
- 构建:优先用项目自己的
gradlewwrapper(版本与项目绑定),其次 PATH 上的gradle; 增量编译跑classes(clean→clean classes,force→--rerun-tasks,offline→--offline)。 - 类路径:不改你的 build.gradle —— 运行器临时注入一个 init script,注册
sbrClasspath任务打印sourceSets.main.runtimeClasspath(IDEA 的做法类似,只是它走自己的模型),用完即删。 - 产物目录:Gradle 的 class 落在
build/classes/{java,kotlin,groovy}/main,与 Maven 的target/classes不同;构建缓存与“跳过构建”的判定按对应目录找,所以增量体验一致。 - 编码参数同时进
GRADLE_OPTS(Gradle 的守护进程与构建脚本读它),不只MAVEN_OPTS。
Kotlin / Groovy 与主类下拉
自动探测主类时扫描 .java / .kt / .groovy 三种扩展名,且覆盖
src/main/{java,kotlin,groovy} 三个源码目录 —— Kotlin Spring 项目也能自动找到主类。
面板里的主类输入框带 datalist 下拉:按当前选中的模块过滤候选,既能一键选、也能自由输入 (手输的值会保留在列表里)。主类写错只能到运行期才暴露,下拉把这类错误提前消掉。
运行测试
Spring Boot Runner: 运行测试… 会在选定的配置上跑测试然后直接退出:不组装类路径、
不写构建缓存、不启动 java。过滤串(com.foo.BarTest、com.foo.*Test)可留空跑全部,
输入过一次后会作为下次的默认值。
实现上是 mvn test -Dtest=<过滤> -DfailIfNoTests=false / gradle test --tests <过滤>;
skipTests 在测试模式下自动失效(否则一个测试都不会执行)。
JDK 版本预检
启动前读一次 pom.xml(或 build.gradle)里的 java.version / maven.compiler.release /
sourceCompatibility,并与本次要用的 java 实际版本对比。JVM 低于项目要求时给一条
带操作入口的警告(“设置 JDK…” / “打开运行/调试配置”),而不是等你撞上
“不支持的发行版” / UnsupportedClassVersionError 再回头找原因。
读不到要求版本或测不到实际版本就不提示 —— 宁可不说话,也不误报;警告与构建并行, 不阻塞启动。
控制台编码方式 —— 修复中文乱码
不注入任何参数时,Windows 上 JVM 按 native.encoding(中文系统 = GBK)编码 stdout,
而 VS Code 集成终端按 UTF-8 解码 —— 中文日志必然乱码(鍵鐩兼枟 那种)。
所以本扩展默认就按 UTF-8 处理,分两步:
- 启动时先在终端执行
chcp 65001(GBK 档是chcp 936),把 Windows 控制台的代码页切过去; - 再把一组编码参数注入应用 JVM。参数同时进
MAVEN_OPTS,Maven 自己的输出 (编译警告、依赖下载日志)也能正确解码。
面板里选的就是「控制台编码方式」:
- UTF-8(推荐,默认) —— 代码页
65001+ 一组-D…=UTF-8; - GBK(控制台切到 936) —— 给必须跟系统代码页走的外部终端;
- 不设置 —— 既不切代码页也不注入参数(写进
launch.json的是空数组, 不会被默认值重新填回来); - 自定义 —— 切到逐项勾选:
file.encoding、sun.jnu.encoding、sun.stdout.encoding、sun.stderr.encoding、console.encoding(取当前值,默认 UTF-8)。
JDK 19 起 sun.stdout.encoding / sun.stderr.encoding 改名成了标准属性
stdout.encoding / stderr.encoding(JEP 400),运行器会在缺标准名时自动补一条 ——
JDK 8 ~ 25 一套配置通吃。若 vmArgs 已手写同名 -Dkey,以手写值为准,注入项被跳过
(终端里有日志)。
不写死路径的 JRebel(热部署)
在面板勾选 自动检测 JRebel(或在 launch.json 里写 "jrebel": true),扩展会在运行时
找到 native agent 并把 -agentpath:<绝对路径> 追加到 JVM 参数。这样就不用手写
-agentpath:C:\\Users\\me\\.vscode\\extensions\\jrebelbyperforcesoftware.jrebel-2026.3.2\\agent\\jrebel\\lib\\jrebel64.dll
——那种写法在 JRebel 升级、目录名里的版本号变化的瞬间就悄悄失效(JVM 会以
Invalid -agentpath argument 拒绝启动)。
查找顺序,命中即止:
springBootRunner.jrebelAgentPath设置 —— 显式覆盖。vscode.extensions.all—— 宿主自己的列表,拿到的是真实的extensionPath。- 宿主的
extensions.json清单 ——identifier.id+version+ 位置的权威来源, 即使 JRebel 已安装但在本窗口被禁用也能生效。 - 扩展目录名扫描 —— 最后的兜底。
来源 2 和 3 带版本号,因此装了多个 JRebel 构建时取最新的。目录名扫描不假设任何命名格式:
同一个 JRebel 在 .vscode 里叫 jrebelbyperforcesoftware.jrebel-2026.3.2,
在 CodeBuddy 里叫 jrebelbyperforcesoftware.jrebel-2026.3.2-universal。
注意:
- 选中的文件同时匹配当前平台与 CPU 架构(
jrebel64.dll/libjrebel64.so/libjrebelaarch64.so/libjrebel64.dylib);载入错误的那一个会让 JVM 报 bad-ELF / cannot-load 失败。 - 若
vmArgs里已经有-agentpath:…jrebel…或-Drebel.home=…,注入会被跳过 ——同一个 native agent 加载两次会让 JVM 中止。发生时你会收到提示。 rebel.xml仍由 JRebel 自己的向导生成,这里只负责挂载 agent。- 启动配置时若什么都没找到,会给出带 设置 agent 路径 操作的警告,而不是静默失败。
面板里的运行 / 已停止状态
正在运行的配置名前会显示绿色 ●,并把 ▶ / 🐞 换成红色停止按钮(■),无需悬停始终可见。
行尾还有一个 ⊗ 清空控制台 按钮,一键清掉该配置自己的终端输出。点击停止只会杀掉该配置的
进程树 —— 其它配置继续运行。
点下运行 / 调试的那一刻就进入「运行中」:PID 文件是在构建开始之前写下的,所以构建还没 跑完时按钮已经是停止态,不会出现手快连点两下、同一终端里叠进两个构建的情况;这时点停止 会连带打断构建。
状态来自运行器写下的按配置区分的 PID 文件
(%TEMP%/spring-boot-runner-<project>-<config>.pid)加存活检查,因此应用自己退出或被外部
强杀时,指示灯会自行熄灭。每 2 秒轮询一次 —— 进程死亡不像调试会话那样有可订阅的事件。
底部状态栏里还有一条同名指示:有服务运行时显示 $(debug-alt) 配置名(多条时显示数量)
并铺上橙色底,点击聚焦该配置的集成终端(多条时弹选择框);一个都没跑时整条隐藏,不占位置。
它同样按 PID 文件每 2 秒重算,因此侧边栏收起、或同时跑着好几条配置时也一眼可见。
打开面板时它扫什么、有多慢
配置列表(读 launch.json)是瞬时的,先发;「模块」下拉和「主类」datalist 需要扫描仓库
源码,是这里最慢的一步 —— 700+ 个 java 文件的仓库实测 0.5~1 秒,而旧实现用同步读文件,
还会阻塞扩展宿主(连累其它扩展一起卡),能拖到 4~20 秒。所以:
- 扫描结果缓存:收起再展开侧边栏、或 webview 被 VS Code 回收后重开,都直接复用;
- 只有
pom.xml/build.gradle/settings.gradle出现变化、或切换工作区才重扫; - 扫描本身已改为异步并发读取(每批 16 个文件、在 Buffer 里搜标记),不再阻塞 VS Code;
- 树工具栏的 ⟳ 重新扫描 可随时手动丢掉缓存重扫(改完构建结构、pom 监视没触发时用), 状态行会给出开始 / 完成反馈。
request: "attach" 字段:hostName(默认 localhost)、port(默认 5005)、timeout。
attach 配置既不构建也不启动任何东西 —— 只附加调试器,也不会为本扩展创建额外的会话。
所有字符串字段都接受常见的 launch.json 变量 —— ${workspaceFolder}、
${workspaceFolderBasename}、${userHome}、${env:NAME} —— 无论走 F5(由 VS Code 替换)
还是从运行/调试配置面板启动(由扩展自己替换)。
文件通过 jsonc 编辑,因此其它调试器(java、node、chrome …)的条目和你自己写的注释都会
保留。type 不是 spring-boot-runner 的条目绝不会被碰;无法解析的 launch.json 只会被报告,
不会被覆盖。
早期版本把配置存在
.vscode/spring-boot-runner.json、读平铺的springBootRunner.*设置。 两者都不再读写 ——.vscode/launch.json是唯一真源。
javaHome(JDK 选择)
javaHome 会把 java 解析为 <javaHome>/bin/java,并且为每次 Maven 调用导出
JAVA_HOME=<javaHome>(同时把 <javaHome>/bin 前置到 PATH),于是 mvn install 与启动应用用
的是同一个 JDK —— mvn.cmd 正是通过 JAVA_HOME 选 java 的。当集成终端里的 java 版本不对时
很有用(比如项目要 JDK 21 而 PATH 上是 JDK 17)。
三种设置方式:
- 面板 ——
运行/调试配置…→JDK(JAVA_HOME,留空用 PATH 上的 java)字段。 - 命令 ——
Spring Boot Runner: 设置 JDK(JAVA_HOME)…提示输入路径并存入选定的运行配置。 - launch.json —— 直接写
"javaHome": "C:\\Program Files\\Java\\jdk-21"。
Maven 页签:模块树与依赖图
- 模块树按聚合 / 父子关系嵌套(
<modules>优先,其次<parent>的relativePath, 再不行按坐标匹配):聚合模块默认展开、叶子默认折叠;工具栏的⊞ 展开/⊟ 折叠一键整树展开或压平。 - 每个模块下:生命周期阶段(hover 出现 ▶ 可直接在终端跑)、声明的插件、依赖项
(打开视图后在后台自动跑
mvn dependency:tree解析,无需手动触发;pom 改动后自动重跑)。 - 树由
@vscode-elements/elements的<vscode-tree>渲染:缩进引导线、展开/选中态与 VS Code 原生树一致,展开/折叠走 组件原生的expandAll()/collapseAll()。 - 工具栏的 ⛓ 依赖图 在编辑器区打开一个面板:左边是模块依赖图(节点 = reactor
模块,边 = 模块间依赖,含传递),右边搜索
groupId:artifactId(或关键字)会列出 「这个包是从哪条链被引进来的」,每条链按 A → B → C 画出来,omitted的原因在悬停提示里。 - pom 扫描(
detectMavenWorkspace)与dependency:tree结果都有缓存:按 pom 文件集合 + mtime 判脏,没动 pom 就不会重扫;需要时用⟳手动强制重跑。
活动栏入口
扩展贡献了一个活动栏容器(叶子+播放图标,media/activity-icon.svg),名字叫
Spring Boot Runner。点开是一个带一键链接的视图:运行 / 调试 / 自定义参数 / 停止。
注意:VS Code 把活动栏图标渲染成单色(主题前景色)—— 彩色的 images/icon.png 只会在扩展
面板里显示,永远不会出现在活动栏。顶层 icon 字段与活动栏图标是两套独立机制。
设置
只剩三个安装级设置;一切描述一次运行的内容都在 launch.json 里。可通过设置界面或
.vscode/settings.json 配置:
| 键 | 默认值 | 含义 |
|---|---|---|
springBootRunner.nodePath |
"" |
运行 run-spring-boot.mjs 用的 Node.js 解释器(留空用 PATH 上的 node)。只有集成终端里找不到 node 时才需要填绝对路径。 |
springBootRunner.runnerScript |
null |
run-spring-boot.mjs 的路径(默认用 scripts/ 下随扩展打包的副本)。 |
springBootRunner.jrebelAgentPath |
"" |
JRebel native agent 路径的覆盖。留空即可 —— 自动探测已覆盖常规情况;只有 JRebel 装在搜索够不到的地方才需要填。支持 ${userHome}。 |
停止是怎么工作的
启动时 run-spring-boot.mjs 把自己的 PID 写入
%TEMP%/spring-boot-runner-<project>[-<配置 id>].pid —— 后缀用配置的稳定 id:
ConfigStore 在读到缺少 id 的旧条目时一次性补写进 launch.json,F5 与面板拿到的是同一个值,
因此同名配置、改名、复制都不会互相覆盖 PID 与 @argfile(旧版本按配置名命名的 pid 文件
在停止时会顺带清一次)。
停止 先礼后兵,给应用留出跑 shutdown hook 的机会:
- 分离调试器;
- 向本扩展自己的集成终端发 Ctrl-C —— shell 转给前台的 node + java,JVM 得以执行
@PreDestroy、关闭连接池、落盘日志(这正是直接taskkill /F会跳过的部分), 绝不碰你自己开的终端; - 轮询最多 8 秒;
- 没退:Unix 给整个进程组发 SIGTERM(java 与 node 同组,JVM 仍能优雅退出),
Windows 试一次不带
-F的 taskkill,再等 3 秒; - 还没退才强杀(
taskkill /PID <pid> /T /F/SIGKILL),最后清理 PID 文件。
在 spring-boot-runner 调试会话里按 Stop 按钮也会触发同样的清理,因此 F5 启动的应用也能
从调试工具栏停止。
界面语言 / Language
扩展描述与全部字段提示(命令面板、设置页、IntelliSense)走 VS Code 官方的
package.nls.json(English)/package.nls.zh-cn.json(中文),按显示语言自动切换; Marketplace 也会按访问者的语言显示 displayName 与 description。配置面板与提示消息由
src/i18n.ts的双语表驱动,同样跟随 VS Code 显示语言。本文档单文件内双语:顶部
中文 / English点击切换,两版章节一一对应。Descriptions and every field hint (command palette, settings, IntelliSense) use the official
package.nls.json(English) /package.nls.zh-cn.json(Chinese) and follow the display language; the Marketplace shows displayName / description in the visitor's language too.The configuration panel and host messages are driven by the bilingual table in
src/i18n.ts, also following the VS Code display language.This document carries both languages in one file: use
中文 / Englishat the top to switch; the two sides have matching sections.
构建与安装(自己执行)
cd spring-boot-runner-vscode
npm install # 同时安装 jsonc-parser(用于安全编辑 launch.json)
npm run compile # 产出 dist/extension.js
# 生成可安装的 .vsix:
npx @vscode/vsce package # 需要 @vscode/vsce,会按需安装
然后在 VS Code 里:扩展 → … → 从 VSIX 安装…,选中生成的 .vsix。
本地开发时在该目录按 F5 即可启动「扩展开发宿主」。
发布到 Open VSX
Open VSX(open-vsx.org)是 VSCodium / Cursor / Windsurf 等非微软发行版使用的注册表。
# 令牌放在仓库根的 .ovsx-token(已在 .vscodeignore 里,不进 vsix、不入库)
npx ovsx publish spring-boot-runner-<version>.vsix --pat "$(cat .ovsx-token)"
- 令牌在 https://open-vsx.org/user-settings/tokens 生成(需先登录并 claim
boluoshu命名空间); - 版本号必须递增 —— Open VSX 不允许覆盖已存在的版本;
- 发布后服务端要异步校验与索引,约 1 分钟后
https://open-vsx.org/api/boluoshu/spring-boot-runner才会返回新版本(ovsx当场就打印 Published)。
Spring Boot Runner (plugin-free) — VS Code Extension
🌐 Language: Chinese | English
A VS Code extension that runs an unpackaged Spring Boot (Maven) project straight from the
command line via java -cp, without the spring-boot-maven-plugin and without the official
VS Code Spring Boot plugin.
It is a thin UI over scripts/run-spring-boot.mjs. One core script, one entry point:
- VS Code extension → human clicks a command in the editor.
The runner is pure Node (ESM), so no Python install is required — only a Maven on PATH and a JDK.
Source repository
Gitee: https://gitee.com/boluoshu/spring-boot-runner (branch master)
git clone https://gitee.com/boluoshu/spring-boot-runner.git
Why
The Spring Boot Dashboard "runs" an app by resolving the @SpringBootApplication main class +
classpath, then calling java -cp <classpath> <MainClass>. This extension reproduces that with
plain command-line tools (maven-dependency-plugin instead of the IDE's JDT classpath resolver)
and never needs spring-boot-maven-plugin. The long classpath is written to a per-project
@argfile in the temp dir, so the launched command stays tiny and avoids the Windows
CreateProcess command-line length limit.
Commands
| Command | What it does |
|---|---|
Spring Boot Runner: Run Project |
Run with a run configuration from .vscode/launch.json. Asks which one when several exist; falls back to auto-detect when none does. |
Spring Boot Runner: Run Tests… |
Run tests and exit without launching java: mvn test / gradle test, with an optional filter (e.g. com.foo.*Test) that is remembered. |
Spring Boot Runner: Run with Debug (JDWP) |
Same, but launches with JDWP (suspend per configuration) and auto-attaches VS Code's Java debugger, so breakpoints work immediately. |
Spring Boot Runner: Run with Custom Args |
Prompt for extra args (e.g. --profiles dev --port 8080), then run. Quote paths with spaces; --java-home here overrides the JDK for this run only. |
Spring Boot Runner: Configure Java Home… |
Prompt for a JDK home (validated against bin/java(.exe)) and store it in the chosen run configuration. Empty input clears it. |
Spring Boot Runner: Stop |
Stop a running/debugging app: detaches the debugger, kills the Java process tree via the PID file (taskkill /PID … /T /F), and sends Ctrl-C to the terminal as a fallback. |
Output runs in a reusable integrated terminal named Spring Boot Runner.
Run/debug configurations — .vscode/launch.json
Run/debug configurations live in the project's .vscode/launch.json, under this extension's
own debugger type spring-boot-runner. They are ordinary debug configurations, so the same file
drives three entry points:
- the Run and Debug view (dropdown in the sidebar),
- F5 (run with debugger) and Ctrl+F5 (run without debugger),
- the Run/Debug Configurations… panel contributed by this extension (an IDEA-style form).
Ctrl+Shift+P → Spring Boot Runner: Run/Debug Configurations… opens the panel: a tree of
configurations on the left, the selected configuration's form on the right. There is no settings
file to edit by hand for run parameters — the panel is an editor for launch.json.
{
"version": "0.2.0",
"configurations": [
{
"type": "spring-boot-runner",
"request": "launch",
"name": "App 1",
"module": "backend/bonlink-agent", // empty = auto-detect
"mainClass": "com.bonlink.PlatformApplication", // empty = auto-detect
"profiles": "dev",
"port": "8081",
"vmArgs": "-Xmx1g -Denv=dev",
"env": "SPRING_DATASOURCE_URL=jdbc:mysql://localhost/db",
"javaHome": "D:\\soft\\Java\\jdk-21.0.8+9",
"skipTests": true
},
{
"type": "spring-boot-runner",
"request": "attach", // runs nothing, only attaches
"name": "Remote JVM debug 1",
"hostName": "10.0.0.5",
"port": 5005
}
]
}
request: "launch" fields:
| Field | Default | Meaning |
|---|---|---|
module |
"" |
Build module directory (Maven pom.xml or Gradle build.gradle(.kts); empty = auto-detect). |
mainClass |
"" |
Fully-qualified main class (empty = auto-detect from @SpringBootApplication; the panel offers a dropdown of candidates). |
programArgs |
"" |
Program arguments, one per line; a single line still splits on spaces (legacy form). |
vmArgs |
"" |
Extra JVM arguments, one per line; a single line still splits on spaces (legacy form). |
extraClasspath |
[] |
Extra classpath entries — as many as you like. JSON array (one entry per line in launch.json) or a single string split on newlines/;. Appended after the Maven-resolved classpath, so missing jars / another module's target/classes can be added without disturbing existing order. Relative paths resolve against the project root; ${workspaceFolder} etc. work. Not part of the build cache — changes apply on the next run. |
encodingArgs |
a UTF-8 set | Console encoding args. Defaults to a UTF-8 set (file.encoding, sun.jnu.encoding, sun.stdout.encoding, sun.stderr.encoding, console.encoding) — the default applies without writing anything to launch.json; an explicit empty array [] means "do not set". The panel exposes a console encoding dropdown (UTF-8 / GBK / none / custom) that also switches the Windows console code page (chcp 65001 / 936) before launching. Same list format as extraClasspath. Also appended to the Maven build's MAVEN_OPTS; a hand-written -D<key> in vmArgs always wins. The JDK 19+ standard names (stdout.encoding / stderr.encoding) are filled in automatically when missing. |
profiles |
"" |
spring.profiles.active. |
port |
"" |
Override server.port. |
workingDir |
"" |
Working directory of the java process (empty = the module directory). |
env |
"" |
Extra KEY=VALUE env vars, one per line; a single line still splits on semicolons (legacy form). |
envFile |
"" |
Path to a .env file to load. |
javaHome |
"" |
JDK home for both the Maven build and the launched app (empty = java on PATH). |
debugPort |
"5005" |
JDWP port used when debugging. |
suspend |
true |
When debugging, suspend the JVM until the debugger attaches. |
jrebel |
false |
Auto-detect the locally installed JRebel extension and inject -agentpath:…/jrebel64.dll. |
force |
false |
Rebuild even when nothing changed (ignore the build cache). |
clean |
false |
Run mvn clean before building. |
skipTests |
true |
Skip tests during the build. |
offline |
false |
Maven offline mode (-o). |
Extra classpath (unlimited entries)
Tick nothing special — just fill 额外 classpath in the panel, one entry per line:
"extraClasspath": [
"${workspaceFolder}/libs/fastjson.jar",
"${workspaceFolder}/other-module/target/classes"
]
Each entry is appended to the classpath the runner built from Maven (app classes + reactor siblings + resolved dependencies). Entries that do not exist are reported as a warning in the terminal but never abort the run.
Gradle projects
The build system is detected automatically: a pom.xml (at the root or in a subdirectory) means
Maven, only build.gradle(.kts) means Gradle — a pure Gradle project no longer fails just because
mvn is not on PATH.
- Build: prefers the project's own
gradlewwrapper (version matches the project), then agradleon PATH; incremental compilation runsclasses(clean→clean classes,force→--rerun-tasks,offline→--offline). - Classpath: your build.gradle is never modified — the runner injects a throwaway init script
that registers an
sbrClasspathtask printingsourceSets.main.runtimeClasspath(same idea as what IDEA does with its own model), then deletes it. - Output dirs: Gradle classes live in
build/classes/{java,kotlin,groovy}/main, nottarget/classes; the build cache and skip-build checks look at the matching directory, so incremental behaviour stays the same. - Encoding arguments also go into
GRADLE_OPTS(what the Gradle daemon and build scripts read), not justMAVEN_OPTS.
Kotlin / Groovy and the main-class dropdown
Auto-detection scans .java, .kt and .groovy across src/main/{java,kotlin,groovy} — Kotlin
Spring projects get a detected main class too.
The main-class field is a datalist dropdown: candidates are filtered by the currently selected module, so you can pick with one click or type freely (typed values stay in the list). A wrong FQCN used to surface only at run time; the dropdown moves that failure earlier.
Running tests
Spring Boot Runner: Run Tests… runs the tests on the chosen configuration and exits: no
classpath assembly, no build-cache write, no java launch. The filter (com.foo.BarTest,
com.foo.*Test) may be empty to run everything, and the last value becomes the default next time.
Under the hood it is mvn test -Dtest=<filter> -DfailIfNoTests=false /
gradle test --tests <filter>; skipTests is ignored in test mode (otherwise no test would run
at all).
JDK version preflight
Before launching, the runner reads java.version / maven.compiler.release /
sourceCompatibility from pom.xml (or build.gradle) and compares it with the actual java
about to be used. When the JVM is older than the project requires you get an actionable
warning (Set JDK… / Open Run/Debug Configurations) instead of hitting an unsupported release or
UnsupportedClassVersionError and only then hunting for the cause.
If the required version cannot be read, or the actual one cannot be measured, nothing is shown — no warning beats a wrong one. The check runs in parallel with the build and never blocks the launch.
Console encoding — fixing garbled Chinese logs
With no arguments injected, a JVM on Windows encodes stdout using native.encoding (GBK on a
Chinese system) while the VS Code integrated terminal decodes as UTF-8 — Chinese log lines come
out as 鍵鐩兼枟-style mojibake. So this extension defaults to UTF-8, in two steps:
- On launch the terminal first runs
chcp 65001(chcp 936for the GBK option) to switch the Windows console code page; - then a set of encoding arguments is injected into the app JVM. The same arguments go to
MAVEN_OPTS, so Maven's own output (compile warnings, dependency downloads) decodes correctly too.
The panel dropdown is the console encoding:
- UTF-8 (recommended, default) — code page
65001plus a set of-D…=UTF-8; - GBK (console switched to 936) — for external terminals that must keep the system code page;
- None — neither switches the code page nor injects anything (written to
launch.jsonas an empty array, so the default does not creep back in); - Custom — tick items individually:
file.encoding,sun.jnu.encoding,sun.stdout.encoding,sun.stderr.encoding,console.encoding(all take the current value, UTF-8 by default).
Since JDK 19, sun.stdout.encoding / sun.stderr.encoding were renamed to the standard properties
stdout.encoding / stderr.encoding (JEP 400); the runner adds the standard name automatically
when missing, so one configuration works across JDK 8 ~ 25. If vmArgs already sets the same
-Dkey, your hand-written value wins and the injected one is skipped (logged in the terminal).
JRebel (hot reload) without hard-coding a path
Tick 自动检测 JRebel in the panel (or write "jrebel": true in launch.json) and the
extension finds the native agent at run time and appends -agentpath:<abs path> to the JVM
arguments. This replaces hand-writing
-agentpath:C:\\Users\\me\\.vscode\\extensions\\jrebelbyperforcesoftware.jrebel-2026.3.2\\agent\\jrebel\\lib\\jrebel64.dll
which silently breaks the moment JRebel updates and the version number in the directory name
changes (the JVM then refuses to start with Invalid -agentpath argument).
Search order, first hit wins:
springBootRunner.jrebelAgentPathsetting — explicit override.vscode.extensions.all— the host's own list, so the realextensionPathis used.- The host's
extensions.jsonmanifest — authoritativeidentifier.id+version+ location, and it works even when JRebel is installed but disabled in this window. - Directory-name scan of the extension folders — last-resort fallback.
Sources 2 and 3 carry a version, so when several JRebel builds are installed the newest wins.
The scan never assumes a directory-name shape: the same JRebel is
jrebelbyperforcesoftware.jrebel-2026.3.2 in .vscode but
jrebelbyperforcesoftware.jrebel-2026.3.2-universal in CodeBuddy.
Notes:
- The file picked matches the current platform and CPU arch (
jrebel64.dll/libjrebel64.so/libjrebelaarch64.so/libjrebel64.dylib); loading the wrong one makes the JVM fail with a bad-ELF / cannot-load error. - If your
vmArgsalready contain-agentpath:…jrebel…or-Drebel.home=…, injection is skipped — loading the same native agent twice aborts the JVM. You are told this happened. rebel.xmlis still generated by JRebel's own wizard; this only mounts the agent.- When the run configuration is started and nothing was found, you get a warning with a Set agent path action rather than a silent no-op.
Running / stopped state in the panel
A configuration that is currently running shows a green ● before its name and replaces the
▶ / 🐞 pair with a red stop button (■), always visible without hovering. The row also carries a
⊗ clear-console button that wipes this extension's terminal output. Clicking stop kills only
that configuration's process tree — other configurations keep running.
The state comes from the per-configuration PID file the runner writes
(%TEMP%/spring-boot-runner-<project>-<config>.pid) combined with a liveness check, so the
indicator clears itself when the app exits on its own or is killed externally. It is polled every
2 s because process death, unlike a debug session, emits no event we can subscribe to.
A status-bar entry mirrors that state: while anything is running it shows $(debug-alt) <name>
(or a count) on an orange background and hides completely when nothing is up; clicking it focuses
the matching terminal (a picker appears when several are running).
What the panel scans, and how slow it is
The configuration list (a launch.json read) is instant and is sent first; the module
dropdown and the main class datalist need a source scan, which is the slow part here — measured
at 0.5–1 s for a repo with 700+ java files, while the old synchronous file reads also blocked the
extension host (dragging other extensions down with it) and could take 4–20 s. Therefore:
- the scan result is cached: collapsing and re-opening the sidebar, or a webview recycled by VS Code, reuses it instead of scanning again;
- only a change to
pom.xml/build.gradle/settings.gradle, or a workspace switch, invalidates the cache; - the scan itself now reads files asynchronously in batches of 16 and searches raw Buffers, so it no longer blocks VS Code;
- the ⟳ rescan button in the tree toolbar drops the cache and scans again on demand (useful after restructuring a build when the pom watcher did not fire), reporting start and finish in the status line.
request: "attach" fields: hostName (default localhost), port (default 5005), timeout.
An attach configuration neither builds nor starts anything — only the debugger is attached, and no
extra session is created for this extension.
All string fields accept the usual launch.json variables — ${workspaceFolder},
${workspaceFolderBasename}, ${userHome}, ${env:NAME} — both when started via F5 (VS Code
substitutes them) and when started from the Run/Debug Configurations panel (the extension
substitutes them itself).
The file is edited through jsonc edits, so entries of other debuggers (java, node, chrome,
…) and any comments you wrote are preserved. Entries whose type is not spring-boot-runner are
never touched, and a launch.json that does not parse is reported instead of being overwritten.
Earlier versions stored configurations in
.vscode/spring-boot-runner.jsonand read flatspringBootRunner.*settings. Both are no longer read or written —.vscode/launch.jsonis the single source of truth.
javaHome (JDK selection)
javaHome resolves java as <javaHome>/bin/java and exports JAVA_HOME=<javaHome> (plus
<javaHome>/bin first on PATH) for every Maven invocation, so mvn install compiles with the same
JDK that launches the app — mvn.cmd picks its java via JAVA_HOME. Useful when the integrated
terminal's java is the wrong version (e.g. the project needs JDK 21 but PATH has JDK 17).
Three ways to set it:
- Panel —
Run/Debug Configurations…→ theJDK (JAVA_HOME)field. - Command —
Spring Boot Runner: Configure Java Home…prompts for the path and stores it in a chosen run configuration. - launch.json — write
"javaHome": "C:\\Program Files\\Java\\jdk-21"directly.
Maven view: module tree and dependency graph
- The module tree nests by aggregation / inheritance (
<modules>first, then the<parent>relativePath, then coordinate matching): aggregators start expanded and leaves start collapsed; the toolbar's⊞ Expand/⊟ Collapseopens or flattens the whole tree. - Under each module: lifecycle phases (hover for a ▶ to run in the terminal), declared
plugins, and dependencies (resolved in the background from a real
mvn dependency:treeas soon as the view opens, and re-run automatically after a pom change). - The tree is rendered with the
<vscode-tree>component from@vscode-elements/elements, so indent guides and expand/selection states match the native VS Code tree. - The ⛓ Graph button opens a panel in the editor area: on the left a module dependency
graph (nodes = reactor modules, edges = inter-module dependencies incl. transitive ones), and a
search box that lists how a package is pulled in — each hit is drawn as the A → B → C chain,
with
omittedreasons in the tooltip. - Both the pom scan (
detectMavenWorkspace) and thedependency:treeresults are cached against the set of pom files plus their mtimes, so nothing is re-scanned until a pom actually changes; the⟳buttons force a rebuild.
Activity Bar entry
The extension contributes an Activity Bar container (leaf+play icon, media/activity-icon.svg)
named Spring Boot Runner. Clicking it opens a view with one-click links for
Run / Debug / Custom Args / Stop.
Note: VS Code renders Activity Bar icons monochrome (theme foreground color) — the colored
images/icon.png only shows in the Extensions panel, never in the Activity Bar. The top-level
icon field and the Activity Bar icon are two independent mechanisms.
Settings
Only three installation-level settings remain; everything that describes a run lives in
launch.json. Set via the Settings UI or .vscode/settings.json:
| Key | Default | Meaning |
|---|---|---|
springBootRunner.nodePath |
"" |
Node.js interpreter for run-spring-boot.mjs (empty = node on PATH). Set an absolute path only if node is not resolvable from the integrated terminal. |
springBootRunner.runnerScript |
null |
Path to run-spring-boot.mjs (defaults to the bundled copy under scripts/). |
springBootRunner.jrebelAgentPath |
"" |
Override for the JRebel native agent path. Leave empty — auto-detection covers the normal cases; use this only when JRebel lives somewhere the search cannot reach. Supports ${userHome}. |
How Stop works
On start, run-spring-boot.mjs writes its PID to
%TEMP%/spring-boot-runner-<project>[-<config id>].pid — the suffix is the configuration's
stable id: ConfigStore backfills it into launch.json once for older entries that lack one,
and F5 plus the panel resolve the same value, so same-named configurations, renames and
duplicates never overwrite each other's PID file or @argfile (pid files named after the
configulation name by older versions are cleaned up as well).
Stop asks before it kills, giving the app room to run its shutdown hooks:
- detach the debugger;
- send Ctrl-C to this extension's own integrated terminal — the shell forwards it to the
foreground node + java, so the JVM can run
@PreDestroy, close connection pools and flush logs (exactly what a baretaskkill /Fskips). Terminals you opened yourself are never touched; - poll for up to 8 s;
- still alive: SIGTERM to the whole process group on Unix (java shares it with node, so the JVM
still exits cleanly), or
taskkillwithout-Fon Windows, then wait 3 more seconds; - only then force-kill (
taskkill /PID <pid> /T /F/SIGKILL) and remove the PID file.
The same cleanup runs when you press the Stop button of a spring-boot-runner debug session,
so F5-started apps are stopped from the debug toolbar too.
Language
Descriptions and field hints (command palette, settings, IntelliSense) use the official
package.nls.json(English) /package.nls.zh-cn.json(Chinese) and follow the display language; the Marketplace shows displayName / description in the visitor's language as well.The configuration panel and host messages are driven by the bilingual table in
src/i18n.ts, following the VS Code display language.This document carries both languages in one file: use
中文 / Englishat the top to switch; the two sides have matching sections.扩展描述与字段提示走
package.nls.*双份,按显示语言自动切换;Marketplace 按访问者语言 显示 displayName 与 description。配置面板与提示消息由
src/i18n.ts双语表驱动,同样跟随 VS Code 显示语言。本文档单文件双语,顶部
中文 / English点击切换,章节一一对应。
Build & install (run these yourself)
cd spring-boot-runner-vscode
npm install # also installs jsonc-parser (used to edit launch.json safely)
npm run compile # emits dist/extension.js
# To produce a .vsix you can install in VS Code:
npx @vscode/vsce package # needs @vscode/vsce; installs on demand
Then in VS Code: Extensions → … → Install from VSIX…, pick the generated .vsix.
For local dev, press F5 in this folder to launch an Extension Development Host.
Publishing to Open VSX
Open VSX (open-vsx.org) is the registry used by non-Microsoft distributions such as VSCodium, Cursor and Windsurf.
# the token lives in .ovsx-token at the repo root (listed in .vscodeignore: never packaged, never committed)
npx ovsx publish spring-boot-runner-<version>.vsix --pat "$(cat .ovsx-token)"
- Create the token at https://open-vsx.org/user-settings/tokens (sign in and claim the
boluoshunamespace first); - the version must increase — Open VSX refuses to overwrite an existing version;
- after publishing, the server validates and indexes asynchronously; expect about a minute before
https://open-vsx.org/api/boluoshu/spring-boot-runnerreports the new version (ovsxitself prints “Published” right away).