https://kostenko.org/blog/2019/06/runtime-class-loading.html

How to use custom ClassLoader to load jars in runtime
28 June 2019

To load calsses in runtime java uses ClassLoader mechanism which is based on next core principles:

delegation - by default uses parent-first delegation, - child ClassLoader will be used if parent is not able to find or load class. This behavior can be changed to child-first by overwriting ClassLoader.loadClass(...);
visibility - child ClassLoader is able to see all the classes loaded by parent but vice-versa is not true;
uniqueness - allows to load a class exactly once, which is basically achieved by delegation and ensures that child ClassLoader doesn't reload the class already loaded by parent;
The main scenarios to use custom ClassLoader is:

Class Instrumentation - modifying classes at runtime. For example, to unit testing, debugging or monitoring;
Isolation of executions - isolate several execution environments within a single process by making visible only a subset of classes for a particular thread, like it does in EE environments;
So, let's see how using of custom ClassLoader looks from source code perspective:

List<File> jars = Arrays.asList(new File("/tmp/jars").listFiles());
URL[] urls = new URL[files.size()];
for (int i = 0; i < jars.size(); i++) {
    try {
        urls[i] = jars.get(i).toURI().toURL();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
URLClassLoader childClassLoader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader());
Then load class with custom ClassLoader:

Class.forName("org.kostenko.examples.core.classloader.ClassLoaderTest", true , childClassLoader);
Note! If your loaded libraries uses some resources like properties or something else, you need to provide context class loader:

Thread.currentThread().setContextClassLoader(childClassLoader);
Also, you can use custom ClassLoaders to load services with Java Service Provider Interface(SPI)

ServiceLoader<MyProvider> serviceLoader = ServiceLoader.load(MyProvider.class, childClassLoader);
...

---
https://stackoverflow.com/questions/60764/how-to-load-jar-files-dynamically-at-runtime

The reason it's hard is security. Classloaders are meant to be immutable; you shouldn't be able to willy-nilly add classes to it at runtime. I'm actually very surprised that works with the system classloader. Here's how you do it making your own child classloader:

URLClassLoader child = new URLClassLoader(
        new URL[] {myJar.toURI().toURL()},
        this.getClass().getClassLoader()
);
Class classToLoad = Class.forName("com.MyClass", true, child);
Method method = classToLoad.getDeclaredMethod("myMethod");
Object instance = classToLoad.newInstance();
Object result = method.invoke(instance);
Painful, but there it is.