Skip to end of metadata
Go to start of metadata

Why does this page exist?

Icon

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:

  1. By default, all dependencies of a WAR module are contained in the WEB-INF/lib folder of a WAR file.
  2. 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.
  3. 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.

Key

Title

Status

MWAR-9

WAR plugin should support minimal WARs for inclusion within an EAR

Open

MWAR-21

Need a way to include limited set of webapp's dependencies

Closed fixed (with commenters protesting)

MWAR-61

Document how to set manifest classpath and exclude dependency from WEB-INF/lib

Closed fixed

MWAR-81

Request enhancement to pattern matching for warSourceIncludes/warSourceExcludes functionality (regular expressions?)

Open

MWAR-111

Transitive dependencies of optional dependencies are included in WEB-INF/lib

Open

MWAR-135

Add an option to exclude content from the generated archive to implement the skinny war process

Closed fixed

MWAR-146

Better support for skinny wars

Closed won't fix

MNG-1991

Can't get transitive dependencies from a war pom when this war is added as a depdency of a project

Open

MNG-2205

"provided" scope dependencies must be transitive

Open

MNG-3269

Different builds for ejb-client optional with parent

Open

MEAR-17

Jar files packed in the EAR file should also be added to application.xml or manifest.mf

Closed won't fix

MEAR-60

Improve support for skinny WAR files

Open

MEAR-75

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

The following issues include patches: MWAR-9, MWAR-21, MWAR-61, MWAR-111, MWAR-146, MWAR-3269.

Note that all of these address the maven-war-plugin.

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:

  1. 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.
  2. 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.
  3. The Manifest class-path is a concern not just for WAR modules, but for other module types embedded into an EAR as well.
  4. Any module can become part of more than one EAR file.
  5. 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)

 

please contribute

References

26 Comments

  1. The sourceExcludes setting is now excluding only files in the webapp source directory (src/main/webapp).

    I've added a packageExcludes that excludes files before packaging. What you're saying is that it should be taken into account with the war:war and war:exploded phase?

  2. snickoll wrote:

    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.

  3. Thanks Barend for doing this work, support for skinny wars is important for me too. A few remarks:

    • When (re)writing a manifest class path, the effect of version conflict resolution should be taken into account
    • These requirements are applicable to every artifact that includes other artifacts, so for instance the war plugin should rewrite the manifest class path in case of a resolved version conflict
    • The use of 'optional' and 'provided' in the war plugin to select a combination of manifest class path and/or web-inf/lib inclusion is a hack
    • Good point, I've added it to the requirements table.
    • Good point, I hadn't thought of that.
    • Agreed.
  4. 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.

  5. /me regrets not having named this page "maven-enterprise-packaging" or something like that.

    A related feature that would be very useful in maven-ear-plugin is automatic gathering of security-role elements from all embedded modules when generating the application deployment descriptor.

  6. 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)

    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).

    Hence when writing the pom you can state which dependencies need to be included and which can be moved (by declaring as "thin")

    Finally inside the pom you can specify which of the wars (thin, normal or both) should be generated.

    Note:  When building "normal" wars the thin scope is treated exactly the same as compile scope.

    When building EARs full ears don't need any special handling, but thin ones would need to include the "thin" dependencies. 

  7. 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).

  8. I have tried to "refine" my concerns, I think it comes down to two points:

    • To be able to easily identify if a WAR file has been modified (repacked) when examining the contents of an EAR
    • To identify which dependencies can be stripped in the WAR rather than the EAR **

    ** This avoids duplication if the same WAR is packaged in multiple EAR's.

    With respect to "heavy-handed" unless there is another use case with "shared components" (hence warranting a generic solution), my proposal is probably too heavy.

  9. I must offer this defect: MNG-2205

    "provided" scope dependencies must be transitive

    , 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.

  10. Hi,

    Just letting you know that I am going to be developing this change.

    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:

    1. Mark a war as skinny in war plugin configuration.
    2. Dependencies must be marked "provided".
    3. "provided" dependencies will have manifest entries in the configurable form of "skinny-lib/etc-1.0.0.jar"
    4. 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)
    5. 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.

    -Timothy

  11. 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?

  12. Hi Michael,

    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.

  13. Thank you very much for your explanation.

    I am on Maven 2.0.5 right now, hence my question about version. You are working on the latest (2.0.9) branch?

  14. Hi,

     I'm in need of this patches also.

    How can I track it and know its ready for usage?

    Asaf

  15. 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.

  16. 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.

  17. 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.

  18. A workaround for Maven 2.0.9 to use skinny wars without duplicating dependencies in the EAR is nicely summarized by this mailing list post to maven-users.

  19. David,

    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(warning) 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.

  20. The tip Danny links to has the drawback of requiring at least a mvn install for it to work, as stated by Martin Hoeller. However, starting with maven 3.0 it seems to work even within a reactor build.

    I haven't explored all the pros and cons of this approach, but just want to share this knowledge with you all.

  21. Hi,

    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 following illustrates the necessary change:

            <dependency>
                <groupId>tim.maven</groupId>
                <artifactId>tim-lib-x</artifactId>
                <version>0.1</version>
                <scope>provided</scope>
                <target>application</target>
            </dependency>

    The following are examples of valid targets:

    application   (/APP-INF/lib)
    bootstrap     ($JAVA_HOME/jre/lib/ext)
    container     ($JEE_SERVER_HOME/lib)
    common      ($JEE_SERVER_HOME/shared)

    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).

    -Tim

  22. 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.

    What kind of code changes do you have Timothy?

  23. Slight improvement to the Skinny WARs recepie (http://maven.apache.org/plugins/maven-war-plugin/examples/skinny-wars.html">Maven) :

    In the EAR pom, instead of copying the dependencies from each of the WAR's pom xmls, just add the dependency on each WAR pom (<type>pom</type>).

    Maven then will resolve all transitive dependencies for WARs and will add them to the defaultJavaBundleDir.

     

  24. This page needs to be updated, many JIRA issues are solved by now...