63

I have runtime dependencies on some external jars that I would like to "rejar" into a single jar. These external dependencies are stored in an external_jars directory, and I'd like to be able to not have to list them all (i.e., not to need to change my build scripts if my dependencies change). Any thoughts?

Google gave me a good answer on how to do this - if you don't mind listing out each jar as a dependency:

http://markmail.org/message/zijbwm46maxzzoo5

Roughly, I want something along the lines of the following, which would combine all jars in the lib directory into out.jar (with some sane overwrite rules).

jar -combine -out out.jar -in lib/*.jar
martin clayton
  • 76,436
  • 32
  • 213
  • 198
Jacob
  • 1,671
  • 4
  • 23
  • 26

11 Answers11

68

Vladimir's answer is a correct one, but I feel that what he suggests implies repacking all jars in a one big out.jar, which is then feeded to Ant Jar task as a single <zipfileset> or something like that. This two-step approach is unnecessary. I'm not sure whether this is connected with Ant version, but I have Ant 1.7.1, and its <jar> task understands <zipgroupfileset>, which allows to feed all contents of third party jars' directly.

<jar destfile="MyApplication.jar">
  <zipgroupfileset dir="lib" includes="*.jar" /> 
  <!-- other options -->
  <manifest>
    <attribute name="Main-Class" value="Main.MainClass" />
  </manifest>
</jar>
nightingale
  • 917
  • 9
  • 7
58

Just use zipgroupfileset with the Ant Zip task

<zip destfile="out.jar">
    <zipgroupfileset dir="lib" includes="*.jar"/>
</zip>

This will flatten all included jar libraries' content.

martin clayton
  • 76,436
  • 32
  • 213
  • 198
Vladimir
  • 6,853
  • 2
  • 26
  • 25
  • Sorry to hijack someone else's question. I did this but now I can get my application to run. It can't find some of the classes. Do I still need to sepcifiy the classpath like java -cp xxx -jar yyy.jar? – uriDium Jul 26 '10 at 14:23
  • 9
    It makes me sad that all the working answers seem to revolve around writing an ant build file and none of them involve a simple command-line invocation. – Mutant Bob Jul 06 '12 at 15:38
  • 2
    If you do it in ant, then you can include it in your build script as a target, which is probably the only time that you want to to this. – Charles Munger Sep 25 '12 at 19:04
  • Bear in mind that this usually works.. unless you're using Spring. Spring puts some files in META-INF that need to be merged. See http://stackoverflow.com/questions/23574979/creating-an-uber-jar-with-spring-dependencies for more information – ticktock Dec 04 '14 at 22:21
23

You could check out jarjar:

http://code.google.com/p/jarjar/

Staale
  • 27,254
  • 23
  • 66
  • 85
  • three minutes to perfect answer = <3 stackoverflow – Jacob Feb 05 '09 at 11:14
  • 2
    doh! I looked into this. It has a lot of advanced functionality that it turns out I don't need. What it is missing (at least from docs on the site) is an easy way to do something like: jar -combine -out out.jar -in lib/*.jar which is what I need – Jacob Feb 05 '09 at 11:21
  • The source code is helpful too! Specifically StandaloneJarProcessor... that is, if you're interested in doing this programmatically. – ticktock Dec 04 '14 at 22:36
9

Try extracting your JAR's to a marshalling directory first:

<target name="combine-jars">
    <mkdir dir="${marshall.dir}"/>
    <unzip dest="${marshall.dir}">
        <fileset dir="${external.jar.dir}">
            <include name="**/*.jar"/>
        </fileset>
    </unzip>
    <jar destfile="${combined.jar}" basedir="${marshall.dir"}>
    <delete dir="${marshall.dir}"/>
</target>

Where ${marshall.dir} is a temporary directory, ${external.jar.dir} is where you keep the JAR's, and ${combined.jar} is the target JAR.

David Grant
  • 13,929
  • 3
  • 57
  • 63
  • 2
    The previous examples will include the other jars -as jar!- in the jar file. This one, on the other hand , will first deflate all the jars to *.class files , and then create one jar from all the class files. – Yossale Jun 15 '09 at 07:30
3

If using maven, why wouldn't you ? :) Just use the maven-shade-plugin, works like a charm !

  <project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>1.5</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <mainClass>com.YOUR_COMPANY.YOUR_MAIN_CLASS</mainClass>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>
Mozart Brocchini
  • 352
  • 1
  • 3
  • 11
2

This is my solution:

<target name="-post-jar">
    <echo>Packaging ${application.title} into a single JAR</echo>

    <jar destfile="${basedir}${file.separator}${dist.dir}${file.separator}_${ant.project.name}_.jar">
        <zipgroupfileset dir="${basedir}${file.separator}${dist.dir}" includes="${ant.project.name}.jar"/>
        <zipgroupfileset dir="${basedir}${file.separator}${dist.dir}${file.separator}lib" includes="*.jar"/>
        <manifest>
            <attribute name="Main-Class" value="${main.class}"/>
        </manifest>
    </jar>
</target>
mbinette
  • 5,094
  • 3
  • 24
  • 32
Matt
  • 21
  • 1
1

If you are building with ant (I am using ant from eclipse), you can just add the extra jar files by saying to ant to add them... Not necessarily the best method if you have a project maintained by multiple people but it works for one person project and is easy.

for example my target that was building the .jar file was:

<jar destfile="${plugin.jar}" basedir="${plugin.build.dir}">
    <manifest>
        <attribute name="Author" value="ntg"/>
        ................................
        <attribute name="Plugin-Version" value="${version.entry.commit.revision}"/>
    </manifest>
</jar>

I just added one line to make it:

<jar ....">
    <zipgroupfileset dir="${external-lib-dir}" includes="*.jar"/>
    <manifest>
        ................................
    </manifest>
</jar>

where

<property name="external-lib-dir" value="C:\...\eclipseWorkspace\Filter\external\...\lib" />

was the dir with the external jars. And that's it... You can add multiple zipgroupfileset tags as well.

ntg
  • 12,950
  • 7
  • 74
  • 95
1

The question is well answered. I wanted mention one tool I find useful - One-Jar. One-Jar handles resources more cleanly (by keeping all of them). This more useful if the code needs to process MANIFEST files.

Sample XML copied from website..

<import file="one-jar-ant-task.xml"/>   

    <target name="hello" depends="init">
        <!-- Build lib.jar -->   
        <javac destdir="${classes.dir}/lib">
            <src path="${lib.dir}" />
        </javac>
        <jar destfile="${build.dir}/lib.jar" >
            <fileset dir="${classes.dir}/lib"/>
        </jar>   
        <!-- Build classes for main.jar -->   
        <javac destdir="${classes.dir}/src">
            <src path="${src.dir}" />
            <classpath path="${build.dir}/lib.jar"/>   
        </javac>
        <!-- Construct the One-JAR file -->   
        <one-jar destfile="hello.jar" manifest="hello.mf">
            <main>
                <!-- Construct main.jar from classes and source code -->
                <fileset dir="${classes.dir}/src"/>
            </main>
            <lib>
                <fileset file="${build.dir}/lib.jar" />
            </lib>
        </one-jar>
        <echo>
          Now you can run the Hello One-JAR example using 
          $ java -jar hello.jar
        </echo>   

    </target>
Jayan
  • 18,003
  • 15
  • 89
  • 143
0

Well, I am not so much in to programming - but something simpler worked for me...if the question meant - combining jar files in to one. Ofcourse, this is manual, dirty solution. I just untarred all the tars...and then..created a new tar file, by adding all the directories formed by untarring- in to the new tar file. it worked.

Sowmya
  • 1
0

Maven or other build tools can't "manage" the resolution of multiple versions of class files. In fact, Maven causes these problems in the first place, through transitive inclusion of all downstream jar files that are not explicitly required by a project.

Suppose somewhere in the transitive closure of a project (all libraries and modules required by the project, and all it's dependent projects, recursively) there are two versions of a class file. How could Maven possibly know which one is the 'correct' one? which one was intended by the programmer?

It can't because this information was lost when explicit dependencies were thrown away in favor of transitive ones (to save XML typing).

  • http://stackoverflow.com/questions/816858/maven-remove-a-single-transitive-dependency explains how to exclude a transitive dependency. – Mutant Bob Jul 06 '12 at 15:37
0

Have you considered using Maven or some other system which manages your dependencies automatically? Then you would not need to specify where each library is located, what their names are, and what transitive dependencies your direct dependencies have. You would just state in one place what the dependency and its version are, and the system would take care of downloading the libraries, configuring the classpath and building the project.

Esko Luontola
  • 73,184
  • 17
  • 117
  • 128