Is it possible to find all classes or interfaces in a given package? (Quickly looking at e.g. Package
, it would seem like no.)
-
2FYI the solution Amit links to works, although it has a bug if the class path has a space character in it (and probably for other non-alphanumeric characters too). if you're using it in any kind of production code, see my comment to his answer for a workaround.– KipCommented Feb 16, 2010 at 20:31
-
2Also note this post.– barfuinCommented Jan 13, 2014 at 9:48
-
1See related answer: stackoverflow.com/a/30149061/4102160– CfxCommented May 10, 2015 at 10:35
-
1Also note this post.– sp00mCommented Dec 2, 2016 at 12:05
-
2See my answer below about ClassGraph, it is currently the most robust method for scanning the classpath and module path.– Luke HutchisonCommented Oct 5, 2019 at 10:48
30 Answers
Due to the dynamic nature of class loaders, this is not possible. Class loaders are not required to tell the VM which classes it can provide, instead they are just handed requests for classes, and have to return a class or throw an exception.
However, if you write your own class loaders, or examine the classpaths and it's jars, it's possible to find this information. This will be via filesystem operations though, and not reflection. There might even be libraries that can help you do this.
If there are classes that get generated, or delivered remotely, you will not be able to discover those classes.
The normal method is instead to somewhere register the classes you need access to in a file, or reference them in a different class. Or just use convention when it comes to naming.
Addendum: The Reflections Library will allow you to look up classes in the current classpath. It can be used to get all classes in a package:
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends Object>> allClasses =
reflections.getSubTypesOf(Object.class);
-
14The inability to query for class names has bugged me for a long time. Sure, it's hard and the performance can vary widely, and for certain classloaders the list is undefined or unbounded, but there are ways this could have been worked around. Commented Feb 6, 2009 at 14:38
-
22Note that this solution will not work as by default getSubTypesOf does not return subtypes of Object. See Aleksander Blomskøld's solution for how to configure the SubTypeScanner. Commented Dec 5, 2012 at 15:14
-
20Reflections requires Guava. Guava is big. Version 14.0.1 is 2.1MB. Commented Jun 25, 2013 at 17:29
-
3Did not work for me. Mac OSX - Reflections dependency version 0.9.9-RC1 (maven) - JDK 1.7. Reconsider the accepted answer. @AleksanderBlomskøld answer is the one to go.!!!!! Commented Oct 5, 2013 at 10:28
-
79If this returns an empty list, initialize the Reflections object like this: Reflections reflections = new Reflections("your.package.here", new SubTypesScanner(false)); Commented Sep 24, 2014 at 14:37
You should probably take a look at the open source Reflections library. With it you can easily achieve what you want.
First, setup the reflections index (it's a bit messy since searching for all classes is disabled by default):
List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));
Then you can query for all objects in a given package:
Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);
-
6Ah, here we go: code.google.com/p/reflections/issues/detail?id=122. Object is excluded by default, but you can rejigger it. Thanks for pointing me to this library, it's great!– mtrcCommented Aug 25, 2012 at 14:50
-
1I ran in to problems on my Mac with this code (related to native libraries), but using
.addUrls(ClasspathHelper.forJavaClassPath())
instead of the above solved them for me. Less code as well! Commented Feb 11, 2013 at 14:55 -
4if anyone wonders a the simplest way to get the default package is having the prefix be an empty String -> "".– JBACommented Oct 17, 2014 at 9:26
-
2"Reflections" library has a tricky license: github.com/ronmamo/reflections/blob/master/COPYING.txt . The trick is that the license allows free use of only the license itself. So to really use the library (not the license) everyone must contact the author and negotiate the terms of use. Commented Feb 17, 2015 at 14:32
-
3
You could use this method1 that uses the ClassLoader
.
/**
* Scans all classes accessible from the context class loader which belong to the given package and subpackages.
*
* @param packageName The base package
* @return The classes
* @throws ClassNotFoundException
* @throws IOException
*/
private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<File>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes.toArray(new Class[classes.size()]);
}
/**
* Recursive method used to find all classes in a given directory and subdirs.
*
* @param directory The base directory
* @param packageName The package name for classes found inside the base directory
* @return The classes
* @throws ClassNotFoundException
*/
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
__________
1 This method was taken originally from http://snippets.dzone.com/posts/show/4831, which was archived by the Internet Archive, as linked to now. The snippet is also available at https://dzone.com/articles/get-all-classes-within-package.
-
9I had a problem with this if my path included spaces. The URL class was escaping spaces to
%20
, but thenew File()
constructor treated that as a literal percent sign two zero. I fixed it by changing thedirs.add(...)
line to this:dirs.add(new File(resource.toURI()));
This also meant I had to addURISyntaxException
to the throws clause ofgetClasses
– KipCommented Feb 16, 2010 at 20:29 -
24You just copied from dzone.com/articles/get-all-classes-within-package! please refer source next time– R.ACommented Oct 23, 2015 at 10:03
-
28+1 because this solution does NOT require external libraries... NEVER, really NEVER couple your code randomly with libraries just to achieve one small thing like this. do you know that you are adding potential attack surface for attackers? Nov 2015 an Apache Commons problem discovered that leads to Remote Command Execution just by having Apache Commons in the classpath of an app deployed on Jboss/Weblogic [foxglovesecurity.com/2015/11/06/…– sc0pCommented Dec 6, 2015 at 12:15
-
1@Qix correctly noted that this code does not support jar. In order to support jars & directories. The code was changed as noted below: Commented Feb 8, 2016 at 15:11
-
2I cannot get this to work. Could we get an example of an input of package name that works, along with an example projet structure?– payneCommented Jun 10, 2020 at 3:36
Google Guava 14 includes a new class ClassPath
with three methods to scan for top level classes:
getTopLevelClasses()
getTopLevelClasses(String packageName)
getTopLevelClassesRecursive(String packageName)
See the ClassPath
javadocs for more info.
-
1As I mentioned in a comment below,
ClassPath
is tagged with@Beta
, so might not be a good idea for some... Commented Jan 9, 2015 at 13:45 -
1Saying that this works where reflection doesn't do is a bit strange, the solution undoubtedly is implemented using reflection (and class loader) functionality. Commented Aug 18, 2016 at 11:53
-
7I think he meant the Reflections library mentioned in the other answer. Commented Aug 18, 2016 at 12:26
-
1
-
2
@Beta
has been recently removed from the source but as far as I know, there is yet a release with this change. Anyway,ClassPath
itself recommends to use classgraph instead. Commented Jan 13, 2022 at 9:42
Spring
This example is for Spring 4, but you can find the classpath scanner in earlier versions as well.
// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));
// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");
// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
Class<?> clazz = Class.forName(bean.getBeanClassName());
// ... do your magic with the class ...
}
Google Guava
Note: In version 14, the API is still marked as @Beta, so beware in production code.
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
if (info.getName().startsWith("my.package.")) {
final Class<?> clazz = info.load();
// do something with your clazz
}
}
-
8Excellent answer. There are too much solutions here which are verbose, non-tested, non-working! This one is fantastic: it is concise and tested (it is from Guava). Very good! It is useful, it deserves more upvotes. Commented Jun 21, 2014 at 7:01
-
1To find nested static classes using guava solution,
getAllClasses()
method can be used. Commented Feb 16, 2016 at 10:38 -
1Spring example not suitable for finding not-concrete classes , such as anntation(
@RabbitListener(...) public @interface XxxListener
) because of the ClassPathScanningCandidateComponentProvider#isCandidateComponent– btpka3Commented Sep 5, 2017 at 8:33 -
1The Spring solution is the only working if run from an executable jar.– LukeCommented Jan 23, 2018 at 14:35
-
1
ClassPathScanningCandidateComponentProvider
works, but it's really meant for Spring beans. Commented Jul 20, 2019 at 1:10
Hello. I always have had some issues with the solutions above (and on other sites).
I, as a developer, am programming a addon for a API. The API prevents the use of any external libraries or 3rd party tools. The setup also consists of a mixture of code in jar or zip files and class files located directly in some directories. So my code had to be able to work arround every setup. After a lot of research I have come up with a method that will work in at least 95% of all possible setups.
The following code is basically the overkill method that will always work.
The code:
This code scans a given package for all classes that are included in it. It will only work for all classes in the current ClassLoader
.
/**
* Private helper method
*
* @param directory
* The directory to start with
* @param pckgname
* The package name to search for. Will be needed for getting the
* Class object.
* @param classes
* if a file isn't loaded but still is in the directory
* @throws ClassNotFoundException
*/
private static void checkDirectory(File directory, String pckgname,
ArrayList<Class<?>> classes) throws ClassNotFoundException {
File tmpDirectory;
if (directory.exists() && directory.isDirectory()) {
final String[] files = directory.list();
for (final String file : files) {
if (file.endsWith(".class")) {
try {
classes.add(Class.forName(pckgname + '.'
+ file.substring(0, file.length() - 6)));
} catch (final NoClassDefFoundError e) {
// do nothing. this class hasn't been found by the
// loader, and we don't care.
}
} else if ((tmpDirectory = new File(directory, file))
.isDirectory()) {
checkDirectory(tmpDirectory, pckgname + "." + file, classes);
}
}
}
}
/**
* Private helper method.
*
* @param connection
* the connection to the jar
* @param pckgname
* the package name to search for
* @param classes
* the current ArrayList of all classes. This method will simply
* add new classes.
* @throws ClassNotFoundException
* if a file isn't loaded but still is in the jar file
* @throws IOException
* if it can't correctly read from the jar file.
*/
private static void checkJarFile(JarURLConnection connection,
String pckgname, ArrayList<Class<?>> classes)
throws ClassNotFoundException, IOException {
final JarFile jarFile = connection.getJarFile();
final Enumeration<JarEntry> entries = jarFile.entries();
String name;
for (JarEntry jarEntry = null; entries.hasMoreElements()
&& ((jarEntry = entries.nextElement()) != null);) {
name = jarEntry.getName();
if (name.contains(".class")) {
name = name.substring(0, name.length() - 6).replace('/', '.');
if (name.contains(pckgname)) {
classes.add(Class.forName(name));
}
}
}
}
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader
*
* @param pckgname
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* if something went wrong
*/
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
throws ClassNotFoundException {
final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
try {
final ClassLoader cld = Thread.currentThread()
.getContextClassLoader();
if (cld == null)
throw new ClassNotFoundException("Can't get class loader.");
final Enumeration<URL> resources = cld.getResources(pckgname
.replace('.', '/'));
URLConnection connection;
for (URL url = null; resources.hasMoreElements()
&& ((url = resources.nextElement()) != null);) {
try {
connection = url.openConnection();
if (connection instanceof JarURLConnection) {
checkJarFile((JarURLConnection) connection, pckgname,
classes);
} else if (connection instanceof FileURLConnection) {
try {
checkDirectory(
new File(URLDecoder.decode(url.getPath(),
"UTF-8")), pckgname, classes);
} catch (final UnsupportedEncodingException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Unsupported encoding)",
ex);
}
} else
throw new ClassNotFoundException(pckgname + " ("
+ url.getPath()
+ ") does not appear to be a valid package");
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
}
} catch (final NullPointerException ex) {
throw new ClassNotFoundException(
pckgname
+ " does not appear to be a valid package (Null pointer exception)",
ex);
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for "
+ pckgname, ioex);
}
return classes;
}
These three methods provide you with the ability to find all classes in a given package.
You use it like this:
getClassesForPackage("package.your.classes.are.in");
The explanation:
The method first gets the current ClassLoader
. It then fetches all resources that contain said package and iterates of these URL
s. It then creates a URLConnection
and determines what type of URl we have. It can either be a directory (FileURLConnection
) or a directory inside a jar or zip file (JarURLConnection
). Depending on what type of connection we have two different methods will be called.
First lets see what happens if it is a FileURLConnection
.
It first checks if the passed File exists and is a directory. If that's the case it checks if it is a class file. If so a Class
object will be created and put in the ArrayList
. If it is not a class file but is a directory, we simply iterate into it and do the same thing. All other cases/files will be ignored.
If the URLConnection
is a JarURLConnection
the other private helper method will be called. This method iterates over all Entries in the zip/jar archive. If one entry is a class file and is inside of the package a Class
object will be created and stored in the ArrayList
.
After all resources have been parsed it (the main method) returns the ArrayList
containig all classes in the given package, that the current ClassLoader
knows about.
If the process fails at any point a ClassNotFoundException
will be thrown containg detailed information about the exact cause.
-
4This example seems to require importing
sun.net.www.protocol.file.FileURLConnection
, which generates a warning at compile-time ("warning: sun.net.www.protocol.file.FileURLConnection is Sun proprietary API and may be removed in a future release"). Is there an alternative to using that class, or can the warning be suppressed using annotations? Commented Mar 30, 2015 at 11:25 -
This method doesn't work for bootstrap classes, like those in java.lang, java.util, ... Those can be found by getting System.getProperty("sun.boot.class.path"), splitting with : or ; (depending on OS), and then running slightly modified versions of the above checkDirectory and checkJarFile. Commented May 17, 2016 at 23:33
-
1You can get around the warning/error by using connection.getClass().getCanonicalName().equals( "sun.net.www.protocol.file.FileURLConnection" ). If you really want to you can create a URLConnection which you think SHOULD use sun.net.www.protocol.file.FileURLConnection and also compare the name of the connection class the name of the class you created. If they are both the same you can treat it as an instance of sun.net.www.protocol.file.FileURLConnection rather than failing in case the class name changes. Commented Jan 6, 2017 at 6:55
-
1@Christian You can avoid the FileURLConnection doing something like this :
if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }
It's what I did on my code to search JPA annotated classes– Zardoz89Commented May 26, 2017 at 10:14 -
@Christian I used it successfully in the past but now, in 2023, it no longer works. Commented May 15, 2023 at 15:10
The most robust mechanism for listing all classes in a given package is currently ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author.)
List<String> classNames = new ArrayList<>();
try (ScanResult scanResult = new ClassGraph().acceptPackages("my.package")
.enableClassInfo().scan()) {
classNames.addAll(scanResult.getAllClasses().getNames());
}
-
Great library ! However, please note that it does not work at runtime for Android projects. There's a way to use it at build-time through Gradle, though.– Paul WCommented Jul 17, 2022 at 19:52
-
1@PaulW correct, and there are no plans to ever support the dex/odex format of the Dalvik runtime, so build-time scanning is the only option. Please suggest changes or improvements to the documentation if you have any. github.com/classgraph/classgraph/wiki/Build-Time-Scanning Commented Jul 18, 2022 at 20:09
-
Does this work with the venerable onejar-maven-plugin? Commented Dec 11, 2022 at 8:38
-
1@JonathanS.Fisher it should do, I know that it works with shaded jars, although the package names may be changed in that case. Commented Dec 12, 2022 at 9:20
Without using any extra libraries:
package test;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception{
List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
for(Class c:classes){
System.out.println("Class: "+c);
}
}
public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{
String dottedPackage = pack.replaceAll("[/]", ".");
List<Class> classes = new ArrayList<Class>();
URL upackage = cl.getResource(pack);
DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
String line = null;
while ((line = dis.readLine()) != null) {
if(line.endsWith(".class")) {
classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
}
}
return classes;
}
}
-
1
-
1For a package "com.mycompany.beans", replace "test" with "com/mycompany/beans" Commented Mar 17, 2016 at 9:31
-
6I get a null when using this code. Seems to only work if your jar is an executable– AlaoCommented Nov 23, 2016 at 17:18
-
2if you obtained the package name from
String pack = getPackage().getName();
, then you have to addpack = pack.replaceAll("[.]", "/");
Commented May 14, 2019 at 13:23 -
1Letting aside that there is no guaranty that you can read a directory (or directory-like resource) this way, what is the reason to write
(InputStream) upackage.getContent()
instead of a straight-forwardupackage.openStream()
? Further, thereadLine
method ofDataInputStream
is deprecated since Java 1.1 and the correct alternativeBufferedReader
>InputStreamReader
exists since that version. Which means, even by the time this answer was written, it was deprecated for 17 years already.– HolgerCommented Nov 13, 2023 at 16:06
In general class loaders do not allow for scanning through all the classes on the classpath. But usually the only used class loader is UrlClassLoader from which we can retrieve the list of directories and jar files (see getURLs) and open them one by one to list available classes. This approach, called class path scanning, is implemented in Scannotation and Reflections.
Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);
Another approach is to use Java Pluggable Annotation Processing API to write annotation processor which will collect all annotated classes at compile time and build the index file for runtime use. This mechanism is implemented in ClassIndex library:
// package-info.java
@IndexSubclasses
package my.package;
// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Notice that no additional setup is needed as the scanning is fully automated thanks to Java compiler automatically discovering any processors found on the classpath.
-
does this discover classes packaged in a Jar? It doesn't seem to work for me.– asgsCommented Jun 18, 2013 at 6:29
-
-
I'm using the Reflections lib. But I got it working after following the workaround mentioned by @Aleksander Blomskøld for recent versions of this lib.– asgsCommented Jun 18, 2013 at 15:32
-
Hi, I'm using eclipse and cannot get it working, ClassIndex.getPackageClasses("my.package") return an empty map– JuanCommented Feb 27, 2019 at 11:06
What about this:
public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
final String pkgPath = pkgName.replace('.', '/');
final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
Path root;
if (pkg.toString().startsWith("jar:")) {
try {
root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
} catch (final FileSystemNotFoundException e) {
root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
}
} else {
root = Paths.get(pkg);
}
final String extension = ".class";
try (final Stream<Path> allPaths = Files.walk(root)) {
allPaths.filter(Files::isRegularFile).forEach(file -> {
try {
final String path = file.toString().replace('/', '.');
final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
allClasses.add(Class.forName(name));
} catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
}
});
}
return allClasses;
}
You can then overload the function:
public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
return getClassesForPackage(pkg.getName());
}
If you need to test it:
public static void main(final String[] argv) throws IOException, URISyntaxException {
for (final Class<?> cls : getClassesForPackage("my.package")) {
System.out.println(cls);
}
for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
System.out.println(cls);
}
}
If your IDE does not have import helper:
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
It works:
from your IDE
for a JAR file
without external dependencies
-
3To work on Windows you have to change file.toString().replace('/', '.') with final String path = file.toString().replace('\\, '.'); Commented Jan 4, 2021 at 19:50
-
Here's how I do it. I scan all the subfolders (sub-packages) and I don't try to load anonymous classes:
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader, recursively, avoiding anonymous classes
*
* @param pckgname
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* if something went wrong
*/
private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
// This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
ArrayList<File> directories = new ArrayList<File>();
String packageToPath = pckgname.replace('.', '/');
try {
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
// Ask for all resources for the packageToPath
Enumeration<URL> resources = cld.getResources(packageToPath);
while (resources.hasMoreElements()) {
directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
}
} catch (NullPointerException x) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
} catch (UnsupportedEncodingException encex) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
} catch (IOException ioex) {
throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
}
ArrayList<Class> classes = new ArrayList<Class>();
// For every directoryFile identified capture all the .class files
while (!directories.isEmpty()){
File directoryFile = directories.remove(0);
if (directoryFile.exists()) {
// Get the list of the files contained in the package
File[] files = directoryFile.listFiles();
for (File file : files) {
// we are only interested in .class files
if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
// removes the .class extension
int index = directoryFile.getPath().indexOf(packageToPath);
String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;
try {
String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);
classes.add(Class.forName(className));
} catch (NoClassDefFoundError e)
{
// do nothing. this class hasn't been found by the loader, and we don't care.
}
} else if (file.isDirectory()){ // If we got to a subdirectory
directories.add(new File(file.getPath()));
}
}
} else {
throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
}
}
return classes;
}
I put together a simple github project that solves this problem:
https://github.com/ddopson/java-class-enumerator
It should work for BOTH file-based classpaths AND for jar files.
If you run 'make' after checking out the project it will print this out:
Cleaning...
rm -rf build/
Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ .
jar cf build/ClassEnumerator.jar -C build/classes/ pro
Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class' => class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class' => class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg' => class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class' => class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class' => class 'test.TestClassEnumeration'
Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/' => class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF' => class 'null'
ClassDiscovery: JarEntry 'pro/' => class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/' => class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class' => class 'null'
ClassDiscovery: JarEntry 'test/' => class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class' => class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class' => class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/' => class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class' => class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class' => class 'test.TestClassEnumeration'
Tests Passed.
See also my other answer
Almost all the answers either uses Reflections
or reads class files from file system. If you try to read classes from file system, you may get errors when you package your application as JAR or other. Also you may not want to use a separate library for that purpose.
Here is another approach which is pure java and not depends on file system.
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class PackageUtil {
public static Collection<Class> getClasses(final String pack) throws Exception {
final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
.map(javaFileObject -> {
try {
final String[] split = javaFileObject.getName()
.replace(".class", "")
.replace(")", "")
.split(Pattern.quote(File.separator));
final String fullClassName = pack + "." + split[split.length - 1];
return Class.forName(fullClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toCollection(ArrayList::new));
}
}
Java 8 is not a must. You can use for loops instead of streams. And you can test it like this
public static void main(String[] args) throws Exception {
final String pack = "java.nio.file"; // Or any other package
PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}
-
2It is not very useful because of: it is need to have JDK to use
ToolProvider.getSystemJavaCompiler()
, this code doesn't scan nested packages. Commented Feb 16, 2016 at 18:28 -
I can't make it working with a package of an external jar Commented Mar 17, 2019 at 15:52
-
Great approach! Instead of the exhaustive splitting you could just use:
final String name = javaFileObject.getName(); return Class.forName(pack + "." + name.substring(name.lastIndexOf(File.separatorChar)+1, name.lastIndexOf(".class")));
or if you don't care on CPU time just use the regex one liner:return Class.forName(pack + "." + javaFileObject.getName().replaceAll(".*"+File.separatorChar+"(.*)\\.class.*", "$1"));
– CoSoCoCommented Jun 27 at 20:23 -
I suspect, if your approach works correctly on Windows. As far as I know, the file separator in jar files is always '/', never '\', even on Windows, so
File.separator
would result in the wrong value on Windows.– CoSoCoCommented Jun 27 at 20:36 -
A still better approach – mainly if you want to scan recursively all subpackages – is:
final String name = javaFileObject.getName().replace(File.separatorChar, '.'); return Class.forName(name.substring(name.indexOf(pack), name.lastIndexOf(".class")));
Or as slower regex one liner:return Class.forName(javaFileObject.getName().replace(File.separatorChar, '.').replaceAll(".*("+pack+".*)\\.class.*", "$1"));
– CoSoCoCommented Jun 27 at 21:51
Yeah using few API's you can, here is how I like doing it, faced this problem which I was using hibernate core & had to find classes which where annotated with a certain annotation.
Make these an custom annotation using which you will mark which classes you want to be picked up.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {
}
Then mark your class with it like
@EntityToBeScanned
public MyClass{
}
Make this utility class which has the following method
public class ClassScanner {
public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
Reflections reflections = new Reflections(".*");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
return annotated;
}
}
Call the allFoundClassesAnnotatedWithEntityToBeScanned() method to get a Set of Classes found.
You will need libs given below
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
If you're in Spring-land you can use PathMatchingResourcePatternResolver
;
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");
Arrays.asList(resources).forEach(r->{
...
});
You need to look up every class loader entry in the class path:
String pkg = "org/apache/commons/lang";
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
System.out.println(url.getFile());
File jar = new File(url.getFile());
// ....
}
If entry is directory, just look up in the right subdirectory:
if (jar.isDirectory()) {
File subdir = new File(jar, pkg);
if (!subdir.exists())
continue;
File[] files = subdir.listFiles();
for (File file : files) {
if (!file.isFile())
continue;
if (file.getName().endsWith(".class"))
System.out.println("Found class: "
+ file.getName().substring(0,
file.getName().length() - 6));
}
}
If the entry is the file, and it's jar, inspect the ZIP entries of it:
else {
// try to open as ZIP
try {
ZipFile zip = new ZipFile(jar);
for (Enumeration<? extends ZipEntry> entries = zip
.entries(); entries.hasMoreElements();) {
ZipEntry entry = entries.nextElement();
String name = entry.getName();
if (!name.startsWith(pkg))
continue;
name = name.substring(pkg.length() + 1);
if (name.indexOf('/') < 0 && name.endsWith(".class"))
System.out.println("Found class: "
+ name.substring(0, name.length() - 6));
}
} catch (ZipException e) {
System.out.println("Not a ZIP: " + e.getMessage());
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
Now once you have all class names withing package, you can try loading them with reflection and analyze if they are classes or interfaces, etc.
-
What would you enter for a package in a Jar file? Commented Jul 23, 2014 at 13:39
-
This example will not go through sub-packages. Maybe that's of interest to some... @mr-tea Just specify the package you are looking for. I put this in a project, specified a test package within that project, compiled and packaged it and called the example form the JAR's main method. Worked like a charm. :) Commented Jan 9, 2015 at 15:49
I've been trying to use the Reflections library, but had some problems using it, and there were too many jars I should include just to simply obtain the classes on a package.
I'll post a solution I've found in this duplicate question: How to get all classes names in a package?
The answer was written by sp00m; I've added some corrections to make it work:
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
public final class ClassFinder {
private final static char DOT = '.';
private final static char SLASH = '/';
private final static String CLASS_SUFFIX = ".class";
private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";
public final static List<Class<?>> find(final String scannedPackage) {
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final String scannedPath = scannedPackage.replace(DOT, SLASH);
final Enumeration<URL> resources;
try {
resources = classLoader.getResources(scannedPath);
} catch (IOException e) {
throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
}
final List<Class<?>> classes = new LinkedList<Class<?>>();
while (resources.hasMoreElements()) {
final File file = new File(resources.nextElement().getFile());
classes.addAll(find(file, scannedPackage));
}
return classes;
}
private final static List<Class<?>> find(final File file, final String scannedPackage) {
final List<Class<?>> classes = new LinkedList<Class<?>>();
if (file.isDirectory()) {
for (File nestedFile : file.listFiles()) {
classes.addAll(find(nestedFile, scannedPackage));
}
//File names with the $1, $2 holds the anonymous inner classes, we are not interested on them.
} else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {
final int beginIndex = 0;
final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
final String className = file.getName().substring(beginIndex, endIndex);
try {
final String resource = scannedPackage + DOT + className;
classes.add(Class.forName(resource));
} catch (ClassNotFoundException ignore) {
}
}
return classes;
}
}
To use it just call the find method as sp00n mentioned in this example: I've added the creation of instances of the classes if needed.
List<Class<?>> classes = ClassFinder.find("com.package");
ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
Constructor constructor = aClass.getConstructor();
//Create an object of the class type
constructor.newInstance();
//...
}
I just wrote a util class, it include test methods, you can have a check ~
IteratePackageUtil.java:
package eric.j2se.reflect;
import java.util.Set;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
/**
* an util to iterate class in a package,
*
* @author eric
* @date Dec 10, 2013 12:36:46 AM
*/
public class IteratePackageUtil {
/**
* <p>
* Get set of all class in a specified package recursively. this only support lib
* </p>
* <p>
* class of sub package will be included, inner class will be included,
* </p>
* <p>
* could load class that use the same classloader of current class, can't load system packages,
* </p>
*
* @param pkg
* path of a package
* @return
*/
public static Set<Class<? extends Object>> getClazzSet(String pkg) {
// prepare reflection, include direct subclass of Object.class
Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
.setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
.filterInputsBy(new FilterBuilder().includePackage(pkg)));
return reflections.getSubTypesOf(Object.class);
}
public static void test() {
String pkg = "org.apache.tomcat.util";
Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
for (Class<? extends Object> clazz : clazzSet) {
System.out.println(clazz.getName());
}
}
public static void main(String[] args) {
test();
}
}
Aleksander Blomskøld's solution did not work for me for parameterized tests @RunWith(Parameterized.class)
when using Maven. The tests were named correctly and also where found but not executed:
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec
A similar issue has been reported here.
In my case @Parameters
is creating instances of each class in a package. The tests worked well when run locally in the IDE. However, when running Maven no classes where found with Aleksander Blomskøld's solution.
I did make it work with the following snipped which was inspired by David Pärsson's comment on Aleksander Blomskøld's answer:
Reflections reflections = new Reflections(new ConfigurationBuilder()
.setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
.addUrls(ClasspathHelper.forJavaClassPath())
.filterInputsBy(new FilterBuilder()
.include(FilterBuilder.prefix(basePackage))));
Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);
Define classes to be scanning in the package test
package test;
public class A {
private class B {}
enum C {}
record D() {}
}
For org.reflections:reflections:0.10.2
, it works for me as follows:
Use reflection lib to scan classes in package test
@Test
void t() {
final String packagePath = "test";
final Reflections reflections =
new Reflections(packagePath, Scanners.SubTypes.filterResultsBy(v -> true));
reflections.getAll(Scanners.SubTypes).forEach(System.out::println);
}
Output
java.lang.constant.Constable
java.lang.Enum
java.lang.Comparable
java.lang.Record
java.lang.Object
java.io.Serializable
test.A$C
test.A$D
test.A$B
test.A
For io.github.classgraph:classgraph:4.8.146
, it works for me as follows:
@Test
void t() {
final String packagePath = "test";
try (ScanResult scanResult = new ClassGraph()
.enableClassInfo()
.ignoreClassVisibility()
.acceptPackages(packagePath)
.scan()) {
scanResult.getAllClasses()
.forEach(v -> {
System.out.println(v.getName());
});
}
}
Output
test.A
test.A$B
test.A$C
test.A$D
I couldn't find a short working snippet for something so simple. So here it is, I made it myself after screwing around for a while:
Reflections reflections =
new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(new SubTypesScanner(false)));
Set<String> typeList = reflections.getAllTypes();
It uses org.reflections
.
-
1Be aware that this works great in Reflections 0.9.12, but does not find any types in version 0.10 (where SubTypesScanner has been deprecated).– lukas84Commented Dec 6, 2021 at 17:38
Provided you are not using any dynamic class loaders you can search the classpath and for each entry search the directory or JAR file.
Worth mentioning
If you want to have a list of all classes under some package, you can use Reflection
the following way:
List<Class> myTypes = new ArrayList<>();
Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
myTypes.add(Class.forName(s));
}
This will create a list of classes that later you can use them as you wish.
It is very possible, but without additional libraries like Reflections
it is hard...
It is hard because you haven't full instrument for get class name.
And, I take the code of my ClassFinder
class:
package play.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Created by LINKOR on 26.05.2017 in 15:12.
* Date: 2017.05.26
*/
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
try {
file = new JarFile(filePath);
} catch (IOException e) {
trouble = true;
}
}
public List<String> findClasses(String pkg) {
ArrayList<String> classes = new ArrayList<>();
Enumeration<JarEntry> entries = file.entries();
while (entries.hasMoreElements()) {
JarEntry cls = entries.nextElement();
if (!cls.isDirectory()) {
String fileName = cls.getName();
String className = fileName.replaceAll("/", ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
}
}
return classes;
}
}
this scans the class loaders and all parent loaders for jar files and directories. the jar files and directories referred by the Class-Path of the jars are also loaded.
this code is testet with Java 8,11,18. on 8 everything works perfectly using the URLClassLoader and the getURLs() method. on 11 it works fine using reflections, but the JVM prints a warning on the stderr stream (not redirectible with System.setErr() with my JVM) on 18 the reflections are useless (throws NoSuchMethod/Field), and the only thing (where I know that it works) is to use the getResource() method. When the class loader loades the resources of the given package from the file system a simple path url is returned. When the class loader loades the resources from a jar a url like 'jar:file:[jar-path]!/[in-jar-path]' is returned.
I have used the answer https://stackoverflow.com/a/1157352/18252455 (from a duplicate question) and added the functionality to read the Class-Path and also search for directory URLs.
/**
* orig description:<br>
* Scans all classloaders for the current thread for loaded jars, and then scans
* each jar for the package name in question, listing all classes directly under
* the package name in question. Assumes directory structure in jar file and class
* package naming follow java conventions (i.e. com.example.test.MyTest would be in
* /com/example/test/MyTest.class)
* <p>
* in addition this method also scans for directories, where also is assumed, that the classes are
* placed followed by the java conventions. (i.e. <code>com.example.test.MyTest</code> would be in
* <code>directory/com/example/test/MyTest.class</code>)
* <p>
* this method also reads the jars Class-Path for other jars and directories. for the jars and
* directories referred in the jars are scanned with the same rules as defined here.<br>
* it is ensured that no jar/directory is scanned exactly one time.
* <p>
* if {@code bailError} is <code>true</code> all errors will be wrapped in a
* {@link RuntimeException}
* and then thrown.<br>
* a {@link RuntimeException} will also be thrown if something unexpected happens.<br>
*
* @param packageName
* the name of the package for which the classes should be searched
* @param allowSubPackages
* <code>true</code> is also classes in sub packages should be found
* @param loader
* the {@link ClassLoader} which should be used to find the URLs and to load classes
* @param bailError
* if all {@link Exception} should be re-thrown wrapped in {@link RuntimeException} and
* if a {@link RuntimeException} should be thrown, when something is not as expected.
* @see https://stackoverflow.com/questions/1156552/java-package-introspection
* @see https://stackoverflow.com/a/1157352/18252455
* @see https://creativecommons.org/licenses/by-sa/2.5/
* @see https://creativecommons.org/licenses/by-sa/2.5/legalcode
*/
public static Set <Class <?>> tryGetClassesForPackage(String packageName, boolean allowSubPackages, ClassLoader loader, boolean bailError) {
Set <URL> jarUrls = new HashSet <URL>();
Set <Path> directorys = new HashSet <Path>();
findClassPools(loader, jarUrls, directorys, bailError, packageName);
Set <Class <?>> jarClasses = findJarClasses(allowSubPackages, packageName, jarUrls, directorys, loader, bailError);
Set <Class <?>> dirClasses = findDirClasses(allowSubPackages, packageName, directorys, loader, bailError);
jarClasses.addAll(dirClasses);
return jarClasses;
}
private static Set <Class <?>> findDirClasses(boolean subPackages, String packageName, Set <Path> directorys, ClassLoader loader, boolean bailError) {
Filter <Path> filter;
Set <Class <?>> result = new HashSet <>();
for (Path startPath : directorys) {
String packagePath = packageName.replace(".", startPath.getFileSystem().getSeparator());
final Path searchPath = startPath.resolve(packagePath).toAbsolutePath();
if (subPackages) {
filter = p -> {
p = p.toAbsolutePath();
Path other;
if (p.getNameCount() >= searchPath.getNameCount()) {
other = searchPath;
} else {
other = searchPath.subpath(0, p.getNameCount());
}
if (p.startsWith(other)) {
return true;
} else {
return false;
}
};
} else {
filter = p -> {
p = p.toAbsolutePath();
if (p.getNameCount() > searchPath.getNameCount() + 1) {
return false;
} else if (p.toAbsolutePath().startsWith(searchPath)) {
return true;
} else {
return false;
}
};
}
if (Files.exists(searchPath)) {
findDirClassFilesRecursive(filter, searchPath, startPath, result, loader, bailError);
} // the package does not have to exist in every directory
}
return result;
}
private static void findDirClassFilesRecursive(Filter <Path> filter, Path path, Path start, Set <Class <?>> classes, ClassLoader loader, boolean bailError) {
try (DirectoryStream <Path> dirStream = Files.newDirectoryStream(path, filter)) {
for (Path p : dirStream) {
if (Files.isDirectory(p)) {
findDirClassFilesRecursive(filter, p, start, classes, loader, bailError);
} else {
Path subp = p.subpath(start.getNameCount(), p.getNameCount());
String str = subp.toString();
if (str.endsWith(".class")) {
str = str.substring(0, str.length() - 6);
String sep = p.getFileSystem().getSeparator();
if (str.startsWith(sep)) {
str = str.substring(sep.length());
}
if (str.endsWith(sep)) {
str = str.substring(0, str.length() - sep.length());
}
String fullClassName = str.replace(sep, ".");
try {
Class <?> cls = Class.forName(fullClassName, false, loader);
classes.add(cls);
} catch (ClassNotFoundException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
}
}
} catch (IOException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
private static Set <Class <?>> findJarClasses(boolean subPackages, String packageName, Set <URL> nextJarUrls, Set <Path> directories, ClassLoader loader, boolean bailError) {
String packagePath = packageName.replace('.', '/');
Set <Class <?>> result = new HashSet <>();
Set <URL> allJarUrls = new HashSet <>();
while (true) {
Set <URL> thisJarUrls = new HashSet <>(nextJarUrls);
thisJarUrls.removeAll(allJarUrls);
if (thisJarUrls.isEmpty()) {
break;
}
allJarUrls.addAll(thisJarUrls);
for (URL url : thisJarUrls) {
try (JarInputStream stream = new JarInputStream(url.openStream())) {
// may want better way to open url connections
readJarClassPath(stream, nextJarUrls, directories, bailError);
JarEntry entry = stream.getNextJarEntry();
while (entry != null) {
String name = entry.getName();
int i = name.lastIndexOf("/");
if (i > 0 && name.endsWith(".class")) {
try {
if (subPackages) {
if (name.substring(0, i).startsWith(packagePath)) {
Class <?> cls = Class.forName(name.substring(0, name.length() - 6).replace("/", "."), false, loader);
result.add(cls);
}
} else {
if (name.substring(0, i).equals(packagePath)) {
Class <?> cls = Class.forName(name.substring(0, name.length() - 6).replace("/", "."), false, loader);
result.add(cls);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
entry = stream.getNextJarEntry();
}
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
private static void readJarClassPath(JarInputStream stream, Set <URL> jarUrls, Set <Path> directories, boolean bailError) {
Object classPathObj = stream.getManifest().getMainAttributes().get(new Name("Class-Path"));
if (classPathObj == null) {
return;
}
if (classPathObj instanceof String) {
String[] entries = ((String) classPathObj).split("\\s+");// should also work with a single space (" ")
for (String entry : entries) {
try {
URL url = new URL(entry);
addFromUrl(jarUrls, directories, url, bailError);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
} else if (bailError) {
throw new RuntimeException("the Class-Path attribute is no String: " + classPathObj.getClass().getName() + " tos='" + classPathObj + "'");
}
}
private static void findClassPools(ClassLoader classLoader, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, String packageName) {
packageName = packageName.replace('.', '/');
while (classLoader != null) {
if (classLoader instanceof URLClassLoader) {
for (URL url : ((URLClassLoader) classLoader).getURLs()) {
addFromUrl(jarUrls, directoryPaths, url, bailError);
System.out.println("rurl-class-loade.url[n]r->'" + url + "'");
}
} else {
URL res = classLoader.getResource("");
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
res = classLoader.getResource("/");
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
res = classLoader.getResource("/" + packageName);
if (res != null) {
res = removePackageFromUrl(res, packageName, bailError);
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
}
res = classLoader.getResource(packageName);
if (res != null) {
res = removePackageFromUrl(res, packageName, bailError);
if (res != null) {
addFromUrl(jarUrls, directoryPaths, res, bailError);
}
}
addFromUnknownClass(classLoader, jarUrls, directoryPaths, bailError, 8);
}
classLoader = classLoader.getParent();
}
}
private static URL removePackageFromUrl(URL res, String packagePath, boolean bailError) {
packagePath = "/" + packagePath;
String urlStr = res.toString();
if ( !urlStr.endsWith(packagePath)) {
if (bailError) {
throw new RuntimeException("the url string does not end with the packagepath! packagePath='" + packagePath + "' urlStr='" + urlStr + "'");
} else {
return null;
}
}
urlStr = urlStr.substring(0, urlStr.length() - packagePath.length());
if (urlStr.endsWith("!")) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
}
if (urlStr.startsWith("jar:")) {
urlStr = urlStr.substring(4);
}
try {
return new URL(urlStr);
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
} else {
return null;
}
}
}
private static void addFromUnknownClass(Object instance, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, int maxDeep) {
Class <?> cls = instance.getClass();
while (cls != null) {
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
Class <?> type = field.getType();
Object value;
try {
value = getValue(instance, field);
if (value != null) {
addFromUnknownValue(value, jarUrls, directoryPaths, bailError, type, field.getName(), maxDeep - 1);
}
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
if (bailError) {
final String version = System.getProperty("java.version");
String vers = version;
if (vers.startsWith("1.")) {
vers = vers.substring(2);
}
int dotindex = vers.indexOf('.');
if (dotindex != -1) {
vers = vers.substring(0, dotindex);
}
int versNum;
try {
versNum = Integer.parseInt(vers);
} catch (NumberFormatException e1) {
throw new RuntimeException("illegal version: '" + version + "' lastError: " + e.getMessage(), e);
}
if (versNum <= 11) {
throw new RuntimeException(e);
}
}
}
}
cls = cls.getSuperclass();
}
}
private static Object getValue(Object instance, Field field) throws IllegalArgumentException, IllegalAccessException, SecurityException {
try {
boolean flag = field.isAccessible();
boolean newflag = flag;
try {
field.setAccessible(true);
newflag = true;
} catch (Exception e) {}
try {
return field.get(instance);
} finally {
if (flag != newflag) {
field.setAccessible(flag);
}
}
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
try {
Field override = AccessibleObject.class.getDeclaredField("override");
boolean flag = override.isAccessible();
boolean newFlag = flag;
try {
override.setAccessible(true);
flag = true;
} catch (Exception s) {}
override.setBoolean(field, true);
if (flag != newFlag) {
override.setAccessible(flag);
}
return field.get(instance);
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e1) {
e.addSuppressed(e1);
throw e;
}
}
}
private static void addFromUnknownValue(Object value, Set <URL> jarUrls, Set <Path> directoryPaths, boolean bailError, Class <?> type, String fieldName, int maxDeep) {
if (Collection.class.isAssignableFrom(type)) {
for (Object obj : (Collection <?>) value) {
URL url = null;
try {
if (obj instanceof URL) {
url = (URL) obj;
} else if (obj instanceof Path) {
url = ((Path) obj).toUri().toURL();
} else if (obj instanceof File) {
url = ((File) obj).toURI().toURL();
}
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
if (url != null) {
addFromUrl(jarUrls, directoryPaths, url, bailError);
}
}
} else if (URL[].class.isAssignableFrom(type)) {
for (URL url : (URL[]) value) {
addFromUrl(jarUrls, directoryPaths, url, bailError);
}
} else if (Path[].class.isAssignableFrom(type)) {
for (Path path : (Path[]) value) {
try {
addFromUrl(jarUrls, directoryPaths, path.toUri().toURL(), bailError);
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
} else if (File[].class.isAssignableFrom(type)) {
for (File file : (File[]) value) {
try {
addFromUrl(jarUrls, directoryPaths, file.toURI().toURL(), bailError);
} catch (MalformedURLException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
} else if (maxDeep > 0) {
addFromUnknownClass(value, jarUrls, directoryPaths, bailError, maxDeep - 1);
}
}
private static void addFromUrl(Set <URL> jarUrls, Set <Path> directoryPaths, URL url, boolean bailError) {
if (url.getFile().endsWith(".jar") || url.getFile().endsWith(".zip")) {
// may want better way to detect jar files
jarUrls.add(url);
} else {
try {
Path path = Paths.get(url.toURI());
if (Files.isDirectory(path)) {
directoryPaths.add(path);
} else if (bailError) {
throw new RuntimeException("unknown url for class loading: " + url);
}
} catch (URISyntaxException e) {
if (bailError) {
throw new RuntimeException(e);
}
}
}
}
imports:
import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
plain java: FindAllClassesUsingPlainJavaReflectionTest.java
@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {
private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
log.error(throwable.getLocalizedMessage());
return new RuntimeException(throwable);
};
private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {
Locale locale = Locale.getDefault();
Charset charset = StandardCharsets.UTF_8;
val fileManager = ToolProvider.getSystemJavaCompiler()
.getStandardFileManager(/* diagnosticListener */ null, locale, charset);
StandardLocation location = StandardLocation.CLASS_PATH;
JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
.getOrElseThrow(asRuntimeException);
String pathToPackageAndClass = basePackageName.replace(".", File.separator);
Function<String, String> mapToClassName = s -> {
String prefix = Arrays.stream(s.split(pathToPackageAndClass))
.findFirst()
.orElse("");
return s.replaceFirst(prefix, "")
.replaceAll(File.separator, ".");
};
return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
.filter(javaFileObject -> javaFileObject.getKind().equals(kind))
.map(FileObject::getName)
.map(fileObjectName -> fileObjectName.replace(".class", ""))
.map(mapToClassName)
.map(className -> Try.of(() -> Class.forName(className))
.getOrElseThrow(asRuntimeException))
.collect(Collectors.toList());
};
@Test
@DisplayName("should get classes recursively in given package")
void test() {
Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
assertThat(classes).hasSizeGreaterThan(4);
classes.stream().map(String::valueOf).forEach(log::info);
}
}
PS: to simplify boilerplates for handling errors, etc, I'm using here vavr
and lombok
libraries
other implementations could be found in my GitHub daggerok/java-reflection-find-annotated-classes-or-methods repo
As of org.reflections
version 0.10
:
org.reflections.scanners.SubTypesScanner
and
org.reflections.Reflections.getAllTypes()
are deprecated. I userd:
public Set<String> getEntityNamesInPackage(String packagePath) {
Reflections reflections = new Reflections(new ConfigurationBuilder()
.filterInputsBy(new FilterBuilder().includePackage(packagePath))
.setUrls(ClasspathHelper.forPackage(packagePath))
.setScanners(SubTypes.filterResultsBy(s -> true)));
return reflections.getAll(SubTypes).stream()
.filter(s -> s.startsWith(packagePath))
.collect(Collectors.toSet());
}
Here is the Java 17 version base on @BrainStone's answer:
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* load classes in a package <br>
* usage: <br>
*
* <pre>
* List<Class<?>> classes = PackageClassLoader.getClassesForPackage("com.replace.this.with.your.package");
* </pre>
*
* Credits to: <a href="https://stackoverflow.com/a/22462785/7425847">BrainStone</a>
*
* @author Camio1945
*/
public class PackageClassLoader {
private PackageClassLoader() {}
/**
* Attempts to list all the classes in the specified package as determined by the context class
* loader
*
* @param packageName the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException if something went wrong
*/
public static List<Class<?>> getClassesForPackage(String packageName)
throws ClassNotFoundException {
final List<Class<?>> classes = new ArrayList<>();
try {
final ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
final Enumeration<URL> resources = cld.getResources(packageName.replace('.', '/'));
URLConnection connection;
// the class is not visible in java 17, so we need to check by comparing the class name
String fileURLConnection = "sun.net.www.protocol.file.FileURLConnection";
for (URL url; resources.hasMoreElements() && ((url = resources.nextElement()) != null); ) {
try {
connection = url.openConnection();
String connectionClassName = connection.getClass().getName();
if (connection instanceof JarURLConnection) {
checkJarFile((JarURLConnection) connection, packageName, classes);
} else if (connectionClassName.equals(fileURLConnection)) {
checkDirectory(
new File(URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8)),
packageName,
classes);
} else {
throw new ClassNotFoundException(
packageName + " (" + url.getPath() + ") does not appear to be a valid package");
}
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for " + packageName, ioex);
}
}
} catch (final NullPointerException ex) {
throw new ClassNotFoundException(
packageName + " does not appear to be a valid package (Null pointer exception)", ex);
} catch (final IOException ioex) {
throw new ClassNotFoundException(
"IOException was thrown when trying to get all resources for " + packageName, ioex);
}
return classes;
}
/**
* Private helper method
*
* @param directory The directory to start with
* @param packageName The package name to search for. Will be needed for getting the Class object.
* @param classes if a file isn't loaded but still is in the directory
* @throws ClassNotFoundException
*/
private static void checkDirectory(File directory, String packageName, List<Class<?>> classes)
throws ClassNotFoundException {
File tmpDirectory;
if (directory.exists() && directory.isDirectory()) {
final String[] files = directory.list();
for (final String file : files) {
if (file.endsWith(".class")) {
try {
classes.add(Class.forName(packageName + '.' + file.substring(0, file.length() - 6)));
} catch (final NoClassDefFoundError e) {
// do nothing. this class hasn't been found by the
// loader, and we don't care.
}
} else if ((tmpDirectory = new File(directory, file)).isDirectory()) {
checkDirectory(tmpDirectory, packageName + "." + file, classes);
}
}
}
}
/**
* Private helper method.
*
* @param connection the connection to the jar
* @param packageName the package name to search for
* @param classes the current ArrayList of all classes. This method will simply add new classes.
* @throws ClassNotFoundException if a file isn't loaded but still is in the jar file
* @throws IOException if it can't correctly read from the jar file.
*/
private static void checkJarFile(
JarURLConnection connection, String packageName, List<Class<?>> classes)
throws ClassNotFoundException, IOException {
final JarFile jarFile = connection.getJarFile();
final Enumeration<JarEntry> entries = jarFile.entries();
String name;
for (JarEntry jarEntry;
entries.hasMoreElements() && ((jarEntry = entries.nextElement()) != null); ) {
name = jarEntry.getName();
if (name.contains(".class")) {
name = name.substring(0, name.length() - 6).replace('/', '.');
if (name.contains(packageName)) {
classes.add(Class.forName(name));
}
}
}
}
}
Usage:
List<Class<?>> classes = PackageClassLoader.getClassesForPackage("com.replace.this.with.your.package");
If you are merely looking to load a group of related classes, then Spring can help you.
Spring can instantiate a list or map of all classes that implement a given interface in one line of code. The list or map will contain instances of all the classes that implement that interface.
That being said, as an alternative to loading the list of classes out of the file system, instead just implement the same interface in all the classes you want to load, regardless of package and use Spring to provide you instances of all of them. That way, you can load (and instantiate) all the classes you desire regardless of what package they are in.
On the other hand, if having them all in a package is what you want, then simply have all the classes in that package implement a given interface.
Note that the interface itself doesn't have to declare any methods - it can be completely empty.
To inject a list of classes implementing a given interface, use the following lines of code...
@Autowired
private List<ISomeInterface> implementationList;
It is also possible to inject a Map of classes using Spring. Read the docs if interested to see how.
Finally, I will offer one other solution that is a bit more elegant than searching the entire file system tree.
Create a custom annotation that builds a catalog of the classes to which it is applied - something like @ClassCatalog.
-
1How about telling us how Spring can do this. Otherwise this is just hearsay.– DanielCommented May 10, 2021 at 10:20
It is not possible, since all classes in the package might not be loaded, while you always knows package of a class.