Resource Transformers

Aggregating classes/resources from several artifacts into one uber JAR is straight forward as long as there is no overlap. Otherwise, some kind of logic to merge resources from several JARs is required. This is where resource transformers kick in.

Transformers in org.apache.maven.plugins.shade.resource
ApacheLicenseResourceTransformer Prevents license duplication
ApacheNoticeResourceTransformer Prepares merged NOTICE
AppendingTransformer Adds content to a resource
ComponentsXmlResourceTransformer Aggregates Plexus components.xml
DontIncludeResourceTransformer Prevents inclusion of matching resources
GroovyResourceTransformer Merges Apache Groovy extends modules
IncludeResourceTransformer Adds files from the project
ManifestResourceTransformer Sets entries in the MANIFEST
PluginXmlResourceTransformer Aggregates Mavens plugin.xml
ResourceBundleAppendingTransformer Merges ResourceBundles
ServicesResourceTransformer Relocated class names in META-INF/services resources and merges them.
XmlAppendingTransformer Adds XML content to an XML resource
Transformers in org.apache.maven.plugins.shade.resource.properties (available since 3.2.2)
PropertiesTransformer Merges properties files owning an ordinal to solve conflicts
OpenWebBeansPropertiesTransformer Merges Apache OpenWebBeans configuration files
MicroprofileConfigTransformer Merges conflicting Microprofile Config properties based on an ordinal

Merging Plexus Component Descriptors with the ComponentsXmlResourceTransformer

JARs for components targeting the Plexus IoC container contain a META-INF/plexus/components.xml entry that declares the component and its requirements. If the uber JAR aggregates multiple Plexus components, a ComponentsXmlResourceTransformer needs to be used to merge the XML descriptors:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer"/>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Since plugin version 1.3, this resource transformer will also update the descriptor to account for relocation of component interfaces/implementations (if any).

Relocate classes of the Maven Plugin Descriptor with the PluginXmlResourceTransformer

With Plugin Tools 3.0 annotations have been introduced. Now references to classes are no longer classnames as String, but the actual Class reference. When you wanted to relocate classes, you had to maintain the META-INF/maven/plugin.xml by hand, but now this can be done with the PluginXmlResourceTransformer

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.PluginXmlResourceTransformer"/>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Concatenating Service Entries with the ServicesResourceTransformer

JAR files providing implementations of some interfaces often ship with a META-INF/services/ directory that maps interfaces to their implementation classes for lookup by the service locator. To relocate the class names of these implementation classes, and to merge multiple implementations of the same interface into one service entry, the ServicesResourceTransformer can be used:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Merging Content of Specific Files with AppendingTransformer, XmlAppendingTransformer and ResourceBundleAppendingTransformer

Some jars contain additional resources (such as properties files) that have the same file name. To avoid overwriting, you can opt to merge them by appending their content into one file. One good example for this is when aggregating both the spring-context and plexus-spring jars. Both of them have the META-INF/spring.handlers file which is used by Spring to handle XML schema namespaces. You can merge the contents of all the files with that specific name using the AppendingTransformer as shown below:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                  <resource>META-INF/spring.handlers</resource>
                </transformer>
                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                  <resource>META-INF/spring.schemas</resource>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

For XML files, you can use the XmlAppendingTransformer instead:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.XmlAppendingTransformer">
                  <resource>META-INF/magic.xml</resource>
                  <!-- Add this to enable loading of DTDs
                  <ignoreDtd>false</ignoreDtd>
                  -->
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Since plugin version 1.3.1, the XmlAppendingTransformer will by default not load DTDs, thereby avoiding network access. The potential downside of this mode is that external entities cannot be resolved which could fail the transformation, e.g. when using the Crimson XML parser as used in some JRE 1.4. If the transformed resource uses external entities, DTD resolution can either be turned back on or a plugin dependency on xerces:xercesImpl:2.9.1 is added to the POM.

For ResourceBundles properties files, you can use the ResourceBundleAppendingTransformer instead, which will respect all available Locales as well:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ResourceBundleAppendingTransformer">
                  <!-- the base name of the resource bundle, a fully qualified class name -->
                  <basename>path/to/Messages</basename>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Excluding Resources with the DontIncludeResourceTransformer

The DontIncludeResourceTransformer allows resources to be excluded when their name ends in a given value.

For example, the following sample excludes all resources ending in .txt.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
                    <resource>.txt</resource>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Since maven-shade-plugin-3.0 it is also possible to give a list of resources which should not be included, like:

<transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
  <resources>
    <resource>.txt</resource>
    <resource>READ.me</resource>
  </resources>
</transformer>

Adding New Resources with the IncludeResourceTransformer

The IncludeResourceTransformer allows project files to be included in the package under a given name.

For example, the following sample includes README.txt in the package as README in the META-INF directory.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.IncludeResourceTransformer">
                    <resource>META-INF/README</resource>
                    <file>README.txt</file>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Setting Manifest Entries with the ManifestResourceTransformer

The ManifestResourceTransformer allows existing entries in the MANIFEST to be replaced and new entries added.

For example, the following sample sets

  • the Main-Class entry to the value of the app.main.class property,
  • the X-Compile-Source-JDK entry to the value of the maven.compile.source property and
  • the X-Compile-Target-JDK entry to the value of the maven.compile.target property.

By default the ManifestResourceTransformer will relocate the following attributes:

  • Export-Package
  • Import-Package
  • Provide-Capability
  • Require-Capability

With additionalAttributes you can specify the attributes that need to be relocated too.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <manifestEntries>
                    <Main-Class>${app.main.class}</Main-Class>
                    <X-Compile-Source-JDK>${maven.compile.source}</X-Compile-Source-JDK>
                    <X-Compile-Target-JDK>${maven.compile.target}</X-Compile-Target-JDK>
                  </manifestEntries>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Licensing

Preventing License Duplication with the ApacheLicenseResourceTransformer

Some open source producers (including the Apache Software Foundation) include a copy of their license in the META-INF directory. These are conventionally named either LICENSE,LICENSE.txt or LICENSE.md. When merging these dependencies, adding these resources may cause confusion. The ApacheLicenseResourceTransformer ensures that duplicate licenses (named according to this convention) are not merged.

For example, the following prevents the license from a commons-collections dependency being merged in

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer">
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Aggregating Notices with the ApacheNoticeResourceTransformer

Some licenses (including the Apache License, Version 2) require that notices are preserved by downstream distributors. ApacheNoticeResourceTransformer automates the assembly of an appropriate NOTICE.

For example, to simply merge in dependent notices:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
                    <addHeader>false</addHeader>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Aggregating Apache Groovy extension modules descriptors with the GroovyResourceTransformer

The Apache Groovy language provides extension modules located at META-INF/services/org.codehaus.groovy.runtime.ExtensionModule, these modules use the property file format. GroovyResourceTransformer automates the assembly of Groovy extension modules NOTICE.

For example, to simply merge the extension modules of several jars:

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.GroovyResourceTransformer">
                  <extModuleName>the-aggregated-module</extModuleName>
                  <extModuleVersion>1.0.0</extModuleVersion>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
 ...
</project>

Merging properties files with PropertiesTransformer

The PropertiesTransformer allows a set of properties files to be merged and to resolve conflicts based on an ordinal giving the priority of each file. An optional alreadyMergedKey enables to have a boolean flag in the file which, if set to true, request to use the file as it as the result of the merge. If two files are considered complete in the merge process then the shade will fail.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.properties.PropertiesTransformer">
                  <!-- required configuration -->
                  <resource>configuration/application.properties</resource>
                  <ordinalKey>ordinal</ordinalKey>
                  <!-- optional configuration -->
                  <alreadyMergedKey>already_merged</alreadyMergedKey>
                  <defaultOrdinal>0</defaultOrdinal>
                  <reverseOrder>false</reverseOrder>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Merging Apache OpenWebBeans configuration with OpenWebBeansPropertiesTransformer

OpenWebBeansPropertiesTransformer preconfigure a PropertiesTransformer for Apache OpenWebBeans configuration files.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.properties.OpenWebBeansPropertiesTransformer" />
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>

Merging Microprofile Config properties with MicroprofileConfigTransformer

MicroprofileConfigTransformer preconfigure a PropertiesTransformer for Microprofile Config. The only required configuration is the ordinal. The alreadyMergedKey is supported but is not defined by the specification.

<project>
  ...
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.properties.MicroprofileConfigTransformer">
                  <resource>configuration/app.properties</resource>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  ...
</project>