diff --git a/appengine-java21/ee8/users/README.md b/appengine-java21/ee8/users/README.md
new file mode 100644
index 00000000000..e8a3c04aa60
--- /dev/null
+++ b/appengine-java21/ee8/users/README.md
@@ -0,0 +1,23 @@
+# Users Authentication sample for Google App Engine
+
+
+ +
+This sample demonstrates how to use the [Users API][appid] on [Google App
+Engine][ae-docs].
+
+[appid]: https://cloud.google.com/appengine/docs/java/users/
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+## Running locally
+This example uses the
+[Maven gcloud plugin](https://cloud.google.com/appengine/docs/legacy/standard/java/using-maven).
+To run this sample locally:
+
+    $ mvn appengine:run
+
+## Deploying
+In the following command, replace YOUR-PROJECT-ID with your
+[Google Cloud Project ID](https://developers.google.com/console/help/new/#projectnumber).
+
+    $ mvn clean package appengine:deploy
diff --git a/appengine-java21/ee8/users/pom.xml b/appengine-java21/ee8/users/pom.xml
new file mode 100644
index 00000000000..3c65115463e
--- /dev/null
+++ b/appengine-java21/ee8/users/pom.xml
@@ -0,0 +1,133 @@
+
+
+
+  4.0.0
+  war
+  1.0-SNAPSHOT
+  com.example.appengine
+  appengine-users-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
+    
+
+    
+    
+      junit
+      junit
+      4.13.2
+      test
+    
+    
+      org.mockito
+      mockito-core
+      4.11.0
+      test
+    
+    
+      com.google.appengine
+      appengine-testing
+      2.0.39
+      test
+    
+    
+      com.google.appengine
+      appengine-api-stubs
+      2.0.39
+      test
+    
+    
+      com.google.appengine
+      appengine-tools-sdk
+      2.0.39
+      test
+    
+    
+      com.google.truth
+      truth
+      1.4.4
+      test
+    
+
+  
+  
+    
+    ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+    
+      
+        org.apache.maven.plugins
+        maven-war-plugin
+        3.4.0
+      
+      
+        org.jacoco
+        jacoco-maven-plugin
+        0.8.13
+      
+      
+        com.google.cloud.tools
+        appengine-maven-plugin
+        2.5.0
+        
+          GCLOUD_CONFIG
+          GCLOUD_CONFIG
+          true
+          true
+        
+      
+    
+  
+
diff --git a/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java b/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java
new file mode 100644
index 00000000000..11a5aafd91b
--- /dev/null
+++ b/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java
@@ -0,0 +1,58 @@
+/* Copyright 2016 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_java21_users_api]
+
+package com.example.appengine.users;
+
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import java.io.IOException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(
+    name = "UserAPI",
+    description = "UserAPI: Login / Logout with UserService",
+    urlPatterns = "/userapi"
+)
+public class UsersServlet extends HttpServlet {
+
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    UserService userService = UserServiceFactory.getUserService();
+
+    String thisUrl = req.getRequestURI();
+
+    resp.setContentType("text/html");
+    if (req.getUserPrincipal() != null) {
+      resp.getWriter()
+          .println(
+              "
+
+This sample demonstrates how to use the [Users API][appid] on [Google App
+Engine][ae-docs].
+
+[appid]: https://cloud.google.com/appengine/docs/java/users/
+[ae-docs]: https://cloud.google.com/appengine/docs/java/
+
+## Running locally
+This example uses the
+[Maven gcloud plugin](https://cloud.google.com/appengine/docs/legacy/standard/java/using-maven).
+To run this sample locally:
+
+    $ mvn appengine:run
+
+## Deploying
+In the following command, replace YOUR-PROJECT-ID with your
+[Google Cloud Project ID](https://developers.google.com/console/help/new/#projectnumber).
+
+    $ mvn clean package appengine:deploy
diff --git a/appengine-java21/ee8/users/pom.xml b/appengine-java21/ee8/users/pom.xml
new file mode 100644
index 00000000000..3c65115463e
--- /dev/null
+++ b/appengine-java21/ee8/users/pom.xml
@@ -0,0 +1,133 @@
+
+
+
+  4.0.0
+  war
+  1.0-SNAPSHOT
+  com.example.appengine
+  appengine-users-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
+    
+
+    
+    
+      junit
+      junit
+      4.13.2
+      test
+    
+    
+      org.mockito
+      mockito-core
+      4.11.0
+      test
+    
+    
+      com.google.appengine
+      appengine-testing
+      2.0.39
+      test
+    
+    
+      com.google.appengine
+      appengine-api-stubs
+      2.0.39
+      test
+    
+    
+      com.google.appengine
+      appengine-tools-sdk
+      2.0.39
+      test
+    
+    
+      com.google.truth
+      truth
+      1.4.4
+      test
+    
+
+  
+  
+    
+    ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+    
+      
+        org.apache.maven.plugins
+        maven-war-plugin
+        3.4.0
+      
+      
+        org.jacoco
+        jacoco-maven-plugin
+        0.8.13
+      
+      
+        com.google.cloud.tools
+        appengine-maven-plugin
+        2.5.0
+        
+          GCLOUD_CONFIG
+          GCLOUD_CONFIG
+          true
+          true
+        
+      
+    
+  
+
diff --git a/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java b/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java
new file mode 100644
index 00000000000..11a5aafd91b
--- /dev/null
+++ b/appengine-java21/ee8/users/src/main/java/com/example/appengine/users/UsersServlet.java
@@ -0,0 +1,58 @@
+/* Copyright 2016 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_java21_users_api]
+
+package com.example.appengine.users;
+
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import java.io.IOException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
+@WebServlet(
+    name = "UserAPI",
+    description = "UserAPI: Login / Logout with UserService",
+    urlPatterns = "/userapi"
+)
+public class UsersServlet extends HttpServlet {
+
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    UserService userService = UserServiceFactory.getUserService();
+
+    String thisUrl = req.getRequestURI();
+
+    resp.setContentType("text/html");
+    if (req.getUserPrincipal() != null) {
+      resp.getWriter()
+          .println(
+              "
Hello, "
+                  + req.getUserPrincipal().getName()
+                  + "!  You can sign out.
");
+    } else {
+      resp.getWriter()
+          .println(
+              "Please sign in.
");
+    }
+  }
+}
+// [END gae_java21_users_api]
diff --git a/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..71f00b07474
--- /dev/null
+++ b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,8 @@
+
+
+  java21
+  
+    
+  
+  true
+
diff --git a/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..5fece3ce81b
--- /dev/null
+++ b/appengine-java21/ee8/users/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,26 @@
+
+
+
+  
+    userapi
+  
+  true
+
diff --git a/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java b/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java
new file mode 100644
index 00000000000..e7195d3f2f2
--- /dev/null
+++ b/appengine-java21/ee8/users/src/test/java/com/example/appengine/users/UsersServletTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.users;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.Mockito.when;
+
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import javax.management.remote.JMXPrincipal;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+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 UsersServlet}.
+ */
+@RunWith(JUnit4.class)
+public class UsersServletTest {
+
+  private static final String FAKE_URL = "fakey.fake.fak";
+  private static final String FAKE_NAME = "Fake";
+  // Set up a helper so that the ApiProxy returns a valid environment for local testing.
+  private final LocalServiceTestHelper helper = new LocalServiceTestHelper();
+
+  @Mock
+  private HttpServletRequest mockRequestNotLoggedIn;
+  @Mock
+  private HttpServletRequest mockRequestLoggedIn;
+  @Mock
+  private HttpServletResponse mockResponse;
+  private StringWriter responseWriter;
+  private UsersServlet servletUnderTest;
+
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.openMocks(this);
+    helper.setUp();
+
+    //  Set up some fake HTTP requests
+    //  If the user isn't logged in, use this request
+    when(mockRequestNotLoggedIn.getRequestURI()).thenReturn(FAKE_URL);
+    when(mockRequestNotLoggedIn.getUserPrincipal()).thenReturn(null);
+
+    //  If the user is logged in, use this request
+    when(mockRequestLoggedIn.getRequestURI()).thenReturn(FAKE_URL);
+    //  Most of the classes that implement Principal have been
+    //  deprecated.  JMXPrincipal seems like a safe choice.
+    when(mockRequestLoggedIn.getUserPrincipal()).thenReturn(new JMXPrincipal(FAKE_NAME));
+
+    // Set up a fake HTTP response.
+    responseWriter = new StringWriter();
+    when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter));
+
+    servletUnderTest = new UsersServlet();
+  }
+
+  @After
+  public void tearDown() {
+    helper.tearDown();
+  }
+
+  @Test
+  public void doGet_userNotLoggedIn_writesResponse() throws Exception {
+    servletUnderTest.doGet(mockRequestNotLoggedIn, mockResponse);
+
+    // If a user isn't logged in, we expect a prompt
+    //  to login to be returned.
+    assertWithMessage("UsersServlet response")
+        .that(responseWriter.toString())
+        .contains("Please .
");
+  }
+
+  @Test
+  public void doGet_userLoggedIn_writesResponse() throws Exception {
+    servletUnderTest.doGet(mockRequestLoggedIn, mockResponse);
+
+    // If a user is logged in, we expect a prompt
+    // to logout to be returned.
+    assertWithMessage("UsersServlet response")
+        .that(responseWriter.toString())
+        .contains("Hello, " + FAKE_NAME + "!");
+    assertWithMessage("UsersServlet response").that(responseWriter.toString()).contains("sign out");
+  }
+}