Skip to content

Resource scanning leads to duplicate results with Spring Boots executable jars. #435

@michael-simons

Description

@michael-simons

NOTE A complete reproducible example is attached.
classgraph_resources_issue.zip Please run with mvn clean package && java -jar target/classgraph_resources_issue-0.0.1-SNAPSHOT.jar

A Spring Boot application packaged can be packaged as executable jar. The format is described here: https://docs.spring.io/spring-boot/docs/2.3.0.RELEASE/reference/html/appendix-executable-jar-format.html#executable-jar-restrictions

Given the following structure:

-> classgraph_resources_issue tree src/main 
src/main
├── java
│   └── com
│       └── example
│           └── classgraph_resources_issue
│               └── ClassgraphResourcesIssueApplication.java
└── resources
    ├── application.properties
    └── foo
        └── a.cypher

6 directories, 3 files

A scan for resources inside the class path under the prefix "foo" like this:

        @Override
	public void run(String... args) {
		
		try (ScanResult scanResult = new ClassGraph()
			//			.overrideClassLoaders(ClassLoader.getSystemClassLoader())
			.whitelistPaths("foo").scan()) {
			List<Resource> resources = scanResult.getResourcesWithExtension("cypher")
				.stream()
				.collect(Collectors.toList());

			resources.forEach(System.err::println);
			if (resources.size() > 1) {
				throw new RuntimeException(":(");
			}
		}
	}

Gives me the expected resources (a.cypher) when run unpackaged.

If I package the application as an executable jar, I get:

jar:file:/Users/msimons/Projects/Issues/classgraph_resources_issue/target/classgraph_resources_issue-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes/foo/a.cypher
jar:file:/Users/msimons/Projects/Issues/classgraph_resources_issue/target/classgraph_resources_issue-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/BOOT-INF/classes/foo/a.cypher

I can "fix" this by using the System class loader only (see commented line above), but this is against Spring Boots recommendation (see link about the format):

System classLoader: Launched applications should use Thread.getContextClassLoader() when loading classes (most libraries and frameworks do so by default). Trying to load nested jar classes with ClassLoader.getSystemClassLoader() fails.

When I use current context class loader, I only get this entry: jar:file:/Users/msimons/Projects/Issues/classgraph_resources_issue/target/classgraph_resources_issue-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/BOOT-INF/classes/foo/a.cypher, which I think is wrong. I can read it through the Resource class, but the getURL()gives me the wrong URL (Note the two times "BOOT-INF/classes" )

The jar looks like this:

x
├── BOOT-INF
│   ├── classes
│   │   ├── application.properties
│   │   ├── com
│   │   │   └── example
│   │   │       └── classgraph_resources_issue
│   │   │           └── ClassgraphResourcesIssueApplication.class
│   │   └── foo
│   │       └── a.cypher
│   ├── classpath.idx
│   └── lib
│       ├── classgraph-4.8.83.jar
│       ├── jakarta.annotation-api-1.3.5.jar
│       ├── jul-to-slf4j-1.7.30.jar
│       ├── log4j-api-2.13.2.jar
│       ├── log4j-to-slf4j-2.13.2.jar
│       ├── logback-classic-1.2.3.jar
│       ├── logback-core-1.2.3.jar
│       ├── slf4j-api-1.7.30.jar
│       ├── snakeyaml-1.26.jar
│       ├── spring-aop-5.2.6.RELEASE.jar
│       ├── spring-beans-5.2.6.RELEASE.jar
│       ├── spring-boot-2.3.0.RELEASE.jar
│       ├── spring-boot-autoconfigure-2.3.0.RELEASE.jar
│       ├── spring-boot-starter-2.3.0.RELEASE.jar
│       ├── spring-boot-starter-logging-2.3.0.RELEASE.jar
│       ├── spring-context-5.2.6.RELEASE.jar
│       ├── spring-core-5.2.6.RELEASE.jar
│       ├── spring-expression-5.2.6.RELEASE.jar
│       └── spring-jcl-5.2.6.RELEASE.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── com.example
│           └── classgraph_resources_issue
│               ├── pom.properties
│               └── pom.xml
└── org
    └── springframework
        └── boot
            └── loader
                ├── ClassPathIndexFile.class
                ├── ExecutableArchiveLauncher.class
                ├── JarLauncher.class
                ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                ├── LaunchedURLClassLoader.class
                ├── Launcher.class
                ├── MainMethodRunner.class
                ├── PropertiesLauncher$1.class
                ├── PropertiesLauncher$ArchiveEntryFilter.class
                ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
                ├── PropertiesLauncher.class
                ├── WarLauncher.class
                ├── archive
                │   ├── Archive$Entry.class
                │   ├── Archive$EntryFilter.class
                │   ├── Archive.class
                │   ├── ExplodedArchive$AbstractIterator.class
                │   ├── ExplodedArchive$ArchiveIterator.class
                │   ├── ExplodedArchive$EntryIterator.class
                │   ├── ExplodedArchive$FileEntry.class
                │   ├── ExplodedArchive$SimpleJarFileArchive.class
                │   ├── ExplodedArchive.class
                │   ├── JarFileArchive$AbstractIterator.class
                │   ├── JarFileArchive$EntryIterator.class
                │   ├── JarFileArchive$JarFileEntry.class
                │   ├── JarFileArchive$NestedArchiveIterator.class
                │   └── JarFileArchive.class
                ├── data
                │   ├── RandomAccessData.class
                │   ├── RandomAccessDataFile$1.class
                │   ├── RandomAccessDataFile$DataInputStream.class
                │   ├── RandomAccessDataFile$FileAccess.class
                │   └── RandomAccessDataFile.class
                ├── jar
                │   ├── AsciiBytes.class
                │   ├── Bytes.class
                │   ├── CentralDirectoryEndRecord$1.class
                │   ├── CentralDirectoryEndRecord$Zip64End.class
                │   ├── CentralDirectoryEndRecord$Zip64Locator.class
                │   ├── CentralDirectoryEndRecord.class
                │   ├── CentralDirectoryFileHeader.class
                │   ├── CentralDirectoryParser.class
                │   ├── CentralDirectoryVisitor.class
                │   ├── FileHeader.class
                │   ├── Handler.class
                │   ├── JarEntry.class
                │   ├── JarEntryFilter.class
                │   ├── JarFile$1.class
                │   ├── JarFile$JarEntryEnumeration.class
                │   ├── JarFile$JarFileType.class
                │   ├── JarFile.class
                │   ├── JarFileEntries$1.class
                │   ├── JarFileEntries$EntryIterator.class
                │   ├── JarFileEntries.class
                │   ├── JarURLConnection$1.class
                │   ├── JarURLConnection$JarEntryName.class
                │   ├── JarURLConnection.class
                │   ├── StringSequence.class
                │   └── ZipInflaterInputStream.class
                ├── jarmode
                │   ├── JarMode.class
                │   ├── JarModeLauncher.class
                │   └── TestJarMode.class
                └── util
                    └── SystemPropertyUtils.class

20 directories, 86 files

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions