Having run into the "skinny wars" issue, I've found there's a lot of info about this, but it's very fragmented. This page is an attempt at centralizing all that information to eventually work out a lasting solution to this problem. Please chime in with whatever you have to add. Thanks! --Barend Garvelink

Summary

There are a lot of issues in the Maven 2.x JIRA about EAR files containing "skinny" WAR files and the manifest class path. Some of these have been resolved, many are unresolved. Some of the solutions currently in place are work-arounds rather than real solutions. The key points:

By default, all dependencies of a WAR module are contained in the WEB-INF/lib folder of a WAR file.

The J2EE spec allows a WAR file (or in fact any other module) contained within an EAR to refer to other modules in that EAR file by mentioning them in the manifest Class-Path. These modules can then be removed from the WEB-INF/lib folder, creating what's informally known as a "skinny" WAR file. This can avoid duplication of library modules.

The current procedure for creating a skinny WAR is explained in [SKIN]. The main problem with this approach is that it requires duplication of dependency data from the WAR project's file into the EAR project's pom. There are more subtle problems too, explained in the next section.

Notes:

wrt. #1: This is the correct behaviour for stand-alone WAR modules and should remain the default.

wrt. #2: In fact, the J2EE specification requires that duplication is eliminated in this way, as mentioned in [MC06].

Problems with the current approach

Here's an overview of the JIRA issues touching on this subject. If you spot any omissions, please add them to the table.

Incorrect file name in class path (in manifest) if specifying different bundleFileName for module

Closed, fixed

MNG-3269 is particularly insidious.

The warSourceExcludes parameter used in [SKIN] feels like a hack to me. The maven-war-plugin figures out what to include like it always does, puts it neatly into a working directory under target/, then when it zips up the final artifact the files listed magically disappear. This 'hackishness' is reflected in the fact that the warSourceExcludes argument affects the standard war:war goal, but is ignored in war:exploded and war:inplace. In my opinion, this parameter should be deprecated and disposed of as soon as a better solution is in place.

While [SKIN] gives you reasonable control over where the libraries end up (albeit awkwardly because of the duplication of the <dependency> elements), the amount of control you have over the manifest class-path is too little. You can configure the entire thing by hand, which is awkward in its own right, or you can influence it with the provided scope (subject to MNG-3269), but that's not really powerful. For example, if you want to use the <bundleFileName> and/or <defaultLibBundleDir> configuration elements of the maven-ear-plugin, you cannot automatically provide a correct manifest class-path.

Patches currently in JIRA

Whose problem is this, anyway?

This issue is reported against both the maven-war-plugin and the maven-ear-plugin. Most of the patches seem to be for the maven-war-plugin. However, in my opinion "skinny" is an issue that should be addressed in the maven-ear-plugin, not in the maven-war-plugin.

My arguments for this are:

A war file is valid as a stand-alone deployment unit. This issue specifically affects war files (and any other modules) that are embedded into an ear file. A war project build need not (and arguably should not) be aware of whether its artifact is ultimately deployed as a fat stand-alone module or as a skinny ear component.

An EAR file can contain internal directory structure (a lib folder is not uncommon) that must be reflected in the manifest class-path of the embedded modules.

The Manifest class-path is a concern not just for WAR modules, but for other module types embedded into an EAR as well.

Any module can become part of more than one EAR file.

Any module that gets embedded into an EAR file ceases to exist as a stand-alone artifact in that scope, justifying the ear plugin's mucking about in a module's contents.

Note that the common way of addressing #1 and #2 (in the build spec of the embeddable artifact) surreptiously creates a circular dependency problem.

Requirements for a lasting solution

As mentioned before, I think this issue should be addressed in maven-ear-plugin, not in maven-war-plugin.

#

Summary

1

The maven-ear-plugin must (be able to) (re)write the manifest class path of any module embedded into its artifact ear file.

2

When (re)writing a manifest class path, the effect of any <bundleFileName> configuration must be taken into account.

3

When (re)writing a manifest class path, the effect of any <defaultLibBundleDir> configuration must be taken into account.

4

The maven-ear-plugin must be able to (selectively) prune the WEB-INF/lib folder any WAR artifacts it embeds.

5

When (re)writing a manifest class path, take conflict resolution into account (Marcel Schutte)

6

Must be able to specify in the ear pom which libraries are affected by #1 and #4 (Bryan Loofbourrow)

What you're saying is that it should be taken into account with the war:war and war:exploded phase?

Probably; reason being that I think the contents of a project artifact shouldn't change depending on whether you zip it or not. The effective pom is the same, the contents should be too.

Truth is, I've so far failed to see a use for the sourceExcludes and packageExcludes parameters outside the role they currently play in solving the skinny wars problem, which I think is the wrong pom to solve that problem in. They might have a reasonable use in some other scenario.

Thanks for doing this page. I like your point of view that this is a problem that should be solved in the ear if possible. Since the main motivation for my use of skinny wars is eliminating duplication of jars in memory, I could accomplish most of my purpose by simply turning on an "move duplicate war jars into the ear" flag on the ear plugin, if only such a thing existed.

One thing you do not include in your summary of the issues is the use case of needing some dependencies for-sure packaged in the war. I've found that there are some things (tag libaries, for example) that don't work unless they're packaged in the war. I don't know exactly why, but I'm guessing it's because of resource loading by just path, not classpath.

So it's important to be able to specify what you do and do not want in the war. For me this is one of the worst parts of the present system. Using combinations of includes and excludes, there is no elegant way to do this at all at present. I expressed this in MWAR-81, which is basically yet another JIRA issue associated with skinny war travails.

Are you proposing to unpack the war strip out the jars that are shared and repack it using the EAR task?

This appears to break the integrity of the original war (the original author defined what should be in it)

I don't believe that's a problem. My rationale here is that the EAR is a new, independently distributable artifact. In the context of that particular EAR file, the WAR file ceases to exist as an independent artifact. It (the war file) remains available in the repository unmodified, and can still be deployed as a stand-alone module (if applicable), or included in any other EAR file, each of which has the choice of stripping it, or not.

As an alternative could I propose

1. Introducing a new scope ("thin" for example).
2. Adding a plugin to generate "thin" wars (in much the same way as the ejb-client jars were/are created).

This involves a rather heavy modification to maven's "global" API; adding a dependency scope affects each and every plugin in existence (that deals with dependencies in some way) in any Maven build where the new scope is used. I think that's (far) too heavy-handed an approach for such a specific problem in such a specific domain (JEE deployment).

, which seems to address David Trott's posts above. I think this seems like part of the solution, in that this would let a skinny war be built, but allow the .ear build to pick up the dependencies and add them to the .ear file.

Thanks for the requirements as it makes it alot easier to ensure that the solution is complete.

I need 1 question answered:

In the context of a war, with dependencies marked as "provided" is it not the only expectation that those dependencies must be included in the encompassing ear? I cannot think of any reason why you would have "provided" dependencies in a war that should not be yanked into the ear.

What I am intending is to do the following:

Mark a war as skinny in war plugin configuration.

Dependencies must be marked "provided".

"provided" dependencies will have manifest entries in the configurable form of "skinny-lib/etc-1.0.0.jar"

EAR plugin will, upon inclusion of a war, check whether it is marked as a skinny war - if so - all provided dependencies will be pulled into the EAR defined lib. (MNG-2205)

The EAR plugin will then rewrite the manifest of the war to update the "skinny-lib/etc-1.0.0.jar" to the appropriate libraries like "APP-INF/lib/etc.jar" as per the various requirements listed above. (MEAR-75)

On a side note i'm also going to try and tackle the ejb-client issue where the dependencies of the client are not the same as the dependencies of the ejb.

Hi Timothy - thank you for taking the time to communicate with me about this and work the issue.

Simple answer is : If 'provided' allows the dependency to appear/behave in a transitive manner instead of breaking the dependency chain, that works. That will let my ear mojo pick up the dependency and bundle it as needed. That's the simple way.

Your approach seems logical to me. So, we will use special mojos (war and ear), or are you modifying the main, core mojos?

I do have to ask, what is with the manifest re-writing? Are there some (non-compliant) app servers out there using the manifest classpath to link these jars together? I see lots of chatting about that, but you don't need the manifest to make the classloading work properly in a compliant container. Is this for non-compliant containers? JBoss maybe?

Also, what release are you fixing this in / when will we see this available?

The J2EE spec says that a jar/war/ejb may specify classpath dependencies within the manifest. Other shortcuts (eg. weblogic with APP-INF/lib) make it easy but this is on a per-app-server basis. The only real solution is to have the ear-plugin rewrite part of the manifest according to the developers needs as specified by the ear's pom and the defaultLibBundleDir property and ensure that this property is a reasonable default "lib" or "APP-INF/lib" . The maven defaults should be sufficient but you have to cater for the situation a developer is in.

I am modifying the main core mojos and will submit these as patches. Ideally I would want as little configuration as possible and it should work correctly.

Timothy, I want to add my thanks that you are picking this up. I want to call your attention to another use case: it is very handy to be able to take a skinny war and build a fat war, for quick deployment and testing purposes.

Here's what I do currently. For me, a skinny war is two projects. The war project itself, and a pom project that contains all of the dependencies. In the war project, I use warSourceIncludes (note that this is broken in the alpha-2 plugin: MWAR-182) to specify the (generally quite small) set of jars that must be packaged in the war, along with an extensive list of extensions for non-jars. I make the war project dependent on the dependency project.

Then, in the ear, I add dependencies on both the war and the dependency project. This works moderately well as an approach to skinny wars, but what you're planning would of course be vastly superior. But note that there's another thing I can, and do, do: make a fat war by creating a war project that depends on both the skinny war project and the dependency project. I'd like to request that you preserve this use case in some fashion.

Some discussion on this topic in MWAR-182. warSourceIncludes, on which the strategy I describe above relies, has been deliberately removed in the 2.1-alpha-2 version of the maven-war-plugin. The discussion is about whether to restore this functionality under the new naming.

The discussion so far appears to be for j2ee 1.4 compliant app servers, where a war (or other module) in an ear has to use manifest-classpath entries to get jars in the ear into its classpath.

Note that with the now rather old JavaEE 5 spec there's an ear lib directory (by default "lib") and anything in it will be in the classpath of all modules in the ear. For this, no manifest classpath entry is needed or desirable.

I think all the app servers are now javaEE 5 compliant so supporting this case as the default might be advisable.

The way I would like this to work is that the dependencies that end up packed in the war are not transitive dependencies of the published pom.xml whereas the dependencies that are not packed into the war do end up as transitive dependencies. The ear plugin can then put all the transitive dependencies into the lib directory. If the pre-javaee5 switch is on it can also add a manifest classpath entry to the war.

Note that this kind of support can be useful for standalone servlet containers also, since they generally provide some kind of server "lib" directory that can serve the same function as the ear lib dir. In this case no manifest classpathe entry is desirable.

Finally I need support like this for the rar plugin as well. Either I'm not using it right yet or there's no control over whether dependencies get packed in the rar vs. whether they end up transitive dependencies of the rar.

I'd like to point out that there are many many people still stuck in J2EE 1.4 land. I recently came off a new project that was to be deployed on WebSphere 6.1 (WebSphere Portal 6.1 if you must know).

At my current engagement people are supporting applications up to 8 years old (can you say 1.3 deployment descriptors?). They have just "upgraded" to WAS 6.1. This is because it takes some big companies 18-24 months to plan for this stuff.

In any event, we can't afford to let this issue fade into the too hard basket.

Its been a while... I have a working solution to this problem on the 2.2.1 codebase. I will submit as a patch once I have put together a comprehensive test pack and make sure that there are no side-effects with dependency management.

Unfortunatly, the cleanest solution still comes down to a POM change. The problem with 'provided' scope is that you have no way to describe what should provide the library. The assumption is that the EAR is the provider however there are actually many possible providers - EAR, Container shared paths, Container classpath, Java extension library, etc. The solution without disrupting too much code is an optional "target" tag that declares the provider however the real solution (maven 3? maven 4) would be to add additional scopes.

The EAR plugin must then take "application" dependencies and put them in the configured default bundle folder. The assembly and dependency plugins must be able to extract provided dependencies allowing deployment/installer assemblies to utilize extract the dependencies in preparation for creating an assembly. The EAR plugin also performs any the necessary rewriting.

I have a concern that the targets are actually new scopes and "provided" is just a placeholder these. Adding new scopes affects quite a bit of code and would disrupt existing plugins (which is bad) while adding a "target" element only affects IDE integration. Existing plugins will treat "provided" scope the same as usual and the "target" is only interpreted by maven-ear-plugin, maven-assembly-plugin and maven-dependency-plugin (s).

I decided to have a look at this problem and agree with Barend that this is best solved within de ear plugin by rewriting the included war. Which is problematic because the manifest must be rewritten too, or the dependencies must added to the classpath of the ear.