294

How do I search the whole classpath for an annotated class?

I'm doing a library and I want to allow the users to annotate their classes, so when the Web application starts I need to scan the whole classpath for certain annotation.

I'm thinking about something like the new functionality for Java EE 5 Web Services or EJB's. You annotate your class with @WebService or @EJB and the system finds these classes while loading so they are accessible remotely.

Alotor
  • 7,407
  • 12
  • 38
  • 36

13 Answers13

244

Use org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

A component provider that scans the classpath from a base package. It then applies exclude and include filters to the resulting classes to find candidates.

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Arthur Ronald
  • 33,349
  • 20
  • 110
  • 136
  • 6
    Thanks for information. Do you also know how to scan classpath for classes whose fields have custom annotation? – javatar Dec 12 '12 at 14:10
  • 6
    @Javatar Use Java's reflection API. .class.getFields() For each field, invoke getAnnotation() – Arthur Ronald Jan 21 '13 at 02:59
  • @ArthurRonaldFDGarcia, how can I scan `sub package` like that? – Zaw Than oo Jan 30 '14 at 08:30
  • @CycDemo ClassPathScanningCandidateComponentProvider includes sub-packages – Arthur Ronald Mar 30 '14 at 05:39
  • 2
    NOTE: If you are doing this inside a Spring application, Spring will still evaluate and act based on `@Conditional` annotations. So if a class has its `@Conditional` value returning false, it won't be returned by `findCandidateComponents`, even if it matched the scanner's filter. This threw me today - I ended up using Jonathan's solution below instead. – Adam Burley Sep 18 '15 at 14:08
  • How do you get the class? Do you have to use the classloader? – Max Feb 07 '16 at 00:04
  • @Max Some example Class string = String.class; – Arthur Ronald Feb 08 '16 at 03:57
  • 1
    @ArthurRonald Sorry, Arthur. I mean that the `BeanDefinition` object does not provide a way to get the class directly. The closest thing seems to be `getBeanClassName` which returns a fully qualified class name, but the exact behavior of this method is not clear. Also, it's not clear which class loader the class was found in. – Max Feb 08 '16 at 11:00
  • NOTE ALSO: this doesn´t work out-of-the-box for Interface classes, which one could need to scan for. Spring actively throws them out (calling the `isCandidateComponent(AnnotatedBeanDefinition beanDefinition)` method, which returnes a false for Interfaces) of the successfully scanned resultset. BUT you can change this behavior and also get back Interfaces - [just follow this answer](http://stackoverflow.com/a/41504372/4964553). **If you use Spring Boot, than you should go with this solution first! The mentioned [fast-classpath-scanner](http://stackoverflow.com/a/25354394/4964553) doesnt work!** – jonashackt Jan 06 '17 at 11:15
  • 4
    @Max Try this: `Class> cl = Class.forName(beanDef.getBeanClassName());` http://farenda.com/spring/find-annotated-classes/ – James Watkins Sep 12 '17 at 15:58
  • 1
    Most of these comments can be solved by using AnnotatedTypeScanner, which is a wrapper around ClassPathScanningCandidateComponentProvider. – Florens Aug 02 '22 at 15:27
158

And another solution is ronmamo's Reflections.

Quick review:

  • Spring solution is the way to go if you're using Spring. Otherwise it's a big dependency.
  • Using ASM directly is a bit cumbersome.
  • Using Java Assist directly is clunky too.
  • Annovention is super lightweight and convenient. No maven integration yet.
  • Reflections indexes everything and then is super fast.
asherbret
  • 5,439
  • 4
  • 38
  • 58
Jonathan
  • 2,646
  • 2
  • 19
  • 5
  • 59
    `new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)`. c'est tout. – zapp Mar 16 '13 at 13:03
  • 5
    do I need to specify a package name? wildcard? what for all classes in classpath? – eastwater Jan 20 '18 at 00:23
  • 1
    Beware that it still has no progress on Java 9 support: https://github.com/ronmamo/reflections/issues/186 – Vadzim Apr 18 '19 at 13:21
  • the org.reflections library doesn't work right under java 13 (maybe earlier, too). The first time it gets called it seems to be ok. subsequent instantiations and uses fail saying the search urls aren't configured. – Evvo Feb 08 '20 at 23:17
  • 4
    The link Google reflections is invalid. It's a library that has nothing to do with Google. – Lluis Martinez Mar 08 '21 at 16:13
  • @LluisMartinez I agree. I edited the answer accordingly – asherbret Jan 12 '22 at 15:41
45

You can find classes with any given annotation with ClassGraph, as well as searching for other criteria of interest, e.g. classes that implement a given interface. (Disclaimer, I am the author of ClassGraph.) ClassGraph can build an abstract representation of the entire class graph (all classes, annotations, methods, method parameters, and fields) in memory, for all classes on the classpath, or for classes in whitelisted packages, and you can query that class graph however you want. ClassGraph supports more classpath specification mechanisms and classloaders than any other scanner, and also works seamlessly with the new JPMS module system, so if you base your code on ClassGraph, your code will be maximally portable. See the API here.

Luke Hutchison
  • 8,186
  • 2
  • 45
  • 40
  • 1
    Does this need Java 8 to run? – David George Sep 18 '14 at 13:57
  • Yes, it requires Java8. Uses streams. I am going to see if I can modify it to use only v7. I can't bear taking on another 700K of dependencies just to get this with `scannotations` or even more with `reflections`. Sigh. – Andrew Sep 25 '14 at 10:39
  • 1
    Updated to use Java7, no problem. Just remove the annoations and convert the functions to use anonymous inner classes. I like the 1 file style. The code is nice an clean, so even though it doesn't support a few things I would like (class + annotation at same time) I think that would be pretty damn easy to add. Great work! If someone can't manage to do the work to modify for v7, they should probably go with `Reflections`. Also, if you are using guava/etc and want to change out the collections, easy as pie. Great comments inside too. – Andrew Sep 25 '14 at 10:52
  • Right, the minimum requirement is now Java 7. The API has changed a little, see the github repo for details. (It's no longer a single source file, but I published the project on Maven Central, so it's still easy to add to a project.) – Luke Hutchison Sep 24 '15 at 14:47
  • How can this be used in conjunction with spring context component scanning ? – Bertie Jun 24 '16 at 02:15
  • I am using this approach in my project also. Thanks a lot. I have a question: is there a way to read the value of the class level custom annotation's value? Maybe I couldn't see it in your project – lamostreta Nov 05 '17 at 14:43
  • @PeterMel no, it is compiled in JDK 7 mode. It does not require JDK 8. Even the support for JPMS module scanning is entirely written using reflection, so as to be backwards compatible all the way back to JDK 7. – Luke Hutchison Aug 01 '18 at 04:04
  • 2
    @Alexandros thanks, you should check out ClassGraph, it is significantly improved over FastClasspathScanner. – Luke Hutchison Aug 01 '18 at 04:05
  • 2
    @AndrewBacker ClassGraph (the new version of FastClasspathScanner) has full support for Boolean operations, via filters or set operations. See code examples here: https://github.com/classgraph/classgraph/wiki/Code-examples – Luke Hutchison Aug 01 '18 at 04:06
  • 1
    @Luke Hutchison Already using ClassGraph. Helped me with migration to Java 10. Really useful library. – Alexandros Aug 03 '18 at 18:22
  • @lamostreta reading annotation parameter values is now supported in ClassGraph. For class annotations, you need: `scanResult.getClassInfo(className).getAnnotationInfo().getParameterValues()` – Luke Hutchison Sep 23 '18 at 23:31
28

If you want a really light weight (no dependencies, simple API, 15 kb jar file) and very fast solution, take a look at annotation-detector found at https://github.com/rmuller/infomas-asl

Disclaimer: I am the author.

rmuller
  • 12,062
  • 4
  • 64
  • 92
20

You can use Java Pluggable Annotation Processing API to write annotation processor which will be executed during the compilation process and will collect all annotated classes and build the index file for runtime use.

This is the fastest way possible to do annotated class discovery because you don't need to scan your classpath at runtime, which is usually very slow operation. Also this approach works with any classloader and not only with URLClassLoaders usually supported by runtime scanners.

The above mechanism is already implemented in ClassIndex library.

To use it annotate your custom annotation with @IndexAnnotated meta-annotation. This will create at compile time an index file: META-INF/annotations/com/test/YourCustomAnnotation listing all annotated classes. You can acccess the index at runtime by executing:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)
Sławek
  • 1,450
  • 14
  • 14
15

There's a wonderful comment by zapp that sinks in all those answers:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)
Zon
  • 18,610
  • 7
  • 91
  • 99
9

Spring has something called a AnnotatedTypeScanner class.
This class internally uses

ClassPathScanningCandidateComponentProvider

This class has the code for actual scanning of the classpath resources. It does this by using the class metadata available at runtime.

One can simply extend this class or use the same class for scanning. Below is the constructor definition.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
swayamraina
  • 2,958
  • 26
  • 28
5

Is it too late to answer. I would say, its better to go by Libraries like ClassPathScanningCandidateComponentProvider or like Scannotations

But even after somebody wants to try some hands on it with classLoader, I have written some on my own to print the annotations from classes in a package:

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

And in config File, you put the package name and unmarshall it to a class .

voucher_wolves
  • 565
  • 10
  • 20
2

The Classloader API doesn't have an "enumerate" method, because class loading is an "on-demand" activity -- you usually have thousands of classes in your classpath, only a fraction of which will ever be needed (the rt.jar alone is 48MB nowadays!).

So, even if you could enumerate all classes, this would be very time- and memory-consuming.

The simple approach is to list the concerned classes in a setup file (xml or whatever suits your fancy); if you want to do this automatically, restrict yourself to one JAR or one class directory.

mfx
  • 7,168
  • 26
  • 29
2

With Spring you can also just write the following using AnnotationUtils class. i.e.:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

For more details and all different methods check official docs: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

magiccrafter
  • 5,175
  • 1
  • 56
  • 50
  • 4
    Nice idea, but if you put a `null` value as the second parameter (which defines the class, in which inheritance hierarchy Spring will scan for a class which uses the Annotation), you´ll allway get a `null` back, according to the implementation. – jonashackt Jan 13 '17 at 08:57
1

If you're looking for an alternative to reflections I'd like to recommend Panda Utilities - AnnotationsScanner. It's a Guava-free (Guava has ~3MB, Panda Utilities has ~200kb) scanner based on the reflections library source code.

It's also dedicated for future-based searches. If you'd like to scan multiple times included sources or even provide an API, which allows someone scanning current classpath, AnnotationsScannerProcess caches all fetched ClassFiles, so it's really fast.

Simple example of AnnotationsScanner usage:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);
dzikoysk
  • 1,560
  • 1
  • 15
  • 27
1

Google Reflection if you want to discover interfaces as well.

Spring ClassPathScanningCandidateComponentProvider is not discovering interfaces.

madhu_karnati
  • 785
  • 1
  • 6
  • 22
0

Google Reflections seems to be much faster than Spring. Found this feature request that adresses this difference: http://www.opensaga.org/jira/browse/OS-738

This is a reason to use Reflections as startup time of my application is really important during development. Reflections seems also to be very easy to use for my use case (find all implementers of an interface).

  • 2
    If you look at the JIRA issue, there is comments there that they moved away from Reflections because of stability issues. – Wim Deblauwe Jan 05 '15 at 16:18
  • Reflections is amazing. However the use of it should be severely frowned upon because it makes code much harder to fix. For example, If your code reflectively looks for a method that you've somehow renamed using an IDE, the IDE probably wouldn't have modified the matching string used when doing reflection; and so it can hide bugs that could've easily been caught at compiletime and instead leaves them to silently crash during runtime. I still love Reflections though – Lorenzo Mar 22 '23 at 17:01