Thanks to mavens behind Apache Maven!

When I started using Apache Maven to build projects, I had no clue on what was happening behind the scenes. Most of us just proceeded further as soon as we saw a “BUILD SUCCESS” statement. Our team lead once gave us few insights on what Maven does internally. This aroused my curiosity to explore more and that brings me here to share some knowledge on the same.

So, what is Apache Maven? It’s definitely difficult to answer it in a single word. All we read everywhere is, it plays the role of a build tool and a project management tool.

I may not be able to showcase the complete beauty of Maven in this blog as there’s always a space to explore more and delve a lot deeper. So, I would like to share my learnings out of few explorations which broadened my understanding.

At its heart, Maven uses configuration files defined with xml to build the projects which are the so called “pom.xml” (Project Object Model) files.

pom.xml requires us to configure the below:

groupId – which is usually the reverse domain name of the organisation that creates the project
artifactId – unique name for the project
version – project version
Thus, a project can be uniquely identified by :: e.g. (com.coviam:test-app:1.0-SNAPSHOT)

Project dependencies, plugins and many more can be configured in POM.

What exactly are these project dependencies?

1. Generally, we add JUnit tests to our projects for testing. So our project now has a dependency with JUnit testing framework. We can add this dependency as shown below:

  <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.12</version>
  </dependency>

The above JUnit jar will be taken from the Maven remote repository –https://repo.maven.apache.org/maven2/junit/junit/4.12

So, you may think what if JUnit also has some other dependencies? Should we add the dependencies of JUnit also to our pom.xml? It’s a big NO. That’s because, Maven would refer to the pom.xml of JUnit jar and manage the dependencies. In fact, a dependency in JUnit’s pom may also have many other dependencies and so on!

2. Besides just building the project, Maven can also generate a new project with a standard project structure by making use of Maven Archetype plugin. Desired type of project can be specified using archetypeArtifactId.

E.g. mvn archetype:generate -DgroupId=com.coviam.app -DartifactId=test-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

So, in https://start.spring.io/ which helps to generate maven-based project with user inputs, you can now relate that the user input fields for Group, Artifact, Version, Dependencies etc. would in turn be mapped to groupId, artifactId, version, dependencies etc. seen in pom.xml

Moving on to Maven build process…

Most of us would have used “mvn clean install” command to build the project. Have you noticed the various steps involved in the build log displayed?

default-clean:

If user specifies “clean” in the command, at first previous build artifacts (files and directories) will be cleaned up. I wondered why we should do the clean-up explicitly, as the jars would anyways get updated with the new code changes made!

How about playing around with mvn clean?

I tried out this experiment of renaming a file and do only “mvn install” (i.e. without clean) to see if the old file still hangs around in the build artifacts (stored in target directory) or not.

a. Initial project structure and build artifacts:

b. Rename an existing file:

I have renamed the file CityServiceImpl.java to CityServiceImpl_new.java and executed “mvn install” command without “clean” option. You can see that the target/classes/ contains the latest .class file only and the old .class file is no more. Same was the case when I deleted a java file too. So far, so good. But… wait a sec! Here’s a very subtle observation — Don’t we still see inputFiles.lst pointing to old java file which doesn’t exist anymore? Why not check if mvn clean would make a difference?

c. Executed “mvn clean install”. You can now see inputFiles.lst referencing to the latest file only.

With such similar experiments, I figured out that inputFiles.lst generated by maven compiler plugin is updated only after mvn clean operation.

Continuing with build phases…

default-compile, default-test, default-package, default-install:

Followed by clean up, it proceeds with the next phases of the build life cycle which are:

default-compile: Compilation of source code
default-test: Runs the tests (Note: This will be skipped if the command has –DskipTests specified)
default-package: Packages the compiled code as a distributable format like JAR
default-install: Installs the package in local maven repository
So, if you execute just “mvn package” command, all the steps in the build life cycle till (default- package) step would only get executed and the jars wouldn’t get copied to local maven repository.

Up for one last exploration?

How about tweaking with pom files by manually adding few dependencies? Consider a multi-module project with two modules say Module A, Module B.

a. Let’s say module A is dependent on module B but not vice versa. pom.xml of module A would have the below:

  <dependency>
     <groupId>com.gavi.test</groupId>
     <artifactId>moduleB</artifactId>
     <version>1.0-SNAPSHOT</version>
  </dependency>

Due to this dependency, maven would build module B first and only then module A.

b. Now, is it possible to make module A and module B interdependent as depicted below?

After adding the dependencies, the two pom.xml files would look like below:

In pom.xml of Module A

  <dependency>
     <groupId>com.gavi.test</groupId>
     <artifactId>moduleB</artifactId>
     <version>1.0-SNAPSHOT</version>
  </dependency>

In pom.xml of Module B

  <dependency>
     <groupId>com.gavi.test</groupId>
     <artifactId>moduleA</artifactId>
     <version>1.0-SNAPSHOT</version>
  </dependency>

I was waiting to see how maven would decide on which module to build first as both have interdependency.

When I tried to build the project out of curiosity, maven errored out as below mentioning about the cyclic reference!!

Amaras-MacBook-Pro:mavenTestApp amarahemavishnupriya$ mvn clean install
[INFO] Scanning for projects...
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.gavi.test:moduleB:1.0-SNAPSHOT'}' and 'Vertex{label='com.gavi.test:moduleA:1.0-SNAPSHOT'}' introduces to cycle in the graph com.gavi.test:moduleA:1.0-SNAPSHOT --> com.gavi.test:moduleB:1.0-SNAPSHOT --> com.gavi.test:moduleA:1.0-SNAPSHOT @
[ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.gavi.test:moduleB:1.0-SNAPSHOT'}' and 'Vertex{label='com.gavi.test:moduleA:1.0-SNAPSHOT'}' introduces to cycle in the graph com.gavi.test:moduleA:1.0-SNAPSHOT --> com.gavi.test:moduleB:1.0-SNAPSHOT --> com.gavi.test:moduleA:1.0-SNAPSHOT -> [Help 1]

So, we must keep this good practice in mind to avoid cyclic dependency while designing the dependencies between modules of project.

That’s all for now, but, a lot more to explore! Thanks to you all and the creative minds behind Apache Maven again!

Share