☰
用Launch4j将Java程序打包成exe:从JAR到双击运行
2026/10/25 1:51:27 网站建设 项目流程

你在网上搜“Java 打成 exe”的时候,大概率绕不开 Launch4j。我用它把不少内部工具和交付给业务方的客户端都做过 Windows 可执行文件,这东西最大的价值不是把 Java 变成什么原生程序,而是让用户“双击就能跑”,不用跟对方解释什么叫 JAR、什么叫 JAVA_HOME、什么叫 java -jar。

如果你正在发愁:JAR 包发给同事,对方电脑没装 JDK 或者装了也不会跑;项目交付给客户,对方就只能看到一堆 .jar 文件,不知道怎么启动;或者你只是想让自己的小工具看起来像个正经软件,有图标、有版本信息、能配置启动内存,那这篇内容基本就是给你写的。

这篇文章会从实际需求出发,把 Launch4j 打包 jar 生成 exe 的完整流程、关键配置、命令行集成、常见坑全部过一遍,包含我在真实项目里踩过的坑和验证过的方案。内容面向“想把 Java 程序变成 Windows 双击就能跑”的开发者,也适合刚接触这个工具、不知道从哪下手的同学直接参考。

1. 为什么要把 jar 打包成 exe

1.1 JAR 文件在普通用户手里,真的不友好

Java 生态里,JAR 是标准的交付格式,但它有个先天问题:它不是一个“用户能直接理解”的文件。你发给一个非技术背景的同事,对方双击 JAR,Windows 大概率弹出“选择打开方式”的对话框,或者用解压软件打开,看到一堆 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 jar

Launch4j 本身不负责下载依赖、不负责解决 ClassNotFound。它只是把 JVM 启动命令封装了一下。所以,你提供给它的是什么样的 JAR,最终产出的 exe 就是什么样的程序。如果你交给 Launch4j 的是一个普通的、没有包含第三方依赖的瘦 JAR,那么即便 exe 成功启动,JVM 也会在运行到某个类时直接抛ClassNotFoundException。

因此,在动手之前你要确认:你手上的 JAR 是 fat jar(也叫 uber jar),里面已经包含了所有运行时依赖,并且执行java -jar 你的jar包.jar能正常启动。

如果你用的是 Maven,最常用的方式是通过spring-boot-maven-plugin的repackage目标来生成可执行 JAR:

<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>

执行mvn clean package之后,target目录下会生成两个 JAR:一个是以.jar结尾的原始 JAR,另一个是.jar.original,你需要使用的是完整的那个 JAR。

如果你不是 Spring Boot 项目,那可以用maven-shade-plugin:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</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.encoding=UTF-8,这能避免中文乱码问题,后面我会专门说。

这些参数最后会被 Launch4j 写进启动器内部,用户双击 exe 时,启动器会拼出类似javaw -Xms256m -Xmx1024m -Dfile.encoding=UTF-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.encoding=UTF-8

有的程序还需要设置-Duser.language=zh -Duser.country=CN,才能让某些框架正确识别中文环境。这个参数不写,你在开发机上测试没事,一旦换机器就出问题,属于典型的“开发环境复现不出来的坑”。

在实际项目里我还有一个习惯:程序启动时把所有日志写到 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> <headerType>gui</headerType> <jar>target/myapp.jar</jar> <outfile>dist/myapp.exe</outfile> <icon>src/main/resources/app.ico</icon> <errTitle>启动失败</errTitle> <jre> <minVersion>1.8.0</minVersion> <maxVersion></maxVersion> <jdkPreference>preferJre</jdkPreference> <opt>-Dfile.encoding=UTF-8</opt> <opt>-Xmx1024m</opt> </jre> <singleInstance> <mutexName>myapp</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> <groupId>com.akathist.maven.plugins</groupId> <artifactId>launch4j-maven-plugin</artifactId> <version>2.5.0</version> <executions> <execution> <id>build-exe</id> <phase>package</phase> <goals> <goal>launch4j</goal> </goals> <configuration> <headerType>gui</headerType> <jar>${project.build.directory}/${project.build.finalName}.jar</jar> <outfile>${project.build.directory}/${project.build.finalName}.exe</outfile> <icon>src/main/resources/app.ico</icon> <errTitle>启动失败</errTitle> <jre> <minVersion>1.8.0</minVersion> <jdkPreference>preferJre</jdkPreference> <opts> <opt>-Dfile.encoding=UTF-8</opt> <opt>-Xmx1024m</opt> </opts> </jre> <singleInstance> <mutexName>myapp</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-resize=256,128,64,48,32,16 output.ico

生成后重新配置图标并保存,重新构建 exe。Windows 的资源管理器对图标有缓存,替换后如果还是旧图标,可以重启资源管理器或把 exe 拷到新目录再看。

5.5 启动速度慢或用户机器从未装过 Java

如果 exe 在用户机器上能启动,但很慢,多半是 JRE 启动本身的消耗,这个不是 Launch4j 能解决的,可以考虑用 CDS(Class 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.configPath=xxx.xml里动态指定。这样做的好处是,开发、测试、生产构建互不干扰,而且每个环境都有一份可追溯的配置。

另外,如果你是分发给团队内部使用,建议在 exe 同级放一个config.properties,把数据库连接、端口号、日志级别等运行参数放进配置文件里,而不是硬编码在 JAR 内部。因为 JAR 一旦打成 exe 后,再改里面的配置就只能重新打包,非常不灵活。把可变配置外置,运维和业务同事自己就能调整,你省心,他们也自由。

最后关于发布类型的决策,我的个人体会是:如果你的工具只需要在几台内网机器上用,Launch4j 就是最轻的答案,别被各种高性能原生打包方案带偏;如果你要交付给大量外部客户,且对方的电脑状况未知,那就再配合 Inno Setup 做一个安装包,把 JRE 一起带进去。Launch4j 负责解决“启动器”,安装包负责解决“环境分发”,两者不冲突。

我这个项目的后续版本大概率也不会换打包方案,因为团队里没有人愿意跟“要不要装 JDK”这种问题继续纠缠下去。如果你也处在类似的交付场景,希望这篇内容能帮你少走几步弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询