
你在网上搜“Java 打成 exe”的时候大概率绕不开 Launch4j。我用它把不少内部工具和交付给业务方的客户端都做过 Windows 可执行文件这东西最大的价值不是把 Java 变成什么原生程序而是让用户“双击就能跑”不用跟对方解释什么叫 JAR、什么叫 JAVA_HOME、什么叫 java -jar。如果你正在发愁JAR 包发给同事对方电脑没装 JDK 或者装了也不会跑项目交付给客户对方就只能看到一堆 .jar 文件不知道怎么启动或者你只是想让自己的小工具看起来像个正经软件有图标、有版本信息、能配置启动内存那这篇内容基本就是给你写的。这篇文章会从实际需求出发把 Launch4j 打包 jar 生成 exe 的完整流程、关键配置、命令行集成、常见坑全部过一遍包含我在真实项目里踩过的坑和验证过的方案。内容面向“想把 Java 程序变成 Windows 双击就能跑”的开发者也适合刚接触这个工具、不知道从哪下手的同学直接参考。1. 为什么要把 jar 打包成 exe1.1 JAR 文件在普通用户手里真的不友好Java 生态里JAR 是标准的交付格式但它有个先天问题它不是一个“用户能直接理解”的文件。你发给一个非技术背景的同事对方双击 JARWindows 大概率弹出“选择打开方式”的对话框或者用解压软件打开看到一堆 class 文件后陷入沉默。就算对方机器装了 JDK任务栏里一闪而过的黑窗口然后什么都没有发生也是经常遇到的情况。这背后的原因很多JAR 的 manifest 文件里没有 Main-Class、程序用了 GUI 依赖但被 javaw 和 java 混用、当前用户没有把 Java 加入 PATH 环境变量等等。你可以教对方配环境变量但一次、两次可以批量交付的时候你不可能跑到每台机器前面去排查。所以从实用主义出发把 JAR 封装成 exe不是技术炫技而是把“运行前提”这件事从用户身上拿掉。用户只需要知道“双击这个程序”至于它内部是不是 Java对用户没有意义。Launch4j 干的正是这件事它用一个原生 Windows 启动器包装你的 JAR用户双击 exe 时启动器负责找到 JRE、按你配置的参数拉起 JVM、加载指定的 JAR。1.2 为什么是 Launch4j而不是 GraalVM、JWrapper 或 exe4j先说结论选 Launch4j不是因为它是功能最全的而是因为它在“轻量”和“可控”之间平衡得最好。我见过很多团队一门心思上 GraalVM想把 Java 程序编译成不依赖 JRE 的原生可执行文件但 GraalVM 的 native-image 对反射、动态代理、序列化这些 Java 常见机制支持得并不完美Spring Boot 项目进去光是解决反射配置就够折腾一阵子而且构建时间动辄几分钟生成的 exe 体积通常在 100MB 以上没有特殊要求的话性价比不高。JWrapper 和 Install4j 这类工具走的是“安装包”路线能做安装向导、开始菜单快捷方式、卸载程序功能确实强大但除非你要做商业级分发否则大部分功能用不上而且 license 收费。Launch4j 是开源免费的生成的 exe 本身就是启动器不把 JVM 塞进去体积只有几百 KB完全够用。对比一下几种常见方案的差异方案产物形态JRE 依赖打包体积复杂度适合场景Launch4j单 exe 启动器 外部 jar需要目标机器有 JRE很小几百 KB 启动器低内部工具、快速交付、Windows 桌面程序分发GraalVM native-image原生 exe不需要 JRE很大100MB高对体积和启动速度有极致要求的场景JWrapper / Install4j安装包可在安装时一起打包 JRE中等偏大中商业软件、多平台安装包Launch4j 最舒服的一点是你现有的 JAR 已经能在java -jar下运行了那扔进 Launch4j 配置一下就能变成 exe不用改任何 Java 代码也不用改变项目结构。对于绝大多数“我希望用户双击就能跑”的场景这是成本最低的路径。2. 动手前要准备的两样东西2.1 你的 JAR 必须是能独立运行的 fat jarLaunch4j 本身不负责下载依赖、不负责解决 ClassNotFound。它只是把 JVM 启动命令封装了一下。所以你提供给它的是什么样的 JAR最终产出的 exe 就是什么样的程序。如果你交给 Launch4j 的是一个普通的、没有包含第三方依赖的瘦 JAR那么即便 exe 成功启动JVM 也会在运行到某个类时直接抛ClassNotFoundException。因此在动手之前你要确认你手上的 JAR 是 fat jar也叫 uber jar里面已经包含了所有运行时依赖并且执行java -jar 你的jar包.jar能正常启动。如果你用的是 Maven最常用的方式是通过spring-boot-maven-plugin的repackage目标来生成可执行 JARbuild plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build执行mvn clean package之后target目录下会生成两个 JAR一个是以.jar结尾的原始 JAR另一个是.jar.original你需要使用的是完整的那个 JAR。如果你不是 Spring Boot 项目那可以用maven-shade-pluginplugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.5.1/version executions execution phasepackage/phase goals goalshade/goal /goals /execution /executions /plugin这里特别提醒一句有些依赖比较冷门比如你在网上看到could not find artifact org.csource:fastdfs-client-java:jar:1.27-snapshot这类报错通常是因为依赖只存在于某个人的私有仓库没有同步到中央仓库。遇到这种情况构建 fat jar 就会失败你先要把缺失的依赖手工mvn install:install-file安装到本地仓库再去打包否则后面一切免谈。2.2 下载 Launch4j 并确认本机 JRE 版本去 Launch4j 官网下载对应系统版本Windows 用户直接下载 Windows 版解压即可无需安装。解压后能看到launch4j.exe这是图形界面还有launch4jc.exe这是命令行工具。Launch4j 本身也是 Java 程序所以你的开发机上需要先有 JRE 或 JDK。如果你的电脑上只装了新版 JDK比如 JDK 17 甚至更高Launch4j 3.x 的界面一般也能跑但要注意生成出来的启动器版本和 JRE 版本匹配关系。建议目标机器使用 64 位 JRE并在配置里指定最低 JRE 版本否则很容易出现“旧机器上找不到合适 JRE”的尴尬情况。还有个细节容易被忽略Launch4j 默认会在启动时优先找系统注册表里的 JRE如果用户机器上同时装 32 位和 64 位 JRE可能导致你明明写好了 JVM 参数却因为加载了错误位数的 JRE 而启动失败。这个问题在后面的配置章节会细说。3. Launch4j 核心配置逐项拆解3.1 基本配置入口、输出文件、图标和程序类型打开 launch4j.exe 后第一步配置在 “Basic” 标签页。主要就四样东西Jar选择你要打包的 fat jar建议用绝对路径别把目录搞混。Outfile生成出来的 exe 路径比如D:\dist\myapp.exe最好和项目目录分开方便后续独立分发。Icon设置 exe 图标注意 Launch4j 只支持.ico格式不支持直接用 png 改后缀。你可以在网上找在线转换工具把 png 转成多尺寸 ico。Header type这个非常关键。如果你打包的是带图形界面的程序比如 Swing 或 JavaFX 应用选GUI这样用户双击后不会弹出黑色控制台窗口如果你打包的是命令行工具选Console这样运行日志能在控制台里打印出来不会一启动就“消失”。我一开始打包 Spring Boot Web 服务时选的 GUI启动后没有窗口但服务本身在后台运行有点“黑盒”的感觉后来发现日志没地方看就改成 Console 模式了。所以这里没有绝对标准取决于你的程序类型和排障习惯。3.2 JRE 设置才是这个工具的灵魂Launch4j 里最容易出错也最值得花时间研究的是 “JRE” 标签页。默认配置极其简陋但不配好你的 exe 换台机器就很容易启动不了。你需要关注的几个选项Min version / Max version指定程序要求的 JRE 版本范围。如果你的程序是用 JDK 8 写的建议Min version填1.8.0如果用了 JDK 17 才有的特性最低版本就该填17甚至更高。Max version一般不用填或者填一个很大的版本号否则用户机器上 JRE 版本太新可能被误判为不兼容。64-bit建议勾选 “Only use 64-bit JDK/JRE” 或者对应地选择“优先 64 位”。现在的机器基本都是 64 位如果启动器加载到一个 32 位的 JRE你的程序大概率因为内存不足或本地库问题启动失败。JDK preference这里有preferJre和preferJdk两个选项。常规运行推荐preferJre因为 JRE 就够用体积小、安装普遍。JVM options这是很多人忽略但特别实用的地方。比如你的程序需要固定初始堆大小可以在JVM options里加-Xms256m -Xmx1024m。如果你的程序涉及读取文件或数据库连接强烈建议加上-Dfile.encodingUTF-8这能避免中文乱码问题后面我会专门说。这些参数最后会被 Launch4j 写进启动器内部用户双击 exe 时启动器会拼出类似javaw -Xms256m -Xmx1024m -Dfile.encodingUTF-8 -jar xxx.jar的命令来运行。3.3 单实例和错误处理体现专业的两个细节进入 “Single instance” 标签页勾选 “Allow only a single instance of the application” 后Launch4j 会检查互斥锁如果程序已经在运行再次双击 exe 就不会再启动一个进程而是把焦点切到已有实例上。这个对于常驻内存的工具、定时器类程序非常重要否则用户多双击几次系统里就多出好几个进程不仅内存爆炸数据还可能被重复处理。进入 “Error handling” 标签页你可以配置程序启动失败时的提示框。比如目标机器没有装 JRE默认弹出的英文报错很容易把非技术用户吓到。你可以把Error title和Error message改成中文比如“程序启动失败请安装 Java 8 或以上版本后重试”。这样至少用户知道发生了什么而不是看到一段乱码一样的英文日志。3.4 中文乱码问题一次配置少踩坑如果你用 Java 读文件、写文件、连数据库或者做桌面程序中文乱码算是最常见的问题之一。原因很简单Windows 默认字符集是 GBK而 Java 默认 charset 从 JDK 8 开始是跟系统区域设置走的。你在本地 IDEA 里跑没问题但用户机器双击 exe 后控制台输出、文件读写可能全变乱码。解决办法就是在 Launch4j 的 JVM options 里加上-Dfile.encodingUTF-8有的程序还需要设置-Duser.languagezh -Duser.countryCN才能让某些框架正确识别中文环境。这个参数不写你在开发机上测试没事一旦换机器就出问题属于典型的“开发环境复现不出来的坑”。在实际项目里我还有一个习惯程序启动时把所有日志写到 exe 同目录下的日志文件里并且在代码里为日志输出单独指定UTF-8编码。这样即使桌面进程没有控制台窗口出问题时也有一份可读的日志文件可以排查不至于两眼一抹黑。4. 命令行构建与 Maven 集成4.1 用 GUI 配置一次然后保存成配置文件Launch4j 的 GUI 可以直接配置并点右侧的齿轮图标生成 exe。但每次都打开图形界面手动选文件、填参数很容易出错尤其是团队里多个人维护项目时版本还容易不一致。我的建议是第一次用 GUI 把所有参数配好点击左上角的 “Save configuration” 保存成一个 XML 文件比如launch4j-config.xml放在项目根目录。之后只需要用命令行工具执行launch4jc.exe launch4j-config.xml就能根据配置文件生成 exe。生成的 XML 大概长这样launch4jConfig headerTypegui/headerType jartarget/myapp.jar/jar outfiledist/myapp.exe/outfile iconsrc/main/resources/app.ico/icon errTitle启动失败/errTitle jre minVersion1.8.0/minVersion maxVersion/maxVersion jdkPreferencepreferJre/jdkPreference opt-Dfile.encodingUTF-8/opt opt-Xmx1024m/opt /jre singleInstance mutexNamemyapp/mutexName /singleInstance /launch4jConfig这套配置文件的好处是它进入了版本管理系统任何一个人 checkout 项目后都能用同样的参数构建。第一次配置 GUI 只相当于一次“初始化”后面你的构建入口就是命令行不再是鼠标点点点。4.2 把 Launch4j 集成进 Maven一键生成 exe如果你用的是 Maven 管理项目那还有更好的方式直接把 Launch4j 插件加进pom.xml。这样执行mvn clean package的时候会在打包完 JAR 之后紧接着自动生成 exe一条命令全部搞定。插件坐标是com.akathist.maven.plugins:launch4j-maven-plugin示例配置plugin groupIdcom.akathist.maven.plugins/groupId artifactIdlaunch4j-maven-plugin/artifactId version2.5.0/version executions execution idbuild-exe/id phasepackage/phase goals goallaunch4j/goal /goals configuration headerTypegui/headerType jar${project.build.directory}/${project.build.finalName}.jar/jar outfile${project.build.directory}/${project.build.finalName}.exe/outfile iconsrc/main/resources/app.ico/icon errTitle启动失败/errTitle jre minVersion1.8.0/minVersion jdkPreferencepreferJre/jdkPreference opts opt-Dfile.encodingUTF-8/opt opt-Xmx1024m/opt /opts /jre singleInstance mutexNamemyapp/mutexName /singleInstance /configuration /execution /executions /plugin这个插件本质上就是帮你调用 Launch4j 的命令行工具但好处在于它和 Maven 生命周期深度绑定你先用spring-boot-maven-plugin或maven-shade-plugin打包出 fat jar再由launch4j-maven-plugin在 package 阶段把它变成 exe整个过程自动完成。执行完mvn clean package后在target目录下会看到.exe文件。这个文件直接拷给别的 Windows 机器就能跑前提是那台机器有对应版本的 JRE这个限制前面聊过了。如果你接入了 CI比如 Jenkins 或 GitHub Actions也可以用同样的命令在流水线里构建省去手动打包的人力。4.3 关于“exe 就是绿色软件”的误区很多第一次接触 Launch4j 的朋友以为生成 exe 后程序就不依赖 Java 了直接把 exe 拷到任何 Windows 机器上双击都能跑。这是个误会。Launch4j 生成的 exe 只是一个“启动器”它本身不包含 JVM也不包含你的 JAR 里的类文件。你需要把 exe 和 JAR 放在一起或者在配置里写清楚 JAR 的绝对路径。如果哪天用户机器连 JRE 都没有启动器会弹出你配置的“启动失败”提示告诉用户去装 Java。如果你的目标是“不装 Java 也能跑”那就得考虑 GraalVM native-image或者用 Inno Setup 之类的工具把 JRE 和 exe 一起打进安装包。这是另一条路线但复杂度会明显上升。Launch4j 能覆盖的恰好是“目标机器上基本都有 JRE 或允许你让用户装一次 JRE”的场景比如企业内部工具、给同事用的快速脚本。5. 常见问题与排查技巧实录5.1 exe 双击后没有反应这是遇到最多的问题。代码在 IDEA 里跑没问题打包成 exe 后双击进程消失了什么窗口都没弹。排查思路按优先级来第一确认 JAR 本身能不能在目标机器上用java -jar跑起来。如果连这个都不行那问题不在 Launch4j而是你的 JAR 有问题或者目标机器的 JRE 版本不对。第二步是检查 Launch4j 的 JRE 最小版本设置。比如你用 JDK 17 编译的 class但minVersion写的是1.8.0启动器找到用户机器上只有 JDK 8加载 class 版本过高程序启动即崩溃而且没有任何提示。你可以把minVersion调到你的 JDK 实际版本比如17。第三查看错误信息。如果你在配置的Error handling里设置了错误标题和消息启动失败时会弹框但如果没设置程序可能直接静默退出。建议开发阶段把 header type 改成Console这样启动时控制台会打印 JVM 的报错堆栈肉眼可见。5.2 路径中包含空格或中文导致找不到文件Windows 上路径含空格是常态比如C:\Program Files\MyApp。Launch4j 本身对路径处理得还行但如果你在 Java 代码里硬编码了相对路径或者用new File(.)获取当前目录那可能拿到的是 JVM 的工作目录不是你 exe 所在目录就会引发找不到配置、找不到日志目录之类的问题。一个通用的解法是在代码里根据“程序所在目录”来定位资源而不是依赖当前工作目录。你可以通过System.getProperty(user.dir)查看当前目录是什么但更可靠的是通过ProtectionDomain获取 JAR 的绝对路径来反推目录String jarPath new File(MyApp.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();然后基于这个jarPath拼接配置和日志路径。配合 Launch4j 的Working directory配置把工作目录设置成 exe 所在目录能省掉很多路径问题。5.3 杀毒软件误报Launch4j 生成的 exe 本质上是一个自有的原生启动器它不是病毒但部分杀毒软件会对“程序加载 JAR 并执行”的行为比较敏感偶尔会报 heuristics 风险或直接隔离。尤其当你用 Launch4j plus 或某些低版本时更容易被误报。这类误报没有 100% 的解法但可以从几个方向缓解使用官网最新版本老版本的特征值可能已经被杀软标记。对 exe 做代码签名。内部分发至少也可以做自签名Windows SmartScreen 的拦截会小一些。如果是商业分发建议上正规的代码签名证书。不要混淆 Launch4j 和所谓的“免杀”思路那是另一类灰色话题正经项目不需要走那条路。5.4 exe 图标不显示或还是默认图标很多人把 png 直接改成 .ico 后缀就扔给 Launch4j结果图标不显示或显示成模糊的默认图标。Launch4j 需要的是真正的 ICO 文件最好包含 16x16、32x32、48x48、256x256 多种尺寸。你可以用在线工具或 ImageMagick 来生成magick input.png -define icon:auto-resize256,128,64,48,32,16 output.ico生成后重新配置图标并保存重新构建 exe。Windows 的资源管理器对图标有缓存替换后如果还是旧图标可以重启资源管理器或把 exe 拷到新目录再看。5.5 启动速度慢或用户机器从未装过 Java如果 exe 在用户机器上能启动但很慢多半是 JRE 启动本身的消耗这个不是 Launch4j 能解决的可以考虑用 CDSClass Data Sharing加速或者转向 GraalVM。如果用户机器真的没有 JRE除了让用户安装 Java 外也可以考虑用 Launch4j 的 bundled JRE 功能把一个精简的 JRE 目录放到 exe 同级的jre子目录下然后在配置里指定bundledJre路径。这样 Launch4j 就会用这个随程序分发的 JRE用户在无 Java 环境下也能跑。代价是整体体积变大但对于交付型项目这是很常见的选择。6. 一些更顺手的实践建议配置文件的版本管理前面提过这里再补充一个组合用法把 Launch4j 的 XML 放在项目根目录然后用 Maven profile 控制不同环境的构建。比如一个 profile 生成 GUI 版一个 profile 生成 Console 版打包参数可以在-Dlaunch4j.configPathxxx.xml里动态指定。这样做的好处是开发、测试、生产构建互不干扰而且每个环境都有一份可追溯的配置。另外如果你是分发给团队内部使用建议在 exe 同级放一个config.properties把数据库连接、端口号、日志级别等运行参数放进配置文件里而不是硬编码在 JAR 内部。因为 JAR 一旦打成 exe 后再改里面的配置就只能重新打包非常不灵活。把可变配置外置运维和业务同事自己就能调整你省心他们也自由。最后关于发布类型的决策我的个人体会是如果你的工具只需要在几台内网机器上用Launch4j 就是最轻的答案别被各种高性能原生打包方案带偏如果你要交付给大量外部客户且对方的电脑状况未知那就再配合 Inno Setup 做一个安装包把 JRE 一起带进去。Launch4j 负责解决“启动器”安装包负责解决“环境分发”两者不冲突。我这个项目的后续版本大概率也不会换打包方案因为团队里没有人愿意跟“要不要装 JDK”这种问题继续纠缠下去。如果你也处在类似的交付场景希望这篇内容能帮你少走几步弯路。