Follows from #31999, which introduced NativeImageResourceProvider as the solution for Flyway classpath scanning in native images. This reports a gap in that implementation: subdirectory migration locations are not supported.
Problem
NativeImageResourceProvider uses a single-level glob when scanning the classpath migration locations at runtime in a native image:
// NativeImageResourceProvider.java
private Resource[] getResources(PathMatchingResourcePatternResolver resolver, Location location, Resource root) {
try {
return resolver.getResources(root.getURI() + "/*");
}
...
}
The /* pattern matches only files directly under the root location. Migration script placed in subdirectories (a common convention in larger projects, e.g. db/migration/1.0.0/V1__init.sql, db/migration/2.0.0/V2__add_column.sql) are silently skipped, causing Flyway to find no migrations and fail or produce an empty schema.
Note: the resources are correctly bundled in the native image when resource hints register db/migration/**. The failure is purely in runtime discovery.
Expected behaviour
All migration scripts under a configured location are found regardless of nesting depth, consistent with the behaviour of Flyway on a regular JVM.
Actual behaviour
Only scripts directly under the location root (e.g. db/migration/V1__init.sql) are found. Scripts in subdirectories (e.g. db/migration/1.0.0/V1__init.sql) are not discovered and Flyway does not run them.
Proposed fix
Change /* to /**/* in getResources():
// before
return resolver.getResources(root.getURI() + "/*");
// after
return resolver.getResources(root.getURI() + "/**/*");
This also requires fixing asClassPathResource to keep the full subdirectory path. With /*, resources are always one level deep, so location.getRootPath() + "/" + filename is correct. When changed to /**/*, subdirectory resources appear and that construction flattens db/migration/1.0.0/V1__init.sql incorrectly to db/migration/V1__init.sql:
// before — correct only for flat locations, breaks with /**/*
String fileNameWithAbsolutePath = location.getRootPath() + "/" + locatedResource.resource().getFilename();
// after — uses the full classpath-relative path from the resolver result
private String resolveClasspathPath(Resource resource, Location location) {
if (resource instanceof ClassPathResource cpr) {
return cpr.getPath(); // e.g. "db/migration/1.0.0/V1__init.sql"
}
return location.getRootPath() + "/" + resource.getFilename(); // fallback
}
Reproduce
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource root = resolver.getResource("classpath:db/migration");
// current — misses subdirectory scripts
Resource[] shallow = resolver.getResources(root.getURI() + "/*");
// expected
Resource[] recursive = resolver.getResources(root.getURI() + "/**/*");
With db/migration/V1__flat.sql and db/migration/1.0.0/V2__subdir.sql on the classpath, shallow contains only V1__flat.sql but recursive contains both.
Spring Boot version: 4.0.0
Follows from #31999, which introduced NativeImageResourceProvider as the solution for Flyway classpath scanning in native images. This reports a gap in that implementation: subdirectory migration locations are not supported.
Problem
NativeImageResourceProvideruses a single-level glob when scanning the classpath migration locations at runtime in a native image:The
/*pattern matches only files directly under the root location. Migration script placed in subdirectories (a common convention in larger projects, e.g.db/migration/1.0.0/V1__init.sql, db/migration/2.0.0/V2__add_column.sql) are silently skipped, causing Flyway to find no migrations and fail or produce an empty schema.Note: the resources are correctly bundled in the native image when resource hints register
db/migration/**. The failure is purely in runtime discovery.Expected behaviour
All migration scripts under a configured location are found regardless of nesting depth, consistent with the behaviour of Flyway on a regular JVM.
Actual behaviour
Only scripts directly under the location root (e.g.
db/migration/V1__init.sql) are found. Scripts in subdirectories (e.g.db/migration/1.0.0/V1__init.sql) are not discovered and Flyway does not run them.Proposed fix
Change
/*to/**/*ingetResources():This also requires fixing
asClassPathResourceto keep the full subdirectory path. With/*, resources are always one level deep, solocation.getRootPath() + "/" + filenameis correct. When changed to/**/*, subdirectory resources appear and that construction flattensdb/migration/1.0.0/V1__init.sqlincorrectly todb/migration/V1__init.sql:Reproduce
With
db/migration/V1__flat.sqlanddb/migration/1.0.0/V2__subdir.sqlon the classpath, shallow contains only V1__flat.sql but recursive contains both.Spring Boot version: 4.0.0