Wednesday, October 12, 2011

Using Maven in Eclipse

Introduction

Maven is a wonderful tool, Eclipse is a wonderful tool (well, versions 3.5, 3.6 SR1 and 3.7 anyway). Using the two together creates an even more awesome tool. This can be a little icky to setup however, especially if you use an Eclipse prior to version 3.7. The deal is that if you do it the way you are supposed to, you'll have no head aches. So lets examine just how you can streamline your Maven in Eclipse experience!

For the purpose of being complete, I'm going to base this article on Eclipse 3.5. Maven integration in Eclipse 3.7 has been streamlined a great deal, so some of the things said you may not even encounter or need.

Also note that this is not a tutorial on Maven - I have existing articles for that purpose. Check the maven label to see all of them.

Plugin installation

Before you can even use Maven, you need a specific plugin called m2eclipse. We'll be installing the latest version, which you can simply get through the Eclipse Marketplace. Alternatively you can use this download site.

After installation and restarting Eclipse you are ready to go.


Update: m2eclipse has been merged into jboss-tools and is now developed as part of that project. You may just want to install the jboss-tools maven integration module to have the latest up to date version.

Configuration

Right now Eclipse is probably updating indexes. When that is finished, you will want to change some configuration before creating your first project.

First of all, Eclipse will be setup with an embedded Maven version. You will want to change this to the Maven installation you use on the command line. Open up the Eclipse preferences, unfold Maven. Here select Installations. Here you can add your own Maven installation directory and make that the active one. At this point Maven may start to update indexes again, let it.


Now, select the maven menu item to get the general properties. Here you will probably want to uncheck Download repository indexes at startup to speed up Eclipse startup time - you can always update indexes manually if you really need to.

Creating a project

When you create a project which you want to Mavenize, Maven should be the one that controls everything; from the project settings to the dependencies. The m2eclipse plugin is there to synchronize the Maven settings with the Eclipse project settings.

To start everything out, lets create a parent project. Select File, new, project.... In the list that opens, select Maven project.



Click next. In the next dialog, select create a simple project. Click next. In the third dialog you define the starting structure of the pom. We want to create a Maven parent pom, so we fill as follows.



A good convention is to give the parent pom an artifactId of 'PROJECTNAME-parent'; here we don't add the -parent part yet though; this is because the artifactId determines what name the Eclipse project will get. So fill in how you want to name your project, then afterwards rename the artifactId in the generated pom.xml file.

At this point click finish to let Eclipse create the parent project for you. Basically you get an empty project with a simple pom.xml file:

<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
  <modelVersion>4.0.0</modelVersion>
  <groupId>maventest</groupId>
  <artifactId>maventest</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>maven test app</name>
  <description>maven test app</description>
</project>

(note: I like to use the XML editor directly, not the built in wizards).

For the sake of demonstration purposes I'm going to leave it like this for now (except change the artifactId to maventest-parent). We'll revisit the parent shortly.

Okay, we're going to make a JEE project. So lets add an EJB module, shall we? Right click the parent project, select maven and then select new maven module project.


This will open the 'New Maven Module' wizard. As you can see the correct parent project has already been selected for you. Again, select create a simple project, fill in the module name (maventest-ejb) and then click next.


In the last dialog there is not much to change; do make sure to set the ejb packaging type, this will make sure that the EJB facet is installed in your Eclipse project which can help for example deployment tools to do the right thing (such as JBoss Tools hot-deployment!).

Click finish to create the module project. The module will appear in Eclipse as a separate project with its own .project and .classpath file; on disc the project will have appeared as a subdirectory of the parent project. This is how Maven expects it to be, so great!

Open up or refresh the parent pom, you'll find that the plugin has added the module for you there. Unfortunately it has not added the module to dependency management, that you'll have to do yourself. We'll get to that in a minute.

check the project in Eclipse; there is something strange there.


If you know a little about Maven, you also know that it defaults to Java 1.4 if you don't tell it what Java version to use. Hence, your project is now also configured with a Java 1.4 runtime. Lets fix that. Not in Eclipse, but in Maven. Open up the parent pom and lets add some plugin configuration.

<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <inherited>true</inherited>
      <configuration>
        <source>1.6</source>
        <target>1.6</target>
        <encoding>UTF-8</encoding>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-resources-plugin</artifactId>
      <configuration>
        <encoding>UTF-8</encoding>
      </configuration>
    </plugin>
    <plugin>
      <artifactId>maven-ejb-plugin</artifactId>
      <inherited>true</inherited>
      <configuration>
        <ejbVersion>3.1</ejbVersion>
      </configuration>
    </plugin>
  </plugins>
</build>

Here we tell Maven we want to compile for Java 6 targets. Depending on your version of Eclipse you may now see that your ejb module has already adapted; if not, right click on the ejb module project, select Maven and then update project configuration. Also note the EJB plugin; by default Maven choose EJB spec 2.1, which is probably not what you want. This way you can manually override to EJB 3.1 spec (or 3.0, if you have to develop for a JEE5 application server).

And POOF!


Eclipse now has the Java 6 runtime setup. Good stuff. Learn this lesson well though: you could have forced the correct runtime through the Eclipse project settings, but what you have to accept here is that the Maven plugin (and thus Maven) is the boss. Everything has to be managed from the poms, not from Eclipse. If you do it any other way you'll find that project settings magically reset at some point in time.

Before we continue, lets deal with one Eclipse oddity first. Open up the project properties and select the build path.

Under the source folders, you'll see all the default Maven folders a module should have. When you investigate the resources however, you'll find this:


By default all resources are excluded! I don't know who came up with that, but whenever you create a new project or you update the project configuration, the exclusions will return. Why? WHY!?

Needless to say, remove all exclusions because otherwise these resources will not be on the classpath of your application, which can especially hurt in unit tests.


Adding dependencies

Again, you want Maven to do the work here. Nothing strange of course, managing dependencies is the main reason you want to use Maven to begin with. But lets see how we can deal with changes to poms from Eclipse.

Lets add a few dependencies. First in dependency management in our parent pom:

<!-- NOTE: make sure maven has access to the java.net repository -->  
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>maventest</groupId> 
        <artifactId>maventest-ejb</artifactId>
        <version>1.0.0-SNAPSHOT</version>
      </dependency>

      <dependency>
        <groupId>javax.jms</groupId> 
        <artifactId>jms</artifactId>
        <version>1.1</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>javax.ejb</groupId>
        <artifactId>ejb-api</artifactId>
        <version>3.0</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>persistence-api</artifactId>
        <version>1.0</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>javax.transaction</groupId>
        <artifactId>jta</artifactId>
        <version>1.1</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>javax.mail</groupId>
        <artifactId>mail</artifactId>
        <version>1.4.4</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.4</version>
        <scope>provided</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

These are some basic dependencies to be able to compile enterprise code - they should all be in Maven central. I mark them all as provided since your application server should provide them for you during runtime.

Now we add them to the dependencies of our EJB module.

<dependencies>
      <dependency>
        <groupId>javax.jms</groupId> 
        <artifactId>jms</artifactId>
      </dependency>
      <dependency>
        <groupId>javax.ejb</groupId>
        <artifactId>ejb-api</artifactId>
      </dependency>
      <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>persistence-api</artifactId>
      </dependency>
      <dependency>
        <groupId>javax.transaction</groupId>
        <artifactId>jta</artifactId>
      </dependency>
      <dependency>
        <groupId>javax.mail</groupId>
        <artifactId>mail</artifactId>
      </dependency>
      <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
      </dependency>
    </dependencies>

As soon as you save the pom of the EJB module, something should have happened in Eclipse:


The dependencies have been added to the project. Not only that but transitive dependencies (javax.activation) are there too. As you can see Maven is an incredibly powerful tool for managing dependencies, even in your IDE!

Depending on your version of Eclipse and/or m2eclipse, updating dependencies may be a little quirky. Sometimes it happens automatically, sometimes it does not (so far under Eclipse 3.7 I've never had problems). When you suspect or know it didn't happen, you have the possibility to force m2eclipse to update the project dependencies. You do this by right clicking the maven module project in which you want to update dependencies, selecting Maven and then update dependencies. If that doesn't help, you should refresh the parent project and try again.

A word on dependencies in Eclipse


As you can see all Maven dependencies are available to the Eclipse project. This also poses a problem which I will now demonstrate. Lets add a dependency on test scope to the project. Dependency management:

<dependency>
  <groupId>hsqldb</groupId>
  <artifactId>hsqldb</artifactId>
  <version>1.8.0.7</version>
  <scope>test</scope>
</dependency>

Pick any dependency you want really. Now lets add it to the EJB module as well:

<dependency>
  <groupId>hsqldb</groupId>
  <artifactId>hsqldb</artifactId>
</dependency>

Upon saving the module pom, the dependency is neatly added to the project dependencies.



And there in lies our problem. This is a dependency for the test scope, which means Maven will only put it on the test classpath. No such luck in Eclipse however, as it does not differentiate between a normal classpath and a test classpath. The test dependency is now also available in your normal application code. This means if you use it there everything will compile just fine in Eclipse, but as soon as you do a mvn clean install on the command prompt things will not compile because of missing dependencies.

I will repeat what I have said many times: tools are there to help you, not to do work for you. Even if these tools make it incredibly easy to get this setup, as you can see if you don't think for yourself you can still screw things up. Thats okay because you are the captain, not the tool. You should know what you are doing.

I will for this reason impose a rule you should follow: before checking something into a version control repository (such as SVN), do a mvn clean install first to be sure everything still works on the Maven side of things. Your coworkers will thank you for it.


Investigate the source of dependencies

As I described in my Maven articles, sometimes you can be quite surprised to find a while stack of dependencies on your classpath that you did not include yourself - the so called transitive dependencies. You have the mvn dependency:tree tool at your disposal, but Eclipse provides a more intuitive view for you. Open up the EJB pom in Eclipse and select dependency hierarchy.


As you can see this gives a neat and clear collapsable tree view of the dependencies, showing exactly what the scope is and which dependency is pulling what dependency transitively into the project.


Adding a second module


Okay we have our EJB module up and running. Now lets add a WAR module which depends on the EJB module. Do this in the same way you added the EJB module, only as packaging type choose 'war' this time. Like before m2eclipse fills in everything admirably. Afterwards we end up with a new module project in Eclipse:


As you can see this time the correct Java runtime is selected by default, because the parent pom instructed thus. If you open the parent pom in Eclipse now you'll also see that the war module has been added to it (but not to dependency management, you should do that now). The plugin will also have created a src/main/webapp directory for you; for easy access to it you may want to add it to your war project's source directories.


While you're there, don't forget to remove the idiotic resource exclusions.

There are no dependencies yet; lets add the EJB module as a dependency to the WAR module:

<dependencies>
      <dependency>
        <groupId>maventest</groupId>
        <artifactId>maventest-ejb</artifactId>
        <scope>provided</scope>
      </dependency>
    </dependencies>

If you did not make any typos in group/artifactId's Eclipse should be able to resolve the maventest-ejb dependency; this is because in Eclipse you have something called workspace resolution which should be turned on by default. Workspace resolution basically means 'look in the project first, and then look in the Maven repository'. This mechanism allows you to edit modules and have the changes immediately visible in other modules (including dependency updates), without having to do a mvn clean install first to get the updated module into your local m2 repository. The versions have to match up however, so if you want changes to be visible you should refer to the latest -SNAPSHOT release.

You can see in Eclipse when a dependency is managed through workspace resolution:


The little folder icon indicates that the dependency comes from the workspace (see the dependencies of the EJB module to notice the difference).

If workspace resolution is not active for some reason, you can enable it now. Do so by right clicking the module project, choosing maven and then Enable workspace resolution (if it is already enabled the option will allow you to disable it).

At this point it is a good idea to do a mvn clean install anyway. Before you can do that, Maven will require you to have a skeleton WEB-INF/web.xml in your war module. I always add it to src/main/webapp.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

  <display-name>maventest</display-name>
</web-app>

The clean install should now work without problems.


The final mile: getting it all into SVN


You have your working application skeleton (perhaps you want to add an EAR module as well... can you make that happen on your own now?), its time to get it all into SVN and then make it work from Eclipse again. I'll assume you have your SVN plugin setup in Eclipse (I use subversive with the svnkit 1.3.0 provider should you wonder).

First of all we have to add some configuration to the parent pom:

<scm>
  <connection>scm:svn:http://yourrepository.com/maventest/trunk</connection>
  <developerConnection>scm:svn:http://yourrepository.com/trunk</developerConnection>
</scm>

I'm leaving out the maven release plugin configuration to keep it to the point.

This part is always a little hairy and I have a quite brutal way of doing so; I don't use Eclipse to check in the project, I use my favorite SVN client in stead. In my case this is TortoiseSVN. The steps I follow are:

1. close Eclipse
2. Add resources to the exclusion list I don't want to check in (target folders mostly)
3. Copy over the project directory to a separate directory, I like to use a directory 'CI' for 'check in'
4. import the project into SVN using the SVN client (by importing the CI folder, not the project folder)
5. Validate that all files of the project are in the repository
6. open Eclipse
7. Remove existing Eclipse project, delete local files from disc
8. Check out the project from SVN using the Eclipse repository exploring view

A discussion point is if you want to check in Eclipse .settings folders; I tend to put them on the exclusion list as they will probably change every time someone else commits changes to the project, which is quite annoying. Its up to you to experiment here what works for you.

After importing the project fresh from SVN again you will end up with only the parent project in your application. The module projects are there on disc, we simply have to import them into the workspace one time. Right click the parent project and choose import. Here, select Maven and then existing Maven Projects.


Click next. This will provide a view of your parent project with under it the two (or three if you added an EAR module) module projects. Uncheck the parent project and then check the module projects again to only make Eclipse attempt to import those projects; the parent is already there so I have no clue why that one is selected also. Note that if you do not unselect it will still work, you'll just get an error message telling you the parent could not be imported.


Click finish to actually import the projects. Everything should be setup correctly right from the start (because the poms are the boss).


Conclusion

There you have it; this should be all the tools you need to properly create Mavenized projects in Eclipse without any frustrations!