Monday, September 5, 2011

Using Maven to Manage Library Dependencies in Eclipse without m2eclipse

Eclipse has a built-in function managing library dependencies. By the usual practice, for each project (as the term means in Eclipse), one downloads the libraries (i.e. jar files) and places them under a directory name lib under the project base directory, and adds the jar files onto the project’s Build Path. This practice has the following disadvantages:
  • One has to figure out what jar files to download and where to place them. 
  • When a library itself depends on other libraries, one has to figure out what the dependencies are, to download them and to add them onto the project’s Build Path. And repeats recursively. 
  •  It is difficult to reuse part of a project’s Build Path for a new project since there is not dependency tree information about the Build Path accessible for Eclipse users 
  •  There is not standard way to share the Build Path in a team 
  •  It is difficult to manage version of the libraries.
A better option is to manage library dependencies of Eclipse projects using Maven. It brings the following advantages:
  • Maven will take care of the jar file downloading. And the downloaded jars will be placed in the local Maven repository with standard layout. 
  •  Maven will take care of transitive dependencies. 
  •  The (direct) dependencies are captured in Maven POM files, which can be easily kept in source control system and can be easily shared within a team. Besides, library versions can be explicitly specified in POM files.
This tutorial shows how to manage library dependencies in an example Eclipse project using Maven without m2eclipse. m2eclipse (now officially called Maven Integration for Eclipse) is a plugin in Eclipse to integrate with Maven. This tutorial, however, does not discuss m2eclipse.  It is assumed that the readers have already installed Maven (and Eclipse). The project is a Hello World program. It, however, uses logback, a third party logging library, to trace program execution into a log file. With a little knowledge about logback, we know that the project depends on the logback-classic library. So in our pom.xml, we are going to have the following dependency

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.29</version>
        </dependency>

(If you need help to find the exact groupId/artifactId/version, please read my post on that topic.)

In fact, logback-classic itself depends on logback-core and slf4j-api, but we do not have to do anything since Maven will take care of it. It is a huge benefit of letting Maven to manage library dependencies inside and outside Eclipse.

When Eclipse is launched, the user must select a workspace by selecting its base directory. If the directory does not exist, Eclipse will create a new workspace with that directory as the base directory. When a user creates a new java project, the user must select a directory as the base directory of the project. A project base directory can be either inside or outside the workspace base directory. The outside option is better. With this option, we decouple projects from workspace. Eclipse workspaces are not version compatible. That means that when a user upgrades his/her Eclipse from version 3.6 to 3.7, he/she has to create a new workspace, to pull the project source code out of the old workspace, and to place them into the new workspace. That work is cut down to the minimum when projects are decoupled from workspace.  In this tutorial, we place the project base directory outside the workspace base directory.

Creating and Configuring a Workspace
Under C:\temp, create a new directory called eclipseworkspace. Open a command line console, cd into C:\temp\eclipseworkspace, and execute the following command:

mvn -Declipse.workspace=. eclipse:configure-workspace

It creates a new workspace and a path variable called M2_REPO pointing to the local maven repository. The path variable will be used later on project Build Paths.

Creating a Project
Under C:\temp, create a new directory hello. Inside hello, create directories as below. 

Create a pom.xml under hello with content as following.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd">
   
    <modelVersion>4.0.0</modelVersion>
    <groupId>ted-gao</groupId>
    <artifactId>dependency-tutorial</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Dependency-Management-Tutorial</name>
    <description>Tutorial on managing dependencies in Eclipse using Maven</description>
    <packaging>jar</packaging>
   
    <build>
        <plugins>
            <!-- ensure that we use JDK 1.6 -->
            <plugin>
                <inherited>true</inherited>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>           
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.29</version>
        </dependency>
    </dependencies>

</project>

In a command line console, cd into hello, and execute the following command:

mvn eclipse:eclipse

It creates Eclipse artifacts for a project.

Please notice the following output from Maven during above command execution:

Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.pom
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.pom (13 KB at 38.9 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-parent/0.9.29/logback-parent-0.9.29.pom
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-parent/0.9.29/logback-parent-0.9.29.pom (14 KB at 27.0 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.pom
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.pom (7 KB at 14.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.pom
Downloaded: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.pom (3 KB at 6.2 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/slf4j/slf4j-parent/1.6.1/slf4j-parent-1.6.1.pom
Downloaded: http://repo1.maven.org/maven2/org/slf4j/slf4j-parent/1.6.1/slf4j-parent-1.6.1.pom (10 KB at 19.1 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.jar
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-classic/0.9.29/logback-classic-0.9.29.jar (239 KB at 486.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.jar
Downloaded: http://repo1.maven.org/maven2/ch/qos/logback/logback-core/0.9.29/logback-core-0.9.29.jar (308 KB at 630.9 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.jar
Downloaded: http://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.1/slf4j-api-1.6.1.jar (25 KB at 51.1 KB/sec)

It shows that even though we only specified that our project depends on logback-classic, Maven figured out that logback-classic depends on logback-core and slf4j-api and included them in our project. Besides, Maven downloaded all the three libraries from the Maven Central Repository.

Developing with Eclipse

Launch Eclipse. When asked to select a workspace, select C:\temp\eclipseworkspace.



From Eclipse Workbench, select the menu item File -> Import…



On the Import dialog box, select General -> Existing Projects into Workspace and click the Next button.



Still on the Import dialog box, browse to select C:\temp\hello as the project root directory, and click the Finish button.



Now the Eclipse Workbench should look like the snapshot below.



Let’s see how well Maven managed library dependencies for us. Right click the project dependency-tutorial in the Package Explorer view, from the context menu, select Properties. On the Properties for dependency-tutorial dialog box, select Java Build Path on the left pane, and the Libraries tab on the right pane. We can see that three libraries, viz. logback-classic, logback-core, and slf4j-api, have been added to the Java Build Path – Maven has done that for us. The three library jar files are in the local Maven repository. The path variable M2_REPO points to the base directory of the local Maven repository.



Now, in Eclipse, create a file called logback.xml under resources. It is a logback configuration file. Write the following content into logback.xml.

<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>dependency-tutorial.log</file>

    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
    </encoder>
  </appender>

  <root level="trace">
    <appender-ref ref="FILE" />
  </root>
</configuration>

Create a Java class with full name tutorial.dependency.HelloWorld.

package tutorial.dependency;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger("tutorial.dependecy.HelloWorld");

        logger.trace("Before saying hello.");
        System.out.println("Hello World!");
        logger.trace("After saying hello.");
    }
}
Run HelloWorld. We will see that Hello World is printed on the console. Refresh the dependency-tutorial project, we will see a new file named dependency-tutorial.log. Open it. It has the following content:

2011-09-06 21:54:26,163 TRACE [main] t.d.HelloWorld [HelloWorld.java:10] Before saying hello.
2011-09-06 21:54:26,166 TRACE [main] t.d.HelloWorld [HelloWorld.java:12] After saying hello.

That is the work done by logback.

Update Dependencies


If you need to change library dependencies later. Change the dependency specification in the pom.xml file (i.e. <dependencies>). Execute mvn eclipse:eclipse again. And right click the project in Eclipse, and select Refresh from the context menu.

[If you are also interested in using sbt to manage library dependencies in Eclipse for Scala programs, please read Using Eclipse and sbt in Scala Programming.]


7 comments:

Anonymous said...

Exactly what I need!
What do we need to do, in order to get the javadoc and the source of these libraries? Just a change in the POM?
--Phil

Ted Gao said...

Thanks for asking this question. I had not thought about it before and had not had answers. It is in deed a good question. So I tried to find answers, and found one: http://stackoverflow.com/questions/2059431/get-source-jars-from-maven-repository. My understanding is that this solution will work only if the library author attached source and javadoc to the artifact when deployed it to the Maven repository.

Anonymous said...

Thank you Ted! In the mean time I tested this method in http://maven.apache.org/plugins/maven-eclipse-plugin/examples/attach-library-sources.html and this works as well!
--Phil

Anonymous said...

Let us assume I want to use this Logback library in other Java project (and/or any other lib available in the "M2 repository").
Do you suggest to do the same process as explained above or can we use a simplest way, please?
--Phil

Ted Gao said...

Phil, the solution that you found is better. I am glad to learn it. Thanks for sharing.

Ted

Ted Gao said...

If two projects share one or two libraries, I will just duplicate the dependencies in the pom file for each of them. On the other hand, if they share a lot libraries, you may consider to factor the common things out into a third pom file and let the other two "inherit" from it. After all, that is normal Maven best practices. I would structure my projects based on Maven best practices and consider Eclipse as an tool that can take advantage of Maven project structure.

Ted

amit said...

Thanks tons for your helpful article. It makes me sad to move away from c++ to java and find it such a PITA for basic setup for build/compile/debug. I wonder if it werent for google search, which shows helpful articles such as yours, java programming would be dead as its a mess to get going otherwise.