Create a Windows EXE Launcher

While packaging, you can have Protector4J build a native Windows launcher for the protected application. It is a plain Win32 executable that runs on a double-click, so your users never need to install Java or go anywhere near run.bat.

The EXE is optional and is not built unless you ask for it. When it is enabled, the package still contains the usual run.sh, run.command, and run.bat. Both ways of starting use exactly the same archive layout, class path, main class, and JVM options.

1. Where it applies

Application typesJava Application, Spring Boot (all three layouts), Tomcat
Target platformswindows-x64, windows-x86, windows-aarch64
Not supportedLibrary Encryption — a single .p4jx archive is not a runnable application directory

You must select at least one Windows target platform. Selecting Linux and macOS targets as well is fine: their packages are still produced, without an EXE, and start through the usual script. If you select no Windows target at all, the task fails immediately.

2. Using the GUI

  1. On the input and target platform page, tick Generate Windows application EXE (x64/x86/ARM64) and select at least one Windows target platform.
  2. The checkbox sits before the split between simple and advanced mode, so either mode can produce an EXE.
  3. Ticking it adds a Windows EXE launcher page where you enter the file name, launcher mode, icon, and Windows version information. Leave it unticked and the wizard goes straight to the final review page.
  4. Once the EXE is enabled, the JVM startup options field appears only on that launcher page, so the same option can never be entered in two places. Whatever you enter goes into both the EXE and the startup scripts.
  5. The final review page lists the platforms that will actually get an EXE, together with the launcher fields you filled in.

3. CLI examples

The shortest form needs only --windows-exe:

p4j --target-platform windows-x64 javaapp app.jar dist --windows-exe

Setting the file name and window mode:

p4j --target-platform windows-x64 javaapp app.jar dist \
  --windows-exe \
  --exe-name MyApp.exe \
  --exe-mode gui \
  --exe-icon assets/app.ico

Building all three Windows architectures at once with complete version resources:

p4j --target-platform windows-x64,windows-x86,windows-aarch64 \
  springboot app.jar dist \
  --windows-exe \
  --exe-name MyService \
  --exe-file-version 1.4.2.0 \
  --exe-product-version 1.4.2.0 \
  --exe-company 'Example Inc.' \
  --exe-product 'Example Service' \
  --exe-description 'Example background service' \
  --exe-copyright 'Copyright (C) 2026 Example Inc.'

Tomcat packages work too; the resulting EXE starts the embedded Tomcat in the foreground:

p4j --target-platform windows-x64 tomcat app.war dist --context /demo --windows-exe

4. Options

OptionDescription
--windows-exeBuild the EXE
--exe-name <name>EXE file name; defaults to the input file name, or tomcat for Tomcat packages
--exe-mode <mode>console (default) or gui
--exe-icon <ico>Optional Windows icon, in .ico format
--exe-file-version <a.b.c.d>PE file version
--exe-product-version <a.b.c.d>PE product version
--exe-company <text>Company name
--exe-product <text>Product name
--exe-description <text>File description, shown in Task Manager and the file properties dialog
--exe-copyright <text>Copyright notice

Any --exe-* option turns on EXE generation by itself, so you do not have to add --windows-exe as well.

The name must be a file name only, with no /, \, or directory component. If you leave off the .exe suffix, it is added for you.

Both version fields follow the same rule: one to four numbers separated by ASCII periods, each between 0 and 65535, such as 1.0.0.1. Windows stores each part as a 16-bit unsigned integer, so values with letters or spaces such as v1.0 or 1.0-beta are rejected. An empty field means 0.0.0.0. Both fields are validated before packaging starts, so mistakes surface immediately rather than partway through.

5. Output structure

Using a Java Application as the example, the EXE sits alongside the startup scripts at the root of the package:

dist/
├── MyApp.exe             # the new Windows launcher
├── app.p4jx
├── vlxjre/
├── lib/
├── run.sh
├── run.command
├── run.bat
└── README.md

The README.md inside the package lists the EXE files that were generated, along with notes on code signing.

6. How the launcher behaves

The directory holding the EXE is the root of the application. The launcher accepts only runtime, archive, class path, and working directory paths inside that root; anything outside the package, and anything redirected through a symbolic link or junction, is rejected and the process does not start. You can therefore move or rename the whole package freely, but you cannot copy the EXE out on its own.

AspectBehavior
RuntimeAlways uses vlxjre\bin\java.exe from inside the package. It never uses the system Java and never reads JAVA_HOME.
Command-line argumentsArguments passed to the EXE are appended to the application's own arguments.
Console modeInherits the current console's standard input and output, waits for the application to finish, and returns its exit code.
GUI modeCreates no console window, which suits desktop applications.
Environment variablesClears JAVA_TOOL_OPTIONS, _JAVA_OPTIONS, JDK_JAVA_OPTIONS, and CLASSPATH before starting, so nothing outside the package can inject startup options.

The JVM options of the EXE are fixed at packaging time, signed together with the option block, and verified at run time; they cannot be changed after deployment. APP_JAVA_OPTS affects run.bat only — the EXE ignores it. To change the JVM options of the EXE, package again.

For the same reason, -javaagent, -agentlib, -agentpath, -Xbootclasspath, and --patch-module cannot be written into the EXE's startup options. Passing any of them through --jvm-option fails the packaging run right away.

7. Code signing

Protector4J neither signs the generated EXE nor touches any Authenticode credentials. All PE modifications — the icon, version resources, and startup options — happen during packaging, so signing has to come last.

  1. Finish packaging and confirm the EXE starts the application correctly.
  2. Apply an Authenticode signature with your own certificate, including an RFC3161 timestamp.
  3. Verify the signature under the Windows /pa policy.

Do not modify the PE file after signing — any change invalidates the signature. To change the icon or version number, package again and re-sign.

8. Task file fields

The exported p4j-task.yml uses format version 2 and stores the following fields when the EXE is enabled. Version 1 task files can still be read.

windowsExe: true
exeName: MyApp.exe
exeMode: console
exeIcon: assets/app.ico
exeFileVersion: 1.4.2.0
exeProductVersion: 1.4.2.0
exeCompany: Example Inc.
exeProduct: Example Service
exeDescription: Example background service
exeCopyright: Copyright (C) 2026 Example Inc.

9. Troubleshooting

SymptomCause and fix
"A Windows target platform is required"The EXE is enabled but no windows-x64, windows-x86, or windows-aarch64 target was selected.
"Invalid version number format"A version field contains letters or spaces, has more than four parts, or has a part outside 0–65535.
"The EXE name must be a file name only"The name contains a path separator. Use a bare file name.
"Icon file not found"--exe-icon points at a .ico that does not exist. Check the path.
Double-clicking shows no window and the application exits at onceThe application is failing to start in GUI mode. Package again in console mode to see the error output.
An EXE copied elsewhere will not runThe launcher needs the runtime and archive in the same package directory. Copy the whole output directory.

For the complete option list, see the CLI Reference; for the GUI wizard, see the GUI Guide.