If you have ever tried to implement a certain functionality as a reusable module in Mendix, there’s a good chance you’ve had to add some jar dependencies. And you have probably realized that managing these dependencies is quite painful. Here is a list of the issues that I have encountered when trying to implement a reusable module: ranked from game-breaking to mildly annoying:

  1. If you want to use a jar file in your module, e.g. pdfbox v2.3, but another version of this jar e.g. v1.8 is already being used by another module, you are out of luck. As far as I know, this is simply not possible. The class loader will pick up only one version, leaving one of the two modules to deal with the wrong version, which usually results in the application hanging or crashing. To make matters worse, this only occurs when invoking a specific java action with a conflicting dependency at runtime.
  2. If you are maintaining a Mendix module and you need to update a jar file within it, you need to make sure everyone deletes the old jar on update. Otherwise, you end up having two jars with a different version in your classloader path, which again likely leads to the application not working.
  3. Mendix requires you to manage transitive dependencies manually. This usually means you have to run the code to see which classes are missing, then find the right jar file and add it to the project manually. Then, rinse and repeat until there are no more “NoClassFound” errors.
  4. Wasting time when exporting modules is my next pet peeve. There are too many jars to include/exclude, especially if you are using something like community commons or the rest module in your project. Of course, that could be solved simply by adding a select/deselect button in the export dependencies dialog, but that is not the point of this post. One way or another, time is lost.

One way to effectively deal with all of these issues is to employ a build tool to produce a so-called fat jar, a single jar file that contains all your dependencies. This on its own resolves multiple issues from the previous list, including: the fat jar file will be updated automatically when the module is updated, thus solving point 2; the build tool can take care of all the transitive dependencies, thus solving point 3, and finally the only dependency is a single one-jar file, eliminating point 4.

Last but not least, the first issue can be resolved using a technique called shadowing. Shadowing replaces patterns in class names with a given string. For example, you can replace org.json with community.commons.org.json. This lets the Java class-loader to load two versions of the json library because they have different class names.

Case study: Community commons

What better module to demonstrate the techniques described above than the community commons? In its current status, it has some 20 or so dependencies (check out that scroll bar).

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/be4fdf65-a338-4b0a-b0d2-6e57c5bf0372/Untitled.png

In this instance, I will be using Gradle. But you can opt to do the same thing using other tools e.g. Maven or JarJarLinks.

Adding dependencies to Gradle

First, I installed the Gradle Eclipse plugin after downloading it from the Eclipse marketplace. Next, I created a new Gradle project. The main file in every gradle project is the gradle build script, where many options can be specified, such as which Java version to use. The dependencies for a Gradle project are also defined in the build script. As you can see below, my first iteration of the build script is mostly standard stuff with the exception of the shadowing tool that I added:

buildscript {
  repositories {
    mavenCentral()//look for dependencies here
  }
  dependencies {
    classpath "com.github.jengelman.gradle.plugins:shadow:2.0.0"
    //this is the tool we will use to build the fat jar and shadow it
  }
}

plugins {
  id 'com.github.johnrengelman.shadow' version '2.0.0'
  id 'java'
}

group 'com.mendix.community-commons'
version '1.0.0'

apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'eclipse'

task wrapper(type: Wrapper) {
  gradleVersion = '3.0'
}

compileJava {
  sourceCompatibility = 1.8
  targetCompatibility = 1.8
}

repositories {
  mavenLocal()
  mavenCentral()
}

dependencies {
  // this is where we need to add all the dependencies of community commons
}

So far so good. Next, I started adding the dependencies from the community commons module:

dependencies {
  // <https://mvnrepository.com/artifact/org.owasp.antisamy/antisamy>
  compile group: 'org.owasp.antisamy', name: 'antisamy', version: '1.5.3'

  // <https://mvnrepository.com/artifact/com.google.guava/guava>
  compile group: 'com.google.guava', name: 'guava', version: '14.0.1'

  // <https://mvnrepository.com/artifact/commons-codec/commons-codec>
  compile group: 'commons-codec', name: 'commons-codec', version: '1.10'

  // <https://mvnrepository.com/artifact/org.apache.pdfbox/jempbox>
  compile group: 'org.apache.pdfbox', name: 'jempbox', version: '1.8.5'

  // <https://mvnrepository.com/artifact/joda-time/joda-time>
  compile group: 'joda-time', name: 'joda-time', version: '2.9.6'

  // <https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload>
  compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.2.1'

  // <https://mvnrepository.com/artifact/commons-io/commons-io>
  compile group: 'commons-io', name: 'commons-io', version: '2.3'

  // <https://mvnrepository.com/artifact/org.apache.commons/commons-lang3>
  compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.0'

  compile group: 'org.apache.servicemix.bundles', name: 'org.apache.servicemix.bundles.batik', version: '1.8_1'

  // <https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox>
  compile group: 'org.apache.pdfbox', name: 'pdfbox', version: '2.0.3'

  // <https://mvnrepository.com/artifact/xerces/xercesImpl>
  compile group: 'xerces', name: 'xercesImpl', version: '2.8.1'

}

I noticed that some of the dependencies are not listed in Maven Central (or at least I could not find them). No problem—I have the jar files from the community commons project on GitHub. I created a folder libs in my gradle project, and then added the com.springsource.org.apache.batik.css-1.7.0.jar and nekohtml.jar to it. Then, I added the following line to my dependencies, which, as you might expect, adds all jar files from the libs folder to the gradle project: compile fileTree(dir: 'libs', include: '*.jar')

With this, we have now resolved all Java dependencies.