0

I would like my projekt to switch at startup between backend libraries, with option to not shade both of them into resulting Jar, while having full compile-them visibility of both. I would also like to avoid loading backends using Class.forName.

Can you help me understand if any of these will lead to a ClassNotFoundError for unshaded library and which will not?

  1. Leaving an unused class field, of type from this library
  2. Leaving an unused method, with arguments or return from this library
  3. Referencing this library in a backend class that will only be loaded conditionally like backend = isNewBackend ? new FancyBackend() : new OldBackend()

If all of these result in error, is there an easier strategy for this?

underflow
  • 73
  • 1
  • 7
  • Are you thinking of compiling with both libraries on the classpath, but then removing one of them from the final deployed result during packaging/deployment? If yes, may I ask why? – E-Riz Feb 13 '23 at 14:15
  • It's a framework, and the user starts the boostrap process specifying which backend library should the framework use. As the libraries are pretty heavy, it would be beneficial to avoid shading unused ones into the jar. Additionally, it would protect against accidental load of classes from another library as their static initialization is currently known to be quite heavy on ClassLoader. – underflow Feb 13 '23 at 14:19

1 Answers1

2

In Java, what's the proper way of using an optional dependency without ClassNotFoundError?

The simple way is to use Class.forName.

I would also like to avoid loading backends using Class.forName.

Erm ... OK.

Can you help me understand if any of these will lead to a ClassNotFoundError for unshaded library and which will not?

If I understand what you are proposing correctly, none of them will work.

Leaving an unused class field, of type from this library

Loading the class that defines that field will trigger1 loading the library that defines the type of the field.

Leaving an unused method, with arguments or return from this library

Loading the class that defines that method will trigger1 loading the library that defines the argument or return type.

Referencing this library in a backend class that will only be loaded conditionally like:

backend = isNewBackend ? new FancyBackend() : new OldBackend()

The code that does that calculation needs to be defined in a class. To running that code that is liable to trigger1 loading both the FancyBackend and OldBackend classes to verify the code.


If you wanted to avoid explicit Class.forName calls, you could potentially implement your optional dependency as a custom Service Provider Interface (SPI). That gives you additional flexibility, but it is more complicated than simply using Class.forName.

Here are some references for SPIs:


1 - Per Does the JVM throw if an unused class is absent? if the JVM implements lazy class loading, loading may not be triggered by mentioning a class in a method or field signature. However, lazy loading is implementation dependent and (AFAIK) not specified. So relying on the JVM to do it will make your application platform dependent.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 2
    Related: [Does the JVM throw if an unused class is absent?](https://stackoverflow.com/q/41965457/2711488) As explained there, you might get away with an absent class in some cases due to lazy loading, but it’s unreliable (implementation dependent) and a construct like `isNewBackend ? new FancyBackend() : new OldBackend()` has a high chance of breaking, as it requires the verifier to load all these classes to check whether they are assignable to the base class. – Holger Feb 14 '23 at 11:03
  • 1
    Further note, the SPI articles are outdated. The API has been extended significantly in Java 9 and the service provider concept became an integral part of the module system. [The class documentation of `ServiceLoader`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html) For SPI examples not possible before Java 9, see [Enum-aware ServiceLoader implementation?](https://stackoverflow.com/q/54719780/2711488) and [Why does JPMS allow annotation types as services](https://stackoverflow.com/q/59593407/2711488). – Holger Feb 14 '23 at 11:24
  • Thanks @Holger. I've incorporated most of your comments. – Stephen C Feb 14 '23 at 12:31