How do you create a standalone application with dependencies intact using Maven?

12,147

Solution 1

Try creating a custom assembly descriptor and add a dependencySet and make sure you specify, unpack as false.

Use this as assembly descriptor,

<?xml version="1.0" encoding="UTF-8"?>
<assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
    <id>distribution</id>
    <formats>
        <format>zip</format>
    </formats>
    <dependencySets>
        <dependencySet>
            <scope>runtime</scope>
            <outputDirectory>lib</outputDirectory>
                        <useProjectArtifact>false</useProjectArtifact>
            <unpack>false</unpack>
        </dependencySet>
    </dependencySets>
</assembly>

Store this file to say src/main/assembly/assembly.xml and update your assembly plugin configuration in pom.xml like this.

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.2.1</version>
            <executions>
                <execution>
                    <id>assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>attached</goal>
                    </goals>
                    <configuration>
                           <descriptor>${basedir}/src/main/assembly/assembly.xml</descriptor>
                    </configuration>
                </execution>
            </executions>
        </plugin>

Here is assembly descriptor reference if you need anything more

http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html

Solution 2

You should check out the Maven Appassembler plugin. You can get a lot more robust package using it than rolling your own assembly.

It generates helpful startup scripts for Unix and Windows that allow you to set predefined JAVA VM options, commandline parameters and classpath.

It also has a concept of configuration directory where you can copy default configurations that user can later change. You can also set the configuration directory to be available on the classpath.

Dependencies can be saved in a Maven style "repo" or you can use a flat style "lib" directory.

You still need the assembly plugin for creating a zip or tar archive.

Here's an example appassembler configuration:

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>appassembler-maven-plugin</artifactId>
    <version>1.2.2</version>
    <configuration>
        <programs>
            <program>
                <mainClass>com.mytools.ReportTool</mainClass>
                <name>ReportTool</name>
            </program>
        </programs>
        <assembleDirectory>${project.build.directory}/ReportTool</assembleDirectory>
        <repositoryName>lib</repositoryName>
        <repositoryLayout>flat</repositoryLayout>
    </configuration>
    <executions>
        <execution>
            <id>assembly</id>
            <phase>package</phase>
            <goals>
                <goal>assemble</goal>
            </goals>
        </execution>
    </executions>
</plugin>

To get a zip archive I use the this assembly:

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
    <id>bin</id>
    <formats>
        <format>zip</format>
    </formats>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}/ReportTool</directory>
            <outputDirectory>/</outputDirectory>
            <includes>
                <include>/**</include>
            </includes>
        </fileSet>
    </fileSets>
</assembly>

Solution 3

As far as I know it is possible to redistribute also LGPL libraries into your own packages as long as they are untouched. The maven assembly plugin creates a jar that contains the original jars inside a lib folder of the archive. So far you are fulfilling the LGPL.

Maybe this question gives some more information about this topic.

(Disclaimer: I'm not a lawyer, so please crosscheck this information ;) )

Solution 4

Add the following plugins in pom.xml. Check the value at mainClass,classpathPrefix,addClasspath tags.

<plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>org.apache.camel.spring.Main</mainClass>
                        <classpathPrefix>lib/</classpathPrefix>
                        <addClasspath>true</addClasspath>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.4</version>
            <configuration>
                <descriptors>
                    <descriptor>src/assembly/some-assembly.xml</descriptor>
                </descriptors>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

Create some-assembly.xml under src/assembly as below.

<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>distribution</id>
<formats>
    <format>zip</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
    <fileSet>
        <directory>${project.build.directory}</directory>
        <outputDirectory>/</outputDirectory>
        <includes>
            <include>*.jar</include>
        </includes>
    </fileSet>
</fileSets>
<dependencySets>
    <dependencySet>
        <scope>runtime</scope>
        <outputDirectory>/lib</outputDirectory>
        <useProjectArtifact>false</useProjectArtifact>
        <unpack>false</unpack>
    </dependencySet>
</dependencySets>

Note that useProjectArtifact flag to false, unpack flag to false. If root folder inside zip file is not required,then one can make includeBaseDirectory to false.

This will create name-version-distribution.zip file. Inside zip file, there will be folder name-version. Inside this folder, your executable jar and lib folder containing all dependency jars will be present. Check manifest.MF file of executable jar. It contains both main class and classpath information.

Share:
12,147
Paul Taylor
Author by

Paul Taylor

Runs Albunack providing awesome artist discographies and the Jaikoz and SongKong Music Tagger applications. Main skills:Java and Databases. Also, photographer concentrating on the less obvious in South-West England, photographs can be seen at Secret Dorset Photo

Updated on July 20, 2022

Comments

  • Paul Taylor
    Paul Taylor almost 2 years

    I have a desktop Java application built using Maven 2 (but I could upgrade to Maven 3 if that helps) with a number of open source dependencies. I'm now trying to package it up as a standalone to make it available to end users without them needing to install maven or anything else.

    I've successfully used maven-assembly-plugin to build a single jar containing all dependencies but this is not really what I want because when using LGPL libraries you are meant to redistribute the libraries you are using as separate jars.

    I want Maven to build a zip containing a jar with my code and a MANIFEST.MF that refers to the other jars I need together with the other jars. This seems like standard practice but I cannot see how to do it.

    Here's an extract from my pom

         <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <encoding>UTF-8</encoding>
                        <compilerVersion>1.6</compilerVersion>
                        <source>1.6</source>
                        <target>1.6</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <argLine>-Dfile.encoding=UTF-8</argLine>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>2.4</version>
                    <configuration>
                        <archive>
                            <manifest>
                                <mainClass>com.company.widget.Main</mainClass>
                                <packageName>com.company.widget</packageName>
                                <addClasspath>true</addClasspath>
                            </manifest>
                        </archive>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.3</version>
                    <configuration>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                        <archive>
                            <manifest>
                                <mainClass>com.company.widget.Main</mainClass>
                                <packageName>com.company.widget</packageName>
                                <addClasspath>true</addClasspath>
                            </manifest>
                        </archive>
                    </configuration>
                    <executions>
                        <execution>
                            <id>make-assembly</id>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build> 
    

    EDIT:Taken on Kals idea

    created a file called descriptor.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <assembly
        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
        <id>distribution</id>
        <formats>
            <format>zip</format>
        </formats>
        <dependencySets>
            <dependencySet>
                <scope>runtime</scope>
                <outputDirectory>lib</outputDirectory>
                <unpack>false</unpack>
            </dependencySet>
        </dependencySets>
    </assembly>
    

    and pom contains:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>2.3</version>
        <configuration>
            <descriptors>
                <descriptor>assembly.xml</descriptor>
            </descriptors>
            <archive>
                <manifest>
                    <mainClass>com.company.widget.cmdline.Main</mainClass>
                    <packageName>com.company.widget</packageName>
                    <addClasspath>true</addClasspath>
                </manifest>
            </archive>
        </configuration>
        <executions>
            <execution>
                <id>make-assembly</id>
                <phase>package</phase>
                <goals>
                    <goal>single</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    

    Now maintains the jar and puts them all in the lib folder, including my own code

  • Paul Taylor
    Paul Taylor about 12 years
    Looks good, but sorry where does this go, is this s separate file or does it go in the pom and do i keep the stuff in my pom shown in the original question
  • Kalpak Gadre
    Kalpak Gadre about 12 years
    It will go into a separate file like assembly.xml which is your assembly descriptor. Configure your assembly plugin to reference it.
  • Paul Taylor
    Paul Taylor about 12 years
    Hi, that essentially works except its put my own code in the lib as well, whereas Id only ant the other jars to be in the lib folder, any ideas that would be great but Ive marked your answer correct.
  • Paul Taylor
    Paul Taylor about 12 years
    Thanks, I was using jar-with-dependencies that doesnt do this, but the assembly.xml suggsted does do this
  • Kalpak Gadre
    Kalpak Gadre about 12 years
    You need to include <useProjectArtifact>false</useProjectArtifact> in the assembly descriptor. See updated answer.
  • Paul Taylor
    Paul Taylor about 12 years
    Ok,that removes my code from the lib, but I didnt want my code not to appear at all I just wanted it to appear in the directoy above. I managed this by adding another dependencySet but unfortunately the manifest classpath created doesnt work because it doesnt take into account the lib folder. Having said all that I think I will just leave my code in lib after all so problem is solved
  • Kalpak Gadre
    Kalpak Gadre about 12 years
    Litte off-topic, If you are adding the classpath entries in the manifest in order to put them on the classpath at runtime, you can also look at classworlds. classworlds.codehaus.org/launchusage.html I regularly use it to simplify the start script for the application. Considering you are working on a Desktop application, I think you would be using runable jar directly?
  • Paul Taylor
    Paul Taylor about 12 years
    Essentially, (although on Windows I wrap into an exe using sourceforge.net/projects/winrun4j), I only need the classpth of my jar available in script/commandline, then the classpaths of the supporting libs are referred to in the classpath manifest within my executable jar.