Spring Boot Version: 2.3.4
Issue:
As referenced in #23096, trying to load our app (which uses quite a lot of big yaml files) still causes the following stack trace:
Caused by: org.yaml.snakeyaml.error.YAMLException: Number of aliases for non-scalar nodes exceeds the specified max=50
at org.yaml.snakeyaml.composer.Composer.composeNode(Composer.java:147)
Downgrading the version of snakeyaml to 1.25 as suggested in the old issue fails as well since these functions in OriginTrackedYamlLoader no longer exist:
java.lang.NoSuchMethodError: 'void org.yaml.snakeyaml.LoaderOptions.setMaxAliasesForCollections(int)'
at org.springframework.boot.env.OriginTrackedYamlLoader.createYaml(OriginTrackedYamlLoader.java:67)
The branch of our app with this version is here. For an example of how we use yaml, see this test (which is failing on 2.3.4), the class that's using the yaml, and the yaml the test is using.
Comment From: wilkinsona
Thanks for the sample. You aren't using any Spring Boot code to load the YAML so, unfortunately, there's nothing we can change here to help you.
The problem is your YamlPropertyFactoryBean
that's using Spring Framework's YamlPropertiesFactoryBean
to load the YAML without configuring the limits. I believe you could do that by overriding the createYaml()
method so that you can customise the loader options. You may want to open a Spring Framework enhancement request to asking for it to be easier to customise the LoaderOptions
.
Alternatively, you may want to move away from using @PropertySource
with a custom factory to allow it to load YAML. For example, you could set spring.config.additional-location
to point to your custom YAML file so that it's read by Spring Boot's config file infrastructure rather than your custom factory.
Comment From: bencalegari
Thanks for the suggestions, that seemed to work! For others looking to solve this, we overwrote createYaml
from YamlPropertiesFactoryBean
:
public class UnlimitedYamlPropertiesFactoryBean extends YamlPropertiesFactoryBean {
@Override
protected Yaml createYaml() {
LoaderOptions loaderOptions = new LoaderOptions();
loaderOptions.setAllowDuplicateKeys(false);
loaderOptions.setMaxAliasesForCollections(Integer.MAX_VALUE);
loaderOptions.setAllowRecursiveKeys(true);
return new Yaml(loaderOptions);
}
}
Then referenced this new class from our overwritten createPropertySource
from PropertySourceFactory
:
public class YamlPropertySourceFactory implements PropertySourceFactory {
@SuppressWarnings({"NullableProblems", "ConstantConditions"})
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) {
YamlPropertiesFactoryBean factory = new UnlimitedYamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
We then referenced that factory when loading our yaml into a configuration:
@Configuration
@PropertySource(value = "classpath:pages-config.yaml", factory = YamlPropertySourceFactory.class)
@ConfigurationProperties(prefix = "shiba-configuration")