diff --git a/appengine-java25/ee11/analytics/README.md b/appengine-java25/ee11/analytics/README.md
new file mode 100644
index 00000000000..8e4f59e0668
--- /dev/null
+++ b/appengine-java25/ee11/analytics/README.md
@@ -0,0 +1,24 @@
+# Google Analytics sample for Google App Engine
+
+
+
+
+Integrating App Engine with Google Analytics using EE11.
+
+## Project setup, installation, and configuration
+
+- Register for [Google Analytics](http://www.google.com/analytics/), create
+an application, and get a tracking Id.
+- [Find your tracking Id](https://support.google.com/analytics/answer/1008080?hl=en)
+and set it as an environment variable in [`appengine-web.xml`](src/main/webapp/WEB-INF/appengine-web.xml).
+
+## Running locally
+This example uses the
+[Maven Cloud CLI based plugin](https://cloud.google.com/appengine/docs/java/tools/using-maven).
+To run this sample locally:
+
+ $ mvn appengine:run
+
+## Deploying
+
+ $ mvn clean package appengine:deploy
diff --git a/appengine-java25/ee11/analytics/pom.xml b/appengine-java25/ee11/analytics/pom.xml
new file mode 100644
index 00000000000..501a65aa822
--- /dev/null
+++ b/appengine-java25/ee11/analytics/pom.xml
@@ -0,0 +1,141 @@
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine
+ appengine-analytics-j25-ee11
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+ 25
+ 25
+ 2.0.29
+
+
+
+
+
+ libraries-bom
+ com.google.cloud
+ import
+ pom
+ 26.28.0
+
+
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ ${appengine.sdk.version}
+
+
+
+ jstl
+ jstl
+ 1.2
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.14
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 6.1.0
+ jar
+ provided
+
+
+
+
+ com.google.appengine
+ appengine-testing
+ ${appengine.sdk.version}
+ test
+
+
+ com.google.appengine
+ appengine-api-stubs
+ ${appengine.sdk.version}
+ test
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.11.0
+ test
+
+
+ com.google.truth
+ truth
+ 1.1.5
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.4.0
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.7.0
+
+ GCLOUD_CONFIG
+ analytics
+ true
+ true
+
+
+
+
+
diff --git a/appengine-java25/ee11/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java b/appengine-java25/ee11/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java
new file mode 100644
index 00000000000..163c56a8877
--- /dev/null
+++ b/appengine-java25/ee11/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.analytics;
+
+// [START gae_java25_ee11_analytics_track]
+
+import com.google.appengine.api.urlfetch.URLFetchService;
+import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import org.apache.http.client.utils.URIBuilder;
+
+@SuppressWarnings("serial")
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(
+ name = "analytics",
+ description = "Analytics: Send Analytics Event to Google Analytics",
+ urlPatterns = "/analytics")
+public class AnalyticsServlet extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException, ServletException {
+ String trackingId = System.getenv("GA_TRACKING_ID");
+ URIBuilder builder = new URIBuilder();
+ builder
+ .setScheme("http")
+ .setHost("www.google-analytics.com")
+ .setPath("/collect")
+ .addParameter("v", "1") // API Version.
+ .addParameter("tid", trackingId) // Tracking ID / Property ID.
+ // Anonymous Client Identifier. Ideally, this should be a UUID that
+ // is associated with particular user, device, or browser instance.
+ .addParameter("cid", "555")
+ .addParameter("t", "event") // Event hit type.
+ .addParameter("ec", "example") // Event category.
+ .addParameter("ea", "test action"); // Event action.
+ URI uri = null;
+ try {
+ uri = builder.build();
+ } catch (URISyntaxException e) {
+ throw new ServletException("Problem building URI", e);
+ }
+ URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
+ URL url = uri.toURL();
+ fetcher.fetch(url);
+ resp.getWriter().println("Event tracked.");
+ }
+}
+// [END gae_java25_ee11_analytics_track]
diff --git a/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..e8c0dcf01e1
--- /dev/null
+++ b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ java25
+ true
+ appengine-analytics-j25-ee11
+
+
+
+
+
+
+
diff --git a/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/web.xml b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..7b9437866da
--- /dev/null
+++ b/appengine-java25/ee11/analytics/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ analytics
+
+
diff --git a/appengine-java25/ee8/analytics/README.md b/appengine-java25/ee8/analytics/README.md
new file mode 100644
index 00000000000..4cc6d78023c
--- /dev/null
+++ b/appengine-java25/ee8/analytics/README.md
@@ -0,0 +1,24 @@
+# Google Analytics sample for Google App Engine
+
+
+
+
+Integrating App Engine with Google Analytics using EE8.
+
+## Project setup, installation, and configuration
+
+- Register for [Google Analytics](http://www.google.com/analytics/), create
+an application, and get a tracking Id.
+- [Find your tracking Id](https://support.google.com/analytics/answer/1008080?hl=en)
+and set it as an environment variable in [`appengine-web.xml`](src/main/webapp/WEB-INF/appengine-web.xml).
+
+## Running locally
+This example uses the
+[Maven Cloud CLI based plugin](https://cloud.google.com/appengine/docs/java/tools/using-maven).
+To run this sample locally:
+
+ $ mvn appengine:run
+
+## Deploying
+
+ $ mvn clean package appengine:deploy
diff --git a/appengine-java25/ee8/analytics/pom.xml b/appengine-java25/ee8/analytics/pom.xml
new file mode 100644
index 00000000000..3c2ae7fcb30
--- /dev/null
+++ b/appengine-java25/ee8/analytics/pom.xml
@@ -0,0 +1,141 @@
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine
+ appengine-analytics-j25-ee8
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+ 25
+ 25
+ 3.0.1
+
+
+
+
+
+ libraries-bom
+ com.google.cloud
+ import
+ pom
+ 26.28.0
+
+
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ ${appengine.sdk.version}
+
+
+
+ jstl
+ jstl
+ 1.2
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.14
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 4.0.1
+ jar
+ provided
+
+
+
+
+ com.google.appengine
+ appengine-testing
+ ${appengine.sdk.version}
+ test
+
+
+ com.google.appengine
+ appengine-api-stubs
+ ${appengine.sdk.version}
+ test
+
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.11.0
+ test
+
+
+ com.google.truth
+ truth
+ 1.1.5
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.4.0
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.8.3
+
+ GCLOUD_CONFIG
+ analytics
+ true
+ true
+
+
+
+
+
diff --git a/appengine-java25/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java b/appengine-java25/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java
new file mode 100644
index 00000000000..ee86db46055
--- /dev/null
+++ b/appengine-java25/ee8/analytics/src/main/java/com/example/appengine/analytics/AnalyticsServlet.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.analytics;
+
+// [START gae_java25_ee8_analytics_track]
+import com.google.appengine.api.urlfetch.URLFetchService;
+import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.http.client.utils.URIBuilder;
+
+@SuppressWarnings("serial")
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(
+ name = "analytics",
+ description = "Analytics: Send Analytics Event to Google Analytics",
+ urlPatterns = "/analytics")
+public class AnalyticsServlet extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException, ServletException {
+ String trackingId = System.getenv("GA_TRACKING_ID");
+ URIBuilder builder = new URIBuilder();
+ builder
+ .setScheme("http")
+ .setHost("www.google-analytics.com")
+ .setPath("/collect")
+ .addParameter("v", "1") // API Version.
+ .addParameter("tid", trackingId) // Tracking ID / Property ID.
+ // Anonymous Client Identifier. Ideally, this should be a UUID that
+ // is associated with particular user, device, or browser instance.
+ .addParameter("cid", "555")
+ .addParameter("t", "event") // Event hit type.
+ .addParameter("ec", "example") // Event category.
+ .addParameter("ea", "test action"); // Event action.
+ URI uri = null;
+ try {
+ uri = builder.build();
+ } catch (URISyntaxException e) {
+ throw new ServletException("Problem building URI", e);
+ }
+ URLFetchService fetcher = URLFetchServiceFactory.getURLFetchService();
+ URL url = uri.toURL();
+ fetcher.fetch(url);
+ resp.getWriter().println("Event tracked.");
+ }
+}
+// [END gae_java25_ee8_analytics_track]
diff --git a/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..3105a34dd14
--- /dev/null
+++ b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ java25
+ true
+ appengine-analytics-j25-ee8
+
+
+
+
+
+
+
diff --git a/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/web.xml b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..a15c139472e
--- /dev/null
+++ b/appengine-java25/ee8/analytics/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ analytics
+
+
diff --git a/appengine-java25/helloworld/README.md b/appengine-java25/helloworld/README.md
new file mode 100644
index 00000000000..b90c452ee8e
--- /dev/null
+++ b/appengine-java25/helloworld/README.md
@@ -0,0 +1,81 @@
+HelloWorld for App Engine Standard (Java 25)
+============================
+
+This sample demonstrates how to deploy an application on Google App Engine.
+
+See the [Google App Engine standard environment documentation][ae-docs] for more
+detailed instructions.
+
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+
+* [Java 25](https://www.oracle.com/java/technologies/downloads/)
+* [Maven](https://maven.apache.org/download.cgi) (at least 3.5)
+* [Google Cloud SDK](https://cloud.google.com/sdk/) (aka gcloud)
+
+## Setup
+
+• Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/)
+
+```
+gcloud init
+```
+
+* Create an App Engine app within the current Google Cloud Project
+
+```
+gcloud app create
+```
+
+* In the `pom.xml`, update the [App Engine Maven Plugin](https://cloud.google.com/appengine/docs/standard/java/tools/maven-reference)
+with your Google Cloud Project Id:
+
+```
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.8.3
+
+ myProjectId
+ GCLOUD_CONFIG
+
+
+```
+**Note:** `GCLOUD_CONFIG` is a special version for autogenerating an App Engine
+version. Change this field to specify a specific version name.
+
+## Maven
+### Running locally
+
+ mvn package appengine:run
+
+To use visit: http://localhost:8080/
+
+### Deploying
+
+ mvn package appengine:deploy
+
+To use visit: https://YOUR-PROJECT-ID.appspot.com
+
+### Testing
+
+ mvn verify
+
+As you add / modify the source code (`src/main/java/...`) it's very useful to add [unit testing](https://cloud.google.com/appengine/docs/java/tools/localunittesting)
+to (`src/main/test/...`). The following resources are quite useful:
+
+* [Junit4](http://junit.org/junit4/)
+* [Mockito](http://mockito.org/)
+* [Truth](http://google.github.io/truth/)
+
+## Gradle
+
+### Running locally
+
+ ./gradlew appengineRun
+
+To use visit: http://localhost:8080/
+
+### Deploying
+
+ ./gradlew appengineDeploy
diff --git a/appengine-java25/helloworld/build.gradle b/appengine-java25/helloworld/build.gradle
new file mode 100644
index 00000000000..f9e6ed67ead
--- /dev/null
+++ b/appengine-java25/helloworld/build.gradle
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// [START gae_standard25_gradle]
+apply plugin: 'java'
+apply plugin: 'war'
+
+buildscript {
+ repositories {
+ // gretty plugin is in Maven Central
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.8.1'
+ classpath 'org.gretty:gretty:4.1.5'
+ }
+}
+apply plugin: 'org.gretty'
+apply plugin: 'com.google.cloud.tools.appengine'
+
+repositories {
+ mavenCentral()
+}
+
+appengine {
+ deploy { // deploy configuration
+ stopPreviousVersion = true // default - stop the current version
+ promote = true // default - & make this the current version
+ projectId = 'GCLOUD_CONFIG'
+ version = 'GCLOUD_CONFIG'
+ }
+}
+
+sourceSets {
+ // In Gradle 8, the default location is app/src/java, which does not match
+ // Maven's directory structure.
+ main.java.srcDirs = ['src/main/java']
+ main.resources.srcDirs = ['src/main/resources', 'src/main/webapp']
+ test.java.srcDirs = ['src/test/java']
+}
+
+dependencies {
+ implementation 'com.google.appengine:appengine-api-1.0-sdk:3.0.1'
+ implementation 'jakarta.servlet:jakarta.servlet-api:6.1.0'
+
+ // Test Dependencies
+ testImplementation 'com.google.appengine:appengine-testing:3.0.1'
+ testImplementation 'com.google.appengine:appengine-api-stubs:3.0.1'
+ testImplementation 'com.google.appengine:appengine-tools-sdk:3.0.1'
+
+ testImplementation 'com.google.truth:truth:1.1.5'
+ testImplementation 'junit:junit:4.13.2'
+ testImplementation 'org.mockito:mockito-core:4.11.0'
+}
+// [END gae_standard25_gradle]
diff --git a/appengine-java25/helloworld/gradle/libs.versions.toml b/appengine-java25/helloworld/gradle/libs.versions.toml
new file mode 100644
index 00000000000..e74f3857bde
--- /dev/null
+++ b/appengine-java25/helloworld/gradle/libs.versions.toml
@@ -0,0 +1,10 @@
+# This file was generated by the Gradle 'init' task.
+# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
+
+[versions]
+guava = "33.2.1-jre"
+junit = "4.13.2"
+
+[libraries]
+guava = { module = "com.google.guava:guava", version.ref = "guava" }
+junit = { module = "junit:junit", version.ref = "junit" }
diff --git a/appengine-java25/helloworld/gradle/wrapper/gradle-wrapper.properties b/appengine-java25/helloworld/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000000..48b43d35063
--- /dev/null
+++ b/appengine-java25/helloworld/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-all.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/appengine-java25/helloworld/gradlew b/appengine-java25/helloworld/gradlew
new file mode 100755
index 00000000000..f5feea6d6b1
--- /dev/null
+++ b/appengine-java25/helloworld/gradlew
@@ -0,0 +1,252 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
+' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/appengine-java25/helloworld/gradlew.bat b/appengine-java25/helloworld/gradlew.bat
new file mode 100644
index 00000000000..9d21a21834d
--- /dev/null
+++ b/appengine-java25/helloworld/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/appengine-java25/helloworld/pom.xml b/appengine-java25/helloworld/pom.xml
new file mode 100644
index 00000000000..bbeed70b022
--- /dev/null
+++ b/appengine-java25/helloworld/pom.xml
@@ -0,0 +1,149 @@
+
+
+
+
+ 4.0.0
+
+ war
+ com.example.appengine
+ helloworld-jdk25
+ 1.0-SNAPSHOT
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.2
+
+
+
+ 25
+ 25
+
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ 3.0.1
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 6.1.0
+ jar
+ provided
+
+
+
+
+ com.google.appengine
+ appengine-testing
+ 2.0.23
+ test
+
+
+ com.google.appengine
+ appengine-api-stubs
+ 2.0.23
+ test
+
+
+ com.google.appengine
+ appengine-tools-sdk
+ 2.0.23
+ test
+
+
+
+ com.google.truth
+ truth
+ 1.1.5
+ test
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.11.0
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.3.1
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.3.1
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.14
+
+
+
+ prepare-agent
+
+
+
+ report
+ prepare-package
+
+ report
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.4.0
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.8.3
+
+
+ myProjectId
+
+ GCLOUD_CONFIG
+
+
+
+
+
diff --git a/appengine-java25/helloworld/settings.gradle b/appengine-java25/helloworld/settings.gradle
new file mode 100644
index 00000000000..9abb5e9318c
--- /dev/null
+++ b/appengine-java25/helloworld/settings.gradle
@@ -0,0 +1,32 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ }
+}
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/*
+ * This file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.10/userguide/multi_project_builds.html in the Gradle documentation.
+ */
+
+plugins {
+ // Apply the foojay-resolver plugin to allow automatic download of JDKs
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
+}
+
+rootProject.name = 'helloworld'
\ No newline at end of file
diff --git a/appengine-java25/helloworld/src/main/java/com/example/appengine/java25/HelloAppEngine.java b/appengine-java25/helloworld/src/main/java/com/example/appengine/java25/HelloAppEngine.java
new file mode 100644
index 00000000000..1f7c1ba4656
--- /dev/null
+++ b/appengine-java25/helloworld/src/main/java/com/example/appengine/java25/HelloAppEngine.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.java25;
+
+import com.google.appengine.api.utils.SystemProperty;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Properties;
+
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(name = "HelloAppEngine", value = "/hello")
+public class HelloAppEngine extends HttpServlet {
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ Properties properties = System.getProperties();
+
+ response.setContentType("text/plain");
+ response.getWriter().println("Hello App Engine - Standard using "
+ + SystemProperty.version.get() + " Java "
+ + properties.get("java.specification.version"));
+ }
+}
diff --git a/appengine-java25/helloworld/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java25/helloworld/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..5fb330b5354
--- /dev/null
+++ b/appengine-java25/helloworld/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,5 @@
+
+
+ java25
+ true
+
\ No newline at end of file
diff --git a/appengine-java25/helloworld/src/main/webapp/WEB-INF/web.xml b/appengine-java25/helloworld/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..702668c5ca3
--- /dev/null
+++ b/appengine-java25/helloworld/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,13 @@
+
+
+ helloworld
+ com.example.appengine.java25.HelloAppEngine
+
+
+ helloworld
+ /*
+
+
\ No newline at end of file
diff --git a/appengine-java25/helloworld/src/test/java/com/example/appengine/java25/HelloAppEngineTest.java b/appengine-java25/helloworld/src/test/java/com/example/appengine/java25/HelloAppEngineTest.java
new file mode 100644
index 00000000000..70b351d99c0
--- /dev/null
+++ b/appengine-java25/helloworld/src/test/java/com/example/appengine/java25/HelloAppEngineTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.appengine.java25;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.when;
+
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link HelloAppEngine}.
+ */
+@RunWith(JUnit4.class)
+public class HelloAppEngineTest {
+
+ private static final String FAKE_URL = "fake.fk/hello";
+ // Set up a helper so that the ApiProxy returns a valid environment for local testing.
+ private final LocalServiceTestHelper helper = new LocalServiceTestHelper();
+
+ @Mock
+ private HttpServletRequest mockRequest;
+ @Mock
+ private HttpServletResponse mockResponse;
+ private StringWriter responseWriter;
+ private HelloAppEngine servletUnderTest;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.openMocks(this);
+ helper.setUp();
+
+ // Set up some fake HTTP requests
+ when(mockRequest.getRequestURI()).thenReturn(FAKE_URL);
+
+ // Set up a fake HTTP response.
+ responseWriter = new StringWriter();
+ when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));
+
+ servletUnderTest = new HelloAppEngine();
+ }
+
+ @After
+ public void tearDown() {
+ helper.tearDown();
+ }
+
+ @Test
+ public void doGetWritesResponse() throws Exception {
+ servletUnderTest.doGet(mockRequest, mockResponse);
+
+ // We expect our hello world response.
+ assertThat(responseWriter.toString())
+ .contains("Hello App Engine - Standard ");
+ }
+}