Skip to content

Loose EAR Manifest Class-Path Issue Fix#1073

Open
sajeerzeji wants to merge 6 commits intoOpenLiberty:mainfrom
sajeerzeji:GH766-Multi_module_issue_with_loose_app
Open

Loose EAR Manifest Class-Path Issue Fix#1073
sajeerzeji wants to merge 6 commits intoOpenLiberty:mainfrom
sajeerzeji:GH766-Multi_module_issue_with_loose_app

Conversation

@sajeerzeji
Copy link
Copy Markdown
Contributor

@sajeerzeji sajeerzeji commented Mar 24, 2026

Fixes #766

Issue

The application failed to start in loose EAR mode with,

StateChangeException: Error while loading class com.ibm.websphere.svt.gs.gsdb.session.MfgCategorySessionBean
ResourceLoadingException: Error while loading JPA entity classes (MfgCategory, Category, etc.)

In packaged EAR mode, JAR files are physically present in the EAR, and Liberty's classloader uses the manifest Class-Path entries to locate dependency JARs within the EAR.

In loose EAR mode (looseApplication = true), there are no physical JAR files. Instead, the plugin generates an XML descriptor (GarageSaleLibertyEAR.ear.xml) that maps source directories. Liberty's classloader needs to know which class directories belong to each module.

The Problem

  • EJB modules depend on other modules (e.g., JPA modules) via compileOnly or implementation dependencies
  • The plugin only added the EJB module's own classes to the loose XML
  • Dependency class directories were not included, causing ClassNotFoundException at runtime

Previous Approach (Reverted)
The previous fix attempted to use manifest Class-Path entries to determine dependencies, requiring users to manually declare Class-Path in their build.gradle files. This approach was reverted after cross-checking with Maven plugin implementation.

Current Approach
Added addDependencyClassDirectories() method that,

  1. Scans both compileClasspath and runtimeClasspath for project dependencies
  2. Automatically adds each dependency's class and resource directories to the module in loose XML
  3. Works without requiring manifest Class-Path configuration

How it works

  • Plugin automatically detects project dependencies from Gradle configurations
  • For each EJB/JAR module, it finds all project dependencies
  • Adds dependency class directories as <dir> elements in the loose XML
  • Liberty's classloader can now find all required classes at runtime

Usage Requirements
No special configuration needed. The plugin automatically handles dependencies declared in build.gradle.

Backward Compatibility

  • Packaged EAR mode (looseApplication = false) continues to work unchanged
  • Existing projects work without modification

Testing
Added a new test TestMultiModuleLooseEarEjbDependency that verifies

  • Loose application XML is generated correctly
  • EJB modules include dependency class directories
  • Module's own classes are still included

Before
image

After
image

…ifest, Adds that dependency's class and resource directories to the module archive element in the loose XML
Copy link
Copy Markdown
Contributor

@venmanyarun venmanyarun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed, add tests

if (jarName && jarName.endsWith(".jar")) {
String depName = jarName.replace(".jar", "")

def depProj = proj.rootProject.findProject(":${depName}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we guaranteed that the dependency project name is the jarName without .jar? I wouldn't think so. We will be missing dependencies if they don't match right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a research on this and found below cases, we can do fallback matching for these cases

  1. Custom archiveBaseName - Projects using archiveBaseName = 'custom-name' produce JARs that don't match the project name
  2. Versioned JARs - Projects with archiveVersion = '1.0' produce JARs like ProjectName-1.0.jar instead of ProjectName.jar
  3. Classified JARs - Projects using archiveClassifier = 'client' produce JARs like ProjectName-client.jar for different variants
  4. Combined customizations - Projects combining base name, version, and classifier produce complex names like custom-2.0-prod.jar

Additionally

  1. Completely custom archiveFileName - Projects using archiveFileName = 'xyz-database-layer.jar' have no relationship between project name and JAR name
  2. Dynamic JAR names - Projects computing JAR names at runtime using expressions like "${env}-${project.name}.jar" may not be resolvable at configuration time
  3. Nested projects with custom naming - Multi-level projects (:parent:child) using custom base names like archiveBaseName = 'parent-child-combined' require path-based matching

//The location of the resource directory should be the same as proj.getProjectDir()/build/resources.
//If the manifest file exists, it is copied to proj.getProjectDir()/build/resources/tmp/META-INF. If it does not exist, one is created there.
addManifestFileWithParent(moduleArchive, f, proj.sourceSets.main.getOutput().getResourcesDir().getParentFile().getCanonicalPath())
// Prefer the jar task's generated manifest (build/tmp/jar/MANIFEST.MF) which has
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do something similar for Liberty Maven plugin?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sajeerzeji Please check by creating similar project in Liberty Maven plugin and try deploying the EAR to see issue exists in LMP

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will do this and will update here

}
// If no .MF file was found in the jar source set (e.g. no static MANIFEST.MF in resources),
// still add the jar task's generated manifest if it exists.
if (!manifestAdded) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we should always do this? We don't even know if any files were added above for this module. Is this ok to do for war and rar?

@sajeerzeji
Copy link
Copy Markdown
Contributor Author

sajeerzeji commented Mar 27, 2026

Hi @cherylking
After reviewing the changes in this PR against the Maven plugin implementation, I identified a gap in the approach. I've reverted the previous changes and implemented a new fix as below,

Previous approach was to use manifest Class Path entries to determine dependencies, requiring users to manually declare Class-Path in their build.gradle files. This approach was reverted after cross-checking with Maven plugin implementation.

Added addDependencyClassDirectories() method that,

  1. Scans both compileClasspath and runtimeClasspath for project dependencies
  2. Automatically adds each dependency's class and resource directories to the module in loose XML
  3. Works without requiring manifest Class-Path configuration

Now the plugin automatically detects project dependencies from Gradle configurations, for each EJB/JAR module, it finds all project dependencies, adds dependency class directories as <dir> elements in the loose XML.

@@ -0,0 +1,8 @@
rootProject.name = 'ejb-dependency'
include ':lib-jar'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here I can see we have direct dependency with both lib-jar and ejb-jar
we should have tests that have

  1. Multi-level transitive dependencies (A depends on B depends on C)
  2. Mixed dependency types (project + external JARs)

@venmanyarun
Copy link
Copy Markdown
Contributor

@sajeerzeji
can you add screen recording ?

@sajeerzeji sajeerzeji requested a review from cherylking April 10, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi module Gradle application failed to deploy into liberty server

3 participants