Package Spring Cloud function

Because we are going to upload the artifact generated by the maven build to AWS Lambda, we need to build an artifact that is shaded, meaning, it has all the dependencies exploded out as individual class files instead of jars. One can use the Maven Shade Plugin for such.

<!--
packages the project’s dependencies into a single, executable JAR file, making it
suitable for deployment to AWS Lambda
-->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <configuration>
    <createDependencyReducedPom>false</createDependencyReducedPom>
    <shadedArtifactAttached>true</shadedArtifactAttached>
    <shadedClassifierName>aws</shadedClassifierName>
    <finalName>cdk4j-examples-aws</finalName>
  </configuration>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <version>@spring-boot-maven-plugin.version@</version>
    </dependency>
  </dependencies>
  <executions>
    <execution>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <createDependencyReducedPom>false</createDependencyReducedPom>
        <shadedArtifactAttached>true</shadedArtifactAttached>
        <shadedClassifierName>aws</shadedClassifierName>
        <finalName>cdk4j-examples-aws</finalName>
        <transformers>
          <!-- This sets the Main-Class in the JAR's manifest to aws FunctionInvoker -->
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
            <mainClass>org.springframework.cloud.function.adapter.aws.FunctionInvoker</mainClass>
          </transformer>
          <!-- 
          Concatenates the content of all files with given name ensuring that every dependency's 
          handler, schema, and component are registered.
          -->
          <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/org.springframework.boot.autoconfigure.AutoConfiguration.imports</resource>
          </transformer>
          <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
            <resource>META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports</resource>
          </transformer>
          <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
            <resource>META-INF/spring.schemas</resource>
          </transformer>
          <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
            <resource>META-INF/spring.components</resource>
          </transformer>
          <!-- Merges all key/value pairs of spring.factories files -->
          <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
            <resource>META-INF/spring.factories</resource>
          </transformer>
        </transformers>
      </configuration>
    </execution>
  </executions>
</plugin>

We Use Transformers in the Maven Shade Plugin because multiple Spring libraries often contain files with the exact same name and path, the default behavior is to overwrite them. Without specific Transformers, your application will lose critical metadata, leading to deployment failures.

Here is a breakdown of why each transformer is necessary:

  1. Preventing Metadata Loss (Appending & Merging) - Spring uses files in the META-INF directory to discover configurations. If five different JARs each have a spring.handlers file, the “Shade” plugin would normally only keep the last one it finds. The AppendingTransformer and PropertiesMergingResourceTransformer concatenates and merges the content of the dependency’s handlers, schemas, factories and components.

    • AppendingTransformer: Instead of overwriting, this concatenates the contents of files like spring.handlers, spring.schemas, and spring.components. This ensures every dependency’s internal logic remains intact.
    • PropertiesMergingResourceTransformer: This specifically merges spring.factories. This file is the “brain” of Spring Boot’s autoconfiguration; merging ensures that all your starters (Web, Cloud, JSON) are recognized.
  2. Defining the Lambda Entry Point - The ManifestResourceTransformer sets the Main-Class in the JAR’s manifest to FunctionInvoker. Unlike a standard desktop app, an AWS Lambda needs this specific class to act as the bridge between the AWS Lambda Event and your Spring Cloud Function bean.

If you omit these transformers, your JAR might build successfully, but the Lambda will likely fail at runtime with ClassNotFoundException or configuration errors because Spring won’t be able to “see” its own internal components.