My usual approach to this is:
Separate out the code which uses the optional library into a different source directory. It should implement interfaces and generally depend upon the main source directory.
In order to enforce dependencies in the build, compile the main source directory without the optional library, and then the source that depends on the optional library (with other class file from other source directory and the library on the compiler classpath).
The main source should attempt to load a single root class in the optional source directory dynamically (Class.forName
, asSubclass
, getConstructor
, newInstance
). The root class' static intialiser should check that the library is really available and throw an exception if it is not. If the root class fails to load, then possibly follow the Null Object pattern.