| ------ |
| Optional Dependencies and Dependency Exclusions |
| ------ |
| Allan Ramirez |
| ------ |
| 23 January 2006 |
| ------ |
| |
| ~~ Licensed to the Apache Software Foundation (ASF) under one |
| ~~ or more contributor license agreements. See the NOTICE file |
| ~~ distributed with this work for additional information |
| ~~ regarding copyright ownership. The ASF licenses this file |
| ~~ to you under the Apache License, Version 2.0 (the |
| ~~ "License"); you may not use this file except in compliance |
| ~~ with the License. You may obtain a copy of the License at |
| ~~ |
| ~~ http://www.apache.org/licenses/LICENSE-2.0 |
| ~~ |
| ~~ Unless required by applicable law or agreed to in writing, |
| ~~ software distributed under the License is distributed on an |
| ~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| ~~ KIND, either express or implied. See the License for the |
| ~~ specific language governing permissions and limitations |
| ~~ under the License. |
| |
| ~~ NOTE: For help with the syntax of this file, see: |
| ~~ http://maven.apache.org/doxia/references/apt-format.html |
| |
| Introduction |
| |
| This section discusses the functionality of optional dependencies and |
| dependency exclusions. This will help users to understand what are they, |
| how to use them, how they work and when is the best way to use them. |
| It also explains why exclusions are made as per dependency basis not in |
| a POM level. |
| |
| * Optional Dependencies |
| |
| Optional dependencies are used when it's not really possible |
| (for whatever reason) to split a project up into sub-modules. |
| The idea is that some of the dependencies are only used for certain features |
| in the project, and will not be needed if that feature isn't used. Ideally, |
| such a feature would be split into a sub-module that depended on the core |
| functionality project...this new subproject would have only non-optional |
| dependencies, since you'd need them all if you decided to use the |
| subproject's functionality. |
| |
| However, since the project cannot be split up (again, for whatever reason), |
| these dependencies are declared optional. If a user wants to use |
| functionality related to an optional dependency, they will have to redeclare |
| that optional dependency in their own project. This is not the most clear |
| way to handle this situation, but then again both optional dependencies and |
| dependency exclusions are stop-gap solutions. |
| |
| ** Why use optional dependencies? |
| |
| It's not only important to declare optional dependencies in order to save |
| space/memory/etc. It's vital to control the list of actual dependencies a |
| person needs in order to use a project, since these jars may eventually make |
| it into a WAR, EAR, EJB, etc. Inclusion of the wrong jars may violate a |
| license agreement, cause classpath issues, etc. |
| |
| ** How do I use the optional tag? |
| |
| A dependency is declared as optional by simply setting the \<optional\> tag |
| to true in your dependency declaration. See the sample below: |
| |
| +---+ |
| |
| <project> |
| ... |
| <dependencies> |
| <!-- declare the dependency to be set as optional --> |
| <dependency> |
| <groupId>sample.ProjectA</groupId> |
| <artifactId>Project-A</artifactId> |
| <version>1.0</version> |
| <scope>compile</scope> |
| <optional>true</optional> <!-- value will be true or false only --> |
| </dependency> |
| </dependencies> |
| </project> |
| |
| +---+ |
| |
| ** How do optional dependencies work? |
| |
| +---+ |
| |
| Project-A -> Project-B |
| |
| +---+ |
| |
| The diagram above says that Project-A depends on Project-B. When A |
| declares B as an optional dependency in its POM, this relationship remains |
| unchanged. Its just like a normal build where Project-B will be added in its |
| classpath. |
| |
| +---+ |
| |
| Project-X -> Project-A |
| |
| +---+ |
| |
| But when another project(Project-X) declares Project-A as a dependency in |
| its POM, the optional dependency takes effect. You'll notice that |
| Project-B is not included in the classpath of Project-X; you will |
| need to declare it directly in your POM in order for B to be included |
| in X's classpath. |
| |
| ** Example |
| |
| Let us say that there is a project named <X2> that has similar functions |
| with <Hibernate> which supports many database drivers/dependencies such as |
| mysql, postgre, oracle etc. All of these dependencies are needed for X2 |
| to build but not for your project, so it is very practical for X2 to |
| declare these dependencies as optional, so that whenever your project |
| declares X2 as a direct dependency in your POM, all the drivers supported |
| by the X2 will not be automatically included to your project's classpath |
| instead you'll have to declare it directly on what driver/dependency of the |
| database you are going to use. |
| |
| * Dependency Exclusions |
| |
| Since maven 2.x resolves dependencies transitively, it is possible |
| for unwanted dependencies to be included in your project's classpath. |
| Projects that you depend on may not have declared their set of dependencies |
| correctly, for example. In order to address this special situtation, |
| maven 2.x has incorporated the notion of explicit dependency exclusion. |
| Exclusions are set on a specific dependency in your POM, and are targeted |
| at a specific groupId and artifactId. When you build your project, that |
| artifact will not be added to your project's classpath <by way of the |
| dependency in which the exclusion was declared>. |
| |
| ** How to use dependency exclusions |
| |
| We add the \<exclusions\> tag under the \<dependency\> section of the pom. |
| |
| +---+ |
| |
| <project> |
| ... |
| <dependencies> |
| <dependency> |
| <groupId>sample.ProjectA</groupId> |
| <artifactId>Project-A</artifactId> |
| <version>1.0</version> |
| <scope>compile</scope> |
| <exclusions> |
| <exclusion> <!-- declare the exclusion here --> |
| <groupId>sample.ProjectB</groupId> |
| <artifactId>Project-B</artifactId> |
| </exclusion> |
| </exclusions> |
| </dependency> |
| </dependencies> |
| </project> |
| |
| +---+ |
| |
| ** How dependency exclusion works and when to use it <<( as a last resort! )>> |
| |
| +---+ |
| |
| Project-A |
| -> Project-B |
| -> Project-D <! -- This dependency should be excluded --> |
| -> Project-E |
| -> Project-F |
| -> Project C |
| |
| +---+ |
| |
| The diagram shows that Project-A depends on both Project-B and C. Project-B |
| depends on Project-D. Project-D depends on both Project-E and F. By default, |
| Project A's classpath will include: |
| |
| +---+ |
| B, C, D, E, F |
| +---+ |
| |
| What if we dont want project D and its dependencies to be added to |
| Project A's classpath because we know some of Project-D's dependencies |
| (maybe Project-E for example) was missing from the repository, and you |
| don't need/want the functionality in Project-B that depends on Project-D |
| anyway. In this case, Project-B's developers could provide a dependency on |
| Project-D that is \<optional\>true\</optional\>, like this: |
| |
| +---+ |
| <dependency> |
| <groupId>sample.ProjectD</groupId> |
| <artifactId>ProjectD</artifactId> |
| <version>1.0-SNAPSHOT</version> |
| <optional>true</optional> |
| </dependency> |
| +---+ |
| |
| HOWEVER, they didn't. As a last resort, you still have the option to exclude it |
| on your side, in Project-A, like this: |
| |
| +---+ |
| |
| <project> |
| <modelVersion>4.0.0</modelVersion> |
| <groupId>sample.ProjectA</groupId> |
| <artifactId>Project-A</artifactId> |
| <version>1.0-SNAPSHOT</version> |
| <packaging>jar</packaging> |
| ... |
| <dependencies> |
| <dependency> |
| <groupId>sample.ProjectB</groupId> |
| <artifactId>Project-B</artifactId> |
| <version>1.0-SNAPSHOT</version> |
| <exclusions> |
| <exclusion> |
| <groupId>sample.ProjectD</groupId> <!-- Exclude Project-D from Project-B --> |
| <artifactId>Project-D</artifactId> |
| </exclusion> |
| </exclusions> |
| </dependency> |
| </dependencies> |
| </project> |
| |
| +---+ |
| |
| If we deploy the Project-A to a repository, and Project-X declares a |
| normal dependency on Project-A, will Project-D be excluded from the |
| classpath still? |
| |
| +---+ |
| |
| Project-X -> Project-A |
| |
| +---+ |
| |
| The answer is <<Yes>>. Project-A has declared that it doesn't need |
| Project-D to run, so it won't be brought in as a transitive dependency |
| of Project-A. |
| |
| Now, consider that Project-X depends on Project-Y, as in the diagram below: |
| |
| +---+ |
| |
| Project-X -> Project-Y |
| -> Project-B |
| -> Project-D |
| ... |
| |
| +---+ |
| |
| Project-Y also has a dependency on Project-B, and it does need the features |
| supported by Project-D. Therefore, it will NOT place an exclusion on |
| Project-D in its dependency list. It may also supply an additional |
| repository, from which we can resolve Project-E. In this case, it's important |
| that Project-D <<is not>> excluded globally, since it is a legitimate |
| dependency of Project-Y. |
| |
| As another scenario, what if the dependency we don't want is Project-E |
| instead of Project-D. How will we exclude it? See the diagram below: |
| |
| +---+ |
| |
| Project-A |
| -> Project-B |
| -> Project-D |
| -> Project-E <!-- Exclude this dependency --> |
| -> Project-F |
| -> Project C |
| |
| +---+ |
| |
| Exclusions work on the entire dependency graph below the point where they |
| are declared. If you wanted to exclude Project-E instead of Project-D, |
| you'd simply change the exclusion to point at Project-E, but you wouldn't |
| move the exclusion down to Project-D...you cannot change Project-D's POM. |
| If you could, you would use optional dependencies instead of exclusions, |
| or split Project-D up into multiple subprojects, each with nothing but |
| normal dependencies. |
| |
| +---+ |
| |
| <project> |
| <modelVersion>4.0.0</modelVersion> |
| <groupId>sample.ProjectA</groupId> |
| <artifactId>Project-A</artifactId> |
| <version>1.0-SNAPSHOT</version> |
| <packaging>jar</packaging> |
| ... |
| <dependencies> |
| <dependency> |
| <groupId>sample.ProjectB</groupId> |
| <artifactId>Project-B</artifactId> |
| <version>1.0-SNAPSHOT</version> |
| <exclusions> |
| <exclusion> |
| <groupId>sample.ProjectE</groupId> <!-- Exclude Project-E from Project-B --> |
| <artifactId>Project-E</artifactId> |
| </exclusion> |
| </exclusions> |
| </dependency> |
| </dependencies> |
| </project> |
| |
| +---+ |
| |
| ** Why exclusions are made on a per-dependency basis, rather than at the POM level |
| |
| This is mainly done to be sure the dependency graph is predictable, and to |
| keep inheritance effects from excluding a dependency that should not be |
| excluded. If you get to the method of last resort and have to put in an |
| exclusion, you should be absolutely certain which of your dependencies is |
| bringing in that unwanted transitive dependency. |