Objetives
The purpose of this article is to serve as a basic guide for the manual migration of non-Maven Java projects to Maven environment. It is especially intended for people with little previous experience in Maven, but with some previous knowledge of Maven.
Project preparation
As it is obvious, to migrate a Java project to Maven, we must have its sources and all the dependent libraries..
Since we are going to make important structural modifications to the project, we should make sure that the sources are not connected to any version control system such as Subversion. To do this, we can either manually remove all .svn directories from our project or from Eclipse disconnect the project.
If we are going to do it manually, we must make sure to delete all .svn directories (or .CVS if the project to be migrated is connected to a CVS). As an example, in Linux we can run the following command from the project directory to delete all references to ‘.svn’ folders:
rm -rf `find . -type d -name .svn`
It will be much easier to do it automatically from Eclipse. To disconnect the project, having it selected we will click on the right button of the mouse: Team -> Disconnect.
Project adaptation
Although the default Maven structure can be modified, it is highly recommended to adapt the project organization to migrate to the more generalized default structure.:
In order to make this modification of the structure, special care must be taken in some aspects or Maven conventions:
- The resource files: Maven makes a distinction between the java class files and the .properties and XML resource files and, in fact, unless we tell it otherwise, it will not treat the non-java files located in the src/main/java directory. For this reason, we will have to locate all the resource files (properties, XML’s) located in the java directory and move them to the resources directory (src/main/resources).
- Delete the classes folder. It is very common that non-Maven projects have in their WEB-INF/classes folder the compiled files of the project. This situation is not only not recommended but can produce problems, so we will eliminate them relying on Maven for their generation when necessary.
Define the pom.xml of the project
Once the project structure has been adapted to the Maven philosophy, the next step is to define the pom.xml of the project.
We will start from a basic pom.xml file. This source file has no dependencies; during the process we will adapt it and later we will refine the configuration until we reach a complete “mavenization” of the project.
Once the pom.xml is placed in the root of the application, we modify it indicating the name of the project and the basic information related to the developers, organization and dependencies repository used.
Before continuing and adding the dependencies, we can make a previous check and verify that indeed our IDE, in this case Eclipse, recognizes correctly the new structure of the project.
To do this we go to the console and in the directory where our project is located we execute the following command:
mvn clean eclipse:eclipse
(It requires to have Maven correctly installed in the machine).
If the environment configuration is correct, we will get an output similar to this:
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'eclipse'.
[INFO] ------------------------------------------------------------------------
[INFO] Building ejemplo
[INFO] task-segment: [clean, eclipse:eclipse]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting file set: /home/felix/workspaces/ejemplo/target (included: [**], excluded: [])
[INFO] Preparing eclipse:eclipse
[INFO] No goals needed for project - skipping
[INFO] [eclipse:eclipse]
[INFO] Adding support for WTP version 1.5.
[INFO] Using source status cache: /home/felix/workspaces/ejemplo/target/mvn-eclipse-cache.properties
[INFO] File /home/felix/workspaces/ejemplo/.project already exists.
Additional settings will be preserved, run mvn eclipse:clean if you want old settings to be removed.
[INFO] Wrote Eclipse project for "ejemplo" to /home/felix/workspaces/ejemplo.
[INFO]
Sources for some artifacts are not available.
Please run the same goal with the -DdownloadSources=true parameter in order to check remote repositories for sources.
List of artifacts without a source archive:
o javax.servlet:servlet-api:2.4
o javax.servlet.jsp:jsp-api:2.1
o junit:junit:3.8.1
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4 seconds
[INFO] Finished at: Wed Sep 3 11:54:28 CET 2008
[INFO] Final Memory: 10M/80M
[INFO] ------------------------------------------------------------------------
We go to Eclipse, refresh the project (F5 with the project selected) and check that the new structure is correct.
Now that we have verified that eclipse has recognized the new structure, the next step is to define in our pom.xml the dependencies. To do this we eliminate all the .jar that are in the /WEB-INF/lib directory and we keep a copy of them to later solve the dependencies of the original project. A recommendation is to cut the /WEB-INF/lib folder to another path of our disk.
Resolution of dependencies
This is the most delicate phase of the process, since we must make sure that all the project dependencies are correctly declared in the pom.xml file. To perform this task it is recommended to use a web application to help us locate the libraries, such as MvnRepository or MvnBrowser (recommended), where we can search for each of our jar and find the definition in the pom.xml that we must use.
Thanks to this web, once we have located the dependency, we will only have to copy the POM Dependency block to our pom.xml file.
We will repeat this process iteratively for all our .jar libraries and adding the dependency definition to our dependencies area, with certain caveats and considerations that we introduce below.
Special considerations
There are times when determining the definition of the dependency in our pom.xml may not be a trivial task. The recommended mechanisms of action according to different cases are discussed below:
The version of the library is not indicated in the file name.
For example, we can find a library called axis-ant.jar, so we will have to previously find out the version of the library in order to define the dependency in the pom.xml file. For this it is usually very effective to unzip the library and consult the file /META-INF/MANIFEST.MF. As an example, in the mentioned library we would see a line similar to the following: “Implementation-Version: 1.2.1 2243 June 14 2005”. Once we know the version we can perform the search in the usual way. We know that the library was generated by maven, but we do not know its definition.
We do not know the groupId, artifactId, etc. We can unzip the .jar and access the pom.xml file located in /META-INF/maven to check the groupId, artifactId and version of the dependency.
Special JEE units.
Some jars, such as servlet.jar, are provided by the application container, so we will have to define them by marking them as provided. This tells Maven that these libraries can be used at compile time, but will not be added to the final packaging. To declare a dependency in this way we will incorporate in our pom.xml a code like this:
javax.servlet
servlet-api
2.4
provided
Libraries already considered in the transitive dependencies
Many of the dependencies depend on other libraries, so Maven will transitively add new libraries to our project. In these cases we do not need to define these new transitive dependencies in our pom.xml. As an example, suppose a project depends on these libraries:
- xalan-2-6.0.jar
- xercesImpl
- xml-apis
When looking for the xalan-2-6.0.jar library, we notice that the library depends in turn on the other two libraries. Therefore, for this example it would be enough to declare the dependency to xalan-2-6.0.jar in our pom.xml.
Conflicts between bookstores
Sometimes we can find that two libraries have in their dependencies the same third library, but of different version. This would cause maven to include in our Classpath 2 versions of the same library, causing conflicts due to duplicate classes. In this case, a directive must be declared in the dependency of one of them to exclude the download of the conflicting library.
For example, in a project we want to use the following library:
jaxws-api
javax.xml.ws
2.1-1
And we have dependency to com.sun.xml.ws / jaxws-rt, this library is brought as a transitive dependency to another version of the library mentioned above. We declare an exclusion to avoid it:
com.sun.xml.ws
jaxws-rt
2.1.4
jaxws-api
javax.xml.ws
We have a library that is in the repositories, but we have not been able to find out the version number.
In this situation, if the library does not seem problematic, we could choose the latest version of the dependency. In case we consider that the version used by the project may cause problems, it is best to add this library manually to our repository (as if it were a project-specific library), as explained in the next section.
The library is project specific.
In these cases we will have to upload the dependency to our library repository and, to avoid later conflicts, it is recommended that it is uploaded with the same groupId of our project. Let’s suppose that while migrating a project we come across example1.jar library that is not in any public repository. In this case we upload it to our repository for example with the following definition:
com.viavansi.examples
example1
0.0.1
In the following figure we see how we deploy a library to Artifactory:
Dependency check
Once we have finished this process of defining all the dependencies in our pom.xml, we will execute in console mvn dependency:tree (located in the directory where the pom.xml file of the project is) to verify that the dependencies that have been incorporated to the project are the correct ones. This verification step is very important in the migrations of this type of projects, since it is very possible that some transitive dependency generates problems. As an example, we can see the following output in which we check that there are no transitive dependencies that conflict with any of our libraries:
[INFO] [dependency:tree]
[INFO] com.viavansi.examples.examples1:war:0.0.1
[INFO] +- junit:junit:jar:3.8.1:test
[INFO] +- javax.servlet.jsp:jsp-api:jar:2.1:provided
[INFO] +- javax.servlet:servlet-api:jar:2.4:provided (scope not updated to compile)
[INFO] +- javax.activation:activation:jar:1.1:compile
[INFO] +- avalon-framework:avalon-framework-impl:jar:4.1.5:compile
[INFO] +- axis:axis-ant:jar:1.2.1:compile
[INFO] +- axis:axis:jar:1.2.1:compile
[INFO] +- javax.mail:mail:jar:1.3.1:compile
[INFO] +- commons-beanutils:commons-beanutils:jar:1.6:compile
[INFO] +- commons-codec:commons-codec:jar:1.3:compile
[INFO] +- commons-collections:commons-collections:jar:3.2:compile
[INFO] +- commons-dbcp:commons-dbcp:jar:1.2.2:compile
[INFO] +- commons-digester:commons-digester:jar:1.6:compile
[INFO] | - xml-apis:xml-apis:jar:1.0.b2:compile
[INFO] +- commons-discovery:commons-discovery:jar:0.2:compile
[INFO] +- commons-httpclient:commons-httpclient:jar:3.1:compile
[INFO] +- commons-io:commons-io:jar:1.3.1:compile
[INFO] +- commons-lang:commons-lang:jar:2.2:compile
[INFO] +- commons-logging:commons-logging:jar:1.1:compile
[INFO] | +- logkit:logkit:jar:1.0.1:compile
[INFO] | - avalon-framework:avalon-framework:jar:4.1.3:compile
[INFO] +- commons-pool:commons-pool:jar:1.3:compile
[INFO] +- displaytag:displaytag:jar:1.1:compile
[INFO] +- displaytag:displaytag-export-poi:jar:1.1:compile
[INFO] +- dom4j:dom4j:jar:1.6.1:compile
[INFO] +- org.extremecomponents:extremecomponents:jar:1.0.1:compile
[INFO] +- com.lowagie:itext:jar:1.4.8:compile
[INFO] +- oro:oro:jar:2.0.7:compile
[INFO] +- javax.xml:jaxrpc-api:jar:1.1:compile
[INFO] +- javax.servlet:jstl:jar:1.1.2:compile
[INFO] +- log4j:log4j:jar:1.2.8:compile
[INFO] +- com.oracle:ojdbc14:jar:9.0.2.0.0:compile
[INFO] +- poi:poi:jar:2.5.1-final-20040804:compile
[INFO] +- javax.xml.soap:saaj-api:jar:1.2:compile
[INFO] +- org.springframework:spring:jar:2.0.5:compile
[INFO] +- taglibs:standard:jar:1.1.2:compile
[INFO] +- struts:struts:jar:1.2.9:compile
[INFO] | +- commons-fileupload:commons-fileupload:jar:1.0:compile
[INFO] | +- commons-validator:commons-validator:jar:1.1.4:compile
[INFO] | - antlr:antlr:jar:2.7.2:compile
[INFO] +- wsdl4j:wsdl4j:jar:1.5.1:compile
[INFO] +- wss4j:wss4j:jar:1.5.0:compile
[INFO] +- xalan:xalan:jar:2.6.0:compile
[INFO] +- org.apache.xmlgraphics:xmlgraphics-commons:jar:1.2:compile
[INFO] +- xml-security:xmlsec:jar:1.2.1:compile
[INFO] +- org.directwebremoting:dwr:jar:2.0.3:compile
[INFO] +- com.viavansi:plantilla-client:jar:0.0.13:compile
[INFO] +- net.sourceforge.barbecue:barbecue:jar:1.5-beta1:compile
If duplicate libraries are detected due to transitive dependencies, we must proceed to define the necessary exclusions in the affected dependencies, as indicated above.