JAR Libraries Tutorial

Russell Bateman
Spring 2010
last update:

Table of Contents

Creating the library project
Creating the library JAR
Creating the application project
Setting up the library in Build Path
Alternative to the Eclipse User Library
Maven
Apache Ivy
Sharing Eclipse projects
Javadoc and JAR source code
Source code
class Foo
class FooApp
Setting up a third-party library: a practical example
Appendix: Some ant commands
Appendix: Sharing User Libraries between workspaces

Introduction

This simple tutorial will demonstrate setting up a Java project with one class that is JAR'd and used as a library to another project which consumes it via the Eclipse Build Path mechanism. The concept of User Libraries, in the Eclipse sense, is covered. Also, there is a note on creating "runnable" JARs.

The purpose of this tutorial is to show this relationship of shared-code project and JAR to another project which is important in terms of understanding Eclipse's Build Path -> Libraries functionality. However, for your own use, it is probably simpler to use the Projects tab in Java Build Path from the consuming project to create a dependency on the shared-code (library) project (in the same workspace). By this, any changes to the shared code are seen immediately without the need to build, copy and refresh a JAR including hot-swapping during Run As/Debug As. I may add a more elaborate illustration of this as an appendix someday.

There is a separate, but parallel discussion of Eclipse's Java Build Path in Eclipse Build Path that may be worth your while reading. It compliments this article.

Prerequisites

This tutorial assumes you already have Eclipse set up. If this is not the case for you, see this link. The tutorial examples are given as done on Eclipse Galileo; with slight modification, they may apply to other versions of Eclipse as well.

Creating the library project

Create a new Java project, JarDemo, in Eclipse. There are no special settings to fuss over in the Java Project wizard, so you can click Finish as soon as you've named the project. Once the poject is there, create a new package...

  1. Create a new package, com.etretatlogiciels.jar.demo. (As long as you keep your naming schemes consistent, there's no reason you have to keep my names.) Right-click on the project or src folder icon and choose New -> Package.
  2. Create a new class, Foo, in that package. Right-click on the icon of the new package you just created, then choose New -> Class.
  3. Type the class code in. You can copy it from Appendix B if you wish.
  4. Run the project by right-clicking on Foo.java in the Package Explorer and choosing Run As -> Java Application. You should get something like the following error output in your console window:
    	X is 99
    			

    ...which proves that this (very minimal) class works. It's what we'll use to represent a real set of classes and methods we'd like to consume from a real project.

Creating the library JAR

JAR this project's contents (there could have been more than one class) by following the steps Eclipse gives. (This step gives you instruction in exploiting Eclipse's help.) Start with the workbench's Help menu:

  1. Help
  2. Search
  3. Use search expression "create jar"
  4. Click topic Creating JAR files
  5. Click link Creating a new JAR file

Follow the steps. For step 4, the resources are just one: the single package. For step 5, add the source and javadoc files.

Right-click on the library project. Choose Export.

 

Then choose JAR File and click Next.

Because we right-clicked the project, JarDemo should already be "partially" selected. This is what we want. If we click the hierarchical triangle next to the checkbox, we'll see that the source code will come along, but not any Eclipse settings. This is what we want.

Obviously we want to Export generated class files and resources which are the essence of the library; this is already clicked. As we want access, for debugging purposes, to the Java source (and Javadoc), we click Export Java source files and resources.

Click Next.

We want to call the JAR file JarDemo.jar and we want to put it at the project's root.

Note here that we don't have any JUnit tests (no Eclipse Source Folder named test), but if we did, we would uncheck that folder in this dialog. (For this reason at least, it is "best practice" to separate your JUnit tests into a separate source folder.)

Click Next again to reach the JAR Manifest Specification. As this is a simple, library JAR without complications (such as a real main entry point we even care about), we click the Generate the manifest file radio button and we won't even save it since, if we need to regenerate it later, we'll just skip past this again anyway.

At this point, you'll see JarDemo.jar at the end of your JarDemo project in the Package Explorer view. When we create our consuming application project, we'll use this JAR.

You can close the Help view on the workbench if you opened it since we've finished with it.

Creating the application project

Just as we did for the library project, create a Java Project. This time we'll name it BuildPathDemo; it's an application into which we'll incorporate the JAR we just created.

Type the class code in. You can copy it from here if you wish.

Setting up the library in Build Path

As you may have noticed in some of the illustrations, I had already created my application project and class code which has given arise to errors—namely that Foo cannot be resolved to a type. This step rectifies that situation.

The second main point of our tutorial is to set up our JAR for the new application project's consumption.

Right-click the new BuildPathDemo project in the Package Explorer and choose Build Path, then Configure Build Path and click the Libraries tab:

Note: I no longer suggest the use of the Eclipse User Library though it is a superb concept and it certainly works. You can set up an Eclipse User Library following the method exposed in Setting up a User Library in Eclipse Build Path. Instead, I recommend the approach detailed in Eclipse Build Path Set-up and Libraries.

At this point, however you accomplished it, you should find a new library in your project (in the Package Explorer view), named JarDemo and the error in FooApp.java should dissipate as well as the demonstration should run.

Source code

Here's the source code to our tutorial.

Foo.java:
package com.etretatlogiciels.jar.demo.lib;

/**
 * Foo is an insignificant integer factory class whose purpose is merely to serve
 * as an example of a library with features.
 *
 * @author Russell Bateman
 */
public class Foo
{
    private volatile int  x = 0;

    /**
     * No-argument constructor.
     */
    public Foo() { /* no argument constructor */ }

    /**
     * Constructor accepting the initial value of the integer to fabricate.
     * @param x
     */
    public Foo( int x )
    {
        this.x = x;
    }

    /**
     * Method to get the current integer value.
     * @return the value (int)
     */
    public final int getX()  { return this.x; }

    /**
     * Method for changing the current integer value;
     * @param x new value for integer (int)
     */
    public final void setX( int x )  { this.x = x; }

    /**
     * Render the current integer value as a string.
     * @return the integer as a string (String)
     */
    public final String toString()  { return x + ""; }

    /**
     * Test this lightly.
     * @param args (none are ever processed)
     */
    public static void main( String[] args )
    {
        Foo  x = new Foo( 99 );

        System.out.println( "X is " + x.toString() );
    }
}
FooApp.java:
package com.etretatlogiciels.jar.demo.app;

public class FooApp
{
    public static void main( String[] args )
    {
        Foo  x = new Foo( 99 );

        System.out.println( "X is " + x.toString() );
    }
}

Setting up a third-party library: a practical example

This is a quick method to follow. It's more or less what you learned to do above in Setting up the library in Build Path.

  1. Download and unzip the desired library in a standard place in your development area. Mine is /home/russ/dev/downloads.
  2.  
  3. Right-click on project that must consume the new library, choose Build Path -> Configure Build Path.
  4.  
  5. Click on the Libraries tab.
  6.  
  7. Click Add Library....
  8.  
  9. Select User Library and click Next.
  10.  
  11. Click User Libraries....
  12.  
  13. Click New...
  14.  
  15. Give the library a nice name. This is what you'll see in Eclipse; it is not anything that will show up in the filesystem.
  16.  
  17. (At this point, you will "fill" the new library with JAR files.)
  18.  
  19. Click Add JARs... and navigate to the JAR or subdirectory of JARs you want to add. Shift-click the ones you want and click Open.
    How to link to source code or Java docs
    1. If there is a JAR containing classes whose source code you know you will want to see, and the source code (projects) came with the download, click on Source attachment, then Edit.
    2. Click on External Folder... and navigate to the folder dominating the source code tree. This would usually be a folder with a child named src underneath which would be something like main/java/<some-package-name>/*.java.

    ...and/or...

    1. If it comes with Javadoc, click on Javadoc location, then Edit.
    2. Click on Javadoc location path: and navigate to the folder dominating the Javadoc tree. This would usually be a folder named docs or Javadocs or with a child named Javadocs underneath which would be a lot of HTML documents (including index.html) and a package path filesystem like com/thoughtworks/xstream....
    3.  
    4. Click OK.
    5. Click OK to add the location path of the JAR's source code.
  20.  
  21. If you think there is some useful dependence in the order in which the various JARs appear in your (new) library, click to select them and use the Up or Down buttons to change their position in the list.
  22.  
  23. Click OK to add the new user library. The box to the left of its name should be checked (because you want to add it to the project).
  24.  
  25. Click Finish.
  26.  
  27. At this point, you should see your new library in the list under the Libraries tab of Java Build Path. Click OK to dismiss the Build Path dialog.
  28.  
  29. Note: the new library should appear at the top level of your project contents in the Package Explorer view just as JRE System Library [] and any others you specificed using Build Path.
  30.  
  31. If your project is a Dynamic Web Project, you'll also want to tick the new library off as a JEE module dependency. Please see Java EE Module Dependencies.

Practically speaking, you're going to do this when in debugging or running you find you're missing a class:

Caused by: java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:307) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:248) ... 11 more

that you identify as coming from a third-party library such as log4j, slf4j, JSF (Apache MyFaces), Hibernate, JSTL, etc., all stuff that is commonly consumed, but not actually part of Java.

For our example here, we're debugging some new Hibernate code. Hibernate happens to use slf4j for its logging and so I have to provide a suitable slf4j JAR.

After adding the library and its JARs, if the class you're looking for is still missing, you must do more research to determine where it comes from. For example, if you are missing org.slf4j.impl.StaticLoggerBinder and though you were going to get it from a library you created called Slf4j and a JAR slf4j-api-1.5.11.jar, you might Google and stumble upon http://www.slf4j.org/codes.html where you learn that this class comes from one of a list of other JARs from among which you must choose one. You remove the API JAR and add slf4j-simple-1.5.11.jar, and try again.

Suddenly, you get further in your debugging and slf4j begins to work putting out logging messages detailing what's not working (and no more java.lang.ClassNotFoundException).

For trouble with Dynamic Web Projects missing symbols, please read Java EE Module Dependencies.

Appendix: Some ant commands

If building your own library JAR, there are several ways for you to distribute it and link it up in other projects so that you have access to Javadoc or source code. Here's some ant code to help you do that.

  1. Generate and distribute Javadoc with your JAR.
  2. Create a JAR with source code in it.
  3. Link a JAR to its source code (outside the project or even workspace) manually.

First, when you create your JAR, be sure to generate the Javadoc too. Here I put it into a subdirectory, doc, off the project root.

build.xml excerpt in source-library project
    <property name="src.dir" value="${build.dir}/src" />
    <property name="doc.dir" value="${build.dir}/doc" />

    <!-- generate Javadoc from the Java source code -->
    <target name="javadoc">
        <mkdir dir="${doc.dir}" />
        <javadoc destdir="${doc.dir}" author="true" version="true">
            <fileset dir="${src.dir}" />
        </javadoc>
    </target>

Distribute the Javadoc along side your JAR in whatever way you wish. This can be an ant command to copy both the JAR and the Javadoc to another project's lib subdirectory, or a neutral place from which to pick it up. Here, my script is in the consuming project and is copying both the JAR and the Javadoc locally for use.

For lack of a better place, it's putting the Javadoc down under a subdirectory, doc, under where the consumed JAR(s) are kept. (There might be a better place.)

build.xml excerpt in consuming project
    <property name="library.home" value="elsewhere" />

    <!-- copy the library to here before building -->
    <target name="copy">
        <copy file="${library.home}/jar/library.jar"
                todir="${build.dir}/lib" />
        <copy todir="${build.dir}/lib/doc">
            <fileset dir="${library.home}/doc" />
        </copy>
    </target>

Then, using Build Path -> Configure Build Path -> Add JARs..., add the library to your project and attach the Javadoc by specifying the path to Javadoc location. (See above, Setting up a third-party library.)

There may be a way to build Javadoc into a JAR; I have so far not found how to do that. If someone knows how to do this, I'd be grateful to learn it.

Second, you can build the JAR to contain source code. I believe if you do, you won't need to do anything to hook it up.

Otherwise (third), you can explicitly hook up source code to a JAR just as you do Javadoc by specifying the path to Source attachment. (I think this is also covered in that article.) Javadoc location. (See above, Example of attaching source.)

Fourth, of course, you sometimes don't want Javadoc or source code in the JAR you distribute for size if not for privacy reasons. You'll need to accommodate that in your ant script.

One last word on Javadoc...

On lines 7-9 above, I've copied Javadoc to the project so that it can be easily linked up for viewing when using interfaces from the JAR. In fact, this is not a good idea because that Javadoc will thenceforth be part of your Eclipse project. Eclipse will attempt to validate the HTML that is the Javadoc. Javadoc-generated HTML is crap and will fill your projects with a bazillion warnings. So, the best procedure is to leave the Javadoc sitting remote to your project and make your link to it there.

Appendix: Sharing User Libraries between workspaces

Elsewhere in this article it's discussed how to create a set of User Libraries. I also discuss how this might not be the best practice anymore. Nevertheless, it's my objective to explain this thoroughly.

These hold only for the workspace in which they were created. When a new workspace is created as often happens, it's empty and you have to reconstitute all those libraries as well as their source-code and Javadoc links. You can help this situation out a bit by exporting your set of User Libraries once created for importing into another workspace. When you are editing the content of your User Libraries set, you'll see buttons for importing and exporting.

When you export a set of User Libraries, what's exported is an XML file containing paths on the host system to those libraries, their source and Javadoc as established. Very useful, but not system-independent. So, this is a good thing to do on your development host, but unless the host you attempt to share this XML file with sports identical paths (the username, the path to the downloaded JARs, etc.), it will not be useful there.

However, as an XML file, it's completely editable. If you're clever enough to change username paths and library versions where applicable, you can still cut down the time to constitute a set of User Libraries on another development host.

Here's a very short example to give you a taste for what you're up against.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<eclipse-userlibraries version="2">
  <library name="Apache Commons" systemlibrary="false">
    <archive javadoc="jar:file:/home/russ/dev/downloads/apache-commons/commons-beanutils-1.8.3/commons-beanutils-1.8.3-javadoc.jar!/"
           path="/home/russ/dev/downloads/apache-commons/commons-beanutils-1.8.3/commons-beanutils-1.8.3.jar"
           source="/home/russ/dev/downloads/apache-commons/commons-beanutils-1.8.3/commons-beanutils-1.8.3-sources.jar"/>
    <archive javadoc="jar:file:/home/russ/dev/downloads/apache-commons/commons-codec-1.4/commons-codec-1.4-javadoc.jar!/"
           path="/home/russ/dev/downloads/apache-commons/commons-codec-1.4/commons-codec-1.4.jar"
           source="/home/russ/dev/downloads/apache-commons/commons-codec-1.4/commons-codec-1.4-sources.jar"/>
    <archive javadoc="jar:file:/home/russ/dev/downloads/apache-commons/commons-collections-3.2.1/commons-collections-3.2.1-javadoc.jar!/"
           path="/home/russ/dev/downloads/apache-commons/commons-collections-3.2.1/commons-collections-3.2.1.jar"
           source="/home/russ/dev/downloads/apache-commons/commons-collections-3.2.1/commons-collections-3.2.1-sources.jar"/>
    <archive javadoc="file:/home/russ/dev/downloads/apache-commons/commons-digester-2.1/apidocs/"
           path="/home/russ/dev/downloads/apache-commons/commons-digester-2.1/commons-digester-2.1.jar"
           source="/home/russ/dev/downloads/apache-commons/commons-digester-2.1/commons-digester-2.1-sources.jar"/>
    <archive javadoc="file:/home/russ/dev/downloads/apache-commons/commons-discovery-0.4/docs/apidocs/"
           path="/home/russ/dev/downloads/apache-commons/commons-discovery-0.4/commons-discovery-0.4.jar"/>
    <archive javadoc="jar:file:/home/russ/dev/downloads/apache-commons/commons-logging-1.1.1/commons-logging-1.1.1-javadoc.jar!/"
           path="/home/russ/dev/downloads/apache-commons/commons-logging-1.1.1/commons-logging-1.1.1.jar"
           source="/home/russ/dev/downloads/apache-commons/commons-logging-1.1.1/commons-logging-1.1.1-sources.jar"/>
  </library>
  <library name="Hibernate 3.6" systemlibrary="false">
    <archive path="/home/russ/dev/downloads/hibernate-3.6.0/lib/required/antlr-2.7.6.jar"/>
    <archive path="/home/russ/dev/downloads/hibernate-3.6.0/lib/required/dom4j-1.6.1.jar"/>
    <archive path="/home/russ/dev/downloads/hibernate-3.6.0/lib/required/javassist-3.12.0.GA.jar"/>
    <archive path="/home/russ/dev/downloads/hibernate-3.6.0/lib/required/jta-1.1.jar"/>
    <archive javadoc="file:/home/russ/dev/downloads/hibernate-3.6.0/documentation/javadocs/"
           path="/home/russ/dev/downloads/hibernate-3.6.0/hibernate3.jar"/>
  </library>
  <library name="JSTL 1.2" systemlibrary="false">
    <archive path="/home/russ/dev/downloads/jstl-1.2/jstl-api-1.2.jar"/>
    <archive path="/home/russ/dev/downloads/jstl-1.2/jstl-impl-1.2.jar"/>
  </library>
  <library name="Log4j" systemlibrary="false">
    <archive path="/home/russ/dev/downloads/apache-log4j-1.2.16/log4j-1.2.16.jar"
           source="/home/russ/dev/downloads/apache-log4j-1.2.16/src"/>
  </library>
  <library name="MyFaces 1.2" systemlibrary="false">
    <archive javadoc="file:/home/russ/dev/downloads/myfaces-core-1.2.8-bin/javadoc/"
           path="/home/russ/dev/downloads/myfaces-core-1.2.8-bin/lib/myfaces-api-1.2.8.jar"/>
    <archive javadoc="file:/home/russ/dev/downloads/myfaces-core-1.2.8-bin/javadoc/"
           path="/home/russ/dev/downloads/myfaces-core-1.2.8-bin/lib/myfaces-impl-1.2.8.jar"/>
  </library>
  <library name="XStream" systemlibrary="false">
    <archive javadoc="file:/home/russ/dev/downloads/xstream-1.3.1/docs/javadoc/"
           path="/home/russ/dev/downloads/xstream-1.3.1/lib/xstream-1.3.1.jar"/>
  </library>
</eclipse-userlibraries>