Logo Computer scientist,
engineer, and educator
• Articles • Articles about computing • Articles about software development • Apache integration software

Creating an Apache Camel route in Java using Maven, step-by-step

Summary

The article follows on from my previous article Creating an Apache Camel route in Java from the ground up. In that article I explained how to build and run an Apache Camel route specified in Java using only a command prompt. In my view, there is no better way to get a detailed understanding of how software like Camel or ActiveMQ can be used than to drive it from the command line, managing all the dependencies manually. If you followed that article, however, you'll have realized how awkward it is to build and run Java code that has large numbers of dependencies that way. Of course, that is why we all use build and dependency management frameworks, even if we don't use IDEs.

This article describes the implementation of exactly the same file-copier Camel route, but using Maven to manage the dependencies, and to build and run the application. Even if you're broadly familiar with the technology, I would recommend that you read the previous article, at least in outline, even if you don't try the detailed steps. Unless you do that, the sophistication of Maven completely obscures the complexity of the build process. Pragmatically, that's not a bad thing; but it's not very educational.

This article describes Maven in outline, then explains how to use it to build a Camel application into a stand-alone JAR file that can be executed; the JAR file will contain all the dependencies needed to run the application.

We proceed step-by-step, starting with the installation of Maven, then entering the necessary code and configuration. However, if you want to see the example working without cutting-and-pasting all the source code, the complete Maven workspace is available from the Downloads section at the end of this article.

About Maven

Apache Maven is a build and dependency management tool for Java development. In some ways it is similar to Ant, in that it automates the steps necessary to build and package an application. It is different from Ant, and indeed from most build management tools, in two important ways:

1. It integrates dependency management, and can retrieve libraries from a central repository, and
2. it is configured in a rather declarative way — in general it is not necessary, and sometimes not even possible, to specify the exact relationships between build steps.

This second point is summed up in the phrase convention over configuration that is often in the context of Maven. Maven assumes that developers will follow essentially the same conventions about such things as file naming and directory layout; if they do, then everything works as if by magic. If not, then getting Maven to behave can be a battle. My experience is that, if you're going to use Maven in a software project, it's best to use it from the very start, and let it set things up as it likes; retro-fitting Maven to an existing, complex project is not a task to that is likely to give much pleasure.

Like Ant, Maven is configured using an XML file (conventionally pom.xml). Although Maven will create a skeleton pom.xml for a project, very often this file will be managed using an IDE tool — it's not impossible to use a text editor, but it's not a whole heap of fun. The XML file not only specifies what operations will be needed to build the project, but also the other components on which it has dependencies. Most of the libraries needed to support development for Apache software (Camel, ActiveMQ, Tomcat...) are available from a central repository, so adding these to your project should involve no more work than adding a few lines to pom.xml.

Whatever its strengths and weaknesses, there's no doubt that Maven has become very popular in the open-source Java domain. Most books and articles about development with Apache integration software more-or-less assume its use, so it's difficult get by without at least a working knowledge of Maven.

Step 1: obtain and install Maven

How easy or difficult this is depends on your platform; it's rarely more difficult than downloading and unpacking a tarball, and setting a few environment variables; and it may be much easier. Binaries are available from the Maven download page, which also contains detailed installation instructions for various platforms.

On RedHat-based Linux system, installation of Maven should be as easy as running

# yum install maven
At the time of writing this will get you version 3.0.5, which is not the latest version, but it's good enough for the purposes of this article.
It goes without saying that you need a Java JVM to run Maven. If you are using a RedHat or Fedora Linux system (and possibly other distributions), be aware that the Linux installer probably installs the OpenJDK Java compiler, which is not compatible with Maven, as it lacks the bits that Maven requires for compiling Java code. If you don't want to replace the platform Java, just install a Sun/Oracle JDK in a different directory, and run Maven using JAVA_HOME=... mvn...
To test that Maven is correctly installed, just run
% mvn --version
which should provide some version information.

Step 2: Create a Maven project workspace

The "convention over configuration" philosophy adopted by Maven requires that a project workspace is set up in a particular way. There's nothing particularly objectionable about its prefered set-up — in fact, it's pretty natural for Java work. However because we don't all naturally set things up in exactly the way Maven likes, the usual way to start working with Maven is to have it create a project workspace, layed out the way it likes. The workspace is configured according to a template which is called an archetype in Maven jargon. There are a great many such archetypes available, to suit different application types. For this example, we'll use the camel-archetype-java archetype. This archetype has the following characteristics.
  • The project file pom.xml specifies dependencies on the Camel core framework, as well as a number of other components that are likely to be useful, such as log4j.
  • The project includes some simple boilerplate code to initialize Camel and configure a simple route; this can be used as the basis of a real application.
  • A default log4j configuration file is created
  • The project file includes a plugin to allow the complete application to be run at the command line, with a classpath that includes all the relevant dependencies.
To create the project workspace using the camel-archetype-java layout, issue the following command in a suitable directory.
$ mvn archetype:generate \
-DarchetypeGroupId=org.apache.camel.archetypes \
-DarchetypeArtifactId=camel-archetype-java \
-DarchetypeVersion=2.12.1  
\-DgroupId=net.kevinboone.mavencameltest\
-DartifactId=mavencameltest
The switches artifactId, groupId, and version uniquely identify the project so that it can, in principle, become part of a Maven repository itself. More prosaically, artifactId becomes the name of the new directory created to hold the project files, and groupId becomes the package name of the boilerplate Java code that Maven generates.

Note that it isn't necessary to install Camel explicitly to build a Camel project with Maven — the necessary bits will be downloaded automatically, and stored in a local repository ($HOME/.m2/repository on Linux). When we say archetypeVersion=x.x.x, we're indicating the version of Camel we want, not the version we have.
The generated project workspace will have a reasonably familiar layout, even if it is not the one a particular developer would have chosen. There is a src/main/java directory which forms the root of the Java source code; a target directory for the output of the build process, and src/main/resources for configuration files. You can freely add new directories to src/main/java and Maven will Do the Right Thing and assume you want them compiled.

I'm not going to reproduce the generated pom.xml here, but there are a few points of note.

   <mainClass>net.kevinboone.mavencameltest.MainApp</mainClass>
This entry specifies the class that will be run when we tell Maven to 'exec:java'. The source of this class is generated automatically, and contains a main() method that initializes Camel and creates a simple route.

  <dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-core</artifactId>
    <version>2.12.1</version>
  </dependency>
This section specifies a dependency — on camel-core in this case. Maven will automatically download and store dependent modules before building the project.

Step 3: Coding the route

The automatically-generated boilerplate code will create a default route; in fact, it should be ready to run as soon as the project workspace is created. However, I want to make this route the same as the one described in the earlier article on Camel, so it will be necessary to replace the default route.

If you look at the boilerplate class src/main/java/net/kevinboone/mavencameltest/MainApp.java you'll see that it delegates the actual route configuration to the class MyRouteBuilder, which is in the same package, and therefore in the same directory. This should be replaced by the following code.

/*=========================================================================
A simple Camel file copier route, for use with the Maven
camel-java archetype.
=========================================================================*/

package net.kevinboone.mavencameltest;
import org.apache.camel.builder.RouteBuilder;

public class MyRouteBuilder extends RouteBuilder
  {
  public void configure()
    {
    // Override the configure() method to define the route. We're
    //  reading from a directory, logging, and then copying to a 
    //  directory. The log class is defined to be
    //  net.kevinboone.mavencameltest.MyRouteBuilder — we'll need
    //  this information for the log4j configuration file
    from ("file:/tmp/in?noop=true")
     .to("log:net.kevinboone.mavencameltest.MyRouteBuilder?level=DEBUG")
       .to("file:/tmp/out");
    }
  }
This route copies files from the directory /tmp/in to /tmp/out, and logs that it has done so. Of course, you might need to change these directories on your system.

Step 3: configure logging

We would like to see the DEBUG-level log output from the route, but not for anything else (unless, of course, we're debugging Camel). To do this, just add the following line to src/main/resources/log4j.properties:
log4j.logger.net.kevinboone.mavencameltest.MyRouteBuilder=DEBUG
Camel itself will get the default, INFO, log level, unless you change it.

Step 4: compiling

With Maven, this could hardly be easier:
$ mvn compile
The first time you execute this command, it could take a long time, because the entire Camel framework and all its dependencies have to be downloaded to the local repository — about 100Mb in total. Thereafter it should be a lot faster.

Note that there's no need to set up any class search path — the Maven dependency management assumes that libraries downloaded to satisfy dependencies should be included on the class path.

The compiled output will be written to the target/classes directory.

Step 5: running the route

Again, with Maven, this could hardly be simpler:
$ mvn exec:java 
This tells Maven (specifically, the exec-maven-plugin) to run the Java class specified in the pom.xml file; again, the classpath is managed automatically according to the dependencies.

To test the route is working, copy a file into /tmp/in (or whatever directory you specified), and check that it appears in /tmp/out. The log should show something like this:

[ExchangePattern: InOnly, BodyType: org.apache.camel.component.file.GenericFile,  
Body: [Body is file based: GenericFile[/tmp/in/test.txt]]]

Step 6: building a self-contained application for distribution

With luck, we're now able to run the Camel application under the control of Maven. But what if we want to distribute the application, or just to run it without Maven? The problem lies in figuring out which library JARs to make available on the class search path. When we built the application manually this was conceptually easy — since we were specifying all the JARs on the command line, we would know exactly which JARs to distribute; but if we're using Maven to manage all the dependencies implicitly, we won't necessarily have that information at hand.

It is possible to get Maven to identify the dependencies in detail but, since we want in this case to build a self-contained application, it's perhaps nicer to ask Maven to build the application in a single JAR file, containing all the dependent classes. We can then run the application using only that one JAR file.

There are various ways to create a single-JAR application using Maven; here I describe how to use the maven-assembly-plugin plugin to do it. Just add the following block of almost-boilerplate code to the plugins section of pom.xml:

      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
	  <archive>
	    <manifest>
	      <mainClass>net.kevinboone.mavencameltest.MainApp</mainClass>
	    </manifest>
	  </archive>
	  <descriptorRefs>
	    <descriptorRef>jar-with-dependencies</descriptorRef>
	  </descriptorRefs>
        </configuration>
	<executions>
	  <execution>
	  <id>make-assembly</id>
	  <phase>package</phase>
	  <goals>
	    <goal>single</goal>
	  </goals>
	  </execution>
	</executions>
      </plugin>
The only program-specific code here is:
	      <mainClass>net.kevinboone.mavencameltest.MainApp</mainClass>
This tells Maven to add to the JAR file's MANIFEST.MF file the line:
main-class: net.kevinboone.mavencameltest.MainApp
This, in turn, will tell the JVM what class's main() method to run when we run the JAR. The generated jar will be
target/mavencameltest-1.0-SNAPSHOT-jar-with-dependencies.jar

To build the complete, self-contained application:

mvn compile assembly:single
Note that we still need to specify compile as a specific step. This illustrates a difference between Maven and tools like Ant (and Make, for that matter): Maven manages dependencies between the project and its libraries, but it isn't easy to specify dependencies between build steps. With Ant, it would be trivially easy to specify that the 'assemble' operation was dependent on successful completion of the 'compile' operation. With Maven there is, so far as I know, no obvious way to do that.

To run the JAR application, just use the command:

java -jar target/mavencameltest-1.0-SNAPSHOT-jar-with-dependencies.jar
Because this JAR is self-contained, it can be used on a system without Maven.

Summary

I hope it's clear that using Maven takes away a lot of the administrative hassle from setting up a Java application with complex dependencies. The only thing left for the developer to do is to write the actual code...

Downloads

Source code bundle

Copyright © 1994-2013 Kevin Boone. Updated May 01 2014