Circular dependencies in Maven and horrible structure of project - java

I have a problem with maven. I have a huge multi-module project with limited possibilities to change the structure of it.
So, let's say I have 3 modules: A,B,C.
A is a parent of B and C.
C uses classes from B, so we have a B as a dependency in C's POM file.
B doesn't need to have a C as a dependency to be successfully compiled.
For now, there aren't any problems.
Unfortunately, B uses C during the runtime (spring, ioc, ...), so someone added C as B's dependency, so we have a horrible cycle in Maven. Build finishes with failure (something like "cycle detected" in log).
I would like to keep it this way (provide somehow C dependency in B module) as I need to compile and deliver B with all needed JAR archives (including JARs from C).
Can I somehow build C and copy its JARs to B's target directory after the full compilation of B? Is there a plugin or tool which can be used by maven to do this?
If this post is not clear, I will try to describe it in more details.
Thanks in advance ;)

It sounds like there are two issues:
How to tell Maven about a runtime-only dependency between artifacts.
How to get Maven to leverage the dependency to pull together all the relevant dependencies.
Maven has a feature of dependency management that allows you to inform maven of such nuances using the "scope" element of the dependency element. In this case, I think you want
<scope>runtime</scope>
See the documentation. In particular:
runtime - This scope indicates that the dependency is not required for compilation, but is for execution. It is in the runtime and test classpaths, but not the compile classpath.
Proper utilization of the runtime scope should resolve the circular dependency issue.
Regarding the second issue, you have not supplied enough information to provide a definitive answer. However, you almost certainly want to use a maven plugin in order to leverage maven's information about the dependencies. For example, if you want to produce a single "fat" jar that contains everything, you want to look at maven-shade-plugin. Another option is maven-assembly-plugin, which is extremely flexible and can include all dependencies in the assembly. There are other plugins that excel at handling various other common circumstances. You may want to formulate a separate question if you have problems with how to use a specific plugin.

Here is how I would do it. From C's pom file:
<profile>
<id>compile</id>
<dependencies>
<dependency>
use B here but do not use in the main dependencies section
</dependency>
+ other dependencies
</dependencies>
</build>
</profile>
For example you can compile C module using mvn compile -Pcompile
From B's pom file:
<profile>
<id>run</id>
<dependencies>
<dependency>
use C here but do not use in the main dependencies section
</dependency>
+ other dependencies
</dependencies>
</build>
</profile>
you run B module using mvn yourcommandforrunning -Prun
In this way you can escape the cyclic dependencies issue.

Related

Understanding Maven dependencies and assembly

I am not very much experienced with Maven and it's compilation and packaging logic gets me confused.
I have some dependencies declares as :
<dependency>
<groupId>com.dependency_group</groupId>
<artifactId>dependency_1</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.dependency_group</groupId>
<artifactId>dependency_2</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
So as far as I understand, dependency_1 will be added to the classpath of my program as something that comes along with my jar, and dependency_2 on the other hand, will be added to the classpath as something that the system runtime will provide upon deployment.
Then I run the package goal of Maven and none of my dependencies are packed with my code (I am using the shade plugin, but even without it nothing changes).
I expected that when some dependency is set as compile scope, it will be exported with my compiled code, since AFAICS, there's no point in setting the classpath saying a dependency will come along with my code, and Maven just don't package that dependency with it. It looks to me as if Maven is not obeying it's contract.
So:
1 - What's the logic behind this?
2 - Do I have to always use the Assembly plugin?
3 - Are there cases where people will define a dependency as compile and will not want it packaged within a jar?
Let me shed some light on the main point here. There are fundamentally two kinds of java artifacts:
Applications, i.e. ears, wars, executable jars
Libraries, i.e. jars that are meant to be used as dependencies for other artifacts.
For Applications your reasoning makes perfectly sense. Wars and Ears automatically package all their compile dependencies and you need no assembly plugin for that. For libraries, you do not pack the dependencies into the library. Maven handles transitive dependency resolution and would be confused if you put a fat jar on the classpath.
The thing is that packaging jar can be both a libary or an application. If you want a standalone application, you need to tell Maven to package everything, e.g. by using the assembly plugin or shade plugin.
You use compile scope when you want some dependencies to come along with your code. For example you want Jackson to be a part of your application if you are using it for json serialization.
You use provided scope, if you want dependency to be on the classpath during the compilation but wont be included within your application. It must be provided by running environment. For example you want Lombok as it is compile only library, or you want to have Servlet Api dependency as provided when you are writing servlet application because such app will be ran on servlet container thus there is no need to pack it within your application (it will be available in container runtime)
Do I have to always use the Assembly plugin
Nobody forces you to do so.

Maven - installed to local repo, not getting transitive dependencies

I have a maven project that I'd like to share with several of my other projects. It has some custom code, and then a few dependencies on things like log4j, jasypt, etc.
I build it and install it to my local maven repo. I can see it's successfully put there. Looking at .m2/repository/derp/Foo/1.0 it has a .pom with all its dependencies defined.
I can also define it as a dependency in my higher level projects, and it compiles.
<dependency>
<groupId>my.group</groupId>
<artifactId>Foo</artifactId>
<version>1.0</version>
</dependency>
I can see the Jar in my 'Maven Dependencies' in eclipse, and expanding that jar I can see it has the correct pom with dependencies in META-INF/maven/derp/Foo/pom.xml.
But my top level project above Foo isn't getting the dependencies that Foo needs. I get runtime exceptions, and I can see none of the transitive dependency jars are in my maven dependencies in eclipse.
What do I need to do to make sure Maven knows to look at the Pom for Foo in my local repo, so it will get all the transitive dependencies it needs?
----- edit -----
Answer to comment below, they are defined like this, with the dependencies tag at the top level under the project tag.
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.2</version>
</dependency>
etc...
These are the jars that maven correctly finds when I am just building and running this Foo project, but maven doesn't include these jars if a project depends on Foo, (Bar), and I find out when I try to run Bar.
What does "top level project above Foo isn't getting the dependencies" mean?
Anything "above" Foo in the build reactor should depend directly on Foo as you have stated. The <dependency/> specified will resolve to Foo's dependencies (within the scope that Foo specifies).
Without understanding the specifics of your project, it's improbable that we can to help you any further.
Some possible common situations:
You expect to be able to get access to test scoped dependencies in some non-test phase of execution. Just not true.
You expect that specifying a dependency on an artifact causes the java runtime to load those dependencies for you automagically. That's also not true. You'll want to invoke the exec:java goal on the maven exec plugin and specify your desired resolution scope within the <configuration/>, possibly also for that <execution/>.
You've mistaken <dependencyManagement> for <dependencies>. This happens way more than I would have expected.

Maven dependency resolution (conflicted)

Let's say I have four projects:
Project A (has a dependency on B and D)
Project B (has a dependency on D)
Project C (has a dependency on D)
Project D
In this scenario if I run project A, Maven will correctly resolve the dependency to D. If I understand this correctly Maven always takes the dependency with the shortest path. Since D is a direct dependency of A it will be used rather then, the D which is specified within B.
But now assume this structure:
Project A (has a dependency on B and C)
Project B (has a dependency on D)
Project C (has a dependency on D)
Project D
In this case the paths to resolving D have the same depth. What happens is that Maven will have a conflict. I know that it is possible to tell Maven that he should exclude dependencies. But my question is how to address such kind of problems. I mean in a real world application you have a lot of dependencies and possibly a lot of conflicts as well.
Is the best practice solution really to exclude stuff or are there other possible solutions to this? I find it very hard to deal with when i suddenly get a ClassNotFound Exception because some versions have changed, which caused Maven to take a different dependency. Of course, knowing this fact makes it a little bit easier to guess that the problem is a dependency conflict.
I'm using maven 2.1-SNAPSHOT.
The maven way of resolving situations like this is to include a <dependencyManagement> section in your project's root pom, where you specify which version of which library will be used.
EDIT:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>foo</groupId>
<artifactId>bar</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</dependencyManagement>
Now no matter which version of library foo:bar is requested by a dependency, version 1.2.3 will always be used for this project and all sub-projects.
Reference:
Dependency Management
Maven can handle both situations without any conflict. Conflicts will exist when two versions of a transitive dependency are required. The ClassNotFoundException you describe results from the app (or a dependency) attempting to use a class not available in the version of the conflicted dependency that actually gets used.
There are multiple ways to fix the problem.
Update the versions of the libraries you are using that depend on the conflicted dependency, so that they all depend on the same version version of that dependency
Declare the conflicted dependency as a direct dependency of your project with the version you want to be included (in the example, the one with the missing class included in it)
Specify which version of the conflicted dependency that transitive dependencies should use, via the <dependencyManagement> section of the POM
Explicitly exclude the unwanted versions of the conflicted dependency from being included with the dependencies that rely on them using an <exclusion>
This is fundamentally not a maven issue, but a java issue.
If Project B and Project C needs two incompatible versions of project D, then you can't use them both in Project A.
The Maven way of resolving conflicts like these is unfortunately, as you already know, to choose which ones to exclude.
Using mvn dependency:analyze and mvn dependency:tree helps in finding what conflicts you have.
You can enforce consistent dependencies in whole project with rule Dependency Convergence.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<DependencyConvergence/>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
One possible strategy is to specify for main project, what version of D to use (the newest one f.g.). However, if library D is not backward-compatible, you have a problem as stated by kukudas - it's impossible to use both libaries in your project.
In such situation there may be necessary to use either B or C in older version, so that both would depend on compatibile versions of D.

Create dependency groups in Maven for reuse - including 'provided' dependencies

I am new to Maven and am setting up my first maven project. I am also creating some maven assets in the form of some poms that can be inherited from or used as dependencies in any future projects as well. I want to group dependencies together and to be able to selectively add them to a project as needed.
I read this article on pom best practices. I like the idea of grouping related dependencies together into poms and then adding the pom as a dependency to a project as needed. This approach works great for compile scoped dependencies. However it fails for provided scoped ones since as transitive dependencies, they get omitted.
Here's an example of what I mean: Lets say I group together web dependencies for my projects into a web-deps pom.xml. These include compile scoped spring framework dependencies and also a provided scoped javaee one:
<modelVersion>4.0.0</modelVersion>
<groupId>com.xyz</groupId>
<artifactId>mvn-web-deps</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>javaee</groupId>
<artifactId>javaee-api</artifactId>
<version>${javaee.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
I then add this pom as a dependency in another project:
<modelVersion>4.0.0</modelVersion>
<groupId>com.xyz</groupId>
<artifactId>project-a</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependency>
<groupId>com.xyz</groupId>
<artifactId>mvn-web-deps</artifactId>
<version>0.0.1-SNAPSHOT</version>
<type>pom</type>
</dependency>
The dependencies in mvn-web-deps now become transitive. Since the dependency reference above is compile scoped, the provided transitive dependency gets omitted.
I want to avoid adding them to the dependency section of a parent since there can only be one parent and a project may need only some of these dependency groups, not all. I can perhaps add them to the dependencyManagement section, but then I will have to redeclare each dependency (sans the version) in each child project.
What is the correct/better way of grouping dependencies while avoiding the issues like above?
The short answer to your question is that you should only include 'provided' dependencies locally where the code requires it to compile, but not in parent pom.xml or other structures. Indicating that you have a 'provided' dependency in global pom.xml is non-sense for maven, because it does not need it to compile in such pom.xml.
Here is the long answer:
When I started using Maven, I had the same idea of trying to group artifacts and dependencies into pom.xml modules hoping they would be useful in the future. Now, that I have a bit more experience, I got to understand that it is a complete waste of time. For me, this was form of over-engineering.
I have learned to split my big projects into separate modules, each in their own subversion repository. I am including dependencies as necessary for each local module in their pom.xml. I release versioned tags of each module as I am coding and as necessary (i.e., when tested and stable).
I build my big projects by creating a separate maven project with its own pom.xml and import my modules as dependencies. From time to time, I update the module's version in the dependency when I have made a release. Then, I let maven do the job of pulling whatever it has to pull, transitively of not, when compiling/releasing the big project.
Maven allows all sorts of complex constructions and hierarchy between pom.xmls, but IMHO this feature creates unnecessary mess and complexities. So far it has not proved to be a real benefit for me. At the beginning, I was hoping that compiling one pom.xml would compile the rest properly in a cascading way. I did get some result, but what a mess to maintain in all the global pom.xml.
Releasing my module's artifacts separately and building my project on these releases has saved me so much time that I can only recommend it. In total, I have less pom.xml to maintain and they are also less complex. For the same final result...
So, if your only reason for building global/structural pom.xml is a hope to save time, I recommend abandoning this idea... Separate code in separate projects, release and THEN compile globally.
I concluded that Maven was not designed for this kind of use-case. I ended up having a parent pom.xml with all the libraries I use added to its <dependencyManagement> section. Any new projects/modules that I create have their pom.xml inherit from the parent pom.xml and add each dependency they need to their own <dependencies> section, minus the version. This scheme allows me to manage the versions for the libraries that I use and the respository declarations they need at a single place. Another advantage (over trying to create dependency bundles somehow) is that this gives more fine-grained control over the libraries added to child poms - only the dependencies that are actually needed are added.
Provided-scope dependencies are indeed inherited from parent POM, but NOT from POM defined as dependencies and I consider that a Maven weakness.
Given that Maven has also difficulties in adding modules as dependencies across module hierarchies, I can't say Maven is a sophisticated tool to manage multi-module projects. Maven expects a strict single-rooted hierarchy that is only suitable for the simplest projects.

How to handle sub projects dependencies in Maven

My project is made of 5 sub projects. One is a War, and the other 4 are jars. Basically the war project needs all 4 jar projects, and their dependencies.
I can strip down the dependencies to have something like war->A->B->C->D. Every sub project add their share of external dependencies (spring, struts, hibernate) so that in the end the war gets everything needed to run.
This looks pretty well organised and square, but then I ask myself if this is very practical to make changes.
Imagine I have to change one line of code in project D, without changing anything to its Maven dependencies. I would have to re-release project D obviously, but then I have to re-release projects C, B, A, and the war just to reflect this change in their pom files. This can be long and annoying, especially if you have to quickly release a new version to fix something in production.
I could make the war depend on all 4 projects, so then I just have to change project D version number in the war pom file. But then I have project A depending indirectly on project D 1.0 and the war specifying project D 1.1. I think that the war direct dependency would win in that case wouldn't it ?
This would make the new war release quicker, but it would also mess my sub projects dependencies, as they would be outdated.
What would be an acceptable way to handle this situation ?
There is no simple answer to your problem.
If you indeed do have a chain of transitive dependencies (A->B->C->D), then releasing each modules up the chain independently is not a bad option. Although it is tedious, there is a good chance your nested dependencies are simple lib jars and will not see changes too often. Hopefully you will not be forced to go through that process frequently. Pretend it would be the same situation as if log4j was updated and all of your modules needed to be updated as well.
Another thing to consider is your WAR's dependencies. Yes, Maven will pull dependencies in automatically for you but it is often a good practice to declare your known dependencies explicitly so you can specify a version number yourself for each module. This would mean A depends on D and the others directly. Unfortunately, if you have conflicting version numbers, as you've described, then you are looking for trouble on your classpath. If you really need to do this though, maven does allow you exclude transitive dependencies explicitly:
<project>
...
<dependencies>
<dependency>
<groupId>my.project</groupId>
<artifactId>module-B</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>my.project</groupId>
<artifactId>module-C</artifactId>
</exclusion>
<exclusion>
<groupId>my.project</groupId>
<artifactId>module-D</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>my.project</groupId>
<artifactId>module-C</artifactId>
<version>1.0</version>
<exclusions>
<exclusion>
<groupId>my.project</groupId>
<artifactId>module-D</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>my.project</groupId>
<artifactId>module-D</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
...
</project>
Here is the documentation describing these optional dependencies and exclusions.
Do you actually need to release B, C, and D independently? If not, consider using an Aggregator pom.xml file at the root of your modules. This will allow you to use SNAPSHOT versions throughout your modules and then release the bunch at once. This is the way our team manages our multi-module project. Using SNAPSHOT dependencies ensures you use the version that was JUST built when those artifacts are needed.
Do you actually release any of projects A to D independently, without the WAR? If not, I don't see any problems with your current setup. You should absolutely use the same version of any module throughout the project. Otherwise you open the door to classloader hell - believe me, you don't want to get there :-(
To make releases easier, the maven-release-plugin may help you.
The best answer these days is now use gradle which is best of ant and maven. I never really liked maven but gradle took alot of the common concepts but made it more like ant in that it is flexible so that there is no an easy answer to your question in gradle ;).

Categories