52

How can I get list of all available classes in CLASSPATH at runtime?
In Eclipse IDE, you can do this by pressing Ctrl+Shift+T.
Is there any method in Java to get it done?

Nathan
  • 8,093
  • 8
  • 50
  • 76
arash
  • 967
  • 1
  • 13
  • 30

4 Answers4

60

You can get all classpath roots by passing an empty String into ClassLoader#getResources().

Enumeration<URL> roots = classLoader.getResources("");

You can construct a File based on URL as follows:

File root = new File(url.getPath());

You can use File#listFiles() to get a list of all files in the given directory:

for (File file : root.listFiles()) {
    // ...
}

You can use the standard java.io.File methods to check if it's a directory and/or to grab the filename.

if (file.isDirectory()) {
    // Loop through its listFiles() recursively.
} else {
    String name = file.getName();
    // Check if it's a .class file or a .jar file and handle accordingly.
}

Depending on the sole functional requirement, I guess that the Reflections library is much more exactly what you want.

thSoft
  • 21,755
  • 5
  • 88
  • 103
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 4
    What is meant by "classpath roots"? I noticed that `classLoader.getResources("")` only returns one or two directories on the classpath, those that seem to contain the .class files I wrote myself (and not those in 3rd party packages). – flow2k Aug 10 '17 at 23:02
  • And if we wanted the complete list of classpath, shouldn't we use `URL[] urls = ((URLClassLoader) classLoader).getURLs()`? – flow2k Aug 10 '17 at 23:03
22

Here's what I wrote to do it. I'm sure it doesn't get everything if you're doing anything weird with the classpath, but it seems to work well for me. Note that it doesn't actually load the classes, it just returns their names. This is so that it won't load all classes into memory, and because some classes in my company's codebase were causing initialization errors if loaded at the wrong time...

public interface Visitor<T> {
    /**
     * @return {@code true} if the algorithm should visit more results,
     * {@code false} if it should terminate now.
     */
    public boolean visit(T t);
}

public class ClassFinder {
    public static void findClasses(Visitor<String> visitor) {
        String classpath = System.getProperty("java.class.path");
        String[] paths = classpath.split(System.getProperty("path.separator"));

        String javaHome = System.getProperty("java.home");
        File file = new File(javaHome + File.separator + "lib");
        if (file.exists()) {
            findClasses(file, file, true, visitor);
        }

        for (String path : paths) {
            file = new File(path);
            if (file.exists()) {
                findClasses(file, file, false, visitor);
            }
        }
    }

    private static boolean findClasses(File root, File file, boolean includeJars, Visitor<String> visitor) {
        if (file.isDirectory()) {
            for (File child : file.listFiles()) {
                if (!findClasses(root, child, includeJars, visitor)) {
                    return false;
                }
            }
        } else {
            if (file.getName().toLowerCase().endsWith(".jar") && includeJars) {
                JarFile jar = null;
                try {
                    jar = new JarFile(file);
                } catch (Exception ex) {

                }
                if (jar != null) {
                    Enumeration<JarEntry> entries = jar.entries();
                    while (entries.hasMoreElements()) {
                        JarEntry entry = entries.nextElement();
                        String name = entry.getName();
                        int extIndex = name.lastIndexOf(".class");
                        if (extIndex > 0) {
                            if (!visitor.visit(name.substring(0, extIndex).replace("/", "."))) {
                                return false;
                            }
                        }
                    }
                }
            }
            else if (file.getName().toLowerCase().endsWith(".class")) {
                if (!visitor.visit(createClassName(root, file))) {
                    return false;
                }
            }
        }

        return true;
    }

    private static String createClassName(File root, File file) {
        StringBuffer sb = new StringBuffer();
        String fileName = file.getName();
        sb.append(fileName.substring(0, fileName.lastIndexOf(".class")));
        file = file.getParentFile();
        while (file != null && !file.equals(root)) {
            sb.insert(0, '.').insert(0, file.getName());
            file = file.getParentFile();
        }
        return sb.toString();
    }
}

To use it:

ClassFinder.findClasses(new Visitor<String>() {
    @Override
    public boolean visit(String clazz) {
        System.out.println(clazz)
        return true; // return false if you don't want to see any more classes
    }
});
Andy
  • 7,885
  • 5
  • 55
  • 61
  • 2
    You should use String[] paths = classpath.split( System.getProperty("path.separator") ); otherwise it wont work on Linux – sherif Apr 26 '14 at 12:58
  • I'd like to give this a try. Can you show the code to use ClassFinder? – Mark Cramer Mar 14 '16 at 18:28
  • This is bloody terrible. Where's the implementation of Visitor interface? – belgariontheking Aug 11 '16 at 17:30
  • @belgariontheking the code that calls `findClasses` should create an implementation (typically an anonymous class). It's very similar to the callback you pass to `forEach` in JavaScript, for example. I could have written the method to return an `Iterable`, but it would have been more complicated (if only Java had generator functions like a lot of other modern languages it would be simple to make it return an `Iterable`). – Andy Aug 12 '16 at 18:18
  • Wouldn't `File.separator` be better so the code is portable? – mhradek Mar 16 '18 at 18:14
12

You can make use of utility classes from the com.google.common.reflect package from the Guava library. E.g. to get all classes in a particular package:

    ClassLoader cl = getClass().getClassLoader();
    Set<ClassPath.ClassInfo> classesInPackage = ClassPath.from(cl).getTopLevelClassesRecursive("com.mycompany.mypackage");

This is concise however the same caveats as other answers describe, still apply, namely, that it will generally only find classes loaded by a 'standard' ClassLoader e.g. URLClassLoader.

Matthew Wise
  • 2,639
  • 26
  • 23
1

Every so-often I look for this. It's kind of difficult because even if you manage to find everything on the classpath, you might not find everything available to a given class loader (for instance, I worked on a project that loaded class definitions directly from a DB once).

The best bet at this point is probably to look into Spring. They scan the classes on the classpath to see if any have annotations that they need to kickstart stuff.

The accepted answer here is a good place to start:

Scanning Java annotations at runtime

Community
  • 1
  • 1
Bill K
  • 62,186
  • 18
  • 105
  • 157