Why Maven dependency exclusion would not cause compile error? - java

Newly exposed to Maven, I can understand the use case of the <exclusion> tag, but not sure why it wouldn't cause compile error:
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>2.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</exclusion>
</exclusions>
</dependency>
...
Is this only possible only when you have another direct dependency on maven-core? otherwise, compile error should happen. (assuming maven-core is used somewhere in maven-embedder)

You are excluding that artifact from that specific dependency, but it could be getting pulled in from another dependency. Using something mvn dependency:tree -Dverbose -Dincludes=maven-core should show you what else is introducing the dependency. The Maven Enforcer plugin can also help exclude transitive dependencies.

There are different possibilities:
As Carl said: Check your dependency:tree if the dependency is not pulled in from somewhere else.
It is possible that maven-core is not used at all, even if maven-embedder indeed uses it: Assume e.g. that maven-embedder has two classes A and B. You only use A, but maven-core is only used by B. Then (if A and B do not use each other), your project might be entirely independent of maven-core. (A side remark: some jars should logically be two separate jars, but where merged together by whatever reason - in our example, one should think about putting A and B in separate artifacts).
It is possible that transitive dependencies are not necessary at compile time, but are used at runtime.

The error will not thrown in the compile time, It will thrown in run time if you use any feature depends on maven-core

Related

Excluding and Reimporting a Maven Dependency with Different Version

This example is a hypothetical scenario based on a real-world problem. Consider a block taken from a pom.xml file as given below. In this scenario, I want to exclude artifact-b dependency from artifact-a and reimport it with a different version. Obviously, excluded artifact-b version is not equal to ${product2.version}.
<dependency>
<groupId>com.company.product1</groupId>
<artifactId>artifact-a</artifactId>
<version>${product1.version}</version>
<exclusions>
<exclusion>
<groupId>com.company.product2</groupId>
<artifactId>artifact-b</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.company.product2</groupId>
<artifactId>artifact-b</artifactId>
<version>${product2.version}</version>
</dependency>
Thus, when mvn install command is executed for the final application, I will only have the artifact-b with ${product2.version} under the folder where the application stores the jars collected from its dependencies.
This is the expected behaviour for my module, but does it also mean that the methods in artifact-a jar will now make calls to the artifact-b jar with ${product2.version}? If so (which is what I expect since target repository does not include the excluded version), what happens if the called method in artifact-b is different in the new version? Does it only check method signatures or are there other factors that can cause compilation/runtime errors?
First of all, as khmarbaise said, the exclusion is unnecessary.
Secondly, increasing the library version can cause all kinds of problems. The compile time problems are mainly method signatures or missing classes, but at runtime, anything can happen, depending on the changes made in the source code of that library.
You can just hope that the maintainers of the library tried to keep everything backwards compatible.

Maven dependencies exclusions not working

I'm using enforcer plugin of Maven and I see a behavior that I dont quite understand and it's dangerous.
Let's say that I have a conflict since dependency A has bla.jar:1.0 and is in conflict with my dependnecy B which has bla.jar:2.0
Then to fix the conflict, I make an exclude of bla.jar:1.0 from A
<dependency>
<groupId>com.foo</groupId>
<artifactId>A</artifactId>
<version>a.version.bla</version>
<exclusions>
<exclusion>
<groupId>com.omg</groupId>
<artifactId>bla</artifactId>
</exclusion>
</exclusions>
</dependency>
expecting the application will get the bla.jar:2.0 fron classpath. But then I see when I run some unit test that the java proce3ss cannot find bla.jar ion the classpath at all and is giving me ClassNotFound in runtime.
Any idea what's wrong here?
I have in my pom defined from top to bottom B and then A
Please note that exclusions are not the best way to resolve dependency version conflicts.
The best approach is to use <dependencyManagement>. It allows you to set a version that replaces all transitive versions of that dependency.
In your case, I would first change the exclusion to <dependencyManagement>. Then I would proceed in the following way:
Check mvn dependency:list which version of the dependency is on the classpath. It should be the one specified in <dependencyManagement> unless there is no version of that dependency in your dependency tree. If you find more than one, then probably the groupId changed at some point. Then you need exclusions.
Check the scope of the dependency and verify that it is indeed compile.
Then open the dependency jar and see whether this jar really contains the class for which you get ClassNotFound. Often classes change from version to version.

Why isn't Maven overridden scope recognized transitively?

This is an interesting state of affairs in Maven that I didn't expect. Maybe someone can explain exactly why this is happening.
I have a parent POM foobar-parent that declares logback-classic with a test scope in the <dependencyManagement> section.
I have a separate project project example that has its own example-parent, which inherits from foobar-parent and also serves as a parent to its submodules.
One submodule example-foo overrides the dependency logback-classic and gives it compile scope:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>compile</scope>
</dependency>
Lastly I have another submodule example-bar which uses example-foo as a dependency.
Strangely, for the effective POM of example-bar, it shows that logback-classic has test scope!! Since example-foo declares logback-classic to be of compile scope (meaning it is required at compile time), and since example-bar has a compile-time dependency to example-foo, I expected example-bar to bring in logback-classic as a transitive dependency.
The way I interpret this is that the test scope specified in the <dependencyManagement> management section of a parent POM will override the scope of a transitive dependency from the compile scope!! Is this a correct interpretation, and is that how Maven is supposed to work?
You are right: "dependency management takes precedence over dependency mediation for transitive dependencies" (taken from Introduction to the Dependency Mechanism)

Is it possible to include only the used classes in a war file with maven build?

Say I have this dependency in my pom.xml file:
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
</dependency>
When I do a
clean install
all the javaee-api-6.0.jar will be included in the war file under WEB-INF\lib folder.
Is it possible that instead of including the whole jar, only classes that I use and their dependencies are included?
If you're deploying into a Java EE application server, that entire JAR is already provided by the application server, and can be omitted from the WAR file. You can accomplish this by putting it into the provided scope:
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
That makes that dependency available for compilation and test execution, but will not package it into the WAR.
In contrast, trying to determine which individual classes you need so you can only include their class files is an ultimately pointless endeveor. The JVM only loads classes when they are used - that is, unused classes are not loaded.
It is generally impossible to identify the used classes at compile time due to reflection. For instance, consider:
System.console().printf("Please specify the implementation class to use");
String className = System.console().readLine();
FooService service = (FooService) Class.forName(className).newInstance();
service.work();
You can get the JVM to log which classes are loaded, but different executions can use different classes ...
It's not a viable option - at least not in maven, although You know which classes You are using, but You don't know what are the dependencies for each class that You imported - so it might be impossible satisfy it's requirements. This is why we are using tools like maven - to ease the process importing a library.
Read some more about reduce size of built project and see what are Your options there
Except for UberJAR, Your biggest chance (IMHO) would be to identify libraries that are provided by the container, and use provided scope for them.
You also could integrate 3rd party tools like ProGuard
You could use exclusions.
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
<exclusion>
<groupId>...</groupId>
<artifactId>...</artifactId>
</exclusion>
</dependency>
But I don't think you could exclude at class-levels. This only excludes dependencies useful when there are conflicting dependencies in your project.
It is really not a viable option in my opinion ,as its almost impossible to know internals what all classes are required at runtime until and unless you are seeing the,implementation of all the,3rd part apis that you are using.
I also think the whole idea behind the maven is to ease the development and build process so that you won't have to do any effort in identifying the artifacts that are required at runtime or compile time. Maven will automatically figure out that for you.

Maven 2 - different dependency versions in test and compile

I have project that depends on commons-httpclient [2.0] (compile).
I would like to write some jbehave tests - jbehave-core 3.4.5 (test).
Both this dependencies depend on commons-lang but in different versions - 1.0.1 and 2.5.
When I execute mvn package I get [BUID FAILURE] in tests section.
There is an exception for my testcase in surefire-plugin output:
java.lang.NoSuchMethodError: org.apache.commons.lang.StringUtils.substringBeforeLast(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
As I looked in source code - in commons-lang 1.0.1 - indeed, there is no StringUtils.substringBeforeLast(...) method.
Why maven uses commons-lang from commons-httpclient (compile) and not from jbehave-core in testing?
I can't afford to exclude this conflicting dependency in commons-httpclient so it must stay in compile time.
So how can this be resolved - commons-lang 2.5 version in testing and 1.0.1 in compile time?
Maven 3:
Maven 3 will attempt to obtain the nearest dependency, effectively ensuring that only one of the compile or test scoped dependency is used for both the compile and test phases.
(Thanks Vineet Reynolds)
Maven 2 (OLD):
try to define 2 different <dependency> tags with different versions and scopes. Use tag <scope>test</scope> inside dependency for tests and <scope>compile</scope> for compilation.
In Maven 3, you can trick maven by adding a dot after groupId
<dependency>
<groupId>groupId.</groupId>
<artifactId>artifactId</artifactId>
<version>version1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>version2</version>
<scope>compile</scope>
</dependency>
The sequence matters here. need to have test first and then compile.
<dependency>
<groupId>groupId.</groupId>
<artifactId>artifactId</artifactId>
<version>version1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>version2</version>
<scope>compile</scope>
</dependency>
Adding a dot doesnt work in pom.xml as . is converted to slash which in return generated incorrect URL.
Is thr any other way to do this
It's a really bad idea to have two different versions for compile and test dependency:
Your non-test code might rely on behavior of the newer JAR and fail when using classes of the older JAR.
When you use the older JAR in your tests, the non-test code would fail with the old JAR.
Otherwise you could have used the older JAR anywhere, with the same version...
If you get both JAR versions into your classpath, you cannot know which one gets picked up when running your tests. That's also a bad idea.
Hence you should get non-test and test to the same JAR version dependency.

Categories

Resources