[camel-k] branch master updated: enhancement(runtime): improve runtime lifecycle handling

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[camel-k] branch master updated: enhancement(runtime): improve runtime lifecycle handling

lburgazzoli-2
This is an automated email from the ASF dual-hosted git repository.

lburgazzoli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git


The following commit(s) were added to refs/heads/master by this push:
     new 07d7817  enhancement(runtime): improve runtime lifecycle handling
07d7817 is described below

commit 07d7817c03014d2b502653978ffa7f8308a5a755
Author: lburgazzoli <[hidden email]>
AuthorDate: Mon Feb 11 14:40:49 2019 +0100

    enhancement(runtime): improve runtime lifecycle handling
---
 pkg/builder/springboot/generator.go                |   2 +-
 runtime/camel-k-runtime-core/pom.xml               |   5 +
 .../java/org/apache/camel/k/ContextCustomizer.java |   2 +-
 .../java/org/apache/camel/k/InMemoryRegistry.java  |   2 +-
 .../main/java/org/apache/camel/k/RoutesLoader.java |   2 +-
 .../src/main/java/org/apache/camel/k/Runtime.java  |  78 +++++++++
 .../java/org/apache/camel/k/RuntimeRegistry.java   |  43 -----
 .../AbstractPhaseListener.java}                    |  28 ++--
 .../apache/camel/k/listener/ContextConfigurer.java |  51 ++++++
 .../k/listener/ContextLifecycleConfigurer.java     |  48 ++++++
 .../apache/camel/k/listener/RoutesConfigurer.java  |  83 ++++++++++
 .../org/apache/camel/k/listener/RoutesDumper.java  |  57 +++++++
 .../camel/k/support/PlatformStreamHandler.java     | 116 ++++++++++++++
 .../org/apache/camel/k/support/RuntimeSupport.java |  92 ++++++++++-
 .../camel/k/groovy/GroovyRoutesLoader.groovy       |   5 +-
 .../camel/k/groovy/dsl/ContextConfiguration.groovy |   6 +-
 .../k/groovy/dsl/IntegrationConfiguration.groovy   |   7 +-
 .../k/groovy/dsl/RegistryConfiguration.groovy      |   6 +-
 .../camel/k/groovy/dsl/IntegrationTest.groovy      |  84 ++++------
 .../java/org/apache/camel/k/jvm/Application.java   | 145 ++---------------
 .../org/apache/camel/k/jvm/ApplicationRuntime.java | 154 ++++++++++++++++++
 .../org/apache/camel/k/jvm/ApplicationSupport.java | 175 +--------------------
 .../main/java/org/apache/camel/k/jvm/Runtime.java  | 127 ---------------
 .../apache/camel/k/jvm/loader/JavaClassLoader.java |   4 +-
 .../camel/k/jvm/loader/JavaScriptLoader.java       |   4 +-
 .../camel/k/jvm/loader/JavaSourceLoader.java       |   4 +-
 .../org/apache/camel/k/jvm/loader/XmlLoader.java   |   4 +-
 .../org/apache/camel/k/jvm/PropertiesTest.java     |  86 +++++-----
 .../org/apache/camel/k/jvm/RoutesLoadersTest.java  |   4 +-
 .../java/org/apache/camel/k/jvm/RuntimeTest.java   |  57 ++++---
 .../org/apache/camel/k/jvm/RuntimeTestSupport.java |  42 -----
 .../org/apache/camel/k/jvm/TestCustomizer.java     |   4 +-
 .../apache/camel/k/kotlin/KotlinRoutesLoader.kt    |   6 +-
 .../camel/k/kotlin/dsl/ContextConfiguration.kt     |   4 +-
 .../camel/k/kotlin/dsl/IntegrationConfiguration.kt |   4 +-
 .../camel/k/kotlin/dsl/RegistryConfiguration.kt    |   4 +-
 .../apache/camel/k/kotlin/dsl/IntegrationTest.kt   |  74 ++++-----
 .../apache/camel/k/spring/boot/Application.java    | 118 +-------------
 .../spring/boot/ApplicationAutoConfiguration.java  | 132 ++++++++++++++++
 .../src/main/resources/META-INF/spring.factories   |   2 +
 .../org/apache/camel/k/yaml/YamlFlowLoader.java    |   4 +-
 41 files changed, 1034 insertions(+), 841 deletions(-)

diff --git a/pkg/builder/springboot/generator.go b/pkg/builder/springboot/generator.go
index 6a7a77c..8d2258e 100644
--- a/pkg/builder/springboot/generator.go
+++ b/pkg/builder/springboot/generator.go
@@ -94,7 +94,7 @@ func GenerateProject(ctx *builder.Context) error {
  ctx.Project.AddDependency(maven.Dependency{
  GroupID:    "org.apache.camel",
  ArtifactID: artifactID + "-starter",
- Version:    ctx.Request.Platform.Build.CamelVersion,
+ Version:    ctx.Catalog.Version,
  Exclusions: &[]maven.Exclusion{
  {
  GroupID:    "com.sun.xml.bind",
diff --git a/runtime/camel-k-runtime-core/pom.xml b/runtime/camel-k-runtime-core/pom.xml
index 12a92f3..c0748d5 100644
--- a/runtime/camel-k-runtime-core/pom.xml
+++ b/runtime/camel-k-runtime-core/pom.xml
@@ -52,6 +52,11 @@
             <artifactId>commons-lang3</artifactId>
             <version>${commons-lang.version}</version>
         </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons-io.version}</version>
+        </dependency>
 
         <!-- ****************************** -->
         <!--                                -->
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/ContextCustomizer.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/ContextCustomizer.java
index 4bcfcdc..c6d6b1d 100644
--- a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/ContextCustomizer.java
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/ContextCustomizer.java
@@ -26,5 +26,5 @@ public interface ContextCustomizer {
      * @param camelContext the camel context to customize.
      * @param registry the runtime registry.
      */
-    void apply(CamelContext camelContext, RuntimeRegistry registry);
+    void apply(CamelContext camelContext, Runtime.Registry registry);
 }
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/InMemoryRegistry.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/InMemoryRegistry.java
index 203a03c..1b6c9e6 100644
--- a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/InMemoryRegistry.java
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/InMemoryRegistry.java
@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
 
 import org.apache.camel.NoSuchBeanException;
 
-public final class InMemoryRegistry implements RuntimeRegistry {
+public final class InMemoryRegistry implements Runtime.Registry {
     private final ConcurrentMap<String, Object> registry;
 
     public InMemoryRegistry() {
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/RoutesLoader.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/RoutesLoader.java
index fe78a54..1392264 100644
--- a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/RoutesLoader.java
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/RoutesLoader.java
@@ -36,5 +36,5 @@ public interface RoutesLoader {
      * @return the RouteBuilder.
      * @throws Exception
      */
-    RouteBuilder load(RuntimeRegistry registry, Source source) throws Exception;
+    RouteBuilder load(Runtime.Registry registry, Source source) throws Exception;
 }
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/Runtime.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/Runtime.java
new file mode 100644
index 0000000..31fbbe5
--- /dev/null
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/Runtime.java
@@ -0,0 +1,78 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k;
+
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.component.properties.PropertiesComponent;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingConsumer;
+
+public interface Runtime {
+    /**
+     * Returns the context associated to this runtime.
+     */
+    CamelContext getContext();
+
+    /**
+     * Returns the registry associated to this runtime.
+     */
+    Registry getRegistry();
+
+    default void setProperties(Properties properties) {
+        PropertiesComponent pc = new PropertiesComponent();
+        pc.setInitialProperties(properties);
+
+        getRegistry().bind("properties", pc);
+    }
+
+    enum Phase {
+        Starting,
+        ConfigureContext,
+        ConfigureRoutes,
+        Started
+    }
+
+    @FunctionalInterface
+    interface Listener {
+        void accept(Phase phase, Runtime runtime);
+    }
+
+    interface Registry extends org.apache.camel.spi.Registry {
+        void bind(String name, Object bean);
+
+        @SuppressWarnings("deprecation")
+        @Override
+        default public Object lookup(String name) {
+            return lookupByName(name);
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        default public <T> T lookup(String name, Class<T> type) {
+            return lookupByNameAndType(name, type);
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        default public <T> Map<String, T> lookupByType(Class<T> type) {
+            return findByTypeWithName(type);
+        }
+    }
+}
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/RuntimeRegistry.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/RuntimeRegistry.java
deleted file mode 100644
index 895fe33..0000000
--- a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/RuntimeRegistry.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.camel.k;
-
-import java.util.Map;
-
-import org.apache.camel.spi.Registry;
-
-public interface RuntimeRegistry extends Registry {
-    void bind(String name, Object bean);
-
-    @SuppressWarnings("deprecation")
-    @Override
-    default public Object lookup(String name) {
-        return lookupByName(name);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    default public <T> T lookup(String name, Class<T> type) {
-        return lookupByNameAndType(name, type);
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    default public <T> Map<String, T> lookupByType(Class<T> type) {
-        return findByTypeWithName(type);
-    }
-}
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/ContextCustomizer.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/AbstractPhaseListener.java
similarity index 62%
copy from runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/ContextCustomizer.java
copy to runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/AbstractPhaseListener.java
index 4bcfcdc..7de4991 100644
--- a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/ContextCustomizer.java
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/AbstractPhaseListener.java
@@ -14,17 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.k;
+package org.apache.camel.k.listener;
 
-import org.apache.camel.CamelContext;
+import org.apache.camel.k.Runtime;
 
-@FunctionalInterface
-public interface ContextCustomizer {
-    /**
-     * Perform CamelContext customization.
-     *
-     * @param camelContext the camel context to customize.
-     * @param registry the runtime registry.
-     */
-    void apply(CamelContext camelContext, RuntimeRegistry registry);
+public abstract class AbstractPhaseListener implements Runtime.Listener {
+    private final Runtime.Phase phase;
+
+    protected AbstractPhaseListener(Runtime.Phase phase) {
+        this.phase = phase;
+    }
+
+    @Override
+    public void accept(Runtime.Phase phase, Runtime runtime) {
+        if (this.phase == phase) {
+            accept(runtime);
+        }
+    }
+
+    protected abstract void accept(Runtime runtime);
 }
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/ContextConfigurer.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/ContextConfigurer.java
new file mode 100644
index 0000000..ef02736
--- /dev/null
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/ContextConfigurer.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k.listener;
+
+import org.apache.camel.k.Runtime;
+import org.apache.camel.k.support.RuntimeSupport;
+
+public class ContextConfigurer extends AbstractPhaseListener {
+    public ContextConfigurer() {
+        super(Runtime.Phase.ConfigureContext);
+    }
+
+    @Override
+    protected void accept(Runtime runtime) {
+        //
+        // Configure the camel context using properties in the form:
+        //
+        //     camel.context.${name} = ${value}
+        //
+        RuntimeSupport.bindProperties(runtime.getContext(), runtime.getContext(), "camel.context.");
+
+        //
+        // Configure the camel rest definition using properties in the form:
+        //
+        //     camel.rest.${name} = ${value}
+        //
+        RuntimeSupport.configureRest(runtime.getContext());
+
+        //
+        // Programmatically configure the camel context.
+        //
+        // This is useful to configure services such as the ClusterService,
+        // RouteController, etc
+        //
+        RuntimeSupport.configureContext(runtime.getContext(), runtime.getRegistry());
+    }
+}
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/ContextLifecycleConfigurer.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/ContextLifecycleConfigurer.java
new file mode 100644
index 0000000..85383c7
--- /dev/null
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/ContextLifecycleConfigurer.java
@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k.listener;
+
+import org.apache.camel.Component;
+import org.apache.camel.k.Runtime;
+import org.apache.camel.k.support.RuntimeSupport;
+import org.apache.camel.support.LifecycleStrategySupport;
+
+public class ContextLifecycleConfigurer extends AbstractPhaseListener {
+    public ContextLifecycleConfigurer() {
+        super(Runtime.Phase.ConfigureContext);
+    }
+
+    @Override
+    protected void accept(Runtime runtime) {
+        //
+        // Configure components upon creation
+        //
+        runtime.getContext().addLifecycleStrategy(new LifecycleStrategySupport() {
+            @SuppressWarnings("unchecked")
+            @Override
+            public void onComponentAdd(String name, Component component) {
+                // The prefix that identifies component properties is the
+                // same one used by camel-spring-boot to configure components
+                // using starters:
+                //
+                //     camel.component.${scheme}.${name} = ${value}
+                //
+                RuntimeSupport.bindProperties(runtime.getContext(), component, "camel.component." + name + ".");
+            }
+        });
+    }
+}
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java
new file mode 100644
index 0000000..8c1b0eb
--- /dev/null
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k.listener;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.k.Constants;
+import org.apache.camel.k.RoutesLoader;
+import org.apache.camel.k.Runtime;
+import org.apache.camel.k.Source;
+import org.apache.camel.k.support.RuntimeSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RoutesConfigurer extends AbstractPhaseListener {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RoutesConfigurer.class);
+
+    public RoutesConfigurer() {
+        super(Runtime.Phase.ConfigureRoutes);
+    }
+
+    @Override
+    protected void accept(Runtime runtime) {
+        final String routes = System.getenv(Constants.ENV_CAMEL_K_ROUTES);
+
+        if (ObjectHelper.isEmpty(routes)) {
+            LOGGER.warn("No valid routes found in {} environment variable", Constants.ENV_CAMEL_K_ROUTES);
+        }
+
+        load(runtime, routes.split(",", -1));
+    }
+
+    protected void load(Runtime runtime, String[] routes) {
+        for (String route: routes) {
+            final Source source;
+            final RoutesLoader loader;
+            final RouteBuilder builder;
+
+            try {
+                source = Source.create(route);
+                loader = RuntimeSupport.loaderFor(runtime.getContext(), source);
+                builder = loader.load(runtime.getRegistry(), source);
+            } catch (Exception e) {
+                throw ObjectHelper.wrapRuntimeCamelException(e);
+            }
+
+            if (builder == null) {
+                throw new IllegalStateException("Unable to load route from: " + route);
+            }
+
+            LOGGER.info("Loading routes from: {}", route);
+
+            try {
+                runtime.getContext().addRoutes(builder);
+            } catch (Exception e) {
+                throw ObjectHelper.wrapRuntimeCamelException(e);
+            }
+        }
+    }
+
+    public static RoutesConfigurer forRoutes(String... routes) {
+        return new RoutesConfigurer() {
+            @Override
+            protected void accept(Runtime runtime) {
+                load(runtime, routes);
+            }
+        };
+    }
+}
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesDumper.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesDumper.java
new file mode 100644
index 0000000..379fc7f
--- /dev/null
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesDumper.java
@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k.listener;
+
+import javax.xml.bind.JAXBException;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.k.Runtime;
+import org.apache.camel.model.ModelHelper;
+import org.apache.camel.model.RoutesDefinition;
+import org.apache.camel.model.rest.RestsDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RoutesDumper extends AbstractPhaseListener {
+    private static final Logger LOGGER = LoggerFactory.getLogger(RoutesDumper.class);
+
+    public RoutesDumper() {
+        super(Runtime.Phase.Started);
+    }
+
+    @Override
+    protected void accept(Runtime runtime) {
+        CamelContext context = runtime.getContext();
+
+        RoutesDefinition routes = new RoutesDefinition();
+        routes.setRoutes(context.getRouteDefinitions());
+
+        RestsDefinition rests = new RestsDefinition();
+        rests.setRests(context.getRestDefinitions());
+
+        try {
+            if (LOGGER.isDebugEnabled() && !routes.getRoutes().isEmpty()) {
+                LOGGER.debug("Routes: \n{}", ModelHelper.dumpModelAsXml(context, routes));
+            }
+            if (LOGGER.isDebugEnabled() && !rests.getRests().isEmpty()) {
+                LOGGER.debug("Rests: \n{}", ModelHelper.dumpModelAsXml(context, rests));
+            }
+        } catch (JAXBException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/PlatformStreamHandler.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/PlatformStreamHandler.java
new file mode 100644
index 0000000..fbd0403
--- /dev/null
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/PlatformStreamHandler.java
@@ -0,0 +1,116 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k.support;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.commons.lang3.StringUtils;
+
+public class PlatformStreamHandler extends URLStreamHandler {
+    public static void configure() {
+        URL.setURLStreamHandlerFactory(protocol -> "platform".equals(protocol) ? new PlatformStreamHandler() : null);
+    }
+
+    @Override
+    protected URLConnection openConnection(URL url) throws IOException {
+        return new URLConnection(url) {
+            @Override
+            public void connect() throws IOException {
+            }
+
+            @Override
+            public InputStream getInputStream() throws IOException {
+                InputStream is = null;
+
+                // check if the file exists
+                Path path = Paths.get(url.getPath());
+                if (Files.exists(path)) {
+                    is = Files.newInputStream(path);
+                }
+
+                // check if the file exists in classpath
+                if (is == null) {
+                    is = ObjectHelper.loadResourceAsStream(url.getPath());
+                }
+
+                if (is == null) {
+                    String name = getURL().getPath().toUpperCase();
+                    name = name.replace(" ", "_");
+                    name = name.replace(".", "_");
+                    name = name.replace("-", "_");
+
+                    String envName = System.getenv(name);
+                    String envType = StringUtils.substringBefore(envName, ":");
+                    String envQuery = StringUtils.substringAfter(envName, "?");
+
+                    envName = StringUtils.substringAfter(envName, ":");
+                    envName = StringUtils.substringBefore(envName, "?");
+
+                    if (envName != null) {
+                        try {
+                            final Map<String, Object> params = URISupport.parseQuery(envQuery);
+                            final boolean compression = Boolean.valueOf((String) params.get("compression"));
+
+                            if (StringUtils.equals(envType, "env")) {
+                                String data = System.getenv(envName);
+
+                                if (data == null) {
+                                    throw new IllegalArgumentException("Unknown env var: " + envName);
+                                }
+
+                                is = new ByteArrayInputStream(data.getBytes());
+                            } else if (StringUtils.equals(envType, "file")) {
+                                Path data = Paths.get(envName);
+
+                                if (!Files.exists(data)) {
+                                    throw new FileNotFoundException(envName);
+                                }
+
+                                is = Files.newInputStream(data);
+                            } else if (StringUtils.equals(envType, "classpath")) {
+                                is = ObjectHelper.loadResourceAsStream(envName);
+                            }
+
+                            if (is != null && compression) {
+                                is = new GZIPInputStream(Base64.getDecoder().wrap(is));
+                            }
+                        } catch (URISyntaxException e) {
+                            throw new IOException(e);
+                        }
+                    }
+                }
+
+                return is;
+            }
+        };
+    }
+}
diff --git a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java
index 4811f9f..ce41289 100644
--- a/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java
+++ b/runtime/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java
@@ -16,6 +16,16 @@
  */
 package org.apache.camel.k.support;
 
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Objects;
 import java.util.Properties;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -23,14 +33,15 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.NoFactoryAvailableException;
 import org.apache.camel.component.properties.PropertiesComponent;
 import org.apache.camel.k.Constants;
-import org.apache.camel.k.RoutesLoader;
 import org.apache.camel.k.ContextCustomizer;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.RoutesLoader;
+import org.apache.camel.k.Runtime;
 import org.apache.camel.k.Source;
 import org.apache.camel.spi.FactoryFinder;
 import org.apache.camel.spi.RestConfiguration;
 import org.apache.camel.util.IntrospectionSupport;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.commons.io.FilenameUtils;
 
 
 public final class RuntimeSupport {
@@ -38,7 +49,7 @@ public final class RuntimeSupport {
     private RuntimeSupport() {
     }
 
-    public static void configureContext(CamelContext context, RuntimeRegistry registry) {
+    public static void configureContext(CamelContext context, Runtime.Registry registry) {
         try {
             FactoryFinder finder = context.getFactoryFinder(Constants.CONTEXT_CUSTOMIZER_RESOURCE_PATH);
             String customizerIDs = System.getenv().getOrDefault(Constants.ENV_CAMEL_K_CUSTOMIZERS, "");
@@ -47,11 +58,15 @@ public final class RuntimeSupport {
                 PropertiesComponent component = context.getComponent("properties", PropertiesComponent.class);
                 Properties properties = component.getInitialProperties();
 
-                customizerIDs = properties.getProperty(Constants.PROPERTY_CAMEL_K_CUSTOMIZER, "");
+                if (properties != null) {
+                    customizerIDs = properties.getProperty(Constants.PROPERTY_CAMEL_K_CUSTOMIZER, "");
+                }
             }
 
-            for (String customizerId: customizerIDs.split(",", -1)) {
-                configureContext(context, customizerId, (ContextCustomizer)finder.newInstance(customizerId), registry);
+            if  (ObjectHelper.isNotEmpty(customizerIDs)) {
+                for (String customizerId : customizerIDs.split(",", -1)) {
+                    configureContext(context, customizerId, (ContextCustomizer) finder.newInstance(customizerId), registry);
+                }
             }
         } catch (NoFactoryAvailableException e) {
             // ignored
@@ -63,7 +78,7 @@ public final class RuntimeSupport {
         );
     }
 
-    public static void configureContext(CamelContext context, String customizerId, ContextCustomizer customizer, RuntimeRegistry registry) {
+    public static void configureContext(CamelContext context, String customizerId, ContextCustomizer customizer, Runtime.Registry registry) {
         bindProperties(context, customizer, "customizer." + customizerId + ".");
         customizer.apply(context, registry);
     }
@@ -85,7 +100,7 @@ public final class RuntimeSupport {
         final Properties properties = component.getInitialProperties();
 
         if (properties == null) {
-            throw new IllegalStateException("PropertiesComponent has no properties");
+            return 0;
         }
 
         return bindProperties(properties, target, prefix);
@@ -135,4 +150,65 @@ public final class RuntimeSupport {
 
         return loader;
     }
+
+    public static Properties loadProperties() {
+        final String conf = System.getenv(Constants.ENV_CAMEL_K_CONF);
+        final String confd = System.getenv(Constants.ENV_CAMEL_K_CONF_D);
+
+        return loadProperties(conf, confd);
+    }
+
+    public static Properties loadProperties(String conf, String confd) {
+        final Properties properties = new Properties();
+
+        // Main location
+        if (ObjectHelper.isNotEmpty(conf)) {
+            if (conf.startsWith(Constants.SCHEME_ENV)) {
+                try (Reader reader = URIResolver.resolveEnv(conf)) {
+                    properties.load(reader);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            } else {
+                try (Reader reader = Files.newBufferedReader(Paths.get(conf))) {
+                    properties.load(reader);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        // Additional locations
+        if (ObjectHelper.isNotEmpty(confd)) {
+            Path root = Paths.get(confd);
+            FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
+                @Override
+                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                    Objects.requireNonNull(file);
+                    Objects.requireNonNull(attrs);
+
+                    String path = file.toFile().getAbsolutePath();
+                    String ext = FilenameUtils.getExtension(path);
+
+                    if (Objects.equals("properties", ext)) {
+                        try (Reader reader = Files.newBufferedReader(Paths.get(path))) {
+                            properties.load(reader);
+                        }
+                    }
+
+                    return FileVisitResult.CONTINUE;
+                }
+            };
+
+            if (Files.exists(root)) {
+                try {
+                    Files.walkFileTree(root, visitor);
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        return properties;
+    }
 }
diff --git a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/GroovyRoutesLoader.groovy b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/GroovyRoutesLoader.groovy
index 7bf787f..bf37327 100644
--- a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/GroovyRoutesLoader.groovy
+++ b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/GroovyRoutesLoader.groovy
@@ -18,7 +18,8 @@ package org.apache.camel.k.groovy
 
 import org.apache.camel.builder.RouteBuilder
 import org.apache.camel.k.RoutesLoader
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime
+
 import org.apache.camel.k.Source
 import org.apache.camel.k.groovy.dsl.IntegrationConfiguration
 import org.apache.camel.k.support.URIResolver
@@ -31,7 +32,7 @@ class GroovyRoutesLoader implements RoutesLoader {
     }
 
     @Override
-    RouteBuilder load(RuntimeRegistry registry, Source source) throws Exception {
+    RouteBuilder load(Runtime.Registry registry, Source source) throws Exception {
         return new RouteBuilder() {
             @Override
             void configure() throws Exception {
diff --git a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/ContextConfiguration.groovy b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/ContextConfiguration.groovy
index d70dd04..ea63d0a 100644
--- a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/ContextConfiguration.groovy
+++ b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/ContextConfiguration.groovy
@@ -17,13 +17,13 @@
 package org.apache.camel.k.groovy.dsl
 
 import org.apache.camel.CamelContext
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime
 
 class ContextConfiguration {
     private final CamelContext context
-    private final RuntimeRegistry registry
+    private final Runtime.Registry registry
 
-    ContextConfiguration(CamelContext context, RuntimeRegistry registry) {
+    ContextConfiguration(CamelContext context, Runtime.Registry registry) {
         this.context = context
         this.registry = registry
     }
diff --git a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/IntegrationConfiguration.groovy b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/IntegrationConfiguration.groovy
index fdc860a..b71d7df 100644
--- a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/IntegrationConfiguration.groovy
+++ b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/IntegrationConfiguration.groovy
@@ -21,18 +21,19 @@ import org.apache.camel.Exchange
 import org.apache.camel.Predicate
 import org.apache.camel.Processor
 import org.apache.camel.builder.RouteBuilder
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime
+
 import org.apache.camel.k.jvm.dsl.Components
 import org.apache.camel.model.RouteDefinition
 
 class IntegrationConfiguration {
-    private final RuntimeRegistry registry
+    private final Runtime.Registry registry
 
     final CamelContext context
     final Components components
     final RouteBuilder builder
 
-    IntegrationConfiguration(RuntimeRegistry registry, RouteBuilder builder) {
+    IntegrationConfiguration(Runtime.Registry registry, RouteBuilder builder) {
         this.registry = registry
         this.context = builder.getContext()
         this.components = new Components(this.context)
diff --git a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/RegistryConfiguration.groovy b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/RegistryConfiguration.groovy
index f695ec1..14545ef 100644
--- a/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/RegistryConfiguration.groovy
+++ b/runtime/camel-k-runtime-groovy/src/main/groovy/org/apache/camel/k/groovy/dsl/RegistryConfiguration.groovy
@@ -16,12 +16,12 @@
  */
 package org.apache.camel.k.groovy.dsl
 
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime
 
 class RegistryConfiguration {
-    private final RuntimeRegistry registry
+    private final Runtime.Registry registry
 
-    RegistryConfiguration(RuntimeRegistry registry) {
+    RegistryConfiguration(Runtime.Registry registry) {
         this.registry = registry
     }
 
diff --git a/runtime/camel-k-runtime-groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy b/runtime/camel-k-runtime-groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy
index 3dbd629..5fbde31 100644
--- a/runtime/camel-k-runtime-groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy
+++ b/runtime/camel-k-runtime-groovy/src/test/groovy/org/apache/camel/k/groovy/dsl/IntegrationTest.groovy
@@ -19,9 +19,9 @@ package org.apache.camel.k.groovy.dsl
 import org.apache.camel.Processor
 import org.apache.camel.component.log.LogComponent
 import org.apache.camel.component.seda.SedaComponent
-import org.apache.camel.k.jvm.Runtime
-import org.apache.camel.main.MainListenerSupport
-import org.apache.camel.main.MainSupport
+import org.apache.camel.k.Runtime
+import org.apache.camel.k.jvm.ApplicationRuntime
+import org.apache.camel.k.listener.RoutesConfigurer
 import spock.lang.Specification
 
 import java.util.concurrent.atomic.AtomicInteger
@@ -30,65 +30,48 @@ import java.util.concurrent.atomic.AtomicReference
 class IntegrationTest extends Specification {
     def "load integration with rest"()  {
         when:
-        def runtime = new Runtime()
-        runtime.setDuration(5)
-        runtime.load(['classpath:routes-with-rest.groovy'] as String[])
-        runtime.addMainListener(new MainListenerSupport() {
-            @Override
-            void afterStart(MainSupport main) {
-                main.stop()
-            }
-        })
-
-        runtime.run()
+            def runtime = new ApplicationRuntime()
+            runtime.addListener(RoutesConfigurer.forRoutes('classpath:routes-with-rest.groovy'))
+            runtime.addListener(Runtime.Phase.Started, { runtime.stop() })
+            runtime.run()
 
         then:
-        runtime.camelContext.restConfiguration.host == 'my-host'
-        runtime.camelContext.restConfiguration.port == 9192
-        runtime.camelContext.getRestConfiguration('undertow', false).host == 'my-undertow-host'
-        runtime.camelContext.getRestConfiguration('undertow', false).port == 9193
-        runtime.camelContext.restDefinitions.size() == 1
-        runtime.camelContext.restDefinitions[0].path == '/my/path'
+            runtime.context.restConfiguration.host == 'my-host'
+            runtime.context.restConfiguration.port == 9192
+            runtime.context.getRestConfiguration('undertow', false).host == 'my-undertow-host'
+            runtime.context.getRestConfiguration('undertow', false).port == 9193
+            runtime.context.restDefinitions.size() == 1
+            runtime.context.restDefinitions[0].path == '/my/path'
     }
 
     def "load integration with bindings"()  {
         when:
-        def runtime = new Runtime()
-        runtime.setDuration(5)
-        runtime.load(['classpath:routes-with-bindings.groovy'] as String[])
-        runtime.addMainListener(new MainListenerSupport() {
-            @Override
-            void afterStart(MainSupport main) {
-                main.stop()
-            }
-        })
-
-        runtime.run()
+            def runtime = new ApplicationRuntime()
+            runtime.addListener(RoutesConfigurer.forRoutes('classpath:routes-with-bindings.groovy'))
+            runtime.addListener(Runtime.Phase.Started, { runtime.stop() })
+            runtime.run()
 
         then:
-        runtime.camelContext.registry.lookup('myEntry1') == 'myRegistryEntry1'
-        runtime.camelContext.registry.lookup('myEntry2') == 'myRegistryEntry2'
-        runtime.camelContext.registry.lookup('myEntry3') instanceof Processor
+            runtime.context.registry.lookupByName('myEntry1') == 'myRegistryEntry1'
+            runtime.context.registry.lookupByName('myEntry2') == 'myRegistryEntry2'
+            runtime.context.registry.lookupByName('myEntry3') instanceof Processor
     }
 
     def "load integration with component configuration"()  {
         given:
-        def sedaSize = new AtomicInteger()
-        def sedaConsumers = new AtomicInteger()
-        def mySedaSize = new AtomicInteger()
-        def mySedaConsumers = new AtomicInteger()
-        def format = new AtomicReference()
+            def sedaSize = new AtomicInteger()
+            def sedaConsumers = new AtomicInteger()
+            def mySedaSize = new AtomicInteger()
+            def mySedaConsumers = new AtomicInteger()
+            def format = new AtomicReference()
 
         when:
-        def runtime = new Runtime()
-        runtime.setDuration(5)
-        runtime.load(['classpath:routes-with-component-configuration.groovy'] as String[])
-        runtime.addMainListener(new MainListenerSupport() {
-            @Override
-            void afterStart(MainSupport main) {
-                def seda = runtime.camelContext.getComponent('seda', SedaComponent)
-                def mySeda = runtime.camelContext.getComponent('mySeda', SedaComponent)
-                def log = runtime.camelContext.getComponent('log', LogComponent)
+            def runtime = new ApplicationRuntime()
+            runtime.addListener(RoutesConfigurer.forRoutes('classpath:routes-with-component-configuration.groovy'))
+            runtime.addListener(Runtime.Phase.Started, {
+                def seda = it.context.getComponent('seda', SedaComponent)
+                def mySeda = it.context.getComponent('mySeda', SedaComponent)
+                def log = it.context.getComponent('log', LogComponent)
 
                 assert seda != null
                 assert mySeda != null
@@ -100,9 +83,8 @@ class IntegrationTest extends Specification {
                 mySedaConsumers = mySeda.concurrentConsumers
                 format = log.exchangeFormatter
 
-                main.stop()
-            }
-        })
+                runtime.stop()
+            })
 
         runtime.run()
 
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/Application.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/Application.java
index f317880..eefed2d 100644
--- a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/Application.java
+++ b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/Application.java
@@ -16,23 +16,12 @@
  */
 package org.apache.camel.k.jvm;
 
-import org.apache.camel.CamelContext;
-import org.apache.camel.Component;
-import org.apache.camel.RuntimeCamelException;
-import org.apache.camel.k.Constants;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.listener.ContextConfigurer;
+import org.apache.camel.k.listener.ContextLifecycleConfigurer;
+import org.apache.camel.k.listener.RoutesConfigurer;
+import org.apache.camel.k.listener.RoutesDumper;
+import org.apache.camel.k.support.PlatformStreamHandler;
 import org.apache.camel.k.support.RuntimeSupport;
-import org.apache.camel.main.MainListenerSupport;
-import org.apache.camel.main.MainSupport;
-import org.apache.camel.model.ModelHelper;
-import org.apache.camel.model.RoutesDefinition;
-import org.apache.camel.model.rest.RestsDefinition;
-import org.apache.camel.support.LifecycleStrategySupport;
-import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.xml.bind.JAXBException;
 
 public class Application {
     static {
@@ -51,123 +40,19 @@ public class Application {
         // from the platform i.e. in knative, resources are provided through
         // env var as it is not possible to mount config maps / secrets.
         //
-        ApplicationSupport.configureStreamHandler();
+        // TODO: we should remove as soon as we get a knative version that
+        //       includes https://github.com/knative/serving/pull/3061
+        //
+        PlatformStreamHandler.configure();
     }
 
-    // *******************************
-    //
-    // Main
-    //
-    // *******************************
-
     public static void main(String[] args) throws Exception {
-        final String routes = System.getenv(Constants.ENV_CAMEL_K_ROUTES);
-
-        if (ObjectHelper.isEmpty(routes)) {
-            throw new IllegalStateException("No valid routes found in " + Constants.ENV_CAMEL_K_ROUTES + " environment variable");
-        }
-
-        Runtime runtime = new Runtime();
-        runtime.setProperties(ApplicationSupport.loadProperties());
-        runtime.addMainListener(new CamelkJvmRuntimeConfigurer(runtime, routes.split(",", -1)));
-        runtime.addMainListener(new RoutesDumper());
-
+        ApplicationRuntime runtime = new ApplicationRuntime();
+        runtime.setProperties(RuntimeSupport.loadProperties());
+        runtime.addListener(new ContextConfigurer());
+        runtime.addListener(new ContextLifecycleConfigurer());
+        runtime.addListener(new RoutesConfigurer());
+        runtime.addListener(new RoutesDumper());
         runtime.run();
     }
-
-    // *******************************
-    //
-    // Listeners
-    //
-    // *******************************
-
-    static class CamelkJvmRuntimeConfigurer extends MainListenerSupport {
-
-        private RuntimeRegistry registry;
-        private Runtime runtime;
-        private String[] routes;
-
-        public CamelkJvmRuntimeConfigurer(Runtime runtime, String[] routes){
-            this.runtime = runtime;
-            this.registry = runtime.getRegistry();
-            this.routes = routes;
-        }
-
-        @Override
-        public void configure(CamelContext context) {
-            //
-            // Configure the camel context using properties in the form:
-            //
-            //     camel.context.${name} = ${value}
-            //
-            RuntimeSupport.bindProperties(context, context, "camel.context.");
-
-            //
-            // Configure the camel rest definition using properties in the form:
-            //
-            //     camel.rest.${name} = ${value}
-            //
-            RuntimeSupport.configureRest(context);
-
-            //
-            // Programmatically configure the camel context.
-            //
-            // This is useful to configure services such as the ClusterService,
-            // RouteController, etc
-            //
-            RuntimeSupport.configureContext(context, registry);
-
-            //
-            // Configure components upon creation
-            //
-            context.addLifecycleStrategy(new LifecycleStrategySupport() {
-                @SuppressWarnings("unchecked")
-                @Override
-                public void onComponentAdd(String name, Component component) {
-                    // The prefix that identifies component properties is the
-                    // same one used by camel-spring-boot to configure components
-                    // using starters:
-                    //
-                    //     camel.component.${scheme}.${name} = ${value}
-                    //
-                    RuntimeSupport.bindProperties(context, component, "camel.component." + name + ".");
-                }
-            });
-
-            //
-            // Load routes
-            //
-            try {
-                runtime.load(routes);
-            } catch (Exception e) {
-                throw new RuntimeCamelException("CamelkJvmRuntimeConfigurer has failed to load routes: "+routes);
-            }
-        }
-    }
-
-    static class RoutesDumper extends MainListenerSupport {
-        static final Logger LOGGER = LoggerFactory.getLogger(RoutesDumper.class);
-
-        @Override
-        public void afterStart(MainSupport main) {
-            CamelContext context = main.getCamelContexts().get(0);
-
-            RoutesDefinition routes = new RoutesDefinition();
-            routes.setRoutes(context.getRouteDefinitions());
-
-            RestsDefinition rests = new RestsDefinition();
-            rests.setRests(context.getRestDefinitions());
-
-            try {
-                if (LOGGER.isDebugEnabled() && !routes.getRoutes().isEmpty()) {
-                    LOGGER.debug("Routes: \n{}", ModelHelper.dumpModelAsXml(context, routes));
-                }
-                if (LOGGER.isDebugEnabled() && !rests.getRests().isEmpty()) {
-                    LOGGER.debug("Rests: \n{}", ModelHelper.dumpModelAsXml(context, rests));
-                }
-            } catch (JAXBException e) {
-                throw new IllegalArgumentException(e);
-            }
-        }
-    }
 }
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/ApplicationRuntime.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/ApplicationRuntime.java
new file mode 100644
index 0000000..97cb805
--- /dev/null
+++ b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/ApplicationRuntime.java
@@ -0,0 +1,154 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k.jvm;
+
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.k.InMemoryRegistry;
+import org.apache.camel.k.Runtime;
+import org.apache.camel.main.MainSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.function.ThrowingConsumer;
+
+public final class ApplicationRuntime implements Runtime {
+    private final Main main;
+    private final ConcurrentMap<String, CamelContext> contextMap;
+    private final Runtime.Registry registry;
+    private final Set<Runtime.Listener> listeners;
+
+    public ApplicationRuntime() {
+        this.contextMap = new ConcurrentHashMap<>();
+        this.registry = new InMemoryRegistry();
+        this.listeners = new LinkedHashSet<>();
+
+        this.main = new Main();
+        this.main.addMainListener(new MainListenerAdapter());
+    }
+
+    @Override
+    public CamelContext getContext() {
+        return contextMap.computeIfAbsent("camel-k", key -> {
+            DefaultCamelContext context = new DefaultCamelContext();
+            context.setName(key);
+            context.setRegistry(this.registry);
+
+            return context;
+        });
+    }
+
+    @Override
+    public Runtime.Registry getRegistry() {
+        return registry;
+    }
+
+    public void run() throws Exception {
+        this.main.run();
+    }
+
+    public void stop()throws Exception {
+        this.main.stop();
+    }
+
+    public void addListener(Runtime.Listener listener) {
+        this.listeners.add(listener);
+    }
+
+    public void addListener(Phase phase, ThrowingConsumer<Runtime, Exception> consumer) {
+        addListener((p, runtime) -> {
+            if (p == phase) {
+                try {
+                    consumer.accept(runtime);
+                } catch (Exception e) {
+                    throw ObjectHelper.wrapRuntimeCamelException(e);
+                }
+            }
+        });
+    }
+
+    private class Main extends org.apache.camel.main.MainSupport {
+        @Override
+        protected ProducerTemplate findOrCreateCamelTemplate() {
+            return getContext().createProducerTemplate();
+        }
+
+        @Override
+        protected Map<String, CamelContext> getCamelContextMap() {
+            getContext();
+
+            return contextMap;
+        }
+
+        @Override
+        protected void doStart() throws Exception {
+            super.doStart();
+            postProcessContext();
+
+            try {
+                getContext().start();
+            } finally {
+                if (getContext().isVetoStarted()) {
+                    completed();
+                }
+            }
+        }
+
+        @Override
+        protected void doStop() throws Exception {
+            super.doStop();
+
+            if (!getCamelContexts().isEmpty()) {
+                getContext().stop();
+            }
+        }
+    }
+
+    private class MainListenerAdapter implements org.apache.camel.main.MainListener {
+
+        @Override
+        public void beforeStart(MainSupport main) {
+            listeners.forEach(l -> l.accept(Phase.Starting, ApplicationRuntime.this));
+        }
+
+        @Override
+        public void configure(CamelContext context) {
+            listeners.forEach(l -> l.accept(Phase.ConfigureContext, ApplicationRuntime.this));
+            listeners.forEach(l -> l.accept(Phase.ConfigureRoutes, ApplicationRuntime.this));
+        }
+
+        @Override
+        public void afterStart(MainSupport main) {
+            listeners.forEach(l -> l.accept(Phase.Started, ApplicationRuntime.this));
+        }
+
+        @Override
+        public void beforeStop(MainSupport main) {
+
+        }
+
+        @Override
+        public void afterStop(MainSupport main) {
+
+        }
+    }
+}
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/ApplicationSupport.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/ApplicationSupport.java
index 97d6cea..3524dd1 100644
--- a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/ApplicationSupport.java
+++ b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/ApplicationSupport.java
@@ -16,34 +16,10 @@
  */
 package org.apache.camel.k.jvm;
 
-import java.io.ByteArrayInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-import java.nio.file.FileVisitResult;
-import java.nio.file.FileVisitor;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.Base64;
-import java.util.Map;
-import java.util.Objects;
 import java.util.Properties;
-import java.util.zip.GZIPInputStream;
 
 import org.apache.camel.k.Constants;
-import org.apache.camel.k.support.URIResolver;
-import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.URISupport;
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.camel.k.support.RuntimeSupport;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.core.LoggerContext;
@@ -54,70 +30,9 @@ public final class ApplicationSupport {
     private ApplicationSupport() {
     }
 
-    public static Properties loadProperties() {
-        final String conf = System.getenv(Constants.ENV_CAMEL_K_CONF);
-        final String confd = System.getenv(Constants.ENV_CAMEL_K_CONF_D);
-
-        return loadProperties(conf, confd);
-    }
-
-    public static Properties loadProperties(String conf, String confd) {
-        final Properties properties = new Properties();
-
-        // Main location
-        if (ObjectHelper.isNotEmpty(conf)) {
-            if (conf.startsWith(Constants.SCHEME_ENV)) {
-                try (Reader reader = URIResolver.resolveEnv(conf)) {
-                    properties.load(reader);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-            } else {
-                try (Reader reader = Files.newBufferedReader(Paths.get(conf))) {
-                    properties.load(reader);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
-        // Additional locations
-        if (ObjectHelper.isNotEmpty(confd)) {
-            Path root = Paths.get(confd);
-            FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
-                @Override
-                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                    Objects.requireNonNull(file);
-                    Objects.requireNonNull(attrs);
-
-                    String path = file.toFile().getAbsolutePath();
-                    String ext = FilenameUtils.getExtension(path);
-
-                    if (Objects.equals("properties", ext)) {
-                        try (Reader reader = Files.newBufferedReader(Paths.get(path))) {
-                            properties.load(reader);
-                        }
-                    }
-
-                    return FileVisitResult.CONTINUE;
-                }
-            };
-
-            if (Files.exists(root)) {
-                try {
-                    Files.walkFileTree(root, visitor);
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-
-        return properties;
-    }
-
     public static void configureLogging() {
         final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
-        final Properties properties = loadProperties();
+        final Properties properties = RuntimeSupport.loadProperties();
 
         properties.entrySet().stream()
             .filter(entry -> entry.getKey() instanceof String)
@@ -135,90 +50,4 @@ public final class ApplicationSupport {
             }
         );
     }
-
-    public static void configureStreamHandler() {
-        URL.setURLStreamHandlerFactory(protocol -> "platform".equals(protocol) ? new PlatformStreamHandler() : null);
-    }
-
-    // ***************************************
-    //
-    //
-    //
-    // ***************************************
-
-    private static class PlatformStreamHandler extends URLStreamHandler {
-        @Override
-        protected URLConnection openConnection(URL url) throws IOException {
-            return new URLConnection(url) {
-                @Override
-                public void connect() throws IOException {
-                }
-
-                @Override
-                public InputStream getInputStream() throws IOException {
-                    InputStream is = null;
-
-                    // check if the file exists
-                    Path path = Paths.get(url.getPath());
-                    if (Files.exists(path)) {
-                        is = Files.newInputStream(path);
-                    }
-
-                    // check if the file exists in classpath
-                    if (is == null) {
-                        is = ObjectHelper.loadResourceAsStream(url.getPath());
-                    }
-
-                    if (is == null) {
-                        String name = getURL().getPath().toUpperCase();
-                        name = name.replace(" ", "_");
-                        name = name.replace(".", "_");
-                        name = name.replace("-", "_");
-
-                        String envName = System.getenv(name);
-                        String envType = StringUtils.substringBefore(envName, ":");
-                        String envQuery = StringUtils.substringAfter(envName, "?");
-
-                        envName = StringUtils.substringAfter(envName, ":");
-                        envName = StringUtils.substringBefore(envName, "?");
-
-                        if (envName != null) {
-                            try {
-                                final Map<String, Object> params = URISupport.parseQuery(envQuery);
-                                final boolean compression = Boolean.valueOf((String) params.get("compression"));
-
-                                if (StringUtils.equals(envType, "env")) {
-                                    String data = System.getenv(envName);
-
-                                    if (data == null) {
-                                        throw new IllegalArgumentException("Unknown env var: " + envName);
-                                    }
-
-                                    is = new ByteArrayInputStream(data.getBytes());
-                                } else if (StringUtils.equals(envType, "file")) {
-                                    Path data = Paths.get(envName);
-
-                                    if (!Files.exists(data)) {
-                                        throw new FileNotFoundException(envName);
-                                    }
-
-                                    is = Files.newInputStream(data);
-                                } else if (StringUtils.equals(envType, "classpath")) {
-                                    is = ObjectHelper.loadResourceAsStream(envName);
-                                }
-
-                                if (is != null && compression) {
-                                    is = new GZIPInputStream(Base64.getDecoder().wrap(is));
-                                }
-                            } catch (URISyntaxException e) {
-                                throw new IOException(e);
-                            }
-                        }
-                    }
-
-                    return is;
-                }
-            };
-        }
-    }
 }
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/Runtime.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/Runtime.java
deleted file mode 100644
index a68545d..0000000
--- a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/Runtime.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.camel.k.jvm;
-
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.camel.CamelContext;
-import org.apache.camel.ProducerTemplate;
-import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.component.properties.PropertiesComponent;
-import org.apache.camel.impl.CompositeRegistry;
-import org.apache.camel.impl.DefaultCamelContext;
-import org.apache.camel.k.InMemoryRegistry;
-import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.RuntimeRegistry;
-import org.apache.camel.k.Source;
-import org.apache.camel.k.support.RuntimeSupport;
-import org.apache.camel.main.MainSupport;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public final class Runtime extends MainSupport {
-    private static final Logger LOGGER = LoggerFactory.getLogger(Runtime.class);
-
-    private final ConcurrentMap<String, CamelContext> contextMap;
-    private final RuntimeRegistry registry;
-
-    public Runtime() {
-        this.contextMap = new ConcurrentHashMap<>();
-        this.registry = new InMemoryRegistry();
-    }
-
-    public void load(String[] routes) throws Exception {
-        for (String route: routes) {
-            final Source source = Source.create(route);
-            final RoutesLoader loader = RuntimeSupport.loaderFor(getCamelContext(), source);
-            final RouteBuilder builder = loader.load(registry, source);
-
-            if (builder == null) {
-                throw new IllegalStateException("Unable to load route from: " + route);
-            }
-
-            LOGGER.info("Routes: {}", route);
-
-            addRouteBuilder(builder);
-        }
-    }
-
-    public RuntimeRegistry getRegistry() {
-        return registry;
-    }
-
-    public CamelContext getCamelContext() {
-        return contextMap.computeIfAbsent("camel-1", key -> {
-            DefaultCamelContext context = new DefaultCamelContext();
-            context.setName(key);
-
-            CompositeRegistry registry = new CompositeRegistry();
-            registry.addRegistry(this.registry);
-            registry.addRegistry(context.getRegistry());
-
-            context.setRegistry(registry);
-
-            return context;
-        });
-    }
-
-    public void setProperties(Properties properties) {
-        PropertiesComponent pc = new PropertiesComponent();
-        pc.setInitialProperties(properties);
-
-        getRegistry().bind("properties", pc);
-    }
-
-    @Override
-    protected ProducerTemplate findOrCreateCamelTemplate() {
-        return getCamelContexts().size() > 0 ? getCamelContexts().get(0).createProducerTemplate() : null;
-    }
-
-    @Override
-    protected Map<String, CamelContext> getCamelContextMap() {
-        getCamelContext();
-
-        return contextMap;
-    }
-
-    @Override
-    protected void doStart() throws Exception {
-        super.doStart();
-        postProcessContext();
-        if (!getCamelContexts().isEmpty()) {
-            try {
-                getCamelContexts().get(0).start();
-            } finally {
-                if (getCamelContexts().get(0).isVetoStarted()) {
-                    completed();
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void doStop() throws Exception {
-        super.doStop();
-
-        if (!getCamelContexts().isEmpty()) {
-            getCamelContexts().get(0).stop();
-        }
-    }
-}
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaClassLoader.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaClassLoader.java
index c13f2d1..eb2a2f5 100644
--- a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaClassLoader.java
+++ b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaClassLoader.java
@@ -22,7 +22,7 @@ import java.util.List;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.k.Constants;
 import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.Runtime;
 import org.apache.camel.k.Source;
 import org.apache.commons.lang3.StringUtils;
 
@@ -33,7 +33,7 @@ public class JavaClassLoader implements RoutesLoader {
     }
 
     @Override
-    public RouteBuilder load(RuntimeRegistry registry, Source source) throws Exception {
+    public RouteBuilder load(Runtime.Registry registry, Source source) throws Exception {
         String path = source.getLocation();
         path = StringUtils.removeStart(path, Constants.SCHEME_CLASSPATH);
         path = StringUtils.removeEnd(path, ".class");
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaScriptLoader.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaScriptLoader.java
index c251f44..5362b46 100644
--- a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaScriptLoader.java
+++ b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaScriptLoader.java
@@ -30,7 +30,7 @@ import javax.script.SimpleBindings;
 import org.apache.camel.CamelContext;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.Runtime;
 import org.apache.camel.k.Source;
 import org.apache.camel.k.jvm.dsl.Components;
 import org.apache.camel.k.support.URIResolver;
@@ -45,7 +45,7 @@ public class JavaScriptLoader implements RoutesLoader {
     }
 
     @Override
-    public RouteBuilder load(RuntimeRegistry registry, Source source) throws Exception {
+    public RouteBuilder load(Runtime.Registry registry, Source source) throws Exception {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaSourceLoader.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaSourceLoader.java
index 85adec2..8a333b7 100644
--- a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaSourceLoader.java
+++ b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/JavaSourceLoader.java
@@ -26,7 +26,7 @@ import org.apache.camel.CamelContext;
 import org.apache.camel.RoutesBuilder;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.Runtime;
 import org.apache.camel.k.Source;
 import org.apache.camel.k.support.URIResolver;
 import org.apache.camel.model.rest.RestConfigurationDefinition;
@@ -42,7 +42,7 @@ public class JavaSourceLoader implements RoutesLoader {
     }
 
     @Override
-    public RouteBuilder load(RuntimeRegistry registry, Source source) throws Exception {
+    public RouteBuilder load(Runtime.Registry registry, Source source) throws Exception {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
diff --git a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/XmlLoader.java b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/XmlLoader.java
index cd6e9ad..6880e3e 100644
--- a/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/XmlLoader.java
+++ b/runtime/camel-k-runtime-jvm/src/main/java/org/apache/camel/k/jvm/loader/XmlLoader.java
@@ -23,7 +23,7 @@ import javax.xml.bind.UnmarshalException;
 
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.Runtime;
 import org.apache.camel.k.Source;
 import org.apache.camel.k.support.URIResolver;
 import org.apache.camel.model.RoutesDefinition;
@@ -40,7 +40,7 @@ public class XmlLoader implements RoutesLoader {
     }
 
     @Override
-    public RouteBuilder load(RuntimeRegistry registry, Source source) throws Exception {
+    public RouteBuilder load(Runtime.Registry registry, Source source) throws Exception {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
diff --git a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/PropertiesTest.java b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/PropertiesTest.java
index 03de91b..cd82ef7 100644
--- a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/PropertiesTest.java
+++ b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/PropertiesTest.java
@@ -19,31 +19,36 @@ package org.apache.camel.k.jvm;
 import java.util.Properties;
 import java.util.concurrent.ThreadLocalRandom;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.component.seda.SedaComponent;
 import org.apache.camel.k.Constants;
 import org.apache.camel.k.ContextCustomizer;
+import org.apache.camel.k.Runtime;
+import org.apache.camel.k.listener.ContextConfigurer;
+import org.apache.camel.k.listener.ContextLifecycleConfigurer;
+import org.apache.camel.k.support.RuntimeSupport;
 import org.junit.jupiter.api.Test;
 
-import static org.apache.camel.k.jvm.RuntimeTestSupport.afterStart;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class PropertiesTest {
 
     @Test
     public void testLoadProperties() throws Exception {
-        Properties properties = ApplicationSupport.loadProperties("src/test/resources/conf.properties", "src/test/resources/conf.d");
+        Properties properties = RuntimeSupport.loadProperties("src/test/resources/conf.properties", "src/test/resources/conf.d");
 
-        Runtime runtime = new Runtime();
+        ApplicationRuntime runtime = new ApplicationRuntime();
         runtime.setProperties(properties);
-        runtime.setDuration(5);
-        runtime.addMainListener(new Application.CamelkJvmRuntimeConfigurer(runtime, new String[0]));
-        runtime.addMainListener(afterStart((main, context) -> {
+        runtime.addListener(new ContextConfigurer());
+        runtime.addListener(new ContextLifecycleConfigurer());
+        runtime.addListener(Runtime.Phase.Started, r -> {
+            CamelContext context = r.getContext();
             assertThat(context.resolvePropertyPlaceholders("{{root.key}}")).isEqualTo("root.value");
             assertThat(context.resolvePropertyPlaceholders("{{001.key}}")).isEqualTo("001.value");
             assertThat(context.resolvePropertyPlaceholders("{{002.key}}")).isEqualTo("002.value");
             assertThat(context.resolvePropertyPlaceholders("{{a.key}}")).isEqualTo("a.002");
-            main.stop();
-        }));
+            runtime.stop();
+        });
 
         runtime.run();
     }
@@ -53,16 +58,17 @@ public class PropertiesTest {
         System.setProperty("my.property", "my.value");
 
         try {
-            Runtime runtime = new Runtime();
+            ApplicationRuntime runtime = new ApplicationRuntime();
             runtime.setProperties(System.getProperties());
-            runtime.setDuration(5);
-            runtime.addMainListener(new Application.CamelkJvmRuntimeConfigurer(runtime, new String[0]));
-            runtime.addMainListener(afterStart((main, context) -> {
+            runtime.addListener(new ContextConfigurer());
+            runtime.addListener(new ContextLifecycleConfigurer());
+            runtime.addListener(Runtime.Phase.Started, r -> {
+                CamelContext context = r.getContext();
                 String value = context.resolvePropertyPlaceholders("{{my.property}}");
 
                 assertThat(value).isEqualTo("my.value");
-                main.stop();
-            }));
+                runtime.stop();
+            });
 
             runtime.run();
         } finally {
@@ -79,16 +85,17 @@ public class PropertiesTest {
         System.setProperty("camel.component.my-seda.queueSize", Integer.toString(queueSize2));
 
         try {
-            Runtime runtime = new Runtime();
+            ApplicationRuntime runtime = new ApplicationRuntime();
             runtime.setProperties(System.getProperties());
-            runtime.setDuration(5);
             runtime.getRegistry().bind("my-seda", new SedaComponent());
-            runtime.addMainListener(new Application.CamelkJvmRuntimeConfigurer(runtime, new String[0]));
-            runtime.addMainListener(afterStart((main, context) -> {
+            runtime.addListener(new ContextConfigurer());
+            runtime.addListener(new ContextLifecycleConfigurer());
+            runtime.addListener(Runtime.Phase.Started, r -> {
+                CamelContext context = r.getContext();
                 assertThat(context.getComponent("seda", true)).hasFieldOrPropertyWithValue("queueSize", queueSize1);
                 assertThat(context.getComponent("my-seda", true)).hasFieldOrPropertyWithValue("queueSize", queueSize2);
-                main.stop();
-            }));
+                runtime.stop();
+            });
 
             runtime.run();
         } finally {
@@ -103,15 +110,16 @@ public class PropertiesTest {
         System.setProperty("camel.context.loadTypeConverters", "false");
 
         try {
-            Runtime runtime = new Runtime();
+            ApplicationRuntime runtime = new ApplicationRuntime();
             runtime.setProperties(System.getProperties());
-            runtime.setDuration(5);
-            runtime.addMainListener(new Application.CamelkJvmRuntimeConfigurer(runtime, new String[0]));
-            runtime.addMainListener(afterStart((main, context) -> {
+            runtime.addListener(new ContextConfigurer());
+            runtime.addListener(new ContextLifecycleConfigurer());
+            runtime.addListener(Runtime.Phase.Started, r -> {
+                CamelContext context = r.getContext();
                 assertThat(context.isMessageHistory()).isFalse();
                 assertThat(context.isLoadTypeConverters()).isFalse();
-                main.stop();
-            }));
+                runtime.stop();
+            });
 
             runtime.run();
         } finally {
@@ -125,34 +133,36 @@ public class PropertiesTest {
         System.setProperty(Constants.PROPERTY_CAMEL_K_CUSTOMIZER, "test");
         System.setProperty("customizer.test.messageHistory", "false");
 
-        Runtime runtime = new Runtime();
+        ApplicationRuntime runtime = new ApplicationRuntime();
         runtime.setProperties(System.getProperties());
-        runtime.setDuration(5);
-        runtime.addMainListener(new Application.CamelkJvmRuntimeConfigurer(runtime, new String[0]));
-        runtime.addMainListener(afterStart((main, context) -> {
+        runtime.addListener(new ContextConfigurer());
+        runtime.addListener(new ContextLifecycleConfigurer());
+        runtime.addListener(Runtime.Phase.Started, r -> {
+            CamelContext context = r.getContext();
             assertThat(context.isMessageHistory()).isFalse();
             assertThat(context.isLoadTypeConverters()).isFalse();
-            main.stop();
-        }));
+            runtime.stop();
+        });
 
         runtime.run();
     }
 
     @Test
     public void testContextCustomizerFromRegistry() throws Exception {
-        Runtime runtime = new Runtime();
+        ApplicationRuntime runtime = new ApplicationRuntime();
         runtime.setProperties(System.getProperties());
-        runtime.setDuration(5);
+        runtime.addListener(new ContextConfigurer());
+        runtime.addListener(new ContextLifecycleConfigurer());
         runtime.getRegistry().bind("c1", (ContextCustomizer) (camelContext, registry) -> {
             camelContext.setMessageHistory(false);
             camelContext.setLoadTypeConverters(false);
         });
-        runtime.addMainListener(new Application.CamelkJvmRuntimeConfigurer(runtime, new String[0]));
-        runtime.addMainListener(afterStart((main, context) -> {
+        runtime.addListener(Runtime.Phase.Started, r -> {
+            CamelContext context = r.getContext();
             assertThat(context.isMessageHistory()).isFalse();
             assertThat(context.isLoadTypeConverters()).isFalse();
-            main.stop();
-        }));
+            runtime.stop();
+        });
 
         runtime.run();
     }
diff --git a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java
index 907f0d0..5a8eea8 100644
--- a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java
+++ b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RoutesLoadersTest.java
@@ -23,7 +23,7 @@ import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.k.InMemoryRegistry;
 import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.Runtime;
 import org.apache.camel.k.Source;
 import org.apache.camel.k.jvm.loader.JavaClassLoader;
 import org.apache.camel.k.jvm.loader.JavaScriptLoader;
@@ -44,7 +44,7 @@ public class RoutesLoadersTest {
     @Test
     public void testLoaderFromRegistry() throws Exception {
         RoutesLoader myLoader = new JavaClassLoader();
-        RuntimeRegistry registry = new InMemoryRegistry();
+        Runtime.Registry registry = new InMemoryRegistry();
         registry.bind("my-loader", myLoader);
 
         Source source = Source.create("classpath:" + MyRoutes.class.getName() + ".class");
diff --git a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTest.java b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTest.java
index 24cd457..b86bbb9 100644
--- a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTest.java
+++ b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTest.java
@@ -22,6 +22,11 @@ import java.util.List;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Route;
+import org.apache.camel.k.Runtime;
+import org.apache.camel.k.listener.ContextConfigurer;
+import org.apache.camel.k.listener.ContextLifecycleConfigurer;
+import org.apache.camel.k.listener.RoutesConfigurer;
+import org.apache.camel.k.support.PlatformStreamHandler;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.ResourceHelper;
 import org.apache.commons.io.IOUtils;
@@ -33,21 +38,24 @@ public class RuntimeTest {
 
     @Test
     void testLoadMultipleRoutes() throws Exception {
-        Runtime runtime = new Runtime();
-        runtime.load(new String[]{
-            "classpath:r1.js",
-            "classpath:r2.mytype?language=js",
-        });
+        ApplicationRuntime runtime = new ApplicationRuntime();
 
         try {
-            runtime.start();
+            runtime.addListener(new ContextConfigurer());
+            runtime.addListener(new ContextLifecycleConfigurer());
+            runtime.addListener(RoutesConfigurer.forRoutes("classpath:r1.js", "classpath:r2.mytype?language=js"));
+            runtime.addListener(Runtime.Phase.Started, r -> {
+                CamelContext context = r.getContext();
+                List<Route> routes = context.getRoutes();
 
-            CamelContext context = runtime.getCamelContext();
-            List<Route> routes = context.getRoutes();
+                assertThat(routes).hasSize(2);
+                assertThat(routes).anyMatch(p -> ObjectHelper.equal("r1", p.getId()));
+                assertThat(routes).anyMatch(p -> ObjectHelper.equal("r2", p.getId()));
 
-            assertThat(routes).hasSize(2);
-            assertThat(routes).anyMatch(p -> ObjectHelper.equal("r1", p.getId()));
-            assertThat(routes).anyMatch(p -> ObjectHelper.equal("r2", p.getId()));
+                runtime.stop();
+            });
+
+            runtime.run();
         } finally {
             runtime.stop();
         }
@@ -56,20 +64,21 @@ public class RuntimeTest {
 
     @Test
     void testLoadRouteAndRest() throws Exception {
-        Runtime runtime = new Runtime();
-        runtime.addMainListener(new Application.RoutesDumper());
-        runtime.load(new String[]{
-            "classpath:routes.xml",
-            "classpath:rests.xml",
-        });
-
+        ApplicationRuntime runtime = new ApplicationRuntime();
         try {
-            runtime.start();
+            runtime.addListener(new ContextConfigurer());
+            runtime.addListener(new ContextLifecycleConfigurer());
+            runtime.addListener(RoutesConfigurer.forRoutes("classpath:routes.xml", "classpath:rests.xml"));
+            runtime.addListener(Runtime.Phase.Started, r -> {
+                CamelContext context = r.getContext();
+
+                assertThat(context.getRouteDefinitions()).isNotEmpty();
+                assertThat(context.getRestDefinitions()).isNotEmpty();
 
-            CamelContext context = runtime.getCamelContext();
+                runtime.stop();
+            });
 
-            assertThat(context.getRouteDefinitions()).isNotEmpty();
-            assertThat(context.getRestDefinitions()).isNotEmpty();
+            runtime.run();
         } finally {
             runtime.stop();
         }
@@ -78,9 +87,9 @@ public class RuntimeTest {
 
     @Test
     void testLoadResource() throws Exception {
-        ApplicationSupport.configureStreamHandler();
+        PlatformStreamHandler.configure();
 
-        CamelContext context = new Runtime().getCamelContext();
+        CamelContext context = new ApplicationRuntime().getContext();
 
         try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(context, "platform:my-resource.txt")) {
             String content = IOUtils.toString(is, Charset.defaultCharset());
diff --git a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTestSupport.java b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTestSupport.java
deleted file mode 100644
index 0918705..0000000
--- a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/RuntimeTestSupport.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.camel.k.jvm;
-
-import org.apache.camel.CamelContext;
-import org.apache.camel.main.MainListener;
-import org.apache.camel.main.MainListenerSupport;
-import org.apache.camel.main.MainSupport;
-import org.apache.camel.util.function.ThrowingBiConsumer;
-
-public final class RuntimeTestSupport {
-    private RuntimeTestSupport() {
-    }
-
-    public static MainListener afterStart(ThrowingBiConsumer<MainSupport, CamelContext, Exception> consumer) {
-        return new MainListenerSupport() {
-            @Override
-            public void afterStart(MainSupport main) {
-                try {
-                    consumer.accept(main, main.getCamelContexts().get(0));
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        };
-    }
-
-}
diff --git a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/TestCustomizer.java b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/TestCustomizer.java
index 1cf0d75..7cd61ab 100644
--- a/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/TestCustomizer.java
+++ b/runtime/camel-k-runtime-jvm/src/test/java/org/apache/camel/k/jvm/TestCustomizer.java
@@ -18,7 +18,7 @@ package org.apache.camel.k.jvm;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.k.ContextCustomizer;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.Runtime;
 
 public class TestCustomizer implements ContextCustomizer {
     private boolean messageHistory = true;
@@ -32,7 +32,7 @@ public class TestCustomizer implements ContextCustomizer {
     }
 
     @Override
-    public void apply(CamelContext camelContext, RuntimeRegistry registry) {
+    public void apply(CamelContext camelContext, Runtime.Registry registry) {
         camelContext.setMessageHistory(messageHistory);
         camelContext.setLoadTypeConverters(false);
     }
diff --git a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt
index 8d5f0e9..38be3d2 100644
--- a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt
+++ b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/KotlinRoutesLoader.kt
@@ -18,18 +18,16 @@ package org.apache.camel.k.kotlin
 
 import org.apache.camel.builder.RouteBuilder
 import org.apache.camel.k.RoutesLoader
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime.Registry
 import org.apache.camel.k.Source
 import org.apache.camel.k.kotlin.dsl.IntegrationConfiguration
 import org.apache.camel.k.support.URIResolver
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import java.io.File
 import java.io.InputStreamReader
 import kotlin.script.experimental.api.*
 import kotlin.script.experimental.host.toScriptSource
 import kotlin.script.experimental.jvm.dependenciesFromClassloader
-import kotlin.script.experimental.jvm.javaHome
 import kotlin.script.experimental.jvm.jvm
 import kotlin.script.experimental.jvmhost.BasicJvmScriptEvaluator
 import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
@@ -45,7 +43,7 @@ class KotlinRoutesLoader : RoutesLoader {
     }
 
     @Throws(Exception::class)
-    override fun load(registry: RuntimeRegistry, source: Source): RouteBuilder? {
+    override fun load(registry: Registry, source: Source): RouteBuilder? {
         return object : RouteBuilder() {
             @Throws(Exception::class)
             override fun configure() {
diff --git a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/ContextConfiguration.kt b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/ContextConfiguration.kt
index 9dbf77f..ce3e8c1 100644
--- a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/ContextConfiguration.kt
+++ b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/ContextConfiguration.kt
@@ -17,9 +17,9 @@
 package org.apache.camel.k.kotlin.dsl
 
 import org.apache.camel.CamelContext
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime.Registry
 
-class ContextConfiguration (val registry: RuntimeRegistry, val context: CamelContext) {
+class ContextConfiguration (val registry: Registry, val context: CamelContext) {
 
     fun registry(block: RegistryConfiguration.() -> Unit): RegistryConfiguration {
         val delegate = RegistryConfiguration(registry)
diff --git a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationConfiguration.kt b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationConfiguration.kt
index 926ad3a..576e8d4 100644
--- a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationConfiguration.kt
+++ b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationConfiguration.kt
@@ -20,11 +20,11 @@ import org.apache.camel.Exchange
 import org.apache.camel.Predicate
 import org.apache.camel.Processor
 import org.apache.camel.builder.RouteBuilder
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime.Registry
 import org.apache.camel.model.RouteDefinition
 
 abstract class IntegrationConfiguration(
-        private val registry : RuntimeRegistry,
+        private val registry : Registry,
         private val builder : RouteBuilder) {
 
     fun rest(block: RestConfiguration.() -> Unit) {
diff --git a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/RegistryConfiguration.kt b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/RegistryConfiguration.kt
index b3abc95..1e1b091 100644
--- a/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/RegistryConfiguration.kt
+++ b/runtime/camel-k-runtime-kotlin/src/main/kotlin/org/apache/camel/k/kotlin/dsl/RegistryConfiguration.kt
@@ -16,9 +16,9 @@
  */
 package org.apache.camel.k.kotlin.dsl
 
-import org.apache.camel.k.RuntimeRegistry
+import org.apache.camel.k.Runtime.Registry
 
-class RegistryConfiguration(val registry: RuntimeRegistry) {
+class RegistryConfiguration(val registry: Registry) {
     fun bind(name: String, value: Any) {
         registry.bind(name, value)
     }
diff --git a/runtime/camel-k-runtime-kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt b/runtime/camel-k-runtime-kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt
index 4b6e85f..ca1f73c 100644
--- a/runtime/camel-k-runtime-kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt
+++ b/runtime/camel-k-runtime-kotlin/src/test/kotlin/org/apache/camel/k/kotlin/dsl/IntegrationTest.kt
@@ -3,8 +3,9 @@ package org.apache.camel.k.kotlin.dsl
 import org.apache.camel.Processor
 import org.apache.camel.component.log.LogComponent
 import org.apache.camel.component.seda.SedaComponent
-import org.apache.camel.main.MainListenerSupport
-import org.apache.camel.main.MainSupport
+import org.apache.camel.k.Runtime
+import org.apache.camel.k.jvm.ApplicationRuntime
+import org.apache.camel.k.listener.RoutesConfigurer
 import org.apache.camel.spi.ExchangeFormatter
 import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.Test
@@ -14,40 +15,28 @@ import java.util.concurrent.atomic.AtomicReference
 class IntegrationTest {
     @Test
     fun `load integration with rest`() {
-        var runtime = org.apache.camel.k.jvm.Runtime()
-        runtime.duration = 5
-        runtime.load(arrayOf("classpath:routes-with-rest.kts"))
-        runtime.addMainListener(object: MainListenerSupport() {
-            override fun afterStart(main: MainSupport) {
-                main.stop()
-            }
-        })
-
+        var runtime = ApplicationRuntime()
+        runtime.addListener(RoutesConfigurer.forRoutes("classpath:routes-with-rest.kts"))
+        runtime.addListener(Runtime.Phase.Started) { runtime.stop() }
         runtime.run()
 
-        assertThat(runtime.camelContext.restConfiguration.host).isEqualTo("my-host")
-        assertThat(runtime.camelContext.restConfiguration.port).isEqualTo(9192)
-        assertThat(runtime.camelContext.getRestConfiguration("undertow", false).host).isEqualTo("my-undertow-host")
-        assertThat(runtime.camelContext.getRestConfiguration("undertow", false).port).isEqualTo(9193)
-        assertThat(runtime.camelContext.restDefinitions.size).isEqualTo(1)
-        assertThat(runtime.camelContext.restDefinitions[0].path).isEqualTo("/my/path")
+        assertThat(runtime.context.restConfiguration.host).isEqualTo("my-host")
+        assertThat(runtime.context.restConfiguration.port).isEqualTo(9192)
+        assertThat(runtime.context.getRestConfiguration("undertow", false).host).isEqualTo("my-undertow-host")
+        assertThat(runtime.context.getRestConfiguration("undertow", false).port).isEqualTo(9193)
+        assertThat(runtime.context.restDefinitions.size).isEqualTo(1)
+        assertThat(runtime.context.restDefinitions[0].path).isEqualTo("/my/path")
     }
 
     @Test
     fun `load integration with binding`() {
-        var runtime = org.apache.camel.k.jvm.Runtime()
-        runtime.duration = 5
-        runtime.load(arrayOf("classpath:routes-with-bindings.kts"))
-        runtime.addMainListener(object: MainListenerSupport() {
-            override fun afterStart(main: MainSupport) {
-                main.stop()
-            }
-        })
-
+        var runtime = ApplicationRuntime()
+        runtime.addListener(RoutesConfigurer.forRoutes("classpath:routes-with-bindings.kts"))
+        runtime.addListener(Runtime.Phase.Started) { runtime.stop() }
         runtime.run()
 
-        assertThat(runtime.camelContext.registry.lookupByName("my-entry")).isEqualTo("myRegistryEntry1")
-        assertThat(runtime.camelContext.registry.lookupByName("my-proc")).isInstanceOf(Processor::class.java)
+        assertThat(runtime.context.registry.lookupByName("my-entry")).isEqualTo("myRegistryEntry1")
+        assertThat(runtime.context.registry.lookupByName("my-proc")).isInstanceOf(Processor::class.java)
     }
 
     @Test
@@ -58,24 +47,21 @@ class IntegrationTest {
         val mySedaConsumers = AtomicInteger()
         val format = AtomicReference<ExchangeFormatter>()
 
-        var runtime = org.apache.camel.k.jvm.Runtime()
-        runtime.duration = 5
-        runtime.load(arrayOf("classpath:routes-with-component-configuration.kts"))
-        runtime.addMainListener(object : MainListenerSupport() {
-            override fun afterStart(main: MainSupport) {
-                val seda = runtime.camelContext.getComponent("seda", SedaComponent::class.java)
-                val mySeda = runtime.camelContext.getComponent("mySeda", SedaComponent::class.java)
-                val log = runtime.camelContext.getComponent("log", LogComponent::class.java)
+        var runtime = ApplicationRuntime()
+        runtime.addListener(RoutesConfigurer.forRoutes("classpath:routes-with-component-configuration.kts"))
+        runtime.addListener(Runtime.Phase.Started) {
+            val seda = runtime.context.getComponent("seda", SedaComponent::class.java)
+            val mySeda = runtime.context.getComponent("mySeda", SedaComponent::class.java)
+            val log = runtime.context.getComponent("log", LogComponent::class.java)
 
-                sedaSize.set(seda!!.queueSize)
-                sedaConsumers.set(seda.concurrentConsumers)
-                mySedaSize.set(mySeda!!.queueSize)
-                mySedaConsumers.set(mySeda.concurrentConsumers)
-                format.set(log!!.exchangeFormatter)
+            sedaSize.set(seda!!.queueSize)
+            sedaConsumers.set(seda.concurrentConsumers)
+            mySedaSize.set(mySeda!!.queueSize)
+            mySedaConsumers.set(mySeda.concurrentConsumers)
+            format.set(log!!.exchangeFormatter)
 
-                main.stop()
-            }
-        })
+            runtime.stop()
+        }
 
         runtime.run()
 
diff --git a/runtime/camel-k-runtime-spring-boot/src/main/java/org/apache/camel/k/spring/boot/Application.java b/runtime/camel-k-runtime-spring-boot/src/main/java/org/apache/camel/k/spring/boot/Application.java
index bfc5b3b..c5e8e37 100644
--- a/runtime/camel-k-runtime-spring-boot/src/main/java/org/apache/camel/k/spring/boot/Application.java
+++ b/runtime/camel-k-runtime-spring-boot/src/main/java/org/apache/camel/k/spring/boot/Application.java
@@ -16,60 +16,41 @@
  */
 package org.apache.camel.k.spring.boot;
 
-import java.util.Map;
 import java.util.Properties;
-import java.util.Set;
 
-import org.apache.camel.CamelContext;
-import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.k.RuntimeRegistry;
-import org.apache.camel.k.Constants;
-import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.jvm.ApplicationSupport;
-import org.apache.camel.k.Source;
+import org.apache.camel.k.support.PlatformStreamHandler;
 import org.apache.camel.k.support.RuntimeSupport;
-import org.apache.camel.spi.Registry;
-import org.apache.camel.spring.boot.CamelContextConfiguration;
-import org.apache.camel.util.ObjectHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
 
 @SpringBootApplication
 public class Application {
-    private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
-
     static {
         //
         // Install a custom protocol handler to support discovering resources
         // from the platform i.e. in knative, resources are provided through
         // env var as it is not possible to mount config maps / secrets.
         //
-        ApplicationSupport.configureStreamHandler();
+        // TODO: we should remove as soon as we get a knative version that
+        //       includes https://github.com/knative/serving/pull/3061
+        //
+        PlatformStreamHandler.configure();
     }
 
     public static void main(String[] args) {
         SpringApplication.run(Application.class, args);
     }
 
-    // *****************************
-    //
-    // Beans
-    //
-    // *****************************
-
     @Bean
     public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
         // load properties using default behaviour
-        final Properties properties = ApplicationSupport.loadProperties();
+        final Properties properties = RuntimeSupport.loadProperties();
 
         // set spring boot specific properties
         properties.put("camel.springboot.main-run-controller", "true");
-        properties.put("camel.springboot.name", "camel-1");
+        properties.put("camel.springboot.name", "camel-k");
         properties.put("camel.springboot.streamCachingEnabled", "true");
         properties.put("camel.springboot.xml-routes", "false");
         properties.put("camel.springboot.xml-rests", "false");
@@ -81,89 +62,4 @@ public class Application {
 
         return configurer;
     }
-
-    @Bean
-    public CamelContextConfiguration routesConfiguration(ConfigurableApplicationContext applicationContext) throws Exception {
-        return new CamelContextConfiguration() {
-            @Override
-            public void beforeApplicationStart(CamelContext context) {
-                final RuntimeRegistry registry = new RuntimeApplicationContextRegistry(applicationContext, context.getRegistry());
-                final String routes = System.getenv(Constants.ENV_CAMEL_K_ROUTES);
-
-                if (ObjectHelper.isEmpty(routes)) {
-                    throw new IllegalStateException("No valid routes found in " + Constants.ENV_CAMEL_K_ROUTES + " environment variable");
-                }
-
-                // Programmatically apply the camel context.
-                //
-                // This is useful to configure services such as the ClusterService,
-                // RouteController, etc
-                //
-                RuntimeSupport.configureContext(context, registry);
-
-                try {
-                    for (String route : routes.split(",")) {
-                        final Source source = Source.create(route);
-                        final RoutesLoader loader = RuntimeSupport.loaderFor(context, source);
-                        final RouteBuilder builder = loader.load(registry, source);
-
-                        if (builder == null) {
-                            throw new IllegalStateException("Unable to load route from: " + route);
-                        }
-
-                        LOGGER.info("Routes: {}", route);
-
-                        context.addRoutes(builder);
-                    }
-                } catch (Exception e) {
-                    throw new IllegalStateException(e);
-                }
-            }
-
-            @Override
-            public void afterApplicationStart(CamelContext camelContext) {
-            }
-        };
-    }
-
-    // *****************************
-    //
-    // Registry
-    //
-    // *****************************
-
-    private static class RuntimeApplicationContextRegistry implements RuntimeRegistry {
-        private final ConfigurableApplicationContext applicationContext;
-        private final Registry registry;
-
-        public RuntimeApplicationContextRegistry(ConfigurableApplicationContext applicationContext, Registry registry) {
-            this.applicationContext = applicationContext;
-            this.registry = registry;
-        }
-
-        @Override
-        public Object lookupByName(String name) {
-            return registry.lookupByName(name);
-        }
-
-        @Override
-        public <T> T lookupByNameAndType(String name, Class<T> type) {
-            return registry.lookupByNameAndType(name, type);
-        }
-
-        @Override
-        public <T> Map<String, T> findByTypeWithName(Class<T> type) {
-            return registry.findByTypeWithName(type);
-        }
-
-        @Override
-        public <T> Set<T> findByType(Class<T> type) {
-            return registry.findByType(type);
-        }
-        @Override
-        public void bind(String name, Object bean) {
-            applicationContext.getBeanFactory().registerSingleton(name, bean);
-        }
-    }
-
 }
diff --git a/runtime/camel-k-runtime-spring-boot/src/main/java/org/apache/camel/k/spring/boot/ApplicationAutoConfiguration.java b/runtime/camel-k-runtime-spring-boot/src/main/java/org/apache/camel/k/spring/boot/ApplicationAutoConfiguration.java
new file mode 100644
index 0000000..7182add
--- /dev/null
+++ b/runtime/camel-k-runtime-spring-boot/src/main/java/org/apache/camel/k/spring/boot/ApplicationAutoConfiguration.java
@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.camel.k.spring.boot;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.k.Runtime;
+import org.apache.camel.k.listener.ContextConfigurer;
+import org.apache.camel.k.listener.RoutesConfigurer;
+import org.apache.camel.k.listener.RoutesDumper;
+import org.apache.camel.k.support.RuntimeSupport;
+import org.apache.camel.spring.boot.CamelContextConfiguration;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+
+@Configuration
+public class ApplicationAutoConfiguration {
+
+    @Bean
+    public CamelContextConfiguration routesConfiguration(ConfigurableApplicationContext applicationContext) throws Exception {
+        return new CamelContextConfigurer(applicationContext, Arrays.asList(new ContextConfigurer(), new RoutesConfigurer(), new RoutesDumper()));
+    }
+
+    // *****************************
+    //
+    //
+    //
+    // *****************************
+
+    private static class CamelContextConfigurer implements CamelContextConfiguration {
+        private final ConfigurableApplicationContext applicationContext;
+        private final List<Runtime.Listener> listeners;
+
+        public CamelContextConfigurer(ConfigurableApplicationContext applicationContext, List<Runtime.Listener> listeners) {
+            this.applicationContext = applicationContext;
+            this.listeners = listeners;
+        }
+
+        @Override
+        public void beforeApplicationStart(CamelContext context) {
+            final Runtime.Registry registry = new RuntimeApplicationContextRegistry(applicationContext, context.getRegistry());
+            final Runtime runtime = new Runtime() {
+                @Override
+                public CamelContext getContext() {
+                    return context;
+                }
+                @Override
+                public Registry getRegistry() {
+                    return registry;
+                }
+            };
+
+            listeners.forEach(l -> l.accept(Runtime.Phase.Starting, runtime));
+            listeners.forEach(l -> l.accept(Runtime.Phase.ConfigureContext, runtime));
+            listeners.forEach(l -> l.accept(Runtime.Phase.ConfigureRoutes, runtime));
+        }
+
+        @Override
+        public void afterApplicationStart(CamelContext context) {
+            final Runtime.Registry registry = new RuntimeApplicationContextRegistry(applicationContext, context.getRegistry());
+            final Runtime runtime = new Runtime() {
+                @Override
+                public CamelContext getContext() {
+                    return context;
+                }
+                @Override
+                public Registry getRegistry() {
+                    return registry;
+                }
+            };
+
+            listeners.forEach(l -> l.accept(Runtime.Phase.Started, runtime));
+
+        }
+    }
+
+    private static class RuntimeApplicationContextRegistry implements Runtime.Registry {
+        private final ConfigurableApplicationContext applicationContext;
+        private final org.apache.camel.spi.Registry registry;
+
+        public RuntimeApplicationContextRegistry(ConfigurableApplicationContext applicationContext, org.apache.camel.spi.Registry registry) {
+            this.applicationContext = applicationContext;
+            this.registry = registry;
+        }
+
+        @Override
+        public Object lookupByName(String name) {
+            return registry.lookupByName(name);
+        }
+
+        @Override
+        public <T> T lookupByNameAndType(String name, Class<T> type) {
+            return registry.lookupByNameAndType(name, type);
+        }
+
+        @Override
+        public <T> Map<String, T> findByTypeWithName(Class<T> type) {
+            return registry.findByTypeWithName(type);
+        }
+
+        @Override
+        public <T> Set<T> findByType(Class<T> type) {
+            return registry.findByType(type);
+        }
+        @Override
+        public void bind(String name, Object bean) {
+            applicationContext.getBeanFactory().registerSingleton(name, bean);
+        }
+    }
+
+}
diff --git a/runtime/camel-k-runtime-spring-boot/src/main/resources/META-INF/spring.factories b/runtime/camel-k-runtime-spring-boot/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..f6df31c
--- /dev/null
+++ b/runtime/camel-k-runtime-spring-boot/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+    org.apache.camel.k.spring.boot.ApplicationAutoConfiguration
\ No newline at end of file
diff --git a/runtime/camel-k-runtime-yaml/src/main/java/org/apache/camel/k/yaml/YamlFlowLoader.java b/runtime/camel-k-runtime-yaml/src/main/java/org/apache/camel/k/yaml/YamlFlowLoader.java
index 8b6b784..98ecd5a 100644
--- a/runtime/camel-k-runtime-yaml/src/main/java/org/apache/camel/k/yaml/YamlFlowLoader.java
+++ b/runtime/camel-k-runtime-yaml/src/main/java/org/apache/camel/k/yaml/YamlFlowLoader.java
@@ -28,7 +28,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.k.RoutesLoader;
-import org.apache.camel.k.RuntimeRegistry;
+import org.apache.camel.k.Runtime;
 import org.apache.camel.k.Source;
 import org.apache.camel.k.support.URIResolver;
 import org.apache.camel.k.yaml.model.Endpoint;
@@ -61,7 +61,7 @@ public class YamlFlowLoader implements RoutesLoader {
 
     @SuppressWarnings("uncheked")
     @Override
-    public RouteBuilder load(RuntimeRegistry registry, Source source) throws Exception {
+    public RouteBuilder load(Runtime.Registry registry, Source source) throws Exception {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {