ClassLoader.getSystemClassLoader().getResourceAsStream(packageName.replaceAll("[.]", "/"));
This doesn't work. If it works on windows, that's.. broken coincidence. The spec clearly says this doesn't work.
What you want, is impossible. ClassLoaders, fundamentally, do not support the idea of 'please give me every class in package X'. They just don't. The one and only primitive they have is 'give me resource X'. That's all you get. Given a class name such as java.lang.String
, you can derive its fully qualified binary name from that and slash it up appropriately (java/lang/String.class
), and then ask for that resource, which is a thing the ClassLoader API supports.
But, you can't take a package and ask for a listing. Simply because ClassLoader has no listResources
method. You are trying to 'read' the resource java/lang
and expecting this to return a listing of files. The API spec does not indicate that a ClassLoader has to work this way, and in fact, most don't, as you have now discovered.
SPI is a solution for allowing 'listing' of things, but SPI is an opt-in system, whether you use java.util.ServiceLoader
with META-INF/services/com.foo.someInterfaceName
style, or using the newer SPI stuff in module-info.java
- the 'provider' has to opt into being listed.
Which gets us back to: Nope. You cannot do what you are trying to do.
But I need to
Well, and I want a pony. And world peace. Your only real option is to hack it. Which means: Every new version of java may break your code, and it's possible (likely even) that some combination of JVM version, JVM provider (azul, openjdk, adoptium, etc), and OS breaks your stuff and there is nobody who will accept your bug reports on it, because it's not a bug.
If you're willing to sign up to that gigantic headache, you can hack it. Specifically, you can ask for a well known resource such as .getResource("java/lang/String.class")
, toString()
the URL object you get, and then go to town on it: Figure out what that URL means and have code for each and every kind of resource URL you expect that knows how to unpack that URL and do the work.
The URL returned by ClassLoaders can be literally anything, up to and including data:
URLs or customized blob://whatever
style URLs. It's therefore literally impossible: You can't write static code that can respond to a world where anybody can write a custom classloader. However, the vast majority of classloaders out there have file
, jar
or jmod
URLs. So, if you write a handler for all 3, you can handle most, but not all, distribution strategies.
file:
is easy enough - convert to a Path, lop off the String.class
, lang
, and java
parts, and then add the package you are interested in, and now you have a Path
object that you can toss at Files.newDirectoryStream
and voila - you have your class listing. This is not a listing of all classes in the package, that's not possible. This is merely a list of all classes in the package from that particular source - there could be more (you can have 2 entries on the classpath that both have the same package. This is common, even, where one place hosts the actual code and another place hosts unit tests in the same package).
For jar:
URLs, find the !
, turn the stuff in between jar:
and !
into a path, open it as a new JarFile
, and look for entries with the right package prefix and you again have what you wanted.
For jmod:
URLs thats a bit more tricky - but the jmod
tool exists and can list jmod contents, and you may have to peruse this SO question about how to read jmod files.
Yes, that's a ton of effort. Yes, that's the point - you're working around a fundamental problem which is that the classloader API simply does not allow you to list package contents and nevertheless you insist on doing it anyway. That usually results in 'lots of fragile, hard to maintain code'.
Another option is to find a library that does this hacky fragile stuff for you. Perhaps the reflections library can do this.
NB: It's clear you got this line from this baeldung tutorial - usually baeldung is high quality. This entry.. isn't.