Consider the following situation. JPMS application with layer tree. In Module X on Layer C it is necessary to create Spring context while Spring framework is located on Layer B :

boot layer
  |- Layer B with Spring framework.
       |- Layer C with Module X and Module Y

In Module X we have the following code

        System.out.println("ThreadContextClassLoader:" + Thread.currentThread().getContextClassLoader());
        System.out.println("This classLoader:" + this.getClass().getClassLoader());

        //now  resource as stream for class from Module Y:
        var stream1 = this.getClass().getClassLoader().getResourceAsStream("com/foo/moduley/ContextConfig.class");
        System.out.println("Stream 1 = " + stream1);

        var stream2 = this.getClass().getClassLoader().getResourceAsStream("org/springframework/context/ApplicationContextAware.class");
        System.out.println("Stream 2 = " + stream2);

        var clazz = this.getClass().getClassLoader().loadClass("org.springframework.context.ApplicationContextAware");
        System.out.println("Class:" + clazz);
        var stream3 = clazz.getModule().getResourceAsStream("org/springframework/context/ApplicationContextAware.class");
        System.out.println("Stream 3 = " + stream3);

And this is output:

    ThreadContextClassLoader:jdk.internal.loader.Loader@426bf2f2
    This classLoader:jdk.internal.loader.Loader@426bf2f2
    Stream 1 = sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@68dc2f53
    Stream 2 = null
    Class:interface org.springframework.context.ApplicationContextAware
    Stream 3 = sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@1d51b6a8

As we see getResourceAsStream works differently in JPMS. Now cosider ClassPathResource.getInputStream() and this line

https://github.com/spring-projects/spring-framework/blob/4e2d3573189b7c0afce62bce29cd915de4077f56/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java#L209

Because if this the given JPMS application won't work.

I suggest either add ModulePathResource class (I looked for it but didn't find) or modify ClassPathResource.getInputStream().

Comment From: snicoll

Please share a sample that we can run ourselves. Code and log output like that is not complete enough, I am afraid.

Comment From: PavelTurk

@snicoll Thank you for a quick reply. Full test that creates layers is here with readme

Comment From: sbrannen

Have you tried using ModuleResource?

Comment From: PavelTurk

@sbrannen No, I haven't. Could you say where I can find information how to use it? As I understand Spring must use it instead of ClassPathResource. But how to make Spring do it?

Comment From: PavelTurk

@sbrannen I've just tried Spring 6.1.1 result is the same:

Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: I/O failure while processing configuration class [com.foo.moduley.ApplicationContextHolder]
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:250)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:188)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:304)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:196)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.parse(ConfigurationClassParser.java:164)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:416)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:289)
    at spring.context@6.1.1/org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:349)
    at spring.context@6.1.1/org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:118)
    at spring.context@6.1.1/org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:775)
    at spring.context@6.1.1/org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:597)
    at com.foo.module.x@1.0.0/com.foo.modulex.ActivatorProvider.start(ActivatorProvider.java:16)
    at com.foo.main@1.0.0/com.foo.main.Project.main(Project.java:42)
Caused by: java.io.FileNotFoundException: class path resource [org/springframework/context/ApplicationContextAware.class] cannot be opened because it does not exist
    at spring.core@6.1.1/org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:215)
    at spring.core@6.1.1/org.springframework.core.type.classreading.SimpleMetadataReader.getClassReader(SimpleMetadataReader.java:54)
    at spring.core@6.1.1/org.springframework.core.type.classreading.SimpleMetadataReader.<init>(SimpleMetadataReader.java:48)
    at spring.core@6.1.1/org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:103)
    at spring.core@6.1.1/org.springframework.core.type.classreading.CachingMetadataReaderFactory.getMetadataReader(CachingMetadataReaderFactory.java:122)
    at spring.core@6.1.1/org.springframework.core.type.classreading.SimpleMetadataReaderFactory.getMetadataReader(SimpleMetadataReaderFactory.java:81)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.asSourceClass(ConfigurationClassParser.java:620)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser$SourceClass.getInterfaces(ConfigurationClassParser.java:946)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.processInterfaces(ConfigurationClassParser.java:386)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass(ConfigurationClassParser.java:332)
    at spring.context@6.1.1/org.springframework.context.annotation.ConfigurationClassParser.processConfigurationClass(ConfigurationClassParser.java:245)
    ... 13 more