2012-06-30

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:

Linux

$ cat >Hello.java <<'END'
public class Hello {
  public static void main(String args[]) {
    System.out.println("Hello, World!");
  }
}
END
$ 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 .

Windows

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\).

3 comments:

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

    ReplyDelete
  2. 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.


    ReplyDelete
  3. 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
    thod
    public void componentHidden(ComponentEvent event)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ..\..\VoiceEliminator\SubComponentListener.java:45: error: The method componentM
    oved(ComponentEvent) of type SubComponentListener must override a superclass met
    hod
    public void componentMoved(ComponentEvent event)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ..\..\VoiceEliminator\SubComponentListener.java:88: error: The method componentR
    esized(ComponentEvent) of type SubComponentListener must override a superclass m
    ethod
    public void componentResized(ComponentEvent event)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ..\..\VoiceEliminator\SubComponentListener.java:92: error: The method componentS
    hown(ComponentEvent) of type SubComponentListener must override a superclass met
    hod
    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)

    C:\Users\User\Desktop\compiler\bin>

    ReplyDelete