4

For the longest time, I thought that in Java you either had one of two types of dependencies:

  • Required compile-time dependencies (dependencies always required at compile time)
  • Possibly optional runtime dependencies (dependency that can be resolved at runtime)

Recently, I found out that compile dependencies can be optional too. For example, commons-beanutils is listed as an optional compile dependency of JXPath.

How can this work? Can a dependency really be used at the time of compilation yet remain fully optional?

EDIT: I might have been unclear. I'm looking for a case where a dependency is used at compile-time and is at the same time fully optional, or an explanation why such a dependency is impossible.

Guillaume F.
  • 1,010
  • 7
  • 21
  • 3
    A class can compile to an interface but the implementation of that interface is not needed during compilation. The implementation is needed during runtime. – Salim Jan 25 '20 at 21:02

3 Answers3

3

A class can compile to an interface but the implementation of that interface is not needed during compilation. The implementation is needed during runtime.

Example commons-logging, JPA, JDBC etc which are frameworks, an application can compile based on these. At runtime an implementation is needed to execute the code. Sample implementations - Common Bean utils, Oracle thin driver, Eclipse link etc.

Salim
  • 2,046
  • 12
  • 13
  • So it is required for compilation, but optional during runtime? – Guillaume F. Jan 25 '20 at 21:12
  • It is a framework with interfaces which is required during compilation and an implementations is required during runtime. – Salim Jan 25 '20 at 21:23
  • If the framework with interfaces is required during compilation, it is not optional. My question regards to frameworks that are used during compilation, yet remain 100% optional. – Guillaume F. Jan 25 '20 at 21:27
  • Jxpath depends on common-beanutils for compilation and runtime. Example a source file BasicTypeConvertor.java (github.com/apache/commons-jxpath/blob/master/src/main/java/org/…) has reference to org.apache.commons.beanutils.ConvertUtils. – Salim Jan 26 '20 at 02:24
  • On JXPath's website, common-beanutils is listed as optional ("Optional - Yes", last column). Are you saying this is inaccurate, and that JXPath depends on it for compilation? – Guillaume F. Jan 26 '20 at 04:53
2

An extensive quote from Maven documentation describes this quite clearly:

Optional dependencies are used when it's not possible (for whatever reason) to split a project 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 depends 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 have to redeclare that optional dependency in their own project. This is not the clearest way to handle this situation, but both optional dependencies and dependency exclusions are stop-gap solutions.

Why use optional dependencies?

Optional dependencies save space and memory. They prevent problematic jars that violate a license agreement or cause classpath issues from being bundled into a WAR, EAR, fat jar, or the like.

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. It's just like a normal build where Project-B will be added in Project-A's classpath.

Project-X -> Project-A

When another project (Project-X) declares Project-A as a dependency in its POM, the optional nature of the dependency takes effect. Project-B is not included in the classpath of Project-X. You need to declare it directly in the POM of Project X for B to be included in X's classpath.

A practical example: imagine that you are a developer of a library/framework SuperLib that is built as one superlib.jar. Your library provides multiple features. Its main feature (that most of the users use) is dependency injection based on a third-party di library. However, one of your classes - EmailApi - offers features to send e-mails, using a third-party email library. Since superlib is one artifact, it needs both di and email to be compiled.

Now put yourself in the position of a user who uses superlib. They are interested in the dependency injection features. This is the core role of your library, so the dependency between superlib and di would not be optional.

However, most users are not interested in sending emails and may be bothered by having a useless email library and its dependencies added to their application (which will cause size increase of their application and may cause a dependency version clash between the dependencies of email and dependencies of the user's application). Therefore, you would mark the dependency on email as optional. As long as the user does not use your EmailApi class, everything will run fine. However, if they do use EmailApi, they will need the email dependency, otherwise the application will fail at runtime with ClassNotFoundException for whichever class from email would be referenced in EmailApi. The user of your library will need to add the email dependency explicitly in their POM.

See also When to use <optional>true</optional> and when to use <scope>provided</scope>.

Adam Michalik
  • 9,678
  • 13
  • 71
  • 102
  • Suppose project A declares B as an optional dependency (`email` lib in your example), and project X declares A as a dependency. X does not refer to B in its code, such that B is not required for X to compile. Can we conclude that the dependency of X on B is strictly needed at runtime? – Guillaume F. Jan 26 '20 at 19:09
  • I meant, if there is a dependency between X and B, is it a runtime dependency? Or could it be a compile-time dependency? (remember, the project compiles even if B is not added as a dependency to X). – Guillaume F. Jan 27 '20 at 14:30
  • @GuillaumeF. Sorry, I messed up with A, B and X in my previous comment. Here I go again: No, we cannot conclude this. From the perspective of X you do not know if you need B at runtime or not (you need to learn it eg. from A's documentation). Let's say that A has two classes: `AUsingB` and `ANotUsingB`. If in X you only use the `ANotUsingB` class, then you don't need B for runtime. If in X you ever use `AUsingB`, then **you will need B as a runtime dependency**, otherwise you'll get a ClassNotFoundException for the class of B that is referenced in `AUsingB`. – Adam Michalik Jan 27 '20 at 15:15
-1

What you described is actually a feature of Maven, the build tool, but not Java itself. Without build tools, using just 'javac' you need to specify all classes or interfaces that directly used in your code. Sure there are options for dynamic class loading and even runtime compilation, but thats not on topic.

One of use-cases with separation on interface and implementation is described in previous answer, another popular case is based on classpath scanning: if some specific class is present in classpath and/or has specific annotation - an optional module will be loaded.

That's how Spring Boot modules are loaded.

Alex Chernyshev
  • 1,719
  • 9
  • 11