Now let's build the module containing our RESTful web service. To start with we'll setup inter-project dependencies. We know we're going to need our authentication-models module, so let's add that and see if the build goes through:

build.gradlemodule-info.java dependencies {

compile project(':authentication-models')

}

module com.alexkudlick.authentication.application {

requires com.alexkudlick.authentication.models;

}



./gradlew clean build



> Task :authentication-application:compileJava FAILED

/home/alex/workspace/modular-java-example/authentication-application/src/main/java/module-info.java:2:

error: module not found: com.alexkudlick.authentication.models

requires com.alexkudlick.authentication.models;

^

1 error



FAILURE: Build failed with an exception.



What? How does this module not exist? It's supplied by the authentication-models project, and we put a dependency in our build.gradle file, so it should be present, right?

Well, it turns out that gradle is not very smart about the order in which it builds projects. Event with the project dependency, it doesn't build authentication-models first. It just builds the projects in alphabetical order, no matter what their relationships are. The solution I've used is to create a build script to build the projects in the order in which they appear in settings.gradle :

#!/usr/bin/env bash



PROJECTS=$(cat settings.gradle | grep include | awk '{print $2}' | tr -d "'")

BUILD_COMMANDS=$(echo $PROJECTS | sed -e "s/ /:build /g" -e "s/$/:build/")

echo "./gradlew clean $BUILD_COMMANDS"

./gradlew clean $BUILD_COMMANDS



Running this gives us that sweet . From now on we'll be using ./build.sh to run the build, not ./gradlew clean build .

Ok, we've gotten that out of the way. We can build both modules. Let's try actually fleshing out the dropwizard application. First we need to add dropwizard as a dependency, and then we can try running the build:

./build.sh



./gradlew clean authentication-models:build authentication-application:build



> Task :authentication-application:compileJava FAILED



error: the unnamed module reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.jersey reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.request.logging reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.jetty reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.logging reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.metrics reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.jackson reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.servlets reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.lifecycle reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jul.to.slf4j reads package javax.annotation from both jsr305 and javax.annotation.api

error: module log4j.over.slf4j reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jcl.over.slf4j reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.jersey2 reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.annotation reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.validation reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.util reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.servlets reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.jvm reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.logback reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.jetty9 reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.json reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.core reads package javax.annotation from both jsr305 and javax.annotation.api

error: module metrics.healthchecks reads package javax.annotation from both jsr305 and javax.annotation.api

error: module argparse4j reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.setuid.java reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.datatype.guava reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.datatype.jsr310 reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.datatype.jdk8 reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.module.paramnames reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.module.afterburner reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.datatype.joda reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.jaxrs.json reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jackson.jaxrs.base reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.module.jaxb reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.databind reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jackson.annotations reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.google.common reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jsr305 reads package javax.annotation from both jsr305 and javax.annotation.api

error: module profiler reads package javax.annotation from both jsr305 and javax.annotation.api

error: module joda.time reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.dataformat.yaml reads package javax.annotation from both jsr305 and javax.annotation.api

error: module com.fasterxml.jackson.core reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.bean.validation reads package javax.annotation from both jsr305 and javax.annotation.api

error: module hibernate.validator reads package javax.annotation from both jsr305 and javax.annotation.api

error: module javax.el reads package javax.annotation from both jsr305 and javax.annotation.api

error: module org.apache.commons.lang3 reads package javax.annotation from both jsr305 and javax.annotation.api

error: module logback.classic reads package javax.annotation from both jsr305 and javax.annotation.api

error: module logback.access reads package javax.annotation from both jsr305 and javax.annotation.api

error: module logback.core reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.servlets reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.webapp reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.servlet reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.security reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.server reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.http reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.io reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.xml reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.util reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.container.servlet reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.container.servlet.core reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.server reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.metainf.services reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jetty.continuation reads package javax.annotation from both jsr305 and javax.annotation.api

error: module checker.qual reads package javax.annotation from both jsr305 and javax.annotation.api

error: module error.prone.annotations reads package javax.annotation from both jsr305 and javax.annotation.api

error: module j2objc.annotations reads package javax.annotation from both jsr305 and javax.annotation.api

error: module animal.sniffer.annotations reads package javax.annotation from both jsr305 and javax.annotation.api

error: module validation.api reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jboss.logging reads package javax.annotation from both jsr305 and javax.annotation.api

error: module classmate reads package javax.annotation from both jsr305 and javax.annotation.api

error: module snakeyaml reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.client reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.media.jaxb reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.common reads package javax.annotation from both jsr305 and javax.annotation.api

error: module javax.ws.rs.api reads package javax.annotation from both jsr305 and javax.annotation.api

error: module javax.annotation.api reads package javax.annotation from both jsr305 and javax.annotation.api

error: module hk2.locator reads package javax.annotation from both jsr305 and javax.annotation.api

error: module hk2.api reads package javax.annotation from both jsr305 and javax.annotation.api

error: module javax.inject reads package javax.annotation from both jsr305 and javax.annotation.api

error: module javax.servlet.api reads package javax.annotation from both jsr305 and javax.annotation.api

error: module jersey.guava reads package javax.annotation from both jsr305 and javax.annotation.api

error: module osgi.resource.locator reads package javax.annotation from both jsr305 and javax.annotation.api

error: module hk2.utils reads package javax.annotation from both jsr305 and javax.annotation.api

error: module aopalliance.repackaged reads package javax.annotation from both jsr305 and javax.annotation.api

error: module javassist reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.configuration reads package javax.annotation from both jsr305 and javax.annotation.api

error: module dropwizard.core reads package javax.annotation from both jsr305 and javax.annotation.api

/home/alex/workspace/modular-java-example/authentication-application/src/main/java/module-info.java:1: error: module com.alexkudlick.authentication.application reads package javax.annotation from both jsr305 and javax.annotation.api

module com.alexkudlick.authentication.application {

^

89 errors



FAILURE: Build failed with an exception.



How did such a small change lead to so many errors?

Debugging Split Packages↑ We've encountered our first real problem with the module system — Split Pacakges. The java module system doesn't allow the same package to be used in multiple modules. So, if there's a module A with classes in com.myexample , there can't be any other module on the classpath (technically, the module path) with classes in com.myexample . Note that this only refers to packages with classes in them, not parent level packages. In our case, we're going to be fine with modules that have classes in com.alexkudlick.authentication.models and com.alexkudlick.authentication.application . But we aren't fine with split packages in our dependencies. Notice that all the errors say the same thing: module X reads package javax.annotation from both jsr305 and javax.annotation.api

This tells us what to look for. But where do we look? The gradle dependencies command is a very important tool in the module builder's toolbox. This command will print out a tree of all of a project's dependencies, which will be critical in debugging split packages. Here we'll run ./gradlew authentication-application:dependencies and search for the jsr305 and javax.annotation.api (noting that java creates automatic module names by translating non-alphanumeric characters to dots). We'll see that we do in fact include dependencies that provide those two modules: +--- project :authentication-models

...

|

| --- com.google.guava:guava:23.5-jre

+--- ...

| +--- com.google.code.findbugs:jsr305:1.3.9 -> 3.0.2

|

--- io.dropwizard:dropwizard-core:1.2.9

+--- io.dropwizard:dropwizard-util:1.2.9

| +--- ...

| +--- com.google.code.findbugs:jsr305:3.0.2

+--- io.dropwizard:dropwizard-jersey:1.2.9

| +--- ...

| +--- org.glassfish.jersey.core:jersey-server:2.25.1

| | +--- org.glassfish.jersey.core:jersey-common:2.25.1

| | | +--- ...

| | | +--- javax.annotation:javax.annotation-api:1.2

, So we're getting jsr305 from both guava and dropwizard-util, and we're getting javax.annotation-api from dropwizard-jersey. Why is this a problem? To figure that out, it will help to take a look at the classes in both these jars. Our suspicion should be that they both contain classes in the javax.annotation package, because that was the package in the warning. Here's another important tool - a gradle task to print the location of all jars in the dependency tree. This will make it easy to inspect the classes in jars in our module path: task printJars {

doLast {

Set<String> printed = new HashSet<>()

configurations.each {

if (it.canBeResolved) {

it.files.each {

if (!printed.contains(it)) {

println it

printed.add(it)

}

}

}

}

}

}

Running ./gradlew authentication-application:printJars will show us the location of the jsr305 and javax.annotation-api jars: /home/alex/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar

/home/alex/.gradle/caches/modules-2/files-2.1/javax.annotation/javax.annotation-api/1.2/479c1e06db31c432330183f5cae684163f186146/javax.annotation-api-1.2.jar

Using jar tvf and some bash fu, we can see that both these jars do indeed contain classes in the javax.annotation package: javax.annotation-api-1.2.jarjsr305-3.0.2.jar jar tvf /home/alex/.gradle/caches/modules-2/files-2.1/javax.annotation/javax.annotation-api/1.2/479c1e06db31c432330183f5cae684163f186146/javax.annotation-api-1.2.jar | grep "javax/annotation/[^/]*.class" | awk '{print $8}'

javax/annotation/Generated.class

javax/annotation/ManagedBean.class

javax/annotation/PostConstruct.class

javax/annotation/PreDestroy.class

javax/annotation/Priority.class

javax/annotation/Resource$AuthenticationType.class

javax/annotation/Resource.class

javax/annotation/Resources.class

jar tvf /home/alex/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/3.0.2/25ea2e8b0c338a877313bd4672d3fe056ea78f0d/jsr305-3.0.2.jar | grep "javax/annotation/[^/]*.class" | awk '{print $8}'

javax/annotation/CheckForNull.class

javax/annotation/CheckForSigned.class

javax/annotation/CheckReturnValue.class

javax/annotation/Detainted.class

javax/annotation/MatchesPattern$Checker.class

javax/annotation/MatchesPattern.class

javax/annotation/Nonnegative$Checker.class

javax/annotation/Nonnegative.class

javax/annotation/Nonnull$Checker.class

javax/annotation/Nonnull.class

javax/annotation/Nullable.class

javax/annotation/OverridingMethodsMustInvokeSuper.class

javax/annotation/ParametersAreNonnullByDefault.class

javax/annotation/ParametersAreNullableByDefault.class

javax/annotation/PropertyKey.class

javax/annotation/RegEx$Checker.class

javax/annotation/RegEx.class

javax/annotation/Signed.class

javax/annotation/Syntax.class

javax/annotation/Tainted.class

javax/annotation/Untainted.class

javax/annotation/WillClose.class

javax/annotation/WillCloseWhenClosed.class

javax/annotation/WillNotClose.class

That means that these two jars are incompatible with a modular build - we can't have a build with both of them on the module path. We need to update our build configuration so that these are not both in our dependency tree. But which jar should we choose to keep on the module path? How do we know which we classes we will need? Since these jars provde disjoint sets of classes, we might need all of the classes on our classpath. What would we do then? In that case, I would use a techinque I call Exclude & Include. We'll talk about that later; for now, it turns out that we don't actually need the classes from the jsr305 module to build and run a dropwizard application, so we can just exclude that dependency: authentication-models/build.gradleauthentication-application/build.gradle implementation(group: 'com.google.guava', name: 'guava', version: '23.5-jre') {

exclude group: 'com.google.code.findbugs', module: 'jsr305'

}

compile(group: 'io.dropwizard', name: 'dropwizard-core', version: '1.2.9') {

exclude group: 'com.google.code.findbugs', module: 'jsr305'

}

And now we get a