Java Deployment Options

You have two choices for deploying a Java function:

Deploy from source

Your function's source code must be in the usual place for Maven projects (src/main/java). The sample functions in this document are directly in src/main/java, with no package declaration in the .java source file. For non-trivial code, you would likely introduce a package. If that package is com.example, then your hierarchy would look like this:

myfunction/
├─ pom.xml
├─ src
    ├─main
        ├─ java
            ├─ com
                ├─ example
                    ├─ MyFunction.java

Use the following command to deploy an HTTP function:

gcloud functions deploy $name --trigger-http \
    --entry-point $function_class --runtime java17

Where:

  • $name is an arbitrary, descriptive name that will be the name of the function once deployed. $name can only contain letters, numbers, underscores, and hyphens.
  • $function_class is the fully qualified name of your class (for example, com.example.MyFunction or just MyFunction if you don't use a package).

Use the following command to deploy an event-driven function:

gcloud functions deploy $name --no-gen2 --entry-point $function_class \
    --trigger-resource $resource_name \
    --trigger-event $event_name \
    --runtime java17

Where:

  • $name is an arbitrary, descriptive name that will be the name of the function once deployed.
  • $function_class is the fully qualified name of your class (for example, com.example.MyFunction or just MyFunction if you don't use a package).
  • $resource_name and $event_name are specific to the events that trigger your function. Examples of supported resources and events are Google Cloud Pub/Sub and Google Cloud Storage.

When deploying a function from source, the Google Cloud CLI uploads the source directory (and everything in it) to Google Cloud to build. To avoid sending unnecessary files, you can use the .gcloudignore file. Edit the .gcloudignore file to ignore common directories like .git and target/. For example, a .gcloudignore file might contain the following:

.git
target
build
.idea

Deploy from a JAR

You can deploy a pre-built JAR that contains the function. This is useful especially if you need to deploy a function that uses dependencies from a private artifact repository that cannot be accessed from Google Cloud's build pipeline when building from source. The JAR can be an uber JAR that contains the function class and all of its dependency classes, or a thin JAR that has Class-Path entries for dependency JARs in the META-INF/MANIFEST.MF file.

Build and deploy an Uber JAR

An uber JAR is a JAR file that contains the function classes as well as all of its dependencies. You can build an uber JAR with both Maven and Gradle. To deploy a Uber JAR, it must be the only JAR file in its own directory, for example:

my-function-deployment/
 ├─ my-function-with-all-dependencies.jar

You can either copy the file into this directory structure, or use Maven and Gradle plugins to generate the correct deployment directory.

Maven

Use the Maven Shade plugin to build an uber JAR. Configure your pom.xml with Shade plugin:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals><goal>shade</goal></goals>
            <configuration>
              <outputFile>${project.build.directory}/deployment/${build.finalName}.jar</outputFile>
              <transformers>
                <!-- This may be needed if you need to shade a signed JAR -->
                <transformer implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
                  <resource>.SF</resource>
                  <resource>.DSA</resource>
                  <resource>.RSA</resource>
                </transformer>
                <!-- This is needed if you have dependencies that use Service Loader. Most Google Cloud client libraries does. -->
                <transformer implementation=
       "org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Build the uber JAR:

mvn package

Then deploy with the following command:

gcloud functions deploy jar-example \
    --entry-point=Example \
    --runtime=java17 \
    --trigger-http \
    --source=target/deployment

Gradle

Use the Shadow plugin for Gradle. Setup the plugin in your build.gradle file:

buildscript {
   repositories {
       jcenter()
   }
   dependencies {
       ...
       classpath "com.github.jengelman.gradle.plugins:shadow:5.2.0"
   }
}

plugins {
   id 'java'
   ...
}
sourceCompatibility = '17.0'
targetCompatibility = '17.0'
apply plugin: 'com.github.johnrengelman.shadow'

shadowJar {
   mergeServiceFiles()
}
...

Now you can run Gradle with the shadowJar command:

gradle shadowJar

Then deploy with the following command:

gcloud functions deploy jar-example \
   --entry-point=Example \
   --runtime=java17 \
   --trigger-http \
   --source=build/libs

Build and deploy a thin JAR with external dependencies

You can build and deploy a thin JAR file rather than an uber JAR. A thin JAR is a JAR file that contains only the function classes without the dependencies embedded in the same JAR file. Because the dependencies are still needed for deployment, you need to set things up as follows:

  • The dependencies must be in a subdirectory relative to the JAR to be deployed.
  • The JAR must have a META-INF/MANIFEST.MF file that includes a Class-Path attribute whose value lists the required dependency paths.

For example, your JAR file my-function.jar has a META-INF/MANIFEST.MF file that has 2 dependencies in the libs/ directory (a space-separated list of relative paths):

Manifest-Version: 1.0
Class-Path: libs/dep1.jar libs/dep2.jar

Your deployment directory should then contain your main function JAR file, as well as a subdirectory with the two dependencies your function depends on:

function-deployment/
├─ my-function.jar
├─ libs
       ├─ dep1.jar
       ├─ dep2.jar

You can build a thin JAR with both Maven and Gradle:

Maven

Use Maven JAR plugin to automatically configure MANIFEST.MF with the paths to the dependencies, and then use the Maven Dependency plugin to copy the dependencies.

<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  <build>
    ...
    <plugins>
      <plugin>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <addClasspath>true</addClasspath>
              <classpathPrefix>libs/</classpathPrefix>
            </manifest>
          </archive>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <overWriteReleases>false</overWriteReleases>
              <includeScope>runtime</includeScope>
              <outputDirectory>${project.build.directory}/libs</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-resources-plugin</artifactId>
        <executions>
          <execution>
            <id>copy-resources</id>
            <phase>package</phase>
            <goals><goal>copy-resources</goal></goals>
            <configuration>
              <outputDirectory>${project.build.directory}/deployment</outputDirectory>
              <resources>
                <resource>
                  <directory>${project.build.directory}</directory>
                  <includes>
                    <include>${build.finalName}.jar</include>
                    <include>libs/**</include>
                  </includes>
                  <filtering>false</filtering>
                </resource>
              </resources>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Build the thin JAR:

mvn package

Then deploy with the following command:

gcloud functions deploy jar-example \
    --entry-point=Example \
    --runtime=java17 \
    --trigger-http \
    --source=target/deployment

Gradle

Update your build.gradle project file to add a new task to fetch the dependencies:

dependencies {
   // API available at compilation only, but provided at runtime
   compileOnly 'com.google.cloud.functions:functions-framework-api:1.0.1'
   // dependencies needed by the function
   // ...
}

jar {
 manifest {
   attributes(
     "Class-Path": provider {
         configurations.runtimeClasspath
           .collect { "libs/${it.name}" }.join(' ')
     }
   )
 }
}

task prepareDeployment(type: Copy) {
 into("${buildDir}/deployment")
 into('.') {
   from jar
 }
 into('libs') {
   from configurations.runtimeClasspath
 }
}