Missinglink
Build time tool for detecting link problems in java projects
Install / Use
/learn @spotify/MissinglinkREADME
missing-link - a maven dependency problem finder
Be warned. This project is still immature and in development. The API may change at any time. It may not find all problems. It may find lots of false positives.
Quickstart - add missinglink to your Maven build
Add the following plugin to pom.xml:
<plugin>
<groupId>com.spotify</groupId>
<artifactId>missinglink-maven-plugin</artifactId>
<version>0.2.1</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
See how to configure the plugin below.
Problem definition
When using Java and Maven, it's easy to get into a state of pulling in a lot of dependencies. Sometimes you even get transitive dependencies (I depend on X which in turn depends on Y). This can lead to conflicting dependencies sometimes.
I depend on libraries X and Y. X depends on Foo v2.0.0 and Y depends on Foo v3.0.0
Thus, I now have transitive dependencies on two different (incompatible) versions of Foo. Which one do I pick?
If I pick v2.0.0, Y may fail in runtime due to missing classes or methods. If I pick v3.0.0, X may fail instead.
In order to solve this, maven has an enforcer plugin which can detect these problems. Then you have to manually choose one of the versions and hope that it works.
You can also try to upgrade library X to use Foo v3.0.0. Sometimes this is tricky and time-consuming, especially if X is a foreign dependency.
A new approach at solving some of the problems
The idea is to programmatically analyze each dependency - what does the code depend on and what does it export - on a lower level. Instead of just looking at version numbers, we look at the actual signatures in the code.
For instance, maybe the difference between Foo v2.0.0 and Foo v3.0.0 is only this method signature:
// Foo v2.0.0
void Foo.
bar(String s, int i);
// Foo v3.0.0
void Foo.
bar(String s, boolean b);
If X or Y doesn't actually use this method, it may not matter if we're using version 2 or 3. This is often the case of large libraries where we only use a small subset of the methods (google guava for instance).
(Note: I am only looking at this from an API perspective - the actual code may have different behaviour which is out of scope for this project)
Maven plugin
This problem finder can be executed against your Maven project from the command-line like:
$ mvn com.spotify:missinglink-maven-plugin:0.2.1:check
The plugin will scan the source code of the current project, the runtime dependencies (from the Maven model), and the bootstrap JDK classes (i.e.
java.lang, java.util) for conflicts. Any conflicts found will be printed out, grouped by category of the conflict, the artifact (jar file) it was found it, and the problematic class.
Requirements
This plugin is using Java 8 language features. While the JVM used to execute Maven must be at version 1.8 or greater, the Maven projects being analyzed can be using any Java source version.
Note that when using a higher JVM version to execute Maven than what the project is being compiled with (the source argument to
maven-compiler-plugin), some care should be taken to make sure that the higher-versioned bootclasspath is not accidentally used with javac.
Configuration of the plugin
Once projects get to be of a certain size, some level of conflicts - mostly innocent - between the various dependencies and inter-dependencies of the libraries used are inevitable. In this case, you will probably want to add the
missinglink-maven-plugin as a <plugin> to your pom.xml so you can tweak some of its configuration options.
For example, ch.qos.logback:logback-core includes a bunch of optional classes that reference groovy.lang classes. Since the logback dependency specifies its dependency on groovy as optional=true, the Groovy jar is not automatically included in your project (unless you explicitly need it).
The missinglink-maven-plugin offers a few configuration options that can be used to reduce the number of warnings to avoid drowning in "false" positives.
The suggested workflow for using this plugin is to execute it against your project once with no configuration, then carefully add dependencies/packages to the ignores list after you are sure these are not true issues.
To add the plugin to your project, add the following to the <plugins> section:
<plugin>
<groupId>com.spotify</groupId>
<artifactId>missinglink-maven-plugin</artifactId>
<version>VERSION</version>
</plugin>
The plugin can be specified to fail the build if any conflicts are found. To automatically execute the plugin on each build, add an <execution> section like:
<configuration>
<failOnConflicts>true</failOnConflicts>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
Exclude some dependencies from analysis
Specific dependencies can be excluded from analysis if you know that all conflicts within that jar are "false" or irrelevant to your project.
For example, logback-core and logback-classic have many references (in optional classes) to classes needed by the Groovy language. To exclude these jars from being analyzed, add an <excludeDependencies> section to <configuration>
like:
<excludeDependencies>
<excludeDependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</excludeDependency>
<excludeDependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</excludeDependency>
</excludeDependencies>
Ignore conflicts in certain packages
Conflicts can be ignored based on the package name of the class that has the conflict. There are separate configuration options for ignoring conflicts on the "source" side of the conflict and the "destination" side of the conflict.
For example, if com.foo.Bar calls a method void doSomething(int) in the
biz.blah.Something class, then com.foo.Bar is on the source/calling side and biz.blah.Something is on the destination/callee side.
Packages on the source side can be ignored with <ignoreSourcePackages> and packages on the destination side can be ignored with
<ignoreDestinationPackages>:
<configuration>
<!-- ignore conflicts with groovy.lang on the caller side -->
<ignoreSourcePackages>
<ignoreSourcePackages>
<package>groovy.lang</package>
</ignoreSourcePackages>
</ignoreSourcePackages>
<!-- ignore conflicts with com.foo on the callee side -->
<ignoreDestinationPackages>
<ignoreDestinationPackage>
<package>com.foo</package>
</ignoreDestinationPackage>
</ignoreDestinationPackages>
</configuration>
By default, all subpackages of the specified packages are also ignored, but this can be disabled on an individual basis by adding
<filterSubpackages>false</filterSubpackages> to the <ignoreSourcePackage>
or <ignoreDestinationPackage> element. Note: In previous releases (<=0.2.5), this setting was named
ignoreSubpackages. Setting ignoreSubpackages in your pom.xml is still supported; the plugin will translate it to the new key value.
Target only conflicts from certain packages
Conversely, the plugin can be configured to only report on conflicts in specific packages, based on the name of the class that has the conflict. There are separate configuration options for targeting conflicts on the "source" side of the conflict and the "destination" side of the conflict.
Packages on the source side can be targeted with <targetSourcePackages> and packages on the destination side can be targeted with <targetDestinationPackages>:
<configuration>
<!-- Only target conflicts coming from groovy.lang source package -->
<targetSourcePackages>
<targetSourcePackage>
<package>groovy.lang</package>
</targetSourcePackage>
</targetSourcePackages>
<!-- Only target conflicts coming from com.foo package on the callee side -->
<targetDestinationPackages>
<targetDestinationPackage>
<package>com.foo</package>
</targetDestinationPackage>
</targetDestinationPackages>
</configuration>
By default, all subpackages of the specified packages are also ignored, but this can be disabled on an individual basis by adding
<filterSubpackages>false</filterSubpackages> to the <ignoreSourcePackage>
or <ignoreDestinationPackage> element.
Note that target* options CANNOT be used in conjunction with ignore* options. You can only specify one or the other.
Caveats and Limitations
Because this plugin analyzes the bytecode of the .class files of your code and all its dependencies, it has a few limitations which prevent conflicts from being found in certain scenarios.
Reflection
When reflection is used to load a class or invoke a method, this tool is not able to follow the call graph past the point of reflection.
Dependency Injection containers
Most DI containers, such as Guice, use reflection to load modules at runtime and wire object graphs together; therefore this tool can't follow the connection between your source code and any modules that might be loaded by Guice or other contain
Related Skills
node-connect
341.8kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
84.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
341.8kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
84.6kCommit, push, and open a PR
