That's exactly what Java ClassLoaders do...
Thus, you can load different versions of the same library at the same time, by loading them with different classoaders. Even, the same version.
Now, there's necessarily the problem of which one of them a particular piece of code wants to use, which is determined by which classloader loaded that piece of code. You also can (and routinely do) have a path of nested classloaders that load different things.
Java was designed this way, right from the very beginning.
The problems with java are:
1. somewhat verbose ("readable")
2. stuff hacked on (generics etc)
3. C-style configuration-over-convention
4. enterprise cultureI don't get how anyone can call it beautiful and productive. Ever take over/inherit some 'older' node code and try to update it or even get to work... it is hell. Even worse if it is from the time when there were competing forks of nodejs, so you have to try to figure out which it prefers. And maybe even then it won't run. (probably because npm's version notation can be unpredictable, so npm install still installs a version that breaks something even though it shouldn't)
This means that a library can turn its API completely inside out and not cause dependency hell. Your dependencies can update their sub-dependencies in their own time. This means there's no pressures against releasing new updates, and leads to the beautiful and very productive chaos that is npm.
Java is not designed to work this way.