Adding a local jar to Maven

A supplier has given a couple of jars to add to our java project which uses Spring Boot and Maven. Those files are not published in any Maven repository and we do not have one at work yet.

Development phase

I am not very knowledgeable of Spring Boot and Maven and had trouble adding those jars to share with my colleague. Because of the lack of a Maven repository, I just wanted to add the jars into our repository in a libs directory, at the same level than the pom.xml.
I did not want to use the method described on the Maven website because it would force every developer to execute those commands.

After some Googling, I got the following:

<dependency>
    <groupId>supplier</groupId>
    <artifactId>jar1</artifactId>
    <version>8.6.2.2</version>
    <scope>system</scope>
    <systemPath>${basedir}/libs/supplier/jar1-8.6.2.2.jar</systemPath>
</dependency>

<dependency>
    <groupId>supplier</groupId>
    <artifactId>jar2</artifactId>
    <version>8.6.2.2</version>
    <scope>system</scope>
    <systemPath>${basedir}/libs/supplier/jar2-8.6.2.2.jar</systemPath>
</dependency>

However, system scope are deprecated. Nonetheless, it did the job for a first version and allowed me to continue with developing the feature.

Release phase

After the feature was developped and tested locally, it was time to release. Creating the jar and deploying to our staging environment, and … the service did not start, there was a cyclic dependency in the initialized beans (excerpt of simplified log):

β”Œβ”€β”€β”€β”€β”€β”
|  subscriberService defined in URL [jar:file:xxx.jar!/BOOT-INF/classes!/SubscriberService.class]
↑     ↓
|  emailSenderService (field private SiteService EmailSenderService.siteService)
↑     ↓
|  siteService (field private SubscriberService SiteService.subscriberService)
β””β”€β”€β”€β”€β”€β”˜

Testing in development mode again started the application correctly, but running the generated package locally, I also had the same cyclic dependency. There was such a cycle in the code base, but it was still running fine locally. Anyway, we tried fixing the cycle, run tests, all good, package, run the package and … NoClassDefFoundError

The two jars I mentioned earlier were not packaged in the final package. Maven does not add system scope packages by default. There were several ways to solve this quickly because we had to release.

Bundle system scope dependencies

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-Maven-plugin</artifactId>
    <configuration>
        <includeSystemScope>true</includeSystemScope>
    </configuration>
</plugin>

This works fine, but is deprecated, it did the job for this release as it was the quickest.

Create a local Maven repository in the jar directory

As explained here: https://gist.github.com/timmolderez/92bea7cc90201cd3273a07cf21d119eb

Define the directory where the jars are located into a Maven repository and add it to our pom.xml. This is just the equivalent of taking the result of the following maven command in the local repository:

$ mvn install:install-file -Dfile=${jarfile} -DartifactId=${artifactId} -Dversion=${version}  -Dpackaging=jar -DgroupId=${groupId}

It seems only ok if there are a few files that do not change.

Create a remote Maven repository

The long term and good way if more external jars are to be added is to create a remote Maven repository so that developers can share packages together. We do not publish individual libs and just have one monorepo for the java code, that’s why a Maven repository was not needed before. However, it adds another external service to maintain.

Install local jars

Dependencies aside, there are several ways to add a local jar to the final package. However, I find this method redundant with declaring dependencies and not very clean.

addjars-Maven-plugin

Using a specific plugin: https://code.google.com/archive/p/addjars-Maven-plugin/

<plugin>
  <groupId>com.googlecode.addjars-Maven-plugin</groupId>
  <artifactId>addjars-Maven-plugin</artifactId>
  <version>1.0.2</version>
  <executions>
    <execution>
        <goals>
            <goal>add-jars</goal>
        </goals>
        <configuration>
            <resources>
                <resource>
                    <directory>${basedir}/lib/foo</directory>
                </resource>
            </resources>
        </configuration>
    </execution>
  </executions>
</plugin>

maven-install-plugin

The solution is described here https://www.baeldung.com/install-local-jar-with-maven/ for the validation phase and here for multiple files during the initialize phase. They both use the standard maven install plugin.

Excerpt:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-install-plugin</artifactId>
    <executions>
        <execution>
            <id>inst_1</id>
            <phase>initialize</phase>
            <goals>
                <goal>install-file</goal>
            </goals>
            <configuration>
                <!-- config for file 1 -->
            </configuration>
        </execution>
        <execution>
            <id>inst_2</id>
            <phase>initialize</phase>
            <goals>
                <goal>install-file</goal>
            </goals>
            <configuration>
                <!-- config for file 2 -->
            </configuration>
        </execution>
        <!-- execution file 3... -->
    </executions>
</plugin>

Conclusion

Still bitten by packaging software woes.
I just included the system scope dependencies to release quicky during the development phase, not expecting it would blow up during release and packaging. Because we do not require a remote repository for our own needs and two jars, I will settle on the local maven repository for the moment.

I found the following page describing how a maven repository works very useful to understand the layout and the role of each file.