While upgrading to 2.4.2 we started seeing this exception on startup:
java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
at org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener.postProcessFields(MockitoTestExecutionListener.java:105)
at org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener.injectFields(MockitoTestExecutionListener.java:89)
at org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener.prepareTestInstance(MockitoTestExecutionListener.java:56)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:244)
at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$6(ClassBasedTestDescriptor.java:350)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:355)
...
Caused by: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property 'spring.profiles.include' imported from location 'class path resource [application-it.yml]' is invalid in a profile specific resource [origin: class path resource [application-it.yml] - 54:14]
at org.springframework.boot.context.config.InvalidConfigDataPropertyException.lambda$throwOrWarn$1(InvalidConfigDataPropertyException.java:124)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1085)
at org.springframework.boot.context.config.InvalidConfigDataPropertyException.throwOrWarn(InvalidConfigDataPropertyException.java:121)
at org.springframework.boot.context.config.ConfigDataEnvironment.checkForInvalidProperties(ConfigDataEnvironment.java:354)
at org.springframework.boot.context.config.ConfigDataEnvironment.applyToEnvironment(ConfigDataEnvironment.java:323)
at org.springframework.boot.context.config.ConfigDataEnvironment.processAndApply(ConfigDataEnvironment.java:236)
at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:97)
at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:89)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:100)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:86)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:82)
at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:63)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:117)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:111)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:62)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:362)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:123)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
... 68 more
Originally we thought the issue was related to: https://github.com/spring-projects/spring-boot/issues/24733 , however after investigating further and asking for advice it was suggested I open a new issue.
In our use case we have a common library for all of our microservices platforms that configures JSON logging. Each service uses spring.profiles.include to import the logging configuration. We use include this way because active profile does not work due to how early in the lifecycle DefaultCacheAwareContextLoaderDelegate is invoked.
I made a simple reproduction: https://github.com/workmanw/spring-bug-demo . This demo shows it working correctly with 2.3.9 and if you change the pom to 2.4.2 it will demonstrate the exception.
Comment From: workmanw
@philwebb Here you go. Please let me know if you have any questions at all regarding the reproduction. I tried to make it as simple as possible. Thank you for your time!
Comment From: philwebb
Thanks for the demo @workmanw. Unfortunately the error you're seeing is due to an intentional change we made in 2.4. We no longer allow spring.profiles.include values from profile specific documents. There's a little bit in the migration guide about why we made this change.
For your sample, I wonder if using spring.profiles.include directly on the test would work? Something like this:
@ExtendWith(SpringExtension.class)
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
properties = "spring.profiles.include=logging"
)
class DemoApplicationTests {
// ...
}
That would be allowed since the include property is not longer in a profile specific section. With that change, the sample gives the following output:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.2)
[[DEMO CONFIG]] ----- 19:30:29.699 [main] INFO c.example.demo.DemoApplicationTests - Starting DemoApplicationTests using Java 11.0.8 on pwebb-a01.vmware.com with PID 77498 (started by pwebb in /Users/pwebb/projects/spring-boot/samples/spring-bug-demo)
[[DEMO CONFIG]] ----- 19:30:29.699 [main] INFO c.example.demo.DemoApplicationTests - The following profiles are active: logging
[[DEMO CONFIG]] ----- 19:30:30.311 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)
[[DEMO CONFIG]] ----- 19:30:30.320 [main] INFO o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]
[[DEMO CONFIG]] ----- 19:30:30.321 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]
[[DEMO CONFIG]] ----- 19:30:30.321 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.41]
[[DEMO CONFIG]] ----- 19:30:30.385 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
[[DEMO CONFIG]] ----- 19:30:30.385 [main] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 671 ms
[[DEMO CONFIG]] ----- 19:30:30.583 [main] INFO o.s.s.c.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'
[[DEMO CONFIG]] ----- 19:30:30.769 [main] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
[[DEMO CONFIG]] ----- 19:30:30.793 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''
[[DEMO CONFIG]] ----- 19:30:30.801 [main] INFO c.example.demo.DemoApplicationTests - Started DemoApplicationTests in 1.43 seconds (JVM running for 2.127)
[[DEMO CONFIG]] ----- 19:30:31.142 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
[[DEMO CONFIG]] ----- 19:30:31.142 [http-nio-8080-exec-1] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
[[DEMO CONFIG]] ----- 19:30:31.143 [http-nio-8080-exec-1] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms
[[DEMO CONFIG]] ----- 19:30:31.211 [SpringContextShutdownHook] INFO o.s.s.c.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'
Does that change work for your real application?
Comment From: workmanw
@philwebb For our Integration Tests removing the spring.profiles.include from the .yaml configuration and adding to every test class does seem to resolve the issue for testing. However this issue still exists for runtime. Each microservice has application-dev.yaml, application-qa.yaml and application-prod.yaml that each include this logging profile.
Backstory: Our application-local.yaml and application-it.yaml use different logging configurations. For dev/qa/prod we have logging configuration that has very verbose and rich JSON structure design to be consumed by splunk. For local and IT tests we use a more minimal logging configuration focus around development and debugging. You cannot override an include the same way you can with an active profile value because include values concatenate. So for that reason, each of our includes reside in separate profiles for the different runtime environments, rather than doing it in the application.yaml.
Do you have any suggestions on how to solve this?
If you would like me to amend the reproduction to encompass this secondary scenario I'd be happy to do so.
Comment From: philwebb
@workmanw You can try the new spring.profiles.group property. It's designed to help with that type of situation (although I must admit it's unfortunately not as easy to use).
If you add the following to your application.yaml then when you activate dev, qa or prod you'll also get logging:
spring:
profiles:
group:
"dev": "logging"
"qa": "logging"
"prod": "logging"
Comment From: workmanw
(although I must admit it's unfortunately not as easy to use).
Actually it seems pretty straight forward to me, unless there is something I'm overlooking. I don't love that it has to go into the main application.yaml rather than the individual application-prod.yaml ones, but it's by no means a deal breaker.
Stepping back, am I approaching this problem right by using profiles this way? Is there a better solution? We definitely want to align with best practices as much as possible.
Sorry if this is turning into a support issue. Just trying to figure out what is the best path forward. Thank you for all your help!
Comment From: philwebb
If your main goal is to extract common config so that you can include it, you might want to try spring.config.import. That's a new feature in Spring Boot 2.4 and it allows you to import one config file from another.
So you might have a file called json-logging.yaml:
logging:
config: "classpath:demo-logback.xml"
...
Then you can import that file whenever you need to. E.g. in application-prod.yaml:
spring:
config:
import: "./json-logging.yaml`
...
We added this feature because we found a lot of people were adding profiles just as a way of getting common config imported. If you're not actually using the logging profile in your code then it might work out better.
Comment From: philwebb
As the group property is working out, I'm going to close this one. Thanks for the providing the feedback.
Comment From: workmanw
@philwebb Thanks again!
Comment From: beardy247
I have to say I'm disappointed in this breaking change. The whole point for us of having separate application-dev.yml files which aren't under source control is to keep dev concerns out of the main application.yml file.
Comment From: workmanw
@beardy247 FWIW That was my initial reaction as well. But once you wrap your head around spring.profiles.include and spring.config.import it does start to make sense. We were able to switch from spring.profiles.include to spring.config.import without issue. Just a little bit of work.
Comment From: wangxiang4
@workmanw yes Just switch spring.config.import to spring.profiles.include or spring.profiles.active to spring.config.activate.on-profile will do a good job migrate