Skip to content

Grails 8 - grails-data-hibernate5-dbmigration bootWar: Cannot invoke "String.split(String)" because "sources" is null - In a classic servlet-container WAR deployment #15377

@jamesfredley

Description

@jamesfredley

org.grails.cli.boot.SpringApplicationWebApplicationInitializer class (causing the NPE during Tomcat WAR deployment) is being transitively included via the Grails Database Migration functionality. Specifically:

  • This originates from org.apache.grails:grails-data-hibernate5-dbmigration
  • In Grails 7+, the Database Migration plugin depends on org.apache.grails:grails-shell for certain scripts and commands (e.g., Liquibase-related operations like dbm-* scripts).
  • grails-shell pulls in Spring Boot CLI components, including the problematic initializer that's only intended for standalone executable JARs—not traditional WAR deployments in an external container like Tomcat.

The presence of this transitive dependency explains why the class is on the classpath and gets invoked by Tomcat during context startup, leading to the null "sources" in MANIFEST.MF.

Workaround for Grails 7, build.gradle:

bootWar {
    classpath = classpath.filter { 
        !it.name.contains('spring-boot-cli') && 
        !it.name.contains('grails-shell')
    }
}

Potential updates for Grails 8:

Option 1: Gradle Feature Variant
In the plugin's build.gradle:

java {
    registerFeature('cli') {
        usingSourceSet(sourceSets.main)
    }
}
dependencies {
    // Core - always included
    implementation("org.liquibase:liquibase-core:${liquibaseVersion}")
    implementation("org.liquibase.ext:liquibase-hibernate5:${liquibaseVersion}")
    
    // CLI feature - opt-in
    cliApi("org.apache.grails:grails-shell-cli:${grailsVersion}") {
        exclude group: 'org.slf4j', module: 'slf4j-simple'
        exclude group: 'org.codehaus.groovy'
    }
}

Consuming apps would then:

// Core functionality (in WAR)
implementation("org.apache.grails:grails-data-hibernate5-dbmigration:7.0.7")
// CLI commands (dev only)
developmentOnly("org.apache.grails:grails-data-hibernate5-dbmigration:7.0.7") {
    capabilities {
        requireCapability("org.apache.grails:grails-data-hibernate5-dbmigration-cli")
    }
}


Option 2: Use compileOnlyApi + Document for Apps (Simpler)
In the plugin's build.gradle:

// Available at compile time for the plugin, but consumers must explicitly add it
compileOnlyApi("org.apache.grails:grails-shell-cli:${grailsVersion}") {
    exclude group: 'org.slf4j', module: 'slf4j-simple'
    exclude group: 'org.codehaus.groovy'
}

Consuming apps add:

developmentOnly("org.apache.grails:grails-shell-cli:7.0.7")


Option 3: Separate Artifact
Split into two published artifacts:

  • grails-data-hibernate5-dbmigration - core runtime (no CLI)
  • grails-data-hibernate5-dbmigration-cli - CLI commands (depends on core + grails-shell-cli)
    Apps use:
implementation("org.apache.grails:grails-data-hibernate5-dbmigration:7.0.7")
developmentOnly("org.apache.grails:grails-data-hibernate5-dbmigration-cli:7.0.7")

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

No status

Relationships

None yet

Development

No branches or pull requests

Issue actions