diff --git a/appengine-java21/ee8/gaeinfo/README.md b/appengine-java21/ee8/gaeinfo/README.md
new file mode 100644
index 00000000000..04be245c18e
--- /dev/null
+++ b/appengine-java21/ee8/gaeinfo/README.md
@@ -0,0 +1,53 @@
+# Google App Engine Information
+
+
+
+
+This sample displays what's going on in your app. It dumps the environment and lots more.
+
+See the [Google App Engine standard environment documentation][ae-docs] for more
+detailed instructions.
+
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+## Setup
+
+Use either:
+
+* `gcloud init`
+* `gcloud auth application-default login`
+
+## Maven
+### Running locally
+
+ $ mvn appengine:run
+
+### Deploying
+
+ $ mvn clean package appengine:deploy
+
+
diff --git a/appengine-java21/ee8/gaeinfo/pom.xml b/appengine-java21/ee8/gaeinfo/pom.xml
new file mode 100644
index 00000000000..70d19dd3843
--- /dev/null
+++ b/appengine-java21/ee8/gaeinfo/pom.xml
@@ -0,0 +1,126 @@
+
+
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+ com.example.appengine
+ appengine-gaeinfo-j21
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+
+ 21
+ 21
+
+
+
+
+
+
+ libraries-bom
+ com.google.cloud
+ import
+ pom
+ 26.28.0
+
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ 2.0.39
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 4.0.4
+ jar
+ provided
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
+
+ com.google.code.gson
+ gson
+
+
+
+ org.thymeleaf
+ thymeleaf
+ 3.1.2.RELEASE
+
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.4.0
+
+
+
+
+ ${basedir}/src/main/webapp/WEB-INF
+ true
+ WEB-INF
+
+
+
+
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.8.0
+
+ GCLOUD_CONFIG
+ GCLOUD_CONFIG
+ true
+ true
+
+
+
+
+
+
+
diff --git a/appengine-java21/ee8/gaeinfo/src/main/java/com/example/appengine/standard/GaeInfoServlet.java b/appengine-java21/ee8/gaeinfo/src/main/java/com/example/appengine/standard/GaeInfoServlet.java
new file mode 100644
index 00000000000..e022e34ec94
--- /dev/null
+++ b/appengine-java21/ee8/gaeinfo/src/main/java/com/example/appengine/standard/GaeInfoServlet.java
@@ -0,0 +1,228 @@
+/*
+ * 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.standard;
+
+// [START gae_java21_gaeinfo_serverlet_example]
+
+import com.google.appengine.api.appidentity.AppIdentityService;
+import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
+import com.google.appengine.api.utils.SystemProperty;
+import com.google.apphosting.api.ApiProxy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParser;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.thymeleaf.TemplateEngine;
+import org.thymeleaf.context.WebContext;
+import org.thymeleaf.templateresolver.WebApplicationTemplateResolver;
+import org.thymeleaf.web.servlet.JakartaServletWebApplication;
+
+@SuppressWarnings({"serial"})
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(
+ name = "GAEInfo",
+ description = "GAEInfo: Write info about GAE Standard",
+ urlPatterns = "/gaeinfo"
+)
+public class GaeInfoServlet extends HttpServlet {
+
+ private final String[] metaPath = {
+ "/computeMetadata/v1/project/numeric-project-id", // (pending)
+ "/computeMetadata/v1/project/project-id",
+ "/computeMetadata/v1/instance/zone",
+ "/computeMetadata/v1/instance/service-accounts/default/aliases",
+ "/computeMetadata/v1/instance/service-accounts/default/",
+ "/computeMetadata/v1/instance/service-accounts/default/scopes",
+ // Tokens work - but are a security risk to display
+ // "/computeMetadata/v1/instance/service-accounts/default/token"
+ };
+
+ final String[] metaServiceAcct = {
+ "/computeMetadata/v1/instance/service-accounts/{account}/aliases",
+ "/computeMetadata/v1/instance/service-accounts/{account}/email",
+ "/computeMetadata/v1/instance/service-accounts/{account}/scopes",
+ // Tokens work - but are a security risk to display
+ // "/computeMetadata/v1/instance/service-accounts/{account}/token"
+ };
+
+ private final String metadata = "http://metadata.google.internal";
+
+ private TemplateEngine templateEngine;
+ private JakartaServletWebApplication application;
+
+ // Use OkHttp from Square as it's quite easy to use for simple fetches.
+ private final OkHttpClient ok =
+ new OkHttpClient.Builder()
+ .readTimeout(500, TimeUnit.MILLISECONDS) // Don't dawdle
+ .writeTimeout(500, TimeUnit.MILLISECONDS)
+ .build();
+
+ // Setup to pretty print returned json
+ private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+ // Fetch Metadata
+ String fetchMetadata(String key) throws IOException {
+ Request request =
+ new Request.Builder()
+ .url(metadata + key)
+ .addHeader("Metadata-Flavor", "Google")
+ .get()
+ .build();
+
+ Response response = ok.newCall(request).execute();
+ return response.body().string();
+ }
+
+ String fetchJsonMetadata(String prefix) throws IOException {
+ Request request =
+ new Request.Builder()
+ .url(metadata + prefix)
+ .addHeader("Metadata-Flavor", "Google")
+ .get()
+ .build();
+
+ Response response = ok.newCall(request).execute();
+
+ // Convert json to prety json
+ return gson.toJson(JsonParser.parseString(response.body().string()));
+ }
+
+ @Override
+ public void init() {
+ // Setup ThymeLeaf
+ application = JakartaServletWebApplication.buildApplication(this.getServletContext());
+ WebApplicationTemplateResolver templateResolver =
+ new WebApplicationTemplateResolver(application);
+
+ templateResolver.setPrefix("/WEB-INF/templates/");
+ templateResolver.setSuffix(".html");
+ templateResolver.setCacheTTLMs(Long.valueOf(1200000L)); // TTL=20m
+
+ // Cache is set to true by default. Set to false if you want templates to
+ // be automatically updated when modified.
+ templateResolver.setCacheable(true);
+
+ templateEngine = new TemplateEngine();
+ templateEngine.setTemplateResolver(templateResolver);
+ }
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ String key = "";
+ final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
+ WebContext ctx = new WebContext(application.buildExchange(req, resp));
+ ctx.setLocale(req.getLocale());
+
+ resp.setContentType("text/html");
+
+ ctx.setVariable("production", SystemProperty.environment.value().name());
+ ctx.setVariable("ServiceAccountName", appIdentity.getServiceAccountName());
+ ctx.setVariable("gcs", appIdentity.getDefaultGcsBucketName());
+
+ ctx.setVariable("appId", SystemProperty.applicationId.get());
+ ctx.setVariable("appVer", SystemProperty.applicationVersion.get());
+ ctx.setVariable("version", SystemProperty.version.get());
+ ctx.setVariable("environment", SystemProperty.environment.get());
+
+ // Environment Atributes
+ ApiProxy.Environment env = ApiProxy.getCurrentEnvironment();
+ Map attr = env.getAttributes();
+ TreeMap m = new TreeMap<>();
+
+ for (String k : attr.keySet()) {
+ Object o = attr.get(k);
+
+ if (o.getClass().getCanonicalName().equals("java.lang.String")) {
+ m.put(k, (String) o);
+ } else if (o.getClass().getCanonicalName().equals("java.lang.Boolean")) {
+ m.put(k, ((Boolean) o).toString());
+ } else {
+ m.put(k, "a " + o.getClass().getCanonicalName());
+ }
+ }
+ ctx.setVariable("attribs", m);
+
+ m = new TreeMap<>();
+ for (Enumeration e = req.getHeaderNames(); e.hasMoreElements(); ) {
+ key = e.nextElement();
+ m.put(key, req.getHeader(key));
+ }
+ ctx.setVariable("headers", m);
+
+ Cookie[] cookies = req.getCookies();
+ m = new TreeMap<>();
+ if (cookies != null && cookies.length != 0) {
+ for (Cookie co : cookies) {
+ m.put(co.getName(), co.getValue());
+ }
+ }
+ ctx.setVariable("cookies", m);
+
+ Properties properties = System.getProperties();
+ m = new TreeMap<>();
+ for (Enumeration> e = properties.propertyNames(); e.hasMoreElements(); ) {
+ key = (String) e.nextElement();
+ m.put(key, (String) properties.get(key));
+ }
+ ctx.setVariable("systemprops", m);
+
+ Map envVar = System.getenv();
+ m = new TreeMap<>(envVar);
+ ctx.setVariable("envvar", m);
+
+ // The metadata server is only on a production system
+ if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {
+
+ m = new TreeMap<>();
+ for (String k : metaPath) {
+ m.put(k, fetchMetadata(k));
+ }
+ ctx.setVariable("Metadata", m.descendingMap());
+
+ m = new TreeMap<>();
+ for (String k : metaServiceAcct) {
+ // substitute a service account for {account}
+ k = k.replace("{account}", appIdentity.getServiceAccountName());
+ m.put(k, fetchMetadata(k));
+ }
+ ctx.setVariable("sam", m.descendingMap());
+
+ // Recursively get all info about service accounts -- Note tokens are leftout by default.
+ ctx.setVariable(
+ "rsa",
+ fetchJsonMetadata("/computeMetadata/v1/instance/service-accounts/?recursive=true"));
+ // Recursively get all data on Metadata server.
+ ctx.setVariable("ram", fetchJsonMetadata("/?recursive=true"));
+ }
+
+ templateEngine.process("index", ctx, resp.getWriter());
+ }
+}
+// [END gae_java21_gaeinfo_serverlet_example]
diff --git a/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..0dc5c37270e
--- /dev/null
+++ b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ true
+ true
+ gaeinfo
+ java21
+
+
+
+ true
+
+
diff --git a/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/logging.properties b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/logging.properties
new file mode 100644
index 00000000000..9f92e52c39f
--- /dev/null
+++ b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/logging.properties
@@ -0,0 +1,27 @@
+# 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.
+
+# A default java.util.logging configuration.
+# (All App Engine logging is through java.util.logging by default).
+#
+# To use this configuration, copy it into your application's WEB-INF
+# folder and add the following to your appengine-web.xml:
+#
+#
+#
+#
+#
+
+# Set the default logging level for all loggers to WARNING
+.level = WARNING
diff --git a/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/templates/index.html b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/templates/index.html
new file mode 100644
index 00000000000..39e55d33fc7
--- /dev/null
+++ b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/templates/index.html
@@ -0,0 +1,97 @@
+
+
+
+
+ GAE standard Metadata
+
+
+
+AppIdentity
+
+ ServiceAccountName | |
+ GCS Bucket | |
+
+SystemProperties
+
+ appId | |
+ appVer | |
+ version | |
+ environment | |
+
+Environment Attributes
+
+Headers
+
+Cookies
+
+Java SystemProperties
+
+Envirionment Variables
+
+
+
+
Metadata
+
+
ServiceAccount Metadata
+
+
Recursive service-accounts
+
+
Recursive all metadata
+
+
+
No Local Metadata Server
+
+
+
diff --git a/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..0b15ad1229a
--- /dev/null
+++ b/appengine-java21/ee8/gaeinfo/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ gaeinfo
+
+ true
+