How to compile Java programs to stand-alone executables for Windows, Linux etc.

This blog post explains how to compile Java programs (e.g. a set of .java, .class and .jar files) to stand-alone binary executables which run on Linux, Windows etc. Each executable is platform-specific, but executables can be generated for many different platforms. A JVM or JRE on the target system is not required to run the executable.

The main idea is to use GCJ, The GNU Compiler for Java to generate the executable. For example, do it like this on Ubuntu Lucid, to generate a Linux executable:


$ cat >Hello.java <<'END'
public class Hello {
  public static void main(String args[]) {
    System.out.println("Hello, World!");
$ sudo apt-get install gcj
$ gcj -v
Target: x86_64-linux-gnu
Configured with: ...
Thread model: posix
gcc version 4.4.3 (Ubuntu 4.4.3-1ubuntu4.1) 
$ gcj --main=Hello -g -o hello Hello.java
$ ./hello
Hello, World
$ $ ls -l hello                                                     
-rwxr-x--- 1 user group 13846 2012-06-30 15:20 hello
$ ldd ./hello
        linux-vdso.so.1 =>  (0x00007fff01fff000)
        libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00007fb352663000)
        libgcj.so.10 => /usr/lib/libgcj.so.10 (0x00007fb34f4fc000)
        libm.so.6 => /lib/libm.so.6 (0x00007fb34f278000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x00007fb34f05b000)
        librt.so.1 => /lib/librt.so.1 (0x00007fb34ee53000)
        libz.so.1 => /lib/libz.so.1 (0x00007fb34ec3b000)
        libdl.so.2 => /lib/libdl.so.2 (0x00007fb34ea37000)
        libc.so.6 => /lib/libc.so.6 (0x00007fb34e6b4000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fb35289a000)

If you don't want the executable to depend on libgcj, you can prepend -static-libgcj to the gcj command-line, but that won't work with the stock gcj package on Ubuntu Lucid, because libgcj.a was not included in the package. However, if you compile your own GCC (and enable Java), that will support -static-libgcj .


GCJ also runs on Windows: in Cygwin and MinGW. (You can generate Win32 executables with it.) GCJ 4.4.0 was released as part of MinGW, but GCJ's setup process is mostly undocumented and contains lots of gotchas. You can't just install the latest MinGW, because after GCC 4.4, MinGW doesn't include GCJ in GCC. So, for your convenience, I created a ZIP archive containing GCJ 4.4.0 for Windows (using MinGW) and all its dependencies. Just download http://pts-mini-gpl.googlecode.com/files/wgcj44-r1.zip, extract it, and start running bin/gcj .

It is a working GCJ (GNU Java Compiler) 4.4.0 compiled for Win32. You can use it to compile .java and .class files to Win32 .exe files. The generated .exe is stand-alone, it doesn't need a JDK or JRE, and it can run on any Win32 system. (There are some bugs, restrictions and incompatibilities between e.g. OpenJDK and GNU ClassPath, the Java standard library GCJ 4.4 uses. So your Java programs won't work out-of-the-box, but it's possible to make small porting changes to make them work.)

It has been tested and found working on Windows XP and Wine 1.2 on Ubuntu Lucid. So you don't need a Windows machine in order the be able to release a Windows .exe version of your Java program. Just run the GCJ 4.4.0 above using Wine on Linux (or Mac OS X etc.) to generate the .exe .

The binaries are from MinGW (thus they are free software under the GPL, parts under different free licenses, see MinGW's license). Most of the files were extracted from archives downloaded from this MinGW download page.

Example invocation in debug mode (with line numbers in exception stack traces):

bin\gcj -static-libgcj -static-libgcc --main=Prog -g -o prog.exe P*.java

This will generate a 44 MB binary. It's pretty huge, but it contains a whole JRE and the Java standard library with debug symbols.

Example invocation in optimized mode (without line numbers in exception stack traces):

bin\gcj -static-libgcj -static-libgcc --main=Prog -s -O2 -o prog.exe P*.java

This will generate a 13 MB binary.

If you get ExceptionInInitializerError when trying to use the classes Date, SimpleDateFormat or Calendar, e.g.

Exception in thread "main" java.lang.ExceptionInInitializerError
   at java.lang.Class.initializeClass(t.exe)
   at java.util.Calendar.getInstance(t.exe)
   at t.main(t.exe)
Caused by: java.lang.NullPointerException
   at java.io.InputStreamReader.read(t.exe)
   at java.io.BufferedReader.fill(t.exe)
   at java.io.BufferedReader.readLine(t.exe)
   at java.util.Properties.load(t.exe)
   at java.util.Properties.load(t.exe)
   at java.util.Calendar.(t.exe)
   at java.lang.Class.initializeClass(t.exe)
   ...2 more

, then add libgcj_properties.a like this (without the line break):

bin\gcj -Wl,--whole-archive -lgcj_properties.a -Wl,--no-whole-archive
    -static-libgcj -static-libgcc --main=Prog -s -O2 -o prog.exe P*.java

You can add the bin directory to the PATH. After that you can invoke gcj directly (without the bin\).


vinay_289 said...

Thanks A Lot, it has saved my time as now i can get directly .exe from .java :) :D

rubypdf said...

I recommend you Avian
Avian is a lightweight virtual machine and class library designed to provide a useful subset of Java's features, suitable for building self-contained applications.

QR said...

I'm getting these sorts of errors.
I want to permit these entries.
Can someone adjust the windows version of the compiler here to tolerate these sorts of compiler occurences?

C:\Users\User\Desktop\compiler\bin>gcj -static-libgcj -static-libgcc --main=Prog
-g -o prog.exe ..\..\VoiceEliminator\*.java
..\..\VoiceEliminator\DataJPanel.java:8: warning: The serializable class DataJPa
nel does not declare a static final serialVersionUID field of type long
class DataJPanel extends JPanel
..\..\VoiceEliminator\DataJPanel.java:18: warning: The field DataJPanel.PARENT_J
FRAME is never read locally
private JFrame PARENT_JFRAME;
..\..\VoiceEliminator\DataJPanel.java:77: warning: The local variable leftChanne
lData is never read
byte [] leftChannelData = dualChannel.getLeftChannel();
..\..\VoiceEliminator\DataJPanel.java:79: warning: The local variable rightChann
elData is never read
byte [] rightChannelData = dualChannel.getRightChannel();
..\..\VoiceEliminator\SubComponentListener.java:18: warning: The field SubCompon
entListener.PRIOR_PLACE is never read locally
private Point PRIOR_PLACE = null;
..\..\VoiceEliminator\SubComponentListener.java:24: warning: The field SubCompon
entListener.store is never read locally
private ComponentEvent store = null;
..\..\VoiceEliminator\SubComponentListener.java:37: error: The method componentH
idden(ComponentEvent) of type SubComponentListener must override a superclass me
public void componentHidden(ComponentEvent event)
..\..\VoiceEliminator\SubComponentListener.java:45: error: The method componentM
oved(ComponentEvent) of type SubComponentListener must override a superclass met
public void componentMoved(ComponentEvent event)
..\..\VoiceEliminator\SubComponentListener.java:88: error: The method componentR
esized(ComponentEvent) of type SubComponentListener must override a superclass m
public void componentResized(ComponentEvent event)
..\..\VoiceEliminator\SubComponentListener.java:92: error: The method componentS
hown(ComponentEvent) of type SubComponentListener must override a superclass met
public void componentShown(ComponentEvent event)
..\..\VoiceEliminator\VoiceEliminator.java:106: warning: The local variable sour
ceLine is never read
SourceDataLine sourceLine = null;
11 problems (4 errors, 7 warnings)