Optimize Your Apigee Java Callout: Reduce Bundle Size with Maven Shade

When building Apigee Java callouts, it’s common to use external libraries for tasks like JSON parsing or data transformation. The challenge is that these libraries can be large, and bundling an entire multi-megabyte dependency into your proxy bundle is inefficient.

Large JAR files lead to bloated API proxy bundles, which in turn cause slower deployment times and can consume unnecessary resources.

The solution is to create an optimized JAR that includes only your code and the specific classes you actually need from your dependencies. This article will show you how to use the Maven Shade Plugin to gain this fine-grained control, keeping your proxy bundles lean and your deployments fast.

The Problem: Bloated JARs and Dependency Management

By default, when you build a Java project, your JAR file only contains your compiled code. Your dependencies are not included. You have two common ways to handle this in Apigee, as supported by the official documentation:

  1. Manual JAR Upload: As the documentation’s “Packaging” section notes, you can place your callout JAR and any third-party library JARs into the proxy’s resources/java folder. This approach can be acceptable for a single, tiny library with no other dependencies. However, the moment your library has its own dependencies (known as transitive dependencies), you must hunt down, download, and upload all of those JARs too. This process quickly becomes tedious and error-prone.

  2. The “Fat JAR” Approach: You could use a plugin to bundle everything—your code and all your dependencies—into one massive “fat” JAR. While this solves the management problem, it creates the bloat problem. You don’t need the entire gson library if you’re only using one or two classes from it.

The Solution: Selective Bundling with Maven Shade

The Maven Shade Plugin is the perfect tool for this. While it can create a “fat” JAR, its real power comes from its ability to filter the contents. You can configure it to “tree shake” your dependencies, including only the specific packages or classes you require.

This gives you the best of both worlds: a single, manageable JAR file that is also small and optimized.

Step 1: Add Your Dependencies in pom.xml

First, ensure you have your dependencies listed in your pom.xml. We will use the official Apigee dependencies from the “How to create a Java Callout” guide, plus Google’s Gson library as our external dependency.


<dependencies>
  <!-- 
    Apigee dependencies (required for compiling the callout)
  -->
  <dependency>
    <groupId>com.apigee.edge</groupId>
    <artifactId>message-flow</artifactId>
    <version>1.0.0</version>
  </dependency>

  <dependency>
    <groupId>com.apigee.edge</groupId>
    <artifactId>expressions</artifactId>
    <version>1.0.0</version>
  </dependency>

  <!-- Other external library: Google Gson -->
  <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

Important: Note that we are using compile for the Apigee libraries, just as shown in the official “how-to” guide. By default, this scope tells the Maven Shade Plugin to bundle these libraries. However, the official JavaCallout policy documentation explicitly warns against this, as it can cause “non-deterministic behavior at runtime because of potential ClassLoader conflicts.”

Therefore, the filter we define in the next step is absolutely critical to prevent this.

Step 2: Combine Tree-Shaking and Manual Filtering

The most effective way to build an optimized JAR is to use both automated tree-shaking (to minimize your external libraries like Gson) and manual filtering (to exclude your provided libraries like message-flow).

Here is the recommended maven-shade-plugin configuration:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <version>3.5.1</version>
      <executions>
        <execution>
          <phase>package</phase>
          <goals>
            <goal>shade</goal>
          </goals>

          <configuration>
          <!-- 
             Part 1: Automated Tree-Shaking
             This analyzes your code and includes only the classes 
             you actually use from your dependencies (like gson).
           -->
            <minimizeJar>true</minimizeJar>

            <entryPoints>
           <!-- 
             IMPORTANT: Change this to the fully-qualified name 
             of your main Java Callout class.
           -->
              <entryPoint>com.mycompany.callouts.MyMainCallout</entryPoint>
            </entryPoints>

           <!-- 
             Part 2: Manual Filtering
             This is essential to explicitly remove files, especially 
             the Apigee libraries that should \*never\* be bundled.
            -->
            <filters>
               <!-- 
                  !! CRITICAL !!
                  Explicitly exclude the Apigee libraries.
                  This honors the official doc's warning about not
                   bundling runtime libraries.
               -->
              <filter>
                <artifact>com.apigee.edge:message-flow:jar:</artifact>
                <excludes>
                  <exclude>**</exclude>
                </excludes>
              </filter>

              <filter>
                <artifact>com.apigee.edge:expressions:jar:</artifact>
                <excludes>
                  <exclude>**</exclude>
                </excludes>
              </filter>
              <!-- 
                Also remove common junk files from all other JARs.
              -->
              <filter>
                <artifact>*:*</artifact>
                <excludes>
                  <exclude>META-INF/*.txt</exclude>
                  <exclude>META-INF/*.md</exclude>
                  <exclude>META-INF/LICENSE*</exclude>
                  <exclude>module-info.class</exclude>
                </excludes>
              </filter>
            </filters>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

How This Configuration Works

  1. minimizeJar + entryPoints (Automated Tree-Shaking):

    • true enables the automatic analysis.

    • com.mycompany.callouts.MyMainCallout tells the plugin where to start its analysis. It builds a dependency graph from this class and discards any class from your compile dependencies (like gson) that isn’t reachable. You must change this to your main callout class.

  2. (Manual Filtering):

  • This is used for rules you want to enforce regardless of tree-shaking.

  • Excluding Apigee Libraries: The filters for com.apigee.edge:message-flow:jar and com.apigee.edge:expressions:jar are a critical safeguard. Because we used compile (as per the docs), the Shade plugin will try to bundle them. These filters explicitly stop that, preventing the runtime ClassLoader conflicts the official documentation warns about.

  • Excluding Junk Files: The global filter (*:*) removes common non-code files (like licenses and markdown files) that minimizeJar might not catch, further reducing size.

Step 3: Build Your Optimized JAR

With this configuration, build your project from the command line:

mvn clean package

When this command completes, navigate to your target directory. You will find two JAR files:

  1. original-my-project-1.0.jar: The original, non-shaded JAR containing only your code.

  2. my-project-1.0.jar: This is your new optimized JAR.

If you inspect the contents of this new JAR (using a tool like jar tvf my-project-1.0.jar or vim tvf my-project-1.0.jar ), you will see it is significantly smaller. It contains your own .class files and only the specific dependency classes you need, and it is guaranteed to not contain the Apigee runtime libraries.

This primary JAR file (e.g., my-project-1.0.jar) is the only file you need to upload to your Apigee API proxy’s resources/java folder.

Conclusion

Using the Maven Shade Plugin with a combination of minimizeJar and filters is a best practice for professional Apigee development. It moves you beyond the basic “fat JAR” approach to a deliberate, optimized build.

By selectively bundling only the code you need—and explicitly excluding what you don’t (especially the Apigee-provided libraries)—you directly reduce your API proxy bundle size, which leads to faster deployment times and a more efficient, manageable proxy.

Credits: Human-in-the-loop

The content in this article was generated by Google Gemini :sparkles: and subsequently edited, reviewed, and tested by @miguelmendoza for technical accuracy.

:speech_balloon: Ask a Google Cloud Sales Specialist about your project