getReceivedMessages() {
+ return Collections.unmodifiableCollection(messages);
+ }
+}
diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/EchoServlet.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/EchoServlet.java
new file mode 100644
index 00000000000..32358f14268
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/EchoServlet.java
@@ -0,0 +1,44 @@
+/*
+ * 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.flexible.websocket.jettynative;
+
+import javax.servlet.annotation.WebServlet;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
+import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+
+/*
+ * Server-side WebSocket upgraded on /echo servlet.
+ */
+@SuppressWarnings("serial")
+@WebServlet(
+ name = "Echo WebSocket Servlet",
+ urlPatterns = {"/echo"})
+public class EchoServlet extends WebSocketServlet implements WebSocketCreator {
+ @Override
+ public void configure(WebSocketServletFactory factory) {
+ factory.setCreator(this);
+ }
+
+ @Override
+ public Object createWebSocket(
+ ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse) {
+ return new ServerSocket();
+ }
+}
diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/Main.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/Main.java
new file mode 100644
index 00000000000..43212718164
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/Main.java
@@ -0,0 +1,149 @@
+/*
+ * 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.flexible.websocket.jettynative;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import org.apache.tomcat.util.scan.StandardJarScanFilter;
+import org.apache.tomcat.util.scan.StandardJarScanner;
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
+import org.eclipse.jetty.jsp.JettyJspServlet;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+
+/**
+ * Starts up the server, including a DefaultServlet that handles static files, and any servlet
+ * classes annotated with the @WebServlet annotation.
+ */
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+
+ // Create a server that listens on port 8080.
+ Server server = new Server(8080);
+ WebAppContext webAppContext = new WebAppContext();
+ server.setHandler(webAppContext);
+
+ // Load static content from inside the jar file.
+ URL webAppDir = Main.class.getClassLoader().getResource("WEB-INF/");
+ System.out.println(webAppDir);
+ webAppContext.setResourceBase(webAppDir.toURI().toString());
+
+ // Enable annotations so the server sees classes annotated with @WebServlet.
+ webAppContext.setConfigurations(
+ new Configuration[] {
+ new AnnotationConfiguration(), new WebInfConfiguration(),
+ });
+
+ webAppContext.setAttribute(
+ "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
+ ".*/target/classes/|.*\\.jar");
+ enableEmbeddedJspSupport(webAppContext);
+
+ ServletHolder holderAltMapping = new ServletHolder();
+ holderAltMapping.setName("index.jsp");
+ holderAltMapping.setForcedPath("/index.jsp");
+ webAppContext.addServlet(holderAltMapping, "/");
+
+ // Start the server! 🚀
+ server.start();
+ System.out.println("Server started!");
+
+ // Keep the main thread alive while the server is running.
+ server.join();
+ }
+
+ private static void enableEmbeddedJspSupport(ServletContextHandler servletContextHandler)
+ throws IOException {
+ // Establish Scratch directory for the servlet context (used by JSP compilation)
+ File tempDir = new File(System.getProperty("java.io.tmpdir"));
+ File scratchDir = new File(tempDir.toString(), "embedded-jetty-jsp");
+
+ if (!scratchDir.exists()) {
+ if (!scratchDir.mkdirs()) {
+ throw new IOException("Unable to create scratch directory: " + scratchDir);
+ }
+ }
+ servletContextHandler.setAttribute("javax.servlet.context.tempdir", scratchDir);
+
+ // Set Classloader of Context to be sane (needed for JSTL)
+ // JSP requires a non-System classloader, this simply wraps the
+ // embedded System classloader in a way that makes it suitable
+ // for JSP to use
+ ClassLoader jspClassLoader = new URLClassLoader(new URL[0], Main.class.getClassLoader());
+ servletContextHandler.setClassLoader(jspClassLoader);
+
+ // Manually call JettyJasperInitializer on context startup
+ servletContextHandler.addBean(new JspStarter(servletContextHandler));
+
+ // Create / Register JSP Servlet (must be named "jsp" per spec)
+ ServletHolder holderJsp = new ServletHolder("jsp", JettyJspServlet.class);
+ holderJsp.setInitOrder(0);
+ holderJsp.setInitParameter("logVerbosityLevel", "DEBUG");
+ holderJsp.setInitParameter("fork", "false");
+ holderJsp.setInitParameter("xpoweredBy", "false");
+ holderJsp.setInitParameter("compilerTargetVM", "1.8");
+ holderJsp.setInitParameter("compilerSourceVM", "1.8");
+ holderJsp.setInitParameter("keepgenerated", "true");
+ servletContextHandler.addServlet(holderJsp, "*.jsp");
+ }
+
+ /**
+ * JspStarter for embedded ServletContextHandlers
+ *
+ * This is added as a bean that is a jetty LifeCycle on the ServletContextHandler. This bean's
+ * doStart method will be called as the ServletContextHandler starts, and will call the
+ * ServletContainerInitializer for the jsp engine.
+ */
+ public static class JspStarter extends AbstractLifeCycle
+ implements ServletContextHandler.ServletContainerInitializerCaller {
+ JettyJasperInitializer sci;
+ ServletContextHandler context;
+
+ public JspStarter(ServletContextHandler context) {
+ this.sci = new JettyJasperInitializer();
+ this.context = context;
+ String skip = "apache-*,ecj-*,jetty-*,asm-*,javax.servlet-*"
+ + "javax.annotation-*,taglibs-standard-spec-*,*.jar";
+ StandardJarScanner jarScanner = new StandardJarScanner();
+ StandardJarScanFilter jarScanFilter = new StandardJarScanFilter();
+ jarScanFilter.setTldSkip(skip);
+ jarScanner.setJarScanFilter(jarScanFilter);
+ this.context.setAttribute("org.apache.tomcat.JarScanner", jarScanner);
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(context.getClassLoader());
+ try {
+ sci.onStartup(null, context.getServletContext());
+ super.doStart();
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+ }
+}
diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/SendServlet.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/SendServlet.java
new file mode 100644
index 00000000000..0feab349ac2
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/SendServlet.java
@@ -0,0 +1,143 @@
+/*
+ * 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.flexible.websocket.jettynative;
+
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.Future;
+import java.util.logging.Logger;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
+import org.eclipse.jetty.websocket.client.WebSocketClient;
+
+@WebServlet("/send")
+/** Servlet that sends the message sent over POST to over a websocket connection. */
+public class SendServlet extends HttpServlet {
+
+ private Logger logger = Logger.getLogger(SendServlet.class.getName());
+
+ private static final String ENDPOINT = "/echo";
+ private static final String WEBSOCKET_PROTOCOL_PREFIX = "ws://";
+ private static final String WEBSOCKET_HTTPS_PROTOCOL_PREFIX = "wss://";
+ private static final String APPENGINE_HOST_SUFFIX = ".appspot.com";
+
+ // GAE_INSTANCE environment is used to detect App Engine Flexible Environment
+ private static final String GAE_INSTANCE_VAR = "GAE_INSTANCE";
+ // GOOGLE_CLOUD_PROJECT environment variable is set to the GCP project ID on App Engine Flexible.
+ private static final String GOOGLE_CLOUD_PROJECT_ENV_VAR = "GOOGLE_CLOUD_PROJECT";
+ // GAE_SERVICE environment variable is set to the GCP service name.
+ private static final String GAE_SERVICE_ENV_VAR = "GAE_SERVICE";
+
+ private final HttpClient httpClient;
+ private final WebSocketClient webSocketClient;
+ private final ClientSocket clientSocket;
+
+ public SendServlet() {
+ this.httpClient = createHttpClient();
+ this.webSocketClient = createWebSocketClient();
+ this.clientSocket = new ClientSocket();
+ }
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
+ String message = request.getParameter("message");
+ try {
+ sendMessageOverWebSocket(message);
+ response.sendRedirect("/");
+ } catch (Exception e) {
+ logger.severe("Error sending message over socket: " + e.getMessage());
+ e.printStackTrace(response.getWriter());
+ response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
+ }
+ }
+
+ private HttpClient createHttpClient() {
+ HttpClient httpClient;
+ if (System.getenv(GAE_INSTANCE_VAR) != null) {
+ // If on HTTPS, create client with SSL Context
+ SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
+ httpClient = new HttpClient(sslContextFactory);
+ } else {
+ // local testing on HTTP
+ httpClient = new HttpClient();
+ }
+ return httpClient;
+ }
+
+ private WebSocketClient createWebSocketClient() {
+ return new WebSocketClient(this.httpClient);
+ }
+
+ private void sendMessageOverWebSocket(String message) throws Exception {
+ if (!httpClient.isRunning()) {
+ try {
+ httpClient.start();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ }
+ if (!webSocketClient.isRunning()) {
+ try {
+ webSocketClient.start();
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ }
+ ClientUpgradeRequest request = new ClientUpgradeRequest();
+ // Attempt connection
+ Future future =
+ webSocketClient.connect(clientSocket, new URI(getWebSocketAddress()), request);
+ // Wait for Connect
+ Session session = future.get();
+ // Send a message
+ session.getRemote().sendString(message);
+ // Close session
+ session.close();
+ }
+
+ /**
+ * Returns the host:port/echo address a client needs to use to communicate with the server. On App
+ * engine Flex environments, result will be in the form wss://project-id.appspot.com/echo
+ */
+ public static String getWebSocketAddress() {
+ // Use ws://127.0.0.1:8080/echo when testing locally
+ String webSocketHost = "127.0.0.1:8080";
+ String webSocketProtocolPrefix = WEBSOCKET_PROTOCOL_PREFIX;
+
+ // On App Engine flexible environment, use wss://project-id.appspot.com/echo
+ if (System.getenv(GAE_INSTANCE_VAR) != null) {
+ String projectId = System.getenv(GOOGLE_CLOUD_PROJECT_ENV_VAR);
+ if (projectId != null) {
+ String serviceName = System.getenv(GAE_SERVICE_ENV_VAR);
+ webSocketHost = serviceName + "-dot-" + projectId + APPENGINE_HOST_SUFFIX;
+ }
+ Preconditions.checkNotNull(webSocketHost);
+ // Use wss:// instead of ws:// protocol when connecting over https
+ webSocketProtocolPrefix = WEBSOCKET_HTTPS_PROTOCOL_PREFIX;
+ }
+ return webSocketProtocolPrefix + webSocketHost + ENDPOINT;
+ }
+}
diff --git a/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ServerSocket.java b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ServerSocket.java
new file mode 100644
index 00000000000..07cfafe0f3e
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/main/java/com/example/flexible/websocket/jettynative/ServerSocket.java
@@ -0,0 +1,62 @@
+/*
+ * 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.flexible.websocket.jettynative;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+import org.eclipse.jetty.websocket.api.Session;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
+import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
+import org.eclipse.jetty.websocket.api.annotations.WebSocket;
+
+/*
+ * Server-side WebSocket : echoes received message back to client.
+ */
+@WebSocket(maxTextMessageSize = 64 * 1024)
+public class ServerSocket {
+ private Logger logger = Logger.getLogger(SendServlet.class.getName());
+ private Session session;
+
+ @OnWebSocketConnect
+ public void onWebSocketConnect(Session session) {
+ this.session = session;
+ logger.fine("Socket Connected: " + session);
+ }
+
+ @OnWebSocketMessage
+ public void onWebSocketText(String message) {
+ logger.fine("Received message: " + message);
+ try {
+ // echo message back to client
+ this.session.getRemote().sendString(message);
+ } catch (IOException e) {
+ logger.severe("Error echoing message: " + e.getMessage());
+ }
+ }
+
+ @OnWebSocketClose
+ public void onWebSocketClose(int statusCode, String reason) {
+ logger.fine("Socket Closed: [" + statusCode + "] " + reason);
+ }
+
+ @OnWebSocketError
+ public void onWebSocketError(Throwable cause) {
+ logger.severe("Websocket error : " + cause.getMessage());
+ }
+}
diff --git a/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/index.jsp b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/index.jsp
new file mode 100644
index 00000000000..8730c529584
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/index.jsp
@@ -0,0 +1,33 @@
+
+
+<%@ page import="com.example.flexible.websocket.jettynative.ClientSocket" %>
+
+
+
+
+ Send a message
+
+ Publish a message
+
+ Last received messages
+ <%= ClientSocket.getReceivedMessages() %>
+
+
diff --git a/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/jetty-web.xml b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 00000000000..475971850a9
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ true
+
+ -org.eclipse.jetty.
+
+
diff --git a/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/js_client.jsp b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/js_client.jsp
new file mode 100644
index 00000000000..ef9d7051928
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/main/webapp/WEB-INF/js_client.jsp
@@ -0,0 +1,85 @@
+
+
+
+<%@ page import="com.example.flexible.websocket.jettynative.SendServlet" %>
+
+ Google App Engine Flexible Environment - WebSocket Echo
+
+
+
+ Echo demo
+
+
+
+
+
+
+
+
+
diff --git a/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/ClientSocketTest.java b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/ClientSocketTest.java
new file mode 100644
index 00000000000..6b8636852ef
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/ClientSocketTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.flexible.websocket.jettynative;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class ClientSocketTest {
+ ClientSocket socket;
+
+ @Before
+ public void setUp() {
+ socket = new ClientSocket();
+ }
+
+ @Test
+ public void testOnMessage() {
+ assertEquals(ClientSocket.getReceivedMessages().size(), 0);
+ socket.onMessage("test");
+ assertEquals(ClientSocket.getReceivedMessages().size(), 1);
+ }
+}
diff --git a/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/SendServletTest.java b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/SendServletTest.java
new file mode 100644
index 00000000000..37916cb6a37
--- /dev/null
+++ b/flexible/java-25/websocket-jetty/src/test/java/com/example/flexible/websocket/jettynative/SendServletTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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.flexible.websocket.jettynative;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class SendServletTest {
+ @Test
+ public void testGetWebSocketAddress() {
+ assertTrue(SendServlet.getWebSocketAddress().contains("/echo"));
+ }
+}
diff --git a/functions/helloworld/helloworld/pom.xml b/functions/helloworld/helloworld/pom.xml
index 6beba0255ca..a78a587063e 100644
--- a/functions/helloworld/helloworld/pom.xml
+++ b/functions/helloworld/helloworld/pom.xml
@@ -50,8 +50,8 @@
- 11
- 11
+ 21
+ 21
diff --git a/functions/v2/pubsub/pom.xml b/functions/v2/pubsub/pom.xml
index f50876162b4..72c34790577 100644
--- a/functions/v2/pubsub/pom.xml
+++ b/functions/v2/pubsub/pom.xml
@@ -31,8 +31,8 @@
- 11
- 11
+ 21
+ 21
UTF-8
diff --git a/run/helloworld-source/README.md b/run/helloworld-source/README.md
new file mode 100644
index 00000000000..0a0f4324b40
--- /dev/null
+++ b/run/helloworld-source/README.md
@@ -0,0 +1,16 @@
+# Cloud Run Hello World Sample
+
+This sample shows how to deploy a Hello World [Spring Boot](https://spring.io/projects/spring-boot)
+application to Cloud Run using source deploy.
+
+For more details on how to work with this sample read the [Google Cloud Run Java Samples README](https://github.com/GoogleCloudPlatform/java-docs-samples/tree/main/run).
+
+[![Run in Google Cloud][run_img]][run_link]
+
+[run_img]: https://storage.googleapis.com/cloudrun/button.svg
+[run_link]: https://deploy.cloud.run/?git_repo=https://github.com/GoogleCloudPlatform/java-docs-samples&dir=run/helloworld
+
+## Dependencies
+
+* **Spring Boot**: Web server framework.
+* **Junit**: [development] Test running framework.
diff --git a/run/helloworld-source/pom.xml b/run/helloworld-source/pom.xml
new file mode 100644
index 00000000000..a14dae2cac7
--- /dev/null
+++ b/run/helloworld-source/pom.xml
@@ -0,0 +1,97 @@
+
+
+
+ 4.0.0
+ com.example.run
+ helloworld-source
+ 0.0.1-SNAPSHOT
+ jar
+
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${spring-boot.version}
+ pom
+ import
+
+
+
+
+ UTF-8
+ UTF-8
+ 25
+ 25
+ 3.5.6
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${spring-boot.version}
+
+
+
+ repackage
+
+
+
+
+
+
+ com.google.cloud.tools
+ jib-maven-plugin
+ 3.4.0
+
+
+ gcr.io/PROJECT_ID/helloworld
+
+
+
+
+
+
+
diff --git a/run/helloworld-source/src/main/java/com/example/helloworld/HelloworldApplication.java b/run/helloworld-source/src/main/java/com/example/helloworld/HelloworldApplication.java
new file mode 100644
index 00000000000..c3165c9aca3
--- /dev/null
+++ b/run/helloworld-source/src/main/java/com/example/helloworld/HelloworldApplication.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 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 cloudrun_helloworld_service]
+
+package com.example.helloworld;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@SpringBootApplication
+public class HelloworldApplication {
+
+ @Value("${NAME:World}")
+ String name;
+
+ @RestController
+ class HelloworldController {
+ @GetMapping("/")
+ String hello() {
+ return "Hello " + name + "!";
+ }
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(HelloworldApplication.class, args);
+ }
+}
+// [END cloudrun_helloworld_service]
diff --git a/run/helloworld-source/src/main/resources/application.properties b/run/helloworld-source/src/main/resources/application.properties
new file mode 100644
index 00000000000..3cebd9ca826
--- /dev/null
+++ b/run/helloworld-source/src/main/resources/application.properties
@@ -0,0 +1,16 @@
+# Copyright 2020 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 cloudrun_helloworld_properties]
+server.port=${PORT:8080}
+# [END cloudrun_helloworld_properties]
diff --git a/run/helloworld-source/src/test/java/com/example/helloworld/HelloworldApplicationTests.java b/run/helloworld-source/src/test/java/com/example/helloworld/HelloworldApplicationTests.java
new file mode 100644
index 00000000000..c35e651b6bc
--- /dev/null
+++ b/run/helloworld-source/src/test/java/com/example/helloworld/HelloworldApplicationTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2020 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.helloworld;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@AutoConfigureMockMvc
+public class HelloworldApplicationTests {
+
+ @Autowired private MockMvc mockMvc;
+
+ @Test
+ public void returnsHelloWorld() throws Exception {
+ mockMvc
+ .perform(get("/"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Hello World!"));
+ }
+}
diff --git a/run/helloworld/pom.xml b/run/helloworld/pom.xml
index 70e213d033f..73c3ab340fa 100644
--- a/run/helloworld/pom.xml
+++ b/run/helloworld/pom.xml
@@ -41,8 +41,8 @@ limitations under the License.
UTF-8
UTF-8
- 17
- 17
+ 25
+ 25
3.2.2