
经常有朋友问我你写了个Java小工具能不能直接给它换成一个双击就能打开的图标越是功能落地的东西越没有多少人愿意在命令行里敲java -jar。哪怕你把运行命令写到txt里对方还是会弹出“难道还要装Java”的疑问。我最近刚把一个内部用的报表生成工具从JAR包转成了EXE用的就是Launch4j。整个过程不算复杂但坑不少今天把这次亲测经验完整记录下来包括步骤、参数和常见问题希望能让后来的人少走弯路。1. 为什么要把 JAR 包装成 EXE1.1 “转EXE”转的到底是什么写Java的人应该都有过这种经历代码写完了本地跑得好好的发给同事一用对方回你一句“这是什么文件图标怎么是咖啡杯”其实jar包没什么问题但普通用户和很多跨组协作的同事并不关心Java生态他们只认Windows下双击能打开的.exe。Launch4j在这个大前提下的定位就很清晰它把一个Java程序的jar文件包进一个Windows可执行外壳里让用户就像运行普通绿色软件一样双击一下就好。但这里要敲一下黑板Launch4j不是编译器它不会把你的Java字节码“翻译”成C语言再变成机器码。它生成的那个exe本质上是一个小型启动器内部逻辑是先去搜索本机或指定目录里的Java运行时环境JRE然后把jar文件作为参数传给java.exe。也就是说exe只是一个“带路的人”真正干活的还是JVM和jar里的代码。可以用一个生活里的例子理解exe是瓶盖jar是瓶里的饮料JRE是你喝饮料的嘴。你要让饮料能被喝掉光有瓶盖没用瓶里的饮料也得真存在。1.2 为什么我最终选了 Launch4j看这个标题可能会有人问都2024年了jar转exe不是还有jpackage、GraalVM、exe4j这些方案吗怎么偏偏用了Launch4j我当时的横评结果说一下吧。jpackage是JDK自带的功能很正统但它对项目的模块化要求比较严格非模块化的Spring Boot项目打起来容易出警告而且它对老JDK版本不友好我现在项目基于Java 11它也能用但生成的安装包在部分Win7上兼容性一般。GraalVM Native Image确实能编出原生exe启动极快但反射、动态代理、资源文件这些地方需要各种配置不然运行时崩得莫名其妙适合有专门时间折腾的团队。exe4j是经典商业软件参数丰富也支持自定义启动画面但授权要钱为了一个小工具去申请许可证不太值。Launch4j则是彻底的开源免费方案配置项不多不少GUI界面直观同时提供了命令行工具launch4jc可以很方便接到CI里。我实际用下来从打开软件到生成exe三分钟。这个性价比就是我选它的核心理由。方案优点缺点适合场景Launch4j开源免费轻量配置清晰依赖外部JRE或绑定JRE工具分发给普通用户jpackage官方支持生成安装包规范对模块化有要求老项目兼容一般JDK 14、新项目GraalVM Native Image原生可执行启动快无JRE依赖配置成本高反射坑多追求极致性能的CLI工具exe4j功能全可定制启动画面收费界面老旧预算充足的企业项目2. 动手前的关键一步先准备好一个能跑的 JAR2.1 用 IDEA/Maven 打包 Fat JAR说实话我在第一次用Launch4j时犯过一个错误直接从IDEA的out目录里拿了一个class文件目录想包装成exe结果自然是失败。Launch4j要求你给的是一个完整可运行的JAR包不是散装的class。所以第一步是把Java项目打成Fat JAR也叫uber JAR把所有第三方依赖、配置文件、资源文件都打进去。我用的是Maven的标准套路pom.xml里配置maven-assembly-plugin指定mainClass为入口assembly打包的时候把依赖append进去。下面是项目里pom.xml的片段你可以直接抄plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-assembly-plugin/artifactId version3.3.0/version configuration archive manifest mainClasscom.github.myapp.Main/mainClass /manifest /archive descriptorRefs descriptorRefjar-with-dependencies/descriptorRef /descriptorRefs /configuration executions execution idmake-assembly/id phasepackage/phase goalsgoalsingle/goal/goals /execution /executions /plugin执行mvn clean package后target目录下会多出一个xxx-jar-with-dependencies.jar这个就是Launch4j的原料。注意如果是Spring Boot项目推荐用它的spring-boot-maven-plugin提供的repackage功能其实也能打出可执行fat jar效果一样只是内部结构略有不同。2.2 验证 JAR 能独立运行打包这个动作看似简单但千万别跳过验证。我把我的教训放前面有一次我打包完jar就直接丢给Launch4j了结果生成的exe在别人电脑上闪退日志里翻来覆去只能看到“找不到主类”。后来回头一查是IDEA的Build Artifact把MANIFEST.MF里的Main-Class写成了编译输出的全类名带扩展名导致java -jar根本没法启动。所以在做任何包装之前先老老实实在终端里跑一句java -jar target/xxx-jar-with-dependencies.jar这一步能验证三件事JAR包里的MANIFEST.MF有没有正确声明Main-Class、第三方依赖是否完整、程序初始化是否正常。只有这条命令能稳定启动程序后边的Launch4j才有意义。我到现在还保持着这个习惯不信你可以试试90%的“exe打包失败”问题追到源头都是jar本身就跑不起来。2.3 如果只有 JAR 包没有源码怎么办还有一类场景你不是开发者本人只是拿到了一个同事给的jar想给它快速套个exe外壳。其实Launch4j的GUI里有个“Import jar”的按钮点开后它会自动扫描JAR内的MANIFEST和入口类实在不行你手动在Main class里填上完整类名也能勉强工作。这里不建议一上来就反编译jar包改代码因为启动器只负责调用入口你不需要动它的内部逻辑。不过这也有个前提jar内必须包含所有第三方依赖。如果只是薄jarthin jar依赖都放在外部的lib目录你需要在Launch4j里把“Classpath”也加上或者干脆把它一起打成一个完整目录结构再想办法。这里建议优先用Maven把fat jar打出来省得后续分发时一堆依赖文件找不齐。3. Launch4j 图形界面打包的完整过程3.1 下载、解压与主界面速览Launch4j目前维护得比较活跃去官网release页面下载对应系统的压缩包就行。注意路径千万不要有中文或空格我见很多人习惯解压到“C:\Program Files (x86)”下面结果因为权限和路径空格导致构建异常虽然软件理论上支持但这种小麻烦真没必要。我自己放在D:\dev\launch4j双击里面的launch4j.exe主界面长得很具远古风情但功能分区很清晰。左侧是配置分类的树形菜单点哪一个右侧就显示对应参数下面还有一块日志输出区。别被这么多选项吓到95%的场景只需要配置“Output file”“Jar”“Icon”和“JRE”这四块。其余高级选项比如单实例锁、进程优先级、环境变量等以后有具体需求再动也不迟。3.2 核心配置项逐个说清楚先说必填项Output file就是你要生成的exe路径通常选和你jar同一个目录文件名建议和项目关联起来例如ReportTool.exe。然后Jar选你刚才验证好的fat jar。到这里你点一下右上角Build按钮其实已经能生成一个能跑的exe了。但为了让它在更多环境里正常工作还得接着往下配置。Icon如果有设计好的.ico就选上去没有可以暂时不填。JRE页签是重头戏我推荐这样设置勾选“Use bundled JRE”把路径设为相对路径jre再把“Search registry”也勾上作为兜底。这个“Bundle JRE”的意思是告诉exe启动时先看exe同目录下有没有jre文件夹有就用它没有就再去注册表找系统安装的Java。这个组合既支持了绿色部署又不妨碍开发机上直接运行。最后如果你打包的是纯JDK 8环境Minimum JRE version填1.8.0Maximum可以留空避免版本误伤。3.3 我实际的 Build 过程与运行结果我的实际流程是先编译出class再用Maven打出fat jar然后在Launch4j里依次填好Output file、Jar、Icon在JRE页签设置成“jre registry”。点下构建按钮日志区很快打印出“Generation completed successfully”然后exe文件就躺在指定的目录了。双击那一刻其实有点紧张但窗口刷地一下弹了出来JavaFX界面完全正常。为了排除“恰好电脑里装了JDK”的干扰我后面又专门在一台未安装任何Java环境的Windows 10虚拟机上测试把exe和一个用jlink裁剪好的jre目录一起拷过去照样双击启动。这说明我配置的bundled JRE生效了也证明Launch4j这种“启动器绑定JRE”的部署模型是能实际落地的。整个过程从下载到测试完大概半小时其中还不包括看界面文档。4. 让EXE真正脱离Java环境捆绑JRE4.1 从JDK里裁剪出一个精简JRE既然要让exe在没Java的机器上也能跑那必须把“干活的人”带过去。最粗暴的办法是把开发机上的整个JDK目录拷贝到jre文件夹一个JDK可能三四百MB发出去不太好看。更优雅的方式是用jlink来裁剪运行时镜像。Java 9以后jlink可以按需组装模块生成一个只要几十MB的jre目录。以我的JavaFX应用为例加上日志模块我用的命令是jlink --module-path %JAVA_HOME%\jmods ^ --add-modules java.base,java.desktop,java.logging,java.naming,java.xml ^ --output jre运行后当前目录下会多出一个jre目录里面是精简的Java运行时。到底要哪些模块这个可以靠运行时报错来来回补比如看到ClassNotFoundException: javax.naming就把java.naming加进去。虽然试错有点笨但裁剪出来的体积从几百MB降到了七八十MB传输和共享都舒服很多。4.2 把 JRE 放到项目目录并配置相对路径这个JRE不是随便放哪儿都行的它必须和exe保持Launch4j配置里所指定的相对位置。我的发布目录结构是这样的dist/ReportTool.exedist/ReportTool.jar也可以嵌入exe里dist/jre/...所以在Launch4j的JRE页签里我填的是jre表示相对于exe所在目录去找。千万不要用绝对路径因为你不知道对方会把文件夹拷到D盘还是U盘。如果发布时希望保留lib目录exe放在根目录jar放在lib下那启动器怎么找jar其实Launch4j的Jar路径也支持相对路径填lib/ReportTool.jar即可。总之整个部署包就是一个相对稳定的目录结构拷到哪里都能跑。4.3 关于“单个EXE文件”的几个补充经常有人在论坛问“能不能生成一个exe里面什么都打包好双击就直接运行”。Launch4j有一个“Embed JAR”选项勾上后jar会被合并进exe文件但运行时它依然要在临时目录里把jar解析出来JRE这一层还是逃不掉。所以严格意义上单个exe还是不能打包JRE进去。如果要做到真正“单文件绿色版”常见思路是拿7-Zip和NSIS做自解压包把整个dist目录压缩成一个exe运行时释放到临时目录或用户目录然后运行里面的主exe。我试过一次可行但启动等待时间多了一两秒而且杀毒软件对这种“自解压释放文件”的行为非常敏感容易误报。所以非必要不建议单文件分发不如学常见商业软件一个安装程序装到目标机器。5. 打包后可能踩到的坑问题与排查清单5.1 EXE双击没反应/闪退exe双击没反应是新手遇到最多的坑。首先要判断这是启动器阶段的问题还是Java代码阶段的问题。最简单的方法是打开cmdcd到exe目录然后直接输入ReportTool.exe回车这时所有JVM和启动器的输出都会停在控制台上。如果提示找不到Java说明是启动器没找到JRE如果翻了长长一串Exception in thread main那就要去查你的jar或依赖。我遇到过一次非常隐蔽的情况jlink裁剪模块时少了java.naming导致Java程序里某个工具类在初始化时才加载这个模块程序一启动就静默退出控制台也只有半个堆栈。后来我把--add-modules补上java.naming再裁剪一次问题就好了。所以排查闪退时别只盯exe要会从底层调用链上找线索。5.2 “Failed to find Java Runtime Environment” 的几种解决办法这个报错是Launch4j生态里最经典的问候语。它出现在启动器找遍了所有候选位置都找不到JRE的时候。造成原因通常有三个第一目标机器确实没有安装Java也没有捆绑jre第二exe位数和系统Java位数不匹配64位exe去找32位JRE注册表项对不上第三Java不是通过标准安装程序装的注册表里没有写入信息启动器自然搜索不到。对策也简单优先在JRE页签配置好jre相对路径这是最稳的然后勾选“Search registry”作为兜底如果还不行手动在“Environment variables”里把JAVA_HOME指到一个实际存在的JDK也可以试试。我把这三种方式都验证过最可靠的是“捆绑JRE”。因为哪怕对方电脑装了Java你还得赌他装的是不是匹配的位数和版本赌不如把命运握在自己手里。5.3 32位/64位体系的匹配问题这是个容易被忽略的细节。Launch4j构建exe时可以选择生成32位或64位启动器你需要保持exe、系统Java、项目依赖三者的位数一致。比如项目里用了一个32位的dll那你就必须找32位JRE同时要生成32位exe。反过来如果程序想申请大堆堆内存比如超过1.5GB推荐直接用64位全套。在分发时如果团队环境不统一我一般会同时构建两个exeReportTool_x86.exe和ReportTool_x64.exe它们的配置其实就是同个XML文件改一下outfile然后分别指定对应的JRE目录。这样现场虽然看着多两个文件但用户那边基本不会因为位数问题打不开。这个技巧看着简单实际救过我好几次场。5.4 杀毒软件误报与图标失效Launch4j生成的exe文件本身很小内部逻辑只是寻找JVM并调用这种“加载器”结构在误报率上确实比普通原生的exe高一点点。如果被误报优先级最高的手段是给exe做代码签名。有商业签名更好没有就先做自签名能挡住一部分AV的检测但不能完全替代。另外不要用UPX给启动器加壳加了壳之后误报率直线上升反而得不偿失。图标失效的原因我遇到过两种一是.ico文件本身不规范Launch4j在Windows上有时会勉强显示但对低DPI或高DPI的终端显示模糊二是资源管理器缓存了旧的图标缓存。解决办法是做一个多尺寸的ico并偶尔强制刷新图标缓存。实测有效。5.5 中文字符串乱码问题Java程序中文乱码是跨Windows环境的老问题和Launch4j并不直接相关但在打包成exe后容易暴露因为控制台编码和JVM默认文件编码经常不一致。最稳妥的方式是在Launch4j的JVM options里加一行-Dfile.encodingUTF-8同时在代码入口设置System.setProperty(file.encoding, UTF-8)。对于日志框架还要同时指定日志文件编码。我之前一个中文路径处理模块不加这个参数时在部分用户电脑上读文件名会变成问号启动器进程本身不报错但业务逻辑就不对了。加上后这几个案例全部通过。现在我把这行配置默认写进所有项目的launch4j.xml算是一个标准教训。6. 用命令行和XML配置把打包自动化6.1 将图形界面配置保存为 XML如果只是偶尔打一次包GUI足够。但如果从版本管理、持续集成的角度看把配置保存成launch4j.xml几乎是必须的。GUI左上角有一个保存按钮会将所有配置持久化为XML项目组所有人共用一个配置就不会出现两个人打出来的exe行为不一致的情况。这个XML可以直接放进Git仓库打包时作为构建产物的一部分留存。6.2 一个完整的 launch4j.xml 参考我贴了自己项目中精简后的配置这个文件可以直接被launch4jc命令行工具消费?xml version1.0 encodingUTF-8? launch4jConfig headerTypegui/headerType outfiledist/ReportTool.exe/outfile jartarget/report-tool.jar/jar iconassets/logo.ico/icon singleInstancefalse/singleInstance jre pathjre/path minVersion1.8.0/minVersion maxVersion/maxVersion opt-Dfile.encodingUTF-8/opt /jre splash fileassets/splash.bmp/file /splash /launch4jConfig注意headerType如果是带标准输出日志的工具建议改成console这样在cmd下能直接看到日志正式面向普通用户时再改回gui。配置里的opt对应JVM启动参数GUI里多个参数可用逗号分隔但在XML里按Launch4j文档来写即可。我用这个文件在CI里跑了几个版本没有遇到问题。6.3 在 Jenkins/GitLab CI 里调用 launch4jc自动化的关键是用命令行工具代替GUI。Windows构建机上直接执行launch4jc.exe launch4j.xml如果构建机是Linux/macOSLaunch4j也有跨平台版本只要机器上有Java运行时用launch4jc照样可以构建。我在GitLab CI里专门配了一个Windows runner每当Git Tag以v开头时自动执行mvn clean package和launch4jc再把dist目录里的exe、jre压缩成一个发布包上传到内网Nexus。整个过程不用人盯着发布效率高了不少。这个自动化和“bat转exe”之类的工具有本质区别它直接面对的是Java程序配置粒度更细比如JVM参数、JRE搜索策略、单实例锁都是bat工具不具备的。所以工具选型时要看清自己面对的是什么类型的程序。最后还是要回到最朴素的建议先确认你的JAR能跑再谈如何包装。Launch4j不会替你解决依赖缺失、JRE位数、模块裁剪这些底层问题它只负责在jar和用户之间搭一座桥。但只要你理解了“exe是启动器jar和JRE才是内容”这个核心再在GUI里配一遍命令行里配一遍基本可以应对绝大多数桌面工具分发场景了。我现在处理新项目已经把这套配置当作标准件来用改一下jar路径、图标、构建参数一分钟出一个exe省下来的时间都拿去找产品聊需求了。希望这篇亲测记录能让你少踩几个我踩过的坑。