diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml
index c26af378a94..c1b762cd0fb 100644
--- a/tests/test-distribution/pom.xml
+++ b/tests/test-distribution/pom.xml
@@ -85,6 +85,13 @@
${project.version}
test
+
+ org.eclipse.jetty.tests
+ test-felix-webapp
+ ${project.version}
+ test
+ war
+
diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/OsgiAppTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/OsgiAppTests.java
new file mode 100644
index 00000000000..09cb1ef8478
--- /dev/null
+++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/OsgiAppTests.java
@@ -0,0 +1,69 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.tests.distribution;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OsgiAppTests extends AbstractDistributionTest
+{
+ @Test
+ public void testFelixWebappStart() throws Exception
+ {
+ String jettyVersion = System.getProperty("jettyVersion");
+ DistributionTester distribution = DistributionTester.Builder.newInstance()
+ .jettyVersion(jettyVersion)
+ .mavenLocalRepository(System.getProperty("mavenRepoPath"))
+ .build();
+
+ String[] args1 = {
+ "--create-startd",
+ "--approve-all-licenses",
+ "--add-to-start=http,deploy,annotations,plus,resources"
+ };
+ try (DistributionTester.Run run1 = distribution.start(args1))
+ {
+ assertTrue(run1.awaitFor(5, TimeUnit.SECONDS));
+ assertEquals(0, run1.getExitValue());
+
+ File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-felix-webapp:war:" + jettyVersion);
+ distribution.installWarFile(war, "test");
+
+ int port = distribution.freePort();
+ try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port))
+ {
+ assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS));
+
+ startHttpClient();
+ ContentResponse response = client.GET("http://localhost:" + port + "/test/info");
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertThat(response.getContentAsString(), containsString("Framework: org.apache.felix.framework"));
+ }
+ }
+ }
+}
diff --git a/tests/test-webapps/pom.xml b/tests/test-webapps/pom.xml
index 811613302a4..89c0bcb2b19 100644
--- a/tests/test-webapps/pom.xml
+++ b/tests/test-webapps/pom.xml
@@ -40,5 +40,6 @@
test-jndi-webapp
test-http2-webapp
test-simple-webapp
+ test-felix-webapp
\ No newline at end of file
diff --git a/tests/test-webapps/test-felix-webapp/pom.xml b/tests/test-webapps/test-felix-webapp/pom.xml
new file mode 100644
index 00000000000..454f1fe61da
--- /dev/null
+++ b/tests/test-webapps/test-felix-webapp/pom.xml
@@ -0,0 +1,50 @@
+
+
+
+ org.eclipse.jetty.tests
+ test-webapps-parent
+ 9.4.20-SNAPSHOT
+
+
+ 4.0.0
+ test-felix-webapp
+ Test :: Jetty Felix Webapp
+ war
+
+
+ ${project.groupId}.felix
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+
+ false
+
+
+
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
+ org.apache.felix
+ org.apache.felix.framework
+ 5.6.2
+
+
+
diff --git a/tests/test-webapps/test-felix-webapp/src/main/java/org/eclipse/jetty/demo/AppListener.java b/tests/test-webapps/test-felix-webapp/src/main/java/org/eclipse/jetty/demo/AppListener.java
new file mode 100755
index 00000000000..69b780ae895
--- /dev/null
+++ b/tests/test-webapps/test-felix-webapp/src/main/java/org/eclipse/jetty/demo/AppListener.java
@@ -0,0 +1,82 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.demo;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.annotation.WebListener;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+@WebListener
+public class AppListener implements ServletContextListener
+{
+ public void contextInitialized(ServletContextEvent sce)
+ {
+ Framework framework = initFelix();
+ sce.getServletContext().setAttribute(Framework.class.getName(), framework);
+ }
+
+ private Framework initFelix()
+ {
+ Map properties = new HashMap<>();
+
+ try
+ {
+ Path cacheDir = Files.createTempDirectory("felix-cache");
+ properties.put(Constants.FRAMEWORK_STORAGE, cacheDir.toAbsolutePath().toString());
+ properties.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
+ properties.put(Constants.FRAMEWORK_BUNDLE_PARENT, Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK);
+ properties.put(Constants.FRAMEWORK_BOOTDELEGATION, "*");
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to configure Felix", e);
+ }
+
+ Framework framework = ServiceLoader.load(FrameworkFactory.class).iterator().next().newFramework(properties);
+
+ try
+ {
+ System.err.println("Initializing felix");
+ framework.init();
+ System.err.println("Starting felix");
+ framework.start();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(System.err);
+ throw new RuntimeException("Unable to start Felix", e);
+ }
+
+ return framework;
+ }
+
+ public void contextDestroyed(ServletContextEvent sce)
+ {
+ }
+}
diff --git a/tests/test-webapps/test-felix-webapp/src/main/java/org/eclipse/jetty/demo/InfoServlet.java b/tests/test-webapps/test-felix-webapp/src/main/java/org/eclipse/jetty/demo/InfoServlet.java
new file mode 100644
index 00000000000..ac33b980b81
--- /dev/null
+++ b/tests/test-webapps/test-felix-webapp/src/main/java/org/eclipse/jetty/demo/InfoServlet.java
@@ -0,0 +1,54 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.demo;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.launch.Framework;
+
+@WebServlet("/info")
+public class InfoServlet extends HttpServlet
+{
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
+ {
+ resp.setStatus(HttpServletResponse.SC_OK);
+ resp.setContentType("text/plain");
+ resp.setCharacterEncoding("utf-8");
+
+ PrintWriter out = resp.getWriter();
+ Framework framework = (Framework)getServletContext().getAttribute(Framework.class.getName());
+ out.printf("Framework: %s\n", framework);
+ BundleContext bundleContext = framework.getBundleContext();
+ out.printf("BundleContext: %s\n", bundleContext);
+ Bundle bundleSelf = bundleContext.getBundle();
+ out.printf("BundleContext.bundle: %s\n", bundleSelf);
+ for (Bundle bundle : bundleContext.getBundles())
+ {
+ out.printf("bundle[]: %s\n", bundle);
+ }
+ }
+}