question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ClassGraph loaded class missing annotations (regression)

See original GitHub issue

I have an application that is functionally equivalent to the given example below. I have:

  1. A Spring Boot application that uses ClassGraph to find uses of an annotation.
  2. A Java application that uses ClassGraph to scan the Spring Boot uber-JAR.

When Java application scans my Spring Boot application, it finds the annotations via AnnotationInfo but loadClass gives me a Class<?> which has no annotations.

If I use ClassGraph 4.8.66, it works. If I run my Spring Boot application independently, it also works.

I have tried with verbose on each level and both find the annotation. I tried to compare runs on 4.8.66 and 4.8.67 but the logs were equivalent.

The example involves a bit of setup. I have setup a minimal Gradle multi-build project: build.gradle

plugins {
	id 'org.springframework.boot' version '2.3.3.RELEASE' apply false
}

subprojects {
	apply plugin: 'java'
	apply plugin: 'maven-publish'

	repositories {
		mavenLocal()
		jcenter()
	}

	publishing {
		repositories {
			publications {
				mavenJava(MavenPublication) {
					from components.java
				}
			}
		}
	}

	sourceCompatibility = '1.11'
	targetCompatibility = '1.11'

	// Works:
	def classGraphVersion = '4.8.66'
	// Does not work (comment out the following line to test working case):
	classGraphVersion = '4.8.92'

	dependencies {
		implementation group: 'io.github.classgraph', name: 'classgraph', version: classGraphVersion
	}
}

gradle.properties

version = 1.0.0
group = com.thespiritxiii.example

settings.gradle

rootProject.name = 'example'
include 'spring'
include 'scanner'

spring/build.gradle

apply plugin: 'org.springframework.boot'

dependencies {
	implementation platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
	implementation group: 'org.springframework.boot', name: 'spring-boot-starter-web'
}

spring/src/main/java/example/Application.java

package example;

import java.lang.annotation.Annotation;
import java.util.function.Function;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ScanResult;

@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		System.out.println("Annotation count: " + find());
		SpringApplication.run(Application.class, args);
	}

	private static <A extends Annotation, T> int findGeneric(
		Class<A> annotationClass,
		Function<A, T> getValue
	) {
		int count = 0;
		final ClassGraph classGraph = new ClassGraph()
			.enableAllInfo()
			.enableInterClassDependencies()
			.enableExternalClasses();

		try (ScanResult scanResult = classGraph.enableAnnotationInfo().scan()) {
			for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(annotationClass.getName())) {
				final Class<?> classType = classInfo.loadClass();
				final A mixin = classType.getAnnotation(annotationClass);
				// This should never be null because I called getClassesWithAnnotation with the same class.
				if (mixin == null) {
					throw new RuntimeException("Unable to find annotation on class: " + classType);
				}
				final T value = getValue.apply(mixin);
				if (value != null) {
					count += 1;
				}
			}
		}

		return count;
	}

	public static final int find() {
		return findGeneric(SomeAnnotation.class, SomeAnnotation::value);
	}
}

spring/src/main/java/example/SomeAnnotation.java

package example;

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 SomeAnnotation {
	String value();
}

spring/src/main/java/example/SomeAnnotatedClass.java

package example;

@SomeAnnotation("String")
public class SomeAnnotatedClass {
}

scanner/build.gradle

apply plugin: 'application'

application {
	mainClassName = 'example.App'
}

dependencies {
	// Spring Boot builds as an Uber jar so we don't need transitive dependencies.
	implementation(group: 'com.thespiritxiii.example', name: 'spring', version: project.version) {
		transitive = false
	}
}

scanner/src/main/java/example/App.java

package example;

import java.lang.reflect.Method;
import java.util.concurrent.ForkJoinPool;

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;

public class App {
	public static void main(String[] args) {
		final ClassGraph classGraph = new ClassGraph()
			.enableAllInfo()
			.enableExternalClasses()
			.enableInterClassDependencies();

		// Run with 1 core (deterministically).
		try (ScanResult scanResult = classGraph.scanAsync(ForkJoinPool.commonPool(), 1).get()) {
			final Class<?> classType = scanResult.getClassInfo("example.Application").loadClass();
			final Method method = classType.getMethod("find");
			final Object value = method.invoke(null);
			System.out.println("Value is: " + value);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

To test:

./gradlew spring:bootRun

Outputs 1.

./gradlew spring:build spring:publishToMavenLocal scanner:run

Throws an exception if ClassGraph is 4.8.67 or above. If 4.8.66, outputs 1.

After testing, delete ~/.m2/repository/com/thespiritxiii/example/spring from the maven local publish task.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:14 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
TheSpiritXIIIcommented, Dec 2, 2020

Thank you for your through investigation and prompt replies! That solution should work for me. 😃

0reactions
lukehutchcommented, Dec 2, 2020

So .overrideClassLoaders(Application.class.getClassLoader()) is now supported as an easier way to solve the problem you ran into.

Note however that sharing a ClassGraphClassLoader between nested scans is not advisable, because scan criteria may differ between the outer scan and the inner scan.

If you do override the classloader in this way, the outer nested scan’s classloader will be deferred to first, followed by attempting to load classes from the inner nested scan’s classloader. This should work to an arbitrary nesting depth.

You’ll need to modify your code using one of the methods I showed above, but this is fixed in 4.8.93.

Thanks for the bug report, and for being responsive with answers!

Read more comments on GitHub >

github_iconTop Results From Across the Web

classgraph/Lobby - Gitter
Basically when i try to create a ClassGraph object inside war class, it is failing to load ClassGraph eventhough jar is present in...
Read more >
Methods of a class returned by ClassInfo.loadClass ... - GitHub
I scanned classes with annotation, and iterate all methods of the class using ... ClassGraph -- initializeLoadedClasses: false ...
Read more >
java - Specification of behavior when annotation missing from ...
I have observed the same behavior; that is, the class seems to load fine when the annotation is not found, but I cannot...
Read more >
ArchUnit User Guide
ArchUnit is a free, simple and extensible library for checking the architecture of your Java code. That is, ArchUnit can check dependencies between...
Read more >
Guide to Classgraph Library - Baeldung
In this brief tutorial, we'll talk about the Classgraph library — what it helps with and how we can use it. Classgraph helps...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found