Repository: tipsy/javalin
Pull request #537 (2019-04-06 17:18:33)

[core] Fix configuration (simpler)
```kotlin
val app = Javalin.create()

app.configure {
    it.addSinglePageRoot("/", "/public/html.html")
    it.addSinglePageRoot("/", "src/test/resources/public/html.html", Location.EXTERNAL)
    it.addStaticFiles("/public")
    it.addStaticFiles("src/test/resources/public", Location.EXTERNAL)
    it.contextPath = "/"
    it.defaultContentType = "text/plain"
    it.dynamicGzip = true
    it.enableWebjars()
    it.enableCorsForOrigins("*", "my-origin")
    it.prefer405over404 = false
    it.autogenerateEtags = true
    it.requestCacheSize = 8192L
    it.requestLogger { ctx, executionTimeMs ->  }
    it.accessManager { handler, ctx, permittedRoles ->  }
    it.sessionHandler { SessionHandler() }
    it.wsContextPath = "/"
    it.wsFactoryConfig {  }
    it.wsLogger {  }
    it.server {
        Server()
    }
}
```

tipsy: @ernestas2k tried a different approach here. Fixed a few of the issues so that it's more of a pure config file now.
ernestas2k: Great work! I like it a lot, config is now static holder and cors situation is handled explicitly in `configure(cfg) {}` which shows there's something dynamic going when applying configuration.




Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c19873ca..e1add818 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -81,7 +81,7 @@ and provides you with a http-client you can use to perform requests.
 You can also give `test` a Javalin instance:
 
 ```
-TestUtil.test(Javalin.create().dontIgnoreTrailingSlashes()) { app, http ->
+TestUtil.test(Javalin.create().configure { ... }) { app, http ->
 ```
 
 `test` will assign the instance a random port and stop it after the test is done.
diff --git a/src/main/java/io/javalin/Javalin.java b/src/main/java/io/javalin/Javalin.java
index 9b56a766..aea50a86 100644
--- a/src/main/java/io/javalin/Javalin.java
+++ b/src/main/java/io/javalin/Javalin.java
@@ -13,38 +13,28 @@ import io.javalin.core.EventAttacher;
 import io.javalin.core.EventManager;
 import io.javalin.core.HandlerMetaInfo;
 import io.javalin.core.HandlerType;
+import io.javalin.core.JavalinConfig;
 import io.javalin.core.JavalinEvent;
 import io.javalin.core.JavalinServer;
 import io.javalin.core.JavalinServlet;
-import io.javalin.core.JettyUtil;
-import io.javalin.core.util.CorsBeforeHandler;
-import io.javalin.core.util.CorsOptionsHandler;
+import io.javalin.core.JavalinWsServlet;
+import io.javalin.core.util.CorsUtil;
 import io.javalin.core.util.LogUtil;
 import io.javalin.core.util.RouteOverviewRenderer;
 import io.javalin.core.util.Util;
 import io.javalin.security.AccessManager;
-import io.javalin.security.CoreRoles;
 import io.javalin.security.Role;
 import io.javalin.serversentevent.SseClient;
 import io.javalin.serversentevent.SseHandler;
-import io.javalin.staticfiles.JettyResourceHandler;
-import io.javalin.staticfiles.Location;
-import io.javalin.staticfiles.StaticFileConfig;
-import io.javalin.websocket.JavalinWsServlet;
 import io.javalin.websocket.WsHandler;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.function.Supplier;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.session.SessionHandler;
-import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
 import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import static io.javalin.security.SecurityUtil.roles;
 
 public class Javalin {
 
@@ -54,16 +44,18 @@ public class Javalin {
 
     protected Map<Class<?>, Object> appAttributes = new HashMap<>();
 
-    protected JavalinServer server = new JavalinServer();
-    protected JavalinServlet servlet = new JavalinServlet(appAttributes);
-    protected JavalinWsServlet wsServlet = new JavalinWsServlet();
+    protected JavalinConfig config = new JavalinConfig();
+
+    protected JavalinServer server = new JavalinServer(config);
+    protected JavalinServlet servlet = new JavalinServlet(appAttributes, config);
+    protected JavalinWsServlet wsServlet = new JavalinWsServlet(config);
 
     protected EventManager eventManager = new EventManager(this);
     public EventAttacher on = eventManager.getEventAttacher();
 
     protected Javalin() {
         Util.logJavalinBanner(showJavalinBanner);
-        Util.logWarningIfNotStartedAfterOneSecond(this.server);
+        Util.logWarningIfNotStartedAfterOneSecond(server);
     }
 
     /**
@@ -87,7 +79,8 @@ public class Javalin {
      * @see Javalin#start()
      */
     public Javalin start(int port) {
-        return port(port).start();
+        server.setServerPort(port);
+        return start();
     }
 
     /**
@@ -112,7 +105,7 @@ public class Javalin {
             log.error("Failed to start Javalin");
             eventManager.fireEvent(JavalinEvent.SERVER_START_FAILED);
             if (e.getMessage() != null && e.getMessage().contains("Failed to bind to")) {
-                throw new RuntimeException("Port already in use. Make sure no other process is using port " + server.getJettyPort() + " and try again.", e);
+                throw new RuntimeException("Port already in use. Make sure no other process is using port " + server.getServerPort() + " and try again.", e);
             } else if (e.getMessage() != null && e.getMessage().contains("Permission denied")) {
                 throw new RuntimeException("Port 1-1023 require elevated privileges (process must be started by admin).", e);
             }
@@ -130,7 +123,7 @@ public class Javalin {
         eventManager.fireEvent(JavalinEvent.SERVER_STOPPING);
         log.info("Stopping Javalin ...");
         try {
-            server.getJettyServer().stop();
+            server.getServer().stop();
         } catch (Exception e) {
             log.error("Javalin failed to stop gracefully", e);
         }
@@ -139,138 +132,29 @@ public class Javalin {
         return this;
     }
 
-    /**
-     * Configure the instance to return 405 (Method Not Allowed) instead of 404 (Not Found) whenever a request method doesn't exists but there are handlers for other methods on the same requested path.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin prefer405over404() {
-        ensureActionIsPerformedBeforeServerStart("Telling Javalin to return 405 instead of 404 when applicable");
-        servlet.setPrefer405over404(true);
-        return this;
-    }
-
     /**
      * Configure instance to not show banner in logs.
      * The method must be called before {@link Javalin#start()}.
      */
     public Javalin disableStartupBanner() {
-        ensureActionIsPerformedBeforeServerStart("Telling Javalin to not show banner in logs");
         showJavalinBanner = false;
         return this;
     }
 
-    /**
-     * Configure instance to treat '/test/' and '/test' as different URLs.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin dontIgnoreTrailingSlashes() {
-        ensureActionIsPerformedBeforeServerStart("Telling Javalin to not ignore slashes");
-        servlet.getMatcher().setIgnoreTrailingSlashes(false);
-        return this;
-    }
-
-    /**
-     * Configure instance to use a custom jetty Server.
-     *
-     * @see <a href="https://javalin.io/documentation#custom-server">Documentation example</a>
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin server(@NotNull Supplier<Server> server) {
-        ensureActionIsPerformedBeforeServerStart("Setting a custom server");
-        this.server.setJettyServer(server.get());
-        return this;
-    }
-
-    /**
-     * Configure instance to use a custom jetty SessionHandler.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin sessionHandler(@NotNull Supplier<SessionHandler> sessionHandler) {
-        ensureActionIsPerformedBeforeServerStart("Setting a custom session handler");
-        server.setJettySessionHandler(JettyUtil.getSessionHandler(sessionHandler));
-        return this;
-    }
-
-    /**
-     * Configure instance to serve static files from path in classpath.
-     * The method can be called multiple times for different locations.
-     * The method must be called before {@link Javalin#start()}.
-     *
-     * @see <a href="https://javalin.io/documentation#static-files">Static files in docs</a>
-     */
-    public Javalin enableStaticFiles(@NotNull String classpathPath) {
-        return enableStaticFiles(classpathPath, Location.CLASSPATH);
-    }
-
-    /**
-     * Configure instance to serve static files from path in the specified location.
-     * The method can be called multiple times for different locations.
-     * The method must be called before {@link Javalin#start()}.
-     *
-     * @see <a href="https://javalin.io/documentation#static-files">Static files in docs</a>
-     */
-    public Javalin enableStaticFiles(@NotNull String path, @NotNull Location location) {
-        ensureActionIsPerformedBeforeServerStart("Enabling static files");
-        if (servlet.getResourceHandler() == null) {
-            servlet.setResourceHandler(new JettyResourceHandler());
-        }
-        servlet.getResourceHandler().addStaticFileConfig(new StaticFileConfig(path, location));
-        return this;
-    }
-
-    /**
-     * Configure instance to serve WebJars.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin enableWebJars() {
-        return enableStaticFiles("/webjars", Location.CLASSPATH);
-    }
-
-    /**
-     * Any request that would normally result in a 404 for the path and its subpaths
-     * instead results in a 200 with the file-content from path in classpath as response body.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin enableSinglePageMode(@NotNull String path, @NotNull String filePath) {
-        return enableSinglePageMode(path, filePath, Location.CLASSPATH);
-    }
-
-    /**
-     * Any request that would normally result in a 404 for the path and its subpaths
-     * instead results in a 200 with the file-content as response body.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin enableSinglePageMode(@NotNull String path, @NotNull String filePath, @NotNull Location location) {
-        ensureActionIsPerformedBeforeServerStart("Enabling single page mode");
-        servlet.getSinglePageHandler().add(path, filePath, location);
-        return this;
-    }
-
-    /**
-     * Configure instance to run on specified context path (common prefix).
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin contextPath(@NotNull String contextPath) {
-        ensureActionIsPerformedBeforeServerStart("Setting the context path");
-        server.setContextPath(Util.normalizeContextPath(contextPath));
-        return this;
-    }
-
     /**
      * Get which port instance is running on
      * Mostly useful if you start the instance with port(0) (random port)
      */
     public int port() {
-        return server.getJettyPort();
+        return server.getServerPort();
     }
 
     /**
-     * Configure instance to run on specified port.
-     * The method must be called before {@link Javalin#start()}.
+     * Set the server port
+     * Mostly useful if you start the instance with port(0) (random port)
      */
     public Javalin port(int port) {
-        ensureActionIsPerformedBeforeServerStart("Setting the port");
-        server.setJettyPort(port);
+        server.setServerPort(port);
         return this;
     }
 
@@ -279,63 +163,8 @@ public class Javalin {
      * The method must be called before {@link Javalin#start()}.
      */
     public Javalin enableDevLogging() {
-        ensureActionIsPerformedBeforeServerStart("Enabling debug-logging");
-        requestLogger(LogUtil::requestDevLogger);
-        wsLogger(LogUtil::wsDevLogger);
-        return this;
-    }
-
-    /**
-     * Configure instance use specified request-logger
-     * The method must be called before {@link Javalin#start()}.
-     * Will override the default logger of {@link Javalin#enableDevLogging()}.
-     */
-    public Javalin requestLogger(@NotNull RequestLogger requestLogger) {
-        ensureActionIsPerformedBeforeServerStart("Setting a custom request logger");
-        this.servlet.setRequestLogger(requestLogger);
-        return this;
-    }
-
-    /**
-     * Configure instance to accept cross origin requests for specified origins.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin enableCorsForOrigin(@NotNull String... origin) {
-        ensureActionIsPerformedBeforeServerStart("Enabling CORS");
-        if (origin.length == 0) throw new IllegalArgumentException("Origins cannot be empty.");
-        this.before("*", new CorsBeforeHandler(origin));
-        this.options("*", new CorsOptionsHandler(), roles(CoreRoles.NO_WRAP));
-        return this;
-    }
-
-    /**
-     * Configure instance to accept cross origin requests for all origins.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin enableCorsForAllOrigins() {
-        return enableCorsForOrigin("*");
-    }
-
-    /**
-     * Configure instance to not gzip dynamic responses.
-     * By default Javalin gzips all responses larger than 1500 bytes.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin disableDynamicGzip() {
-        ensureActionIsPerformedBeforeServerStart("Disabling dynamic GZIP");
-        servlet.setDynamicGzipEnabled(false);
-        return this;
-    }
-
-    /**
-     * Configure instance to automatically add ETags for GET requests.
-     * Static files already have ETags, this will calculate a checksum for
-     * dynamic GET responses and return 304 if the content has not changed.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin enableAutogeneratedEtags() {
-        ensureActionIsPerformedBeforeServerStart("Enabling autogenerated etags");
-        servlet.setAutogeneratedEtagsEnabled(true);
+        config.requestLogger(LogUtil::requestDevLogger);
+        config.wsLogger(LogUtil::wsDevLogger);
         return this;
     }
 
@@ -354,32 +183,10 @@ public class Javalin {
      * The method must be called before {@link Javalin#start()}.
      */
     public Javalin enableRouteOverview(@NotNull String path, @NotNull Set<Role> permittedRoles) {
-        if (this.servlet.getMatcher().hasEntries()) {
+        if (servlet.getMatcher().hasEntries()) {
             throw new IllegalStateException("The route-overview listens for 'onHandlerAdded' events. It must be enabled before adding handlers.");
         }
-        return this.get(path, new RouteOverviewRenderer(this), permittedRoles);
-    }
-
-    /**
-     * Configure instance to use the specified content-type as a default
-     * value for all responses. This can be overridden in any Handler.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin defaultContentType(@NotNull String contentType) {
-        ensureActionIsPerformedBeforeServerStart("Changing default content type");
-        servlet.setDefaultContentType(contentType);
-        return this;
-    }
-
-    /**
-     * Configure instance to stop caching requests larger than the specified body size.
-     * The default value is 4096 bytes.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin maxBodySizeForRequestCache(long bodySizeInBytes) {
-        ensureActionIsPerformedBeforeServerStart("Changing request cache body size");
-        servlet.setMaxRequestCacheBodySize(bodySizeInBytes);
-        return this;
+        return get(path, new RouteOverviewRenderer(this), permittedRoles);
     }
 
     /**
@@ -389,7 +196,6 @@ public class Javalin {
      * The method must be called before {@link Javalin#start()}.
      */
     public Javalin attribute(Class clazz, Object obj) {
-        ensureActionIsPerformedBeforeServerStart("Registering app attributes");
         appAttributes.put(clazz, obj);
         return this;
     }
@@ -405,34 +211,6 @@ public class Javalin {
         return (T) appAttributes.get(clazz);
     }
 
-    /**
-     * Configure instance to not cache any requests.
-     * If you call this method you will not be able to log request-bodies.
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin disableRequestCache() {
-        return maxBodySizeForRequestCache(0);
-    }
-
-    private void ensureActionIsPerformedBeforeServerStart(@NotNull String action) {
-        if (server.getStarted()) {
-            throw new IllegalStateException(action + " must be done before starting the server.");
-        }
-    }
-
-    /**
-     * Sets the access manager for the instance. Secured endpoints require one to be set.
-     * The method must be called before {@link Javalin#start()}.
-     *
-     * @see <a href="https://javalin.io/documentation#access-manager">Access manager in docs</a>
-     * @see AccessManager
-     */
-    public Javalin accessManager(@NotNull AccessManager accessManager) {
-        ensureActionIsPerformedBeforeServerStart("Setting an AccessManager");
-        this.servlet.setAccessManager(accessManager);
-        return this;
-    }
-
     /**
      * Adds an exception mapper to the instance.
      * Useful for turning exceptions into standardized errors/messages/pages
@@ -484,12 +262,11 @@ public class Javalin {
      * This is the method that all the verb-methods (get/post/put/etc) call.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin addHandler(@NotNull HandlerType handlerType, @NotNull String path, @NotNull Handler handler, @NotNull Set<Role> roles) {
         servlet.addHandler(handlerType, path, handler, roles);
-        eventManager.fireHandlerAddedEvent(new HandlerMetaInfo(handlerType, Util.prefixContextPath(server.getContextPath(), path), handler, roles));
+        eventManager.fireHandlerAddedEvent(new HandlerMetaInfo(handlerType, Util.prefixContextPath(servlet.getConfig().contextPath, path), handler, roles));
         return this;
     }
 
@@ -597,7 +374,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin get(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -609,7 +385,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin post(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -621,7 +396,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin put(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -633,7 +407,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin patch(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -645,7 +418,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin delete(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -657,7 +429,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin head(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -669,7 +440,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin trace(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -681,7 +451,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin connect(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -693,7 +462,6 @@ public class Javalin {
      * Requires an access manager to be set on the instance.
      *
      * @see AccessManager
-     * @see Javalin#accessManager(AccessManager)
      * @see <a href="https://javalin.io/documentation#handlers">Handlers in docs</a>
      */
     public Javalin options(@NotNull String path, @NotNull Handler handler, @NotNull Set<Role> permittedRoles) {
@@ -770,27 +538,20 @@ public class Javalin {
      */
     public Javalin ws(@NotNull String path, @NotNull Consumer<WsHandler> ws) {
         wsServlet.addHandler(path, ws);
-        eventManager.fireHandlerAddedEvent(new HandlerMetaInfo(HandlerType.WEBSOCKET, Util.prefixContextPath(server.getContextPath(), path), ws, new HashSet<>()));
+        eventManager.fireHandlerAddedEvent(new HandlerMetaInfo(HandlerType.WEBSOCKET, Util.prefixContextPath(wsServlet.getConfig().contextPath, path), ws, new HashSet<>()));
         return this;
     }
 
-    /**
-     * Configures a web socket handler to be called after every web socket event
-     * Will override the default logger of {@link Javalin#enableDevLogging()}.
-     */
-    public Javalin wsLogger(@NotNull Consumer<WsHandler> ws) {
-        ensureActionIsPerformedBeforeServerStart("Adding a WebSocket logger");
-        wsServlet.setWsLogger(ws);
-        return this;
-    }
+    // ********************************************************************************************
+    // Configuration
+    // ********************************************************************************************
 
-    /**
-     * Configure the WebSocketServletFactory of the instance
-     * The method must be called before {@link Javalin#start()}.
-     */
-    public Javalin wsFactoryConfig(@NotNull Consumer<WebSocketServletFactory> wsFactoryConfig) {
-        ensureActionIsPerformedBeforeServerStart("Setting a custom WebSocket factory config");
-        wsServlet.setWsFactoryConfig(wsFactoryConfig);
+    public Javalin configure(Consumer<JavalinConfig> config) {
+        if (this.server.getStarted()) throw new IllegalStateException("Cannot call 'configure()' after server start");
+        config.accept(this.config);
+        if (!this.config.corsOrigins.isEmpty()) {
+            CorsUtil.enableCorsForOrigin(this.servlet, this.config.corsOrigins);
+        }
         return this;
     }
 
diff --git a/src/main/java/io/javalin/core/JavalinConfig.java b/src/main/java/io/javalin/core/JavalinConfig.java
new file mode 100644
index 00000000..f929ae45
--- /dev/null
+++ b/src/main/java/io/javalin/core/JavalinConfig.java
@@ -0,0 +1,107 @@
+/*
+ * Javalin - https://javalin.io
+ * Copyright 2017 David Åse
+ * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
+ */
+
+package io.javalin.core;
+
+import io.javalin.RequestLogger;
+import io.javalin.core.util.SinglePageHandler;
+import io.javalin.security.AccessManager;
+import io.javalin.security.SecurityUtil;
+import io.javalin.staticfiles.JettyResourceHandler;
+import io.javalin.staticfiles.Location;
+import io.javalin.staticfiles.ResourceHandler;
+import io.javalin.staticfiles.StaticFileConfig;
+import io.javalin.websocket.WsHandler;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
+import org.jetbrains.annotations.NotNull;
+
+// @formatter:off
+public class JavalinConfig {
+
+    // ********************************************************************************************
+    // HTTP Servlet
+    // ********************************************************************************************
+
+    public boolean dynamicGzip = true;
+    public boolean autogenerateEtags = false;
+    public boolean prefer405over404 = false;
+    public String defaultContentType = "text/plain";
+    public String contextPath = "/";
+    public Long requestCacheSize = 4096L;
+    public List<String> corsOrigins = new ArrayList<>();
+    RequestLogger requestLogger;
+    ResourceHandler resourceHandler;
+    AccessManager accessManager = SecurityUtil::noopAccessManager;
+    SinglePageHandler singlePageHandler = new SinglePageHandler();
+    SessionHandler sessionHandler;
+
+    public void enableWebjars() { addStaticFiles("/webjars", Location.CLASSPATH); }
+    public void addStaticFiles(@NotNull String classpathPath) { addStaticFiles(classpathPath, Location.CLASSPATH); }
+    public void addStaticFiles(@NotNull String path, @NotNull Location location) {
+        if (resourceHandler == null) this.resourceHandler = new JettyResourceHandler();
+        this.resourceHandler.addStaticFileConfig(new StaticFileConfig(path, location));
+    }
+
+    public void addSinglePageRoot(@NotNull String path, @NotNull String filePath) { addSinglePageRoot(path, filePath, Location.CLASSPATH); }
+    public void addSinglePageRoot(@NotNull String path, @NotNull String filePath, @NotNull Location location) {
+        this.singlePageHandler.add(path, filePath, location);
+    }
+
+    public void enableCorsForAllOrigins() { enableCorsForOrigin("*"); }
+    public void enableCorsForOrigin(@NotNull String... origins) {
+        if (origins.length == 0) throw new IllegalArgumentException("Origins cannot be empty.");
+        this.corsOrigins = Arrays.asList(origins);
+    }
+
+    public void accessManager(@NotNull AccessManager accessManager) {
+        this.accessManager = accessManager;
+    }
+
+    public void requestLogger(@NotNull RequestLogger requestLogger) {
+        this.requestLogger = requestLogger;
+    }
+
+    public void sessionHandler(@NotNull Supplier<SessionHandler> sessionHandlerSupplier) {
+        this.sessionHandler = JettyUtil.getSessionHandler(sessionHandlerSupplier);
+    }
+
+    // ********************************************************************************************
+    // WebSocket Servlet
+    // ********************************************************************************************
+
+    Consumer<WebSocketServletFactory> wsFactoryConfig;
+    public String wsContextPath ="/";
+    public WsHandler wsLogger;
+
+    public void wsFactoryConfig(@NotNull Consumer<WebSocketServletFactory> wsFactoryConfig) {
+        this.wsFactoryConfig = wsFactoryConfig;
+    }
+
+    public void wsLogger(@NotNull Consumer<WsHandler> ws) {
+        WsHandler logger = new WsHandler();
+        ws.accept(logger);
+        this.wsLogger = logger;
+    }
+
+    // ********************************************************************************************
+    // Server
+    // ********************************************************************************************
+
+    Server server;
+
+    public void server(Supplier<Server> server) {
+        this.server = server.get();
+    }
+
+}
+// @formatter:on
diff --git a/src/main/java/io/javalin/core/JavalinServer.kt b/src/main/java/io/javalin/core/JavalinServer.kt
index 03b05491..9bfe418e 100644
--- a/src/main/java/io/javalin/core/JavalinServer.kt
+++ b/src/main/java/io/javalin/core/JavalinServer.kt
@@ -7,8 +7,6 @@
 package io.javalin.core
 
 import io.javalin.Javalin
-import io.javalin.core.util.Header
-import io.javalin.websocket.JavalinWsServlet
 import org.eclipse.jetty.server.*
 import org.eclipse.jetty.server.handler.HandlerCollection
 import org.eclipse.jetty.server.handler.HandlerList
@@ -16,15 +14,17 @@ import org.eclipse.jetty.server.handler.HandlerWrapper
 import org.eclipse.jetty.server.handler.StatisticsHandler
 import org.eclipse.jetty.server.session.SessionHandler
 import org.eclipse.jetty.servlet.ServletContextHandler
+import org.eclipse.jetty.servlet.ServletContextHandler.SESSIONS
 import org.eclipse.jetty.servlet.ServletHolder
 import org.eclipse.jetty.util.thread.QueuedThreadPool
-import java.io.ByteArrayInputStream
 import java.net.BindException
 import java.util.function.Supplier
 import javax.servlet.http.HttpServletRequest
 import javax.servlet.http.HttpServletResponse
 
-class JavalinServer {
+class JavalinServer(val config: JavalinConfig) {
+
+    var serverPort = 7000
 
     private val jettyDefaultLogger = org.eclipse.jetty.util.log.Log.getLog()
 
@@ -32,21 +32,17 @@ class JavalinServer {
         disableJettyLogger()
     }
 
-    lateinit var jettyServer: Server
-    lateinit var jettySessionHandler: SessionHandler
-
-    var jettyPort = 7000
-    var contextPath = "/"
+    var server = config.server
     var started = false
 
     @Throws(BindException::class)
     fun start(javalinServlet: JavalinServlet, javalinWsServlet: JavalinWsServlet) {
 
-        if (!::jettyServer.isInitialized) jettyServer = defaultServer()
-        if (!::jettySessionHandler.isInitialized) jettySessionHandler = defaultSessionHandler()
+        config.server = config.server ?: defaultServer()
+        javalinServlet.config.sessionHandler = javalinServlet.config.sessionHandler ?: defaultSessionHandler()
         val nullParent = null // javalin handlers are orphans
 
-        val httpHandler = object : ServletContextHandler(nullParent, contextPath, SESSIONS) {
+        val httpHandler = object : ServletContextHandler(nullParent, javalinServlet.config.contextPath, SESSIONS) {
             override fun doHandle(target: String, jettyRequest: Request, request: HttpServletRequest, response: HttpServletResponse) {
                 if (request.isWebSocket()) return // don't touch websocket requests
                 try {
@@ -59,42 +55,30 @@ class JavalinServer {
                 }
                 jettyRequest.isHandled = true
             }
-        }.apply {
-            this.sessionHandler = jettySessionHandler
-        }
+        }.apply { this.sessionHandler = javalinServlet.config.sessionHandler }
 
-        val webSocketHandler = ServletContextHandler(nullParent, contextPath).apply {
+        val webSocketHandler = ServletContextHandler(nullParent, javalinWsServlet.config.wsContextPath, SESSIONS).apply {
             addServlet(ServletHolder(javalinWsServlet), "/*")
         }
 
-        val notFoundHandler = object : SessionHandler() {
-            override fun doHandle(target: String, jettyRequest: Request, request: HttpServletRequest, response: HttpServletResponse) {
-                val msg = "Not found. Request is below context-path (context-path: '$contextPath')"
-                response.status = 404
-                ByteArrayInputStream(msg.toByteArray()).copyTo(response.outputStream)
-                response.outputStream.close()
-                Javalin.log.warn("Received a request below context-path (context-path: '$contextPath'). Returned 404.")
-            }
-        }
-
-        jettyServer.apply {
-            handler = attachJavalinHandlers(server.handler, HandlerList(httpHandler, webSocketHandler, notFoundHandler))
+        config.server.apply {
+            handler = attachJavalinHandlers(server.handler, HandlerList(httpHandler, webSocketHandler))
             connectors = connectors.takeIf { it.isNotEmpty() } ?: arrayOf(ServerConnector(server).apply {
-                this.port = jettyPort
+                this.port = serverPort
             })
         }.start()
 
-        jettyServer.connectors.filterIsInstance<ServerConnector>().forEach {
-            Javalin.log.info("Listening on ${it.protocol}://${it.host ?: "localhost"}:${it.localPort}$contextPath")
+        config.server.connectors.filterIsInstance<ServerConnector>().forEach {
+            Javalin.log.info("Listening on ${it.protocol}://${it.host ?: "localhost"}:${it.localPort}${javalinServlet.config.contextPath}")
         }
 
-        jettyServer.connectors.filter { it !is ServerConnector }.forEach {
+        config.server.connectors.filter { it !is ServerConnector }.forEach {
             Javalin.log.info("Binding to: $it")
         }
 
         reEnableJettyLogger()
         started = true
-        jettyPort = (jettyServer.connectors[0] as? ServerConnector)?.localPort ?: -1
+        serverPort = (config.server.connectors[0] as? ServerConnector)?.localPort ?: -1
     }
 
     private fun disableJettyLogger() = org.eclipse.jetty.util.log.Log.setLog(NoopLogger()) // disable logger before server creation
@@ -126,7 +110,6 @@ class JavalinServer {
         else -> throw IllegalStateException("Cannot insert Javalin handlers into a Handler that is not a HandlerCollection or HandlerWrapper")
     }
 
-    private fun HttpServletRequest.isWebSocket(): Boolean = this.getHeader(Header.SEC_WEBSOCKET_KEY) != null
 }
 
 object JettyUtil {
diff --git a/src/main/java/io/javalin/core/JavalinServlet.kt b/src/main/java/io/javalin/core/JavalinServlet.kt
index 213de230..9056cd48 100644
--- a/src/main/java/io/javalin/core/JavalinServlet.kt
+++ b/src/main/java/io/javalin/core/JavalinServlet.kt
@@ -6,36 +6,27 @@
 
 package io.javalin.core
 
-import io.javalin.*
+import io.javalin.Context
+import io.javalin.Handler
+import io.javalin.MethodNotAllowedResponse
+import io.javalin.NotFoundResponse
 import io.javalin.core.util.*
-import io.javalin.security.AccessManager
 import io.javalin.security.CoreRoles
 import io.javalin.security.Role
-import io.javalin.security.SecurityUtil
-import io.javalin.staticfiles.ResourceHandler
 import java.io.InputStream
 import java.util.zip.GZIPOutputStream
 import javax.servlet.http.HttpServletRequest
 import javax.servlet.http.HttpServletResponse
 
-class JavalinServlet(private val appAttributes: Map<Class<*>, Any>) {
+class JavalinServlet(private val appAttributes: Map<Class<*>, Any>, val config: JavalinConfig) {
 
     val matcher = PathMatcher()
     val exceptionMapper = ExceptionMapper()
     val errorMapper = ErrorMapper()
-    val singlePageHandler = SinglePageHandler()
-    var resourceHandler: ResourceHandler? = null
-    var accessManager = AccessManager { handler, ctx, permittedRoles -> SecurityUtil.noopAccessManager(handler, ctx, permittedRoles) }
-    var requestLogger: RequestLogger? = null
-    var dynamicGzipEnabled = true
-    var autogeneratedEtagsEnabled = false
-    var defaultContentType = "text/plain"
-    var maxRequestCacheBodySize = 4096L
-    var prefer405over404 = false
 
     fun service(servletRequest: HttpServletRequest, res: HttpServletResponse) {
 
-        val req = CachedRequestWrapper(servletRequest, maxRequestCacheBodySize) // cached for reading multiple times
+        val req = CachedRequestWrapper(servletRequest, config.requestCacheSize) // cached for reading multiple times
         val type = HandlerType.fromServletRequest(req)
         val requestUri = req.requestURI.removePrefix(req.contextPath)
         val ctx = Context(req, res, appAttributes)
@@ -54,11 +45,11 @@ class JavalinServlet(private val appAttributes: Map<Class<*>, Any>) {
                 return@tryWithExceptionMapper // return 200, there is a get handler
             }
             if (type == HandlerType.HEAD || type == HandlerType.GET) { // let Jetty check for static resources
-                if (resourceHandler?.handle(req, res, matcher.ignoreTrailingSlashes) == true) return@tryWithExceptionMapper
-                if (singlePageHandler.handle(ctx)) return@tryWithExceptionMapper
+                if (config.resourceHandler?.handle(req, res) == true) return@tryWithExceptionMapper
+                if (config.singlePageHandler.handle(ctx)) return@tryWithExceptionMapper
             }
             val availableHandlerTypes = MethodNotAllowedUtil.findAvailableHttpHandlerTypes(matcher, requestUri)
-            if (prefer405over404 && availableHandlerTypes.isNotEmpty()) {
+            if (config.prefer405over404 && availableHandlerTypes.isNotEmpty()) {
                 throw MethodNotAllowedResponse(details = MethodNotAllowedUtil.getAvailableHandlerTypes(ctx, availableHandlerTypes))
             }
             throw NotFoundResponse()
@@ -77,7 +68,7 @@ class JavalinServlet(private val appAttributes: Map<Class<*>, Any>) {
         fun writeResult(res: HttpServletResponse) { // can be sync or async
             if (res.isCommitted || ctx.resultStream() == null) return // nothing to write
             val resultStream = ctx.resultStream()!!
-            if (res.getHeader(Header.ETAG) != null || (autogeneratedEtagsEnabled && type == HandlerType.GET)) {
+            if (res.getHeader(Header.ETAG) != null || (config.autogenerateEtags && type == HandlerType.GET)) {
                 val serverEtag = res.getHeader(Header.ETAG) ?: Util.getChecksumAndReset(resultStream) // calculate if not set
                 res.setHeader(Header.ETAG, serverEtag)
                 if (serverEtag == req.getHeader(Header.IF_NONE_MATCH)) {
@@ -99,13 +90,13 @@ class JavalinServlet(private val appAttributes: Map<Class<*>, Any>) {
 
         LogUtil.setup(ctx, matcher) // start request lifecycle
         ctx.header(Header.SERVER, "Javalin")
-        ctx.contentType(defaultContentType)
+        ctx.contentType(config.defaultContentType)
         tryBeforeAndEndpointHandlers()
         if (ctx.resultFuture() == null) { // finish request synchronously
             tryErrorHandlers()
             tryAfterHandlers()
             writeResult(res)
-            requestLogger?.handle(ctx, LogUtil.executionTimeMs(ctx))
+            config.requestLogger?.handle(ctx, LogUtil.executionTimeMs(ctx))
             return // sync lifecycle complete
         } else { // finish request asynchronously
             val asyncContext = req.startAsync()
@@ -122,7 +113,7 @@ class JavalinServlet(private val appAttributes: Map<Class<*>, Any>) {
                 tryErrorHandlers()
                 tryAfterHandlers()
                 writeResult(asyncContext.response as HttpServletResponse)
-                requestLogger?.handle(ctx, LogUtil.executionTimeMs(ctx))
+                config.requestLogger?.handle(ctx, LogUtil.executionTimeMs(ctx))
                 asyncContext.complete() // async lifecycle complete
             }
         }
@@ -130,13 +121,13 @@ class JavalinServlet(private val appAttributes: Map<Class<*>, Any>) {
 
     private fun hasGetHandlerMapped(requestUri: String) = matcher.findEntries(HandlerType.GET, requestUri).isNotEmpty()
 
-    private fun gzipShouldBeDone(ctx: Context) = dynamicGzipEnabled
+    private fun gzipShouldBeDone(ctx: Context) = config.dynamicGzip
             && ctx.resultStream()?.available() ?: 0 > 1500 // mtu is apparently ~1500 bytes
             && (ctx.header(Header.ACCEPT_ENCODING) ?: "").contains("gzip", ignoreCase = true)
 
     fun addHandler(handlerType: HandlerType, path: String, handler: Handler, roles: Set<Role>) {
         val shouldWrap = handlerType.isHttpMethod() && !roles.contains(CoreRoles.NO_WRAP)
-        val protectedHandler = if (shouldWrap) Handler { ctx -> accessManager.manage(handler, ctx, roles) } else handler
+        val protectedHandler = if (shouldWrap) Handler { ctx -> config.accessManager.manage(handler, ctx, roles) } else handler
         matcher.add(HandlerEntry(handlerType, path, protectedHandler, handler))
     }
 }
diff --git a/src/main/java/io/javalin/core/JavalinWsServlet.kt b/src/main/java/io/javalin/core/JavalinWsServlet.kt
new file mode 100644
index 00000000..78b58ca1
--- /dev/null
+++ b/src/main/java/io/javalin/core/JavalinWsServlet.kt
@@ -0,0 +1,46 @@
+/*
+ * Javalin - https://javalin.io
+ * Copyright 2017 David Åse
+ * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
+ */
+
+package io.javalin.core
+
+import io.javalin.Javalin
+import io.javalin.core.util.Header
+import io.javalin.core.util.Util
+import io.javalin.websocket.WsEntry
+import io.javalin.websocket.WsHandler
+import io.javalin.websocket.WsPathMatcher
+import org.eclipse.jetty.websocket.servlet.WebSocketCreator
+import org.eclipse.jetty.websocket.servlet.WebSocketServlet
+import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory
+import java.util.function.Consumer
+import javax.servlet.ServletRequest
+import javax.servlet.ServletResponse
+import javax.servlet.http.HttpServletRequest
+import javax.servlet.http.HttpServletResponse
+
+class JavalinWsServlet(val config: JavalinConfig) : WebSocketServlet() {
+
+    var wsPathMatcher = WsPathMatcher(config)
+
+    override fun configure(factory: WebSocketServletFactory) {
+        config.wsFactoryConfig?.accept(factory)
+        factory.creator = WebSocketCreator { req, res ->
+            wsPathMatcher.findEntry(req) ?: res.sendError(404, "WebSocket handler not found")
+            wsPathMatcher // this is a long-lived object handling multiple connections
+        }
+    }
+
+    override fun service(req: ServletRequest?, res: ServletResponse?) = if ((req as HttpServletRequest).isWebSocket()) {
+        super.service(req, res) // if websocket, handle normally
+    } else { // if not websocket (and not handled by http-handler), this request is below the context path
+        Util.writeResponse(res as HttpServletResponse, "Not found. Request is below context-path", 404)
+        Javalin.log.warn("Received a request below context-path. Returned 404.")
+    }
+
+    fun addHandler(path: String, ws: Consumer<WsHandler>) = wsPathMatcher.add(WsEntry(path, WsHandler().apply { ws.accept(this) }))
+}
+
+fun HttpServletRequest.isWebSocket(): Boolean = this.getHeader(Header.SEC_WEBSOCKET_KEY) != null
diff --git a/src/main/java/io/javalin/core/PathMatcher.kt b/src/main/java/io/javalin/core/PathMatcher.kt
index 9e459e28..03a9b1d8 100644
--- a/src/main/java/io/javalin/core/PathMatcher.kt
+++ b/src/main/java/io/javalin/core/PathMatcher.kt
@@ -44,7 +44,7 @@ class PathParser(
 
 }
 
-class PathMatcher(var ignoreTrailingSlashes: Boolean = true) {
+class PathMatcher {
 
     private val handlerEntries = HandlerType.values().associateTo(EnumMap<HandlerType, ArrayList<HandlerEntry>>(HandlerType::class.java)) {
         it to arrayListOf()
@@ -63,7 +63,6 @@ class PathMatcher(var ignoreTrailingSlashes: Boolean = true) {
     private fun match(entry: HandlerEntry, requestPath: String): Boolean = when {
         entry.path == "*" -> true
         entry.path == requestPath -> true
-        !this.ignoreTrailingSlashes && slashMismatch(entry.path, requestPath) -> false
         else -> entry.matches(requestPath)
     }
 
diff --git a/src/main/java/io/javalin/core/util/CorsUtil.kt b/src/main/java/io/javalin/core/util/CorsUtil.kt
index 50c5e677..bfa922d4 100644
--- a/src/main/java/io/javalin/core/util/CorsUtil.kt
+++ b/src/main/java/io/javalin/core/util/CorsUtil.kt
@@ -8,19 +8,20 @@ package io.javalin.core.util
 
 import io.javalin.Context
 import io.javalin.Handler
+import io.javalin.core.HandlerType
+import io.javalin.core.JavalinServlet
+import io.javalin.security.CoreRoles
 
-class CorsOptionsHandler : Handler {
-    override fun handle(ctx: Context) {
-        ctx.header(Header.ACCESS_CONTROL_REQUEST_HEADERS)?.let {
-            ctx.header(Header.ACCESS_CONTROL_ALLOW_HEADERS, it)
-        }
-        ctx.header(Header.ACCESS_CONTROL_REQUEST_METHOD)?.let {
-            ctx.header(Header.ACCESS_CONTROL_ALLOW_METHODS, it)
-        }
+object CorsUtil {
+    @JvmStatic
+    fun enableCorsForOrigin(servlet: JavalinServlet, origins: List<String>) {
+        servlet.addHandler(HandlerType.BEFORE, "*", CorsBeforeHandler(origins), setOf())
+        servlet.addHandler(HandlerType.OPTIONS, "*", CorsOptionsHandler(), setOf(CoreRoles.NO_WRAP))
     }
 }
 
-class CorsBeforeHandler(private val origins: Array<String>) : Handler {
+
+class CorsBeforeHandler(private val origins: List<String>) : Handler {
     override fun handle(ctx: Context) {
         (ctx.header(Header.ORIGIN) ?: ctx.header(Header.REFERER))?.let { header ->
             origins.map { it.removeSuffix("/") }.firstOrNull { it == "*" || header.startsWith(it) }?.let {
@@ -30,3 +31,14 @@ class CorsBeforeHandler(private val origins: Array<String>) : Handler {
         }
     }
 }
+
+class CorsOptionsHandler : Handler {
+    override fun handle(ctx: Context) {
+        ctx.header(Header.ACCESS_CONTROL_REQUEST_HEADERS)?.let {
+            ctx.header(Header.ACCESS_CONTROL_ALLOW_HEADERS, it)
+        }
+        ctx.header(Header.ACCESS_CONTROL_REQUEST_METHOD)?.let {
+            ctx.header(Header.ACCESS_CONTROL_ALLOW_METHODS, it)
+        }
+    }
+}
diff --git a/src/main/java/io/javalin/core/util/Util.kt b/src/main/java/io/javalin/core/util/Util.kt
index fcc4c4a7..5fdbbe07 100644
--- a/src/main/java/io/javalin/core/util/Util.kt
+++ b/src/main/java/io/javalin/core/util/Util.kt
@@ -9,6 +9,7 @@ package io.javalin.core.util
 import io.javalin.InternalServerErrorResponse
 import io.javalin.Javalin
 import io.javalin.core.JavalinServer
+import java.io.ByteArrayInputStream
 import java.io.ByteArrayOutputStream
 import java.io.File
 import java.io.InputStream
@@ -16,6 +17,7 @@ import java.net.URL
 import java.util.*
 import java.util.zip.Adler32
 import java.util.zip.CheckedInputStream
+import javax.servlet.http.HttpServletResponse
 
 object Util {
 
@@ -110,6 +112,11 @@ object Util {
         return false
     }
 
+    fun writeResponse(response: HttpServletResponse, responseBody: String, status: Int) {
+        response.status = status
+        ByteArrayInputStream(responseBody.toByteArray()).copyTo(response.outputStream)
+        response.outputStream.close()
+    }
 
     @JvmStatic
     fun logWarningIfNotStartedAfterOneSecond(server: JavalinServer) = Thread {
diff --git a/src/main/java/io/javalin/staticfiles/JettyResourceHandler.kt b/src/main/java/io/javalin/staticfiles/JettyResourceHandler.kt
index 07387435..4c6714e0 100644
--- a/src/main/java/io/javalin/staticfiles/JettyResourceHandler.kt
+++ b/src/main/java/io/javalin/staticfiles/JettyResourceHandler.kt
@@ -51,14 +51,14 @@ class JettyResourceHandler : io.javalin.staticfiles.ResourceHandler {
         return staticFileConfig.path
     }
 
-    override fun handle(httpRequest: HttpServletRequest, httpResponse: HttpServletResponse, ignoreTrailingSlashes: Boolean): Boolean {
+    override fun handle(httpRequest: HttpServletRequest, httpResponse: HttpServletResponse): Boolean {
         val target = httpRequest.getAttribute("jetty-target") as String
         val baseRequest = httpRequest.getAttribute("jetty-request") as Request
         for (gzipHandler in handlers) {
             try {
                 val resourceHandler = (gzipHandler.handler as ResourceHandler)
                 val resource = resourceHandler.getResource(target)
-                if (resource.isFile() || resource.isDirectoryWithWelcomeFile(resourceHandler, target, ignoreTrailingSlashes)) {
+                if (resource.isFile() || resource.isDirectoryWithWelcomeFile(resourceHandler, target)) {
                     val maxAge = if (target.startsWith("/immutable/") || resourceHandler is WebjarHandler) 31622400 else 0
                     httpResponse.setHeader(Header.CACHE_CONTROL, "max-age=$maxAge")
                     gzipHandler.handle(target, baseRequest, httpRequest, httpResponse)
@@ -74,9 +74,7 @@ class JettyResourceHandler : io.javalin.staticfiles.ResourceHandler {
 
     private fun Resource?.isFile() = this != null && this.exists() && !this.isDirectory
 
-    private fun Resource?.isDirectoryWithWelcomeFile(handler: ResourceHandler, target: String, ignoreTrailingSlashes: Boolean) =
-            this != null && this.isDirectory && handler.getResource(welcomeFilePath(target, ignoreTrailingSlashes))?.exists() == true
-
-    private fun welcomeFilePath(target: String, ignoreTrailingSlashes: Boolean) = if (!target.endsWith("/") && ignoreTrailingSlashes) "$target/index.html" else "${target}index.html"
+    private fun Resource?.isDirectoryWithWelcomeFile(handler: ResourceHandler, target: String) =
+            this != null && this.isDirectory && handler.getResource("${target.removeSuffix("/")}/index.html")?.exists() == true
 
 }
diff --git a/src/main/java/io/javalin/staticfiles/ResourceHandler.kt b/src/main/java/io/javalin/staticfiles/ResourceHandler.kt
index 11ec7710..6f3ab13d 100644
--- a/src/main/java/io/javalin/staticfiles/ResourceHandler.kt
+++ b/src/main/java/io/javalin/staticfiles/ResourceHandler.kt
@@ -7,6 +7,6 @@ enum class Location { CLASSPATH, EXTERNAL; }
 data class StaticFileConfig(val path: String, val location: Location)
 
 interface ResourceHandler {
-    fun handle(httpRequest: HttpServletRequest, httpResponse: HttpServletResponse, ignoreTrailingSlashes: Boolean): Boolean
+    fun handle(httpRequest: HttpServletRequest, httpResponse: HttpServletResponse): Boolean
     fun addStaticFileConfig(config: StaticFileConfig)
 }
diff --git a/src/main/java/io/javalin/websocket/JavalinWsServlet.kt b/src/main/java/io/javalin/websocket/JavalinWsServlet.kt
deleted file mode 100644
index b4eaaaab..00000000
--- a/src/main/java/io/javalin/websocket/JavalinWsServlet.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Javalin - https://javalin.io
- * Copyright 2017 David Åse
- * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
- */
-
-package io.javalin.websocket
-
-import org.eclipse.jetty.websocket.servlet.WebSocketCreator
-import org.eclipse.jetty.websocket.servlet.WebSocketServlet
-import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory
-import java.util.function.Consumer
-
-class JavalinWsServlet : WebSocketServlet() {
-
-    var wsFactoryConfig: Consumer<WebSocketServletFactory>? = null
-    var wsPathMatcher = WsPathMatcher()
-
-    override fun configure(factory: WebSocketServletFactory) {
-        wsFactoryConfig?.accept(factory)
-        factory.creator = WebSocketCreator { req, res ->
-            wsPathMatcher.findEntry(req) ?: res.sendError(404, "WebSocket handler not found")
-            wsPathMatcher // this is a long-lived object handling multiple connections
-        }
-    }
-
-    fun addHandler(path: String, ws: Consumer<WsHandler>) =
-            wsPathMatcher.add(WsEntry(path, WsHandler().apply { ws.accept(this) }))
-
-    fun setWsLogger(ws: Consumer<WsHandler>) {
-        wsPathMatcher.wsLogger = WsHandler().apply { ws.accept(this) }
-    }
-
-}
diff --git a/src/main/java/io/javalin/websocket/WsPathMatcher.kt b/src/main/java/io/javalin/websocket/WsPathMatcher.kt
index a9020a6e..d7da80a5 100644
--- a/src/main/java/io/javalin/websocket/WsPathMatcher.kt
+++ b/src/main/java/io/javalin/websocket/WsPathMatcher.kt
@@ -6,6 +6,7 @@
 
 package io.javalin.websocket
 
+import io.javalin.core.JavalinConfig
 import io.javalin.core.PathParser
 import org.eclipse.jetty.websocket.api.Session
 import org.eclipse.jetty.websocket.api.UpgradeRequest
@@ -25,10 +26,9 @@ data class WsEntry(val path: String, val handler: WsHandler) {
  * Session IDs are generated and tracked here, and path-parameters are cached for performance.
  */
 @WebSocket
-class WsPathMatcher {
+class WsPathMatcher(val config: JavalinConfig) {
 
     val wsEntries = mutableListOf<WsEntry>()
-    var wsLogger: WsHandler? = null
     private val sessionIds = ConcurrentHashMap<Session, String>()
     private val sessionPathParams = ConcurrentHashMap<Session, Map<String, String>>()
 
@@ -41,7 +41,7 @@ class WsPathMatcher {
         findEntry(session)?.let {
             val ctx = WsConnectContext(sessionIds[session]!!, session, sessionPathParams[session]!!, it.path)
             it.handler.connectHandler?.handleConnect(ctx)
-            wsLogger?.connectHandler?.handleConnect(ctx)
+            config.wsLogger?.connectHandler?.handleConnect(ctx)
         }
 
     }
@@ -51,7 +51,7 @@ class WsPathMatcher {
         findEntry(session)?.let {
             val ctx = WsMessageContext(sessionIds[session]!!, session, sessionPathParams[session]!!, it.path, message)
             it.handler.messageHandler?.handleMessage(ctx)
-            wsLogger?.messageHandler?.handleMessage(ctx)
+            config.wsLogger?.messageHandler?.handleMessage(ctx)
         }
     }
 
@@ -60,7 +60,7 @@ class WsPathMatcher {
         findEntry(session)?.let {
             val ctx = WsBinaryMessageContext(sessionIds[session]!!, session, sessionPathParams[session]!!, it.path, buffer.toTypedArray(), offset, length)
             it.handler.binaryMessageHandler?.handleBinaryMessage(ctx)
-            wsLogger?.binaryMessageHandler?.handleBinaryMessage(ctx)
+            config.wsLogger?.binaryMessageHandler?.handleBinaryMessage(ctx)
         }
     }
 
@@ -69,7 +69,7 @@ class WsPathMatcher {
         findEntry(session)?.let {
             val ctx = WsErrorContext(sessionIds[session]!!, session, sessionPathParams[session]!!, it.path, throwable)
             it.handler.errorHandler?.handleError(ctx)
-            wsLogger?.errorHandler?.handleError(ctx)
+            config.wsLogger?.errorHandler?.handleError(ctx)
         }
     }
 
@@ -78,7 +78,7 @@ class WsPathMatcher {
         findEntry(session)?.let {
             val ctx = WsCloseContext(sessionIds[session]!!, session, sessionPathParams[session]!!, it.path, statusCode, reason)
             it.handler.closeHandler?.handleClose(ctx)
-            wsLogger?.closeHandler?.handleClose(ctx)
+            config.wsLogger?.closeHandler?.handleClose(ctx)
         }
         destroy(session)
     }


Test patch:
diff --git a/src/test/java/io/javalin/TestAccessManager.kt b/src/test/java/io/javalin/TestAccessManager.kt
index 79a4e2c8..bba670eb 100644
--- a/src/test/java/io/javalin/TestAccessManager.kt
+++ b/src/test/java/io/javalin/TestAccessManager.kt
@@ -11,6 +11,7 @@ import com.mashape.unirest.http.Unirest
 import io.javalin.TestAccessManager.MyRoles.ROLE_ONE
 import io.javalin.TestAccessManager.MyRoles.ROLE_TWO
 import io.javalin.apibuilder.ApiBuilder.get
+import io.javalin.security.AccessManager
 import io.javalin.security.Role
 import io.javalin.security.SecurityUtil.roles
 import io.javalin.util.TestUtil
@@ -21,12 +22,14 @@ class TestAccessManager {
 
     enum class MyRoles : Role { ROLE_ONE, ROLE_TWO, ROLE_THREE }
 
-    private val managedApp = Javalin.create().accessManager { handler, ctx, permittedRoles ->
-        val userRole = ctx.queryParam("role")
-        if (userRole != null && permittedRoles.contains(MyRoles.valueOf(userRole))) {
-            handler.handle(ctx)
-        } else {
-            ctx.status(401).result("Unauthorized")
+    private val managedApp = Javalin.create().configure { config ->
+        config.accessManager { handler, ctx, permittedRoles ->
+            val userRole = ctx.queryParam("role")
+            if (userRole != null && permittedRoles.contains(MyRoles.valueOf(userRole))) {
+                handler.handle(ctx)
+            } else {
+                ctx.status(401).result("Unauthorized")
+            }
         }
     }
 
diff --git a/src/test/java/io/javalin/TestApiBuilder.kt b/src/test/java/io/javalin/TestApiBuilder.kt
index 22b678b0..f4cd72cc 100644
--- a/src/test/java/io/javalin/TestApiBuilder.kt
+++ b/src/test/java/io/javalin/TestApiBuilder.kt
@@ -110,8 +110,8 @@ class TestApiBuilder {
 
     @Test
     fun `ApiBuilder works with two services at once`() {
-        val app1 = Javalin.create().port(0).start()
-        val app2 = Javalin.create().port(0).start()
+        val app1 = Javalin.create().start(0)
+        val app2 = Javalin.create().start(0)
         app1.routes { get("/hello-1") { ctx -> ctx.result("Hello-1") } }
         app1.routes { get("/hello-2") { ctx -> ctx.result("Hello-2") } }
         app2.routes { get("/hello-1") { ctx -> ctx.result("Hello-1") } }
diff --git a/src/test/java/io/javalin/TestConfiguration.kt b/src/test/java/io/javalin/TestConfiguration.kt
new file mode 100644
index 00000000..d210ccd9
--- /dev/null
+++ b/src/test/java/io/javalin/TestConfiguration.kt
@@ -0,0 +1,50 @@
+/*
+ * Javalin - https://javalin.io
+ * Copyright 2017 David Åse
+ * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
+ */
+
+package io.javalin
+
+import io.javalin.staticfiles.Location
+import io.javalin.util.TestUtil
+import org.eclipse.jetty.server.Server
+import org.eclipse.jetty.server.session.SessionHandler
+import org.junit.Test
+
+class TestConfiguration {
+
+    @Test(expected = IllegalStateException::class)
+    fun `Javalin#servlet() throws if used after Javalin#configure()`() = TestUtil.test { app, http ->
+        app.configure { }
+    }
+
+    @Test
+    fun `test all config options`() {
+        val app = Javalin.create()
+        app.configure {
+            it.addSinglePageRoot("/", "/public/html.html")
+            it.addSinglePageRoot("/", "src/test/resources/public/html.html", Location.EXTERNAL)
+            it.addStaticFiles("/public")
+            it.addStaticFiles("src/test/resources/public", Location.EXTERNAL)
+            it.contextPath = "/"
+            it.defaultContentType = "text/plain"
+            it.dynamicGzip = true
+            it.enableWebjars()
+            it.enableCorsForOrigin("*", "my-origin")
+            it.prefer405over404 = false
+            it.autogenerateEtags = true
+            it.requestCacheSize = 8192L
+            it.requestLogger { ctx, executionTimeMs ->  }
+            it.accessManager { handler, ctx, permittedRoles ->  }
+            it.sessionHandler { SessionHandler() }
+            it.wsContextPath = "/"
+            it.wsFactoryConfig {  }
+            it.wsLogger {  }
+            it.server {
+                Server()
+            }
+        }
+    }
+
+}
diff --git a/src/test/java/io/javalin/TestContextPath.kt b/src/test/java/io/javalin/TestContextPath.kt
index 1b1d831e..3907df4c 100644
--- a/src/test/java/io/javalin/TestContextPath.kt
+++ b/src/test/java/io/javalin/TestContextPath.kt
@@ -39,28 +39,46 @@ class TestContextPath {
     }
 
     @Test
-    fun `router works with context -path`() = TestUtil.test(Javalin.create().contextPath("/context-path")) { app, http ->
-        app.get("/hello") { ctx -> ctx.result("Hello World") }
-        assertThat(http.getBody("/hello")).isEqualTo("Not found. Request is below context-path (context-path: '/context-path')")
-        assertThat(http.getBody("/context-path/hello")).isEqualTo("Hello World")
+    fun `router works with context-path`() {
+        val javalin = Javalin.create().configure { it.contextPath = "/context-path" }
+        TestUtil.test(javalin) { app, http ->
+            app.get("/hello") { ctx -> ctx.result("Hello World") }
+            assertThat(http.getBody("/hello")).isEqualTo("Not found. Request is below context-path")
+            assertThat(http.getBody("/context-path/hello")).isEqualTo("Hello World")
+        }
     }
 
     @Test
-    fun `router works with multi-level context-path`() = TestUtil.test(Javalin.create().contextPath("/context-path/path-context")) { app, http ->
-        app.get("/hello") { ctx -> ctx.result("Hello World") }
-        assertThat(http.get("/context-path/").status).isEqualTo(404)
-        assertThat(http.getBody("/context-path/path-context/hello")).isEqualTo("Hello World")
+    fun `router works with multi-level context-path`() {
+        val javalin = Javalin.create().configure { it.contextPath = "/context-path/path-context" }
+        TestUtil.test(javalin) { app, http ->
+            app.get("/hello") { ctx -> ctx.result("Hello World") }
+            assertThat(http.get("/context-path/").status).isEqualTo(404)
+            assertThat(http.getBody("/context-path/path-context/hello")).isEqualTo("Hello World")
+        }
     }
 
     @Test
-    fun `static-files work with context-path`() = TestUtil.test(Javalin.create().contextPath("/context-path").enableStaticFiles("/public")) { _, http ->
-        assertThat(http.get("/script.js").status).isEqualTo(404)
-        assertThat(http.getBody("/context-path/script.js")).contains("JavaScript works")
+    fun `static-files work with context-path`() {
+        val javalin = Javalin.create().configure { servlet ->
+            servlet.addStaticFiles("/public")
+            servlet.contextPath = "/context-path"
+        }
+        TestUtil.test(javalin) { _, http ->
+            assertThat(http.get("/script.js").status).isEqualTo(404)
+            assertThat(http.getBody("/context-path/script.js")).contains("JavaScript works")
+        }
     }
 
     @Test
-    fun `welcome-files work with context-path`() = TestUtil.test(Javalin.create().contextPath("/context-path").enableStaticFiles("/public")) { _, http ->
-        assertThat(http.getBody("/context-path/subdir/")).isEqualTo("<h1>Welcome file</h1>")
+    fun `welcome-files work with context-path`() {
+        val javalin = Javalin.create().configure { servlet ->
+            servlet.addStaticFiles("/public")
+            servlet.contextPath = "/context-path"
+        }
+        TestUtil.test(javalin) { _, http ->
+            assertThat(http.getBody("/context-path/subdir/")).isEqualTo("<h1>Welcome file</h1>")
+        }
     }
 
 }
diff --git a/src/test/java/io/javalin/TestCors.kt b/src/test/java/io/javalin/TestCors.kt
index 1ceebd0d..31a979cc 100644
--- a/src/test/java/io/javalin/TestCors.kt
+++ b/src/test/java/io/javalin/TestCors.kt
@@ -23,28 +23,37 @@ class TestCors {
 
     @Test(expected = IllegalArgumentException::class)
     fun `enableCorsForOrigin() throws for empty varargs`() {
-        Javalin.create().enableCorsForOrigin()
+        Javalin.create().configure { it.enableCorsForOrigin() }
     }
 
     @Test
-    fun `enableCorsForOrigin() enables cors for specific origins`() = TestUtil.test(Javalin.create().enableCorsForOrigin("origin-1", "referer-1")) { app, http ->
-        app.get("/") { ctx -> ctx.result("Hello") }
-        assertThat(Unirest.get(http.origin).asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]).isNull()
-        assertThat(Unirest.get(http.origin).header(ORIGIN, "origin-1").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("origin-1")
-        assertThat(Unirest.get(http.origin).header(REFERER, "referer-1").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("referer-1")
+    fun `enableCorsForOrigin() enables cors for specific origins`() {
+        val javalin = Javalin.create().configure { it.enableCorsForOrigin("origin-1", "referer-1") }
+        TestUtil.test(javalin) { app, http ->
+            app.get("/") { ctx -> ctx.result("Hello") }
+            assertThat(Unirest.get(http.origin).asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]).isNull()
+            assertThat(Unirest.get(http.origin).header(ORIGIN, "origin-1").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("origin-1")
+            assertThat(Unirest.get(http.origin).header(REFERER, "referer-1").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("referer-1")
+        }
     }
 
     @Test
-    fun `enableCorsForAllOrigins() enables cors for all origins`() = TestUtil.test(Javalin.create().enableCorsForAllOrigins()) { app, http ->
-        app.get("/") { ctx -> ctx.result("Hello") }
-        assertThat(Unirest.get(http.origin).header("Origin", "some-origin").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("some-origin")
-        assertThat(Unirest.get(http.origin).header("Origin", "some-origin").asString().headers[ACCESS_CONTROL_ALLOW_CREDENTIALS]!![0]).isEqualTo("true") // cookies allowed
-        assertThat(Unirest.get(http.origin).header("Referer", "some-referer").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("some-referer")
-        assertThat(Unirest.get(http.origin).asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]).isNull()
+    fun `enableCorsForAllOrigins() enables cors for all origins`() {
+        val javalin = Javalin.create().configure { it.enableCorsForAllOrigins() }
+        TestUtil.test(javalin) { app, http ->
+            app.get("/") { ctx -> ctx.result("Hello") }
+            assertThat(Unirest.get(http.origin).header("Origin", "some-origin").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("some-origin")
+            assertThat(Unirest.get(http.origin).header("Origin", "some-origin").asString().headers[ACCESS_CONTROL_ALLOW_CREDENTIALS]!![0]).isEqualTo("true") // cookies allowed
+            assertThat(Unirest.get(http.origin).header("Referer", "some-referer").asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]!![0]).isEqualTo("some-referer")
+            assertThat(Unirest.get(http.origin).asString().headers[ACCESS_CONTROL_ALLOW_ORIGIN]).isNull()
+        }
     }
 
-    private val accessManagedCorsApp = Javalin.create().enableCorsForAllOrigins().accessManager { _, ctx, _ ->
-        ctx.status(401).result("Unauthorized")
+    private val accessManagedCorsApp = Javalin.create().configure {
+        it.enableCorsForAllOrigins()
+        it.accessManager { _, ctx, _ ->
+            ctx.status(401).result("Unauthorized")
+        }
     }
 
     @Test
diff --git a/src/test/java/io/javalin/TestCustomJetty.kt b/src/test/java/io/javalin/TestCustomJetty.kt
index 7182eb92..13bfad72 100644
--- a/src/test/java/io/javalin/TestCustomJetty.kt
+++ b/src/test/java/io/javalin/TestCustomJetty.kt
@@ -36,8 +36,9 @@ class TestCustomJetty {
     @Test
     fun `embedded server can have custom jetty Handler`() {
         val statisticsHandler = StatisticsHandler()
-        val server = Server().apply { handler = statisticsHandler }
-        TestUtil.test(Javalin.create().server { server }) { app, http ->
+        val newServer = Server().apply { handler = statisticsHandler }
+        val javalin = Javalin.create().configure { it.server { newServer } }
+        TestUtil.test(javalin) { app, http ->
             app.get("/") { ctx -> ctx.result("Hello World") }
             val requests = 5
             for (i in 0 until requests) {
@@ -55,8 +56,9 @@ class TestCustomJetty {
         val logCount = AtomicLong(0)
         val requestLogHandler = RequestLogHandler().apply { requestLog = RequestLog { _, _ -> logCount.incrementAndGet() } }
         val handlerChain = StatisticsHandler().apply { handler = requestLogHandler }
-        val server = Server().apply { handler = handlerChain }
-        TestUtil.test(Javalin.create().server { server }) { app, http ->
+        val newServer = Server().apply { handler = handlerChain }
+        val javalin = Javalin.create().configure { it.server { newServer } }
+        TestUtil.test(javalin) { app, http ->
             app.get("/") { ctx -> ctx.result("Hello World") }
             val requests = 10
             for (i in 0 until requests) {
@@ -74,8 +76,9 @@ class TestCustomJetty {
     fun `embedded server can have a wrapped handler collection`() {
         val handlerCollection = HandlerCollection()
         val handlerChain = StatisticsHandler().apply { handler = handlerCollection }
-        val server = Server().apply { handler = handlerChain }
-        TestUtil.test(Javalin.create().server { server }) { app, http ->
+        val newServer = Server().apply { handler = handlerChain }
+        val javalin = Javalin.create().configure { it.server { newServer } }
+        TestUtil.test(javalin) { app, http ->
             app.get("/") { ctx -> ctx.result("Hello World") }
             val requests = 10
             for (i in 0 until requests) {
@@ -90,7 +93,7 @@ class TestCustomJetty {
 
     @Test
     fun `custom SessionHandler works`() {
-        val server = Server()
+        val newServer = Server()
         val fileSessionHandler = SessionHandler().apply {
             httpOnly = true
             sessionCache = DefaultSessionCache(this).apply {
@@ -100,14 +103,14 @@ class TestCustomJetty {
                 }
             }
         }
-        val app = Javalin.create()
-                .sessionHandler { fileSessionHandler }
-                .server { server }
-                .port(0)
-                .start()
-        val httpHandler = (((server.handlers[0] as HandlerWrapper).handler as HandlerList).handlers.first() as ServletContextHandler)
+        val javalin = Javalin.create()
+                .configure {
+                    it.sessionHandler { fileSessionHandler }
+                    it.server { newServer }
+                }.start(0)
+        val httpHandler = (((newServer.handlers[0] as HandlerWrapper).handler as HandlerList).handlers.first() as ServletContextHandler)
         assertThat(httpHandler.sessionHandler).isEqualTo(fileSessionHandler)
-        app.stop()
+        javalin.stop()
 
         val baseDir = File(System.getProperty("java.io.tmpdir"))
         File(baseDir, "javalin-session-store-for-test").deleteRecursively()
@@ -115,7 +118,7 @@ class TestCustomJetty {
 
     @Test
     fun `default SessionHandler works`() {
-        Javalin.create().sessionHandler { SessionHandler() }
+        Javalin.create().configure { it.sessionHandler { SessionHandler() } }
     }
 
     @Test
@@ -129,12 +132,14 @@ class TestCustomJetty {
                 }.getSessionDataStore(sessionHandler)
             }
         }
-        Javalin.create().sessionHandler { sqlSessionHandler(driver = "null", url = "null") }
+        Javalin.create().configure {
+            it.sessionHandler { sqlSessionHandler(driver = "null", url = "null") }
+        }
     }
 
     @Test
     fun `custom ContextHandlerCollection works`() {
-        val server = Server()
+        val newServer = Server()
         val handler = ContextHandlerCollection().apply {
             setContextClass(ServletContextHandler::class.java)
             val ctx = addContext("/foo", ".") as ServletContextHandler
@@ -145,9 +150,10 @@ class TestCustomJetty {
             }), "/foo")
 
         }
-        server.handler = handler
+        newServer.handler = handler
 
-        TestUtil.test(Javalin.create().server { server }) { app, http ->
+        val javalin = Javalin.create().configure { it.server { newServer } }
+        TestUtil.test(javalin) { app, http ->
             app.get("/bar") { ctx -> ctx.result("Hello") }
             assertThat(http.getBody("/foo/foo")).isEqualTo("yo dude")
             assertThat(http.get("/foo/baz").status).isEqualTo(404)
@@ -158,7 +164,7 @@ class TestCustomJetty {
 
     @Test
     fun `custom Servlet works`() {
-        val server = Server().apply {
+        val newServer = Server().apply {
             handler = ContextHandlerCollection().apply {
                 handlers = arrayOf<Handler>(ServletContextHandler().apply {
                     contextPath = "/other-servlet"
@@ -166,7 +172,8 @@ class TestCustomJetty {
                 })
             }
         }
-        TestUtil.test(Javalin.create().server { server }) { app, http ->
+        val javalin = Javalin.create().configure { it.server { newServer } }
+        TestUtil.test(javalin) { app, http ->
             app.get("/") { ctx -> ctx.result("Hello Javalin World!") }
             assertThat(http.getBody("/")).contains("Hello Javalin World!")
             assertThat(http.getBody("/other-servlet")).contains("Hello Servlet World!")
@@ -174,3 +181,4 @@ class TestCustomJetty {
     }
 
 }
+
diff --git a/src/test/java/io/javalin/TestEncoding.kt b/src/test/java/io/javalin/TestEncoding.kt
index 1af3182f..b14cb555 100644
--- a/src/test/java/io/javalin/TestEncoding.kt
+++ b/src/test/java/io/javalin/TestEncoding.kt
@@ -64,13 +64,13 @@ class TestEncoding {
     }
 
     @Test
-    fun `setting a default content-type works`() = TestUtil.test(Javalin.create().defaultContentType("application/json")) { app, http ->
+    fun `setting a default content-type works`() = TestUtil.test(Javalin.create().configure { it.defaultContentType = "application/json" }) { app, http ->
         app.get("/default") { ctx -> ctx.result("not json") }
         assertThat(http.get("/default").headers.getFirst(Header.CONTENT_TYPE)).contains("application/json")
     }
 
     @Test
-    fun `content-type can be overridden in handler`() = TestUtil.test(Javalin.create().defaultContentType("application/json")) { app, http ->
+    fun `content-type can be overridden in handler`() = TestUtil.test(Javalin.create().configure { it.defaultContentType = "application/json" }) { app, http ->
         app.get("/override") { ctx ->
             ctx.res.characterEncoding = "utf-8"
             ctx.res.contentType = "text/html"
diff --git a/src/test/java/io/javalin/TestEtags.kt b/src/test/java/io/javalin/TestEtags.kt
index 6b1ac167..12951f4a 100644
--- a/src/test/java/io/javalin/TestEtags.kt
+++ b/src/test/java/io/javalin/TestEtags.kt
@@ -14,7 +14,7 @@ import org.junit.Test
 
 class TestEtags {
 
-    val etagsEnabledApp = Javalin.create().enableAutogeneratedEtags()
+    val etagsEnabledApp = Javalin.create().configure { it.autogenerateEtags = true }
 
     @Test
     fun `default app does not set etags for GET`() = TestUtil.test { app, http ->
diff --git a/src/test/java/io/javalin/TestFilters.kt b/src/test/java/io/javalin/TestFilters.kt
index e962556f..5ae95763 100644
--- a/src/test/java/io/javalin/TestFilters.kt
+++ b/src/test/java/io/javalin/TestFilters.kt
@@ -74,15 +74,4 @@ class TestFilters {
         assertThat(http.get("/maped").headers.getFirst("X-FILTER")).isEqualTo("After-filter beats before-filter")
     }
 
-    @Test
-    fun `before-handler can add trailing slashes`() = TestUtil.test(Javalin.create().dontIgnoreTrailingSlashes()) { app, http ->
-        app.before { ctx ->
-            if (!ctx.path().endsWith("/")) {
-                ctx.redirect(ctx.path() + "/")
-            }
-        }
-        app.get("/ok/", TestUtil.okHandler)
-        assertThat(http.getBody("/ok")).isEqualTo("OK")
-    }
-
 }
diff --git a/src/test/java/io/javalin/TestGracefulShutdown.java b/src/test/java/io/javalin/TestGracefulShutdown.java
index 39e2ec71..f3807223 100644
--- a/src/test/java/io/javalin/TestGracefulShutdown.java
+++ b/src/test/java/io/javalin/TestGracefulShutdown.java
@@ -30,23 +30,23 @@ public class TestGracefulShutdown {
 
     @Test
     public void t1_shutdown_is_graceful_with_default_config() throws Exception {
-        Javalin app = sharedJavalinConfiguration().start();
+        Javalin app = sharedJavalinConfiguration().start(0);
         testIfShutdownIsGraceful(app);
     }
 
     @Test
     public void t2_shutdown_is_graceful_when_custom_server_has_statisticshandler() throws Exception {
-        Javalin app = sharedJavalinConfiguration().server(() -> {
+        Javalin app = sharedJavalinConfiguration().configure(c -> c.server(() -> {
             Server server = new Server();
             server.insertHandler(new StatisticsHandler());
             return server;
-        }).start();
+        })).start(0);
         testIfShutdownIsGraceful(app);
     }
 
     @Test(expected = ExecutionException.class)
     public void t3_shutdown_is_not_graceful_when_custom_server_has_no_statisticshandler() throws Exception {
-        Javalin app = sharedJavalinConfiguration().server(Server::new).start();
+        Javalin app = sharedJavalinConfiguration().configure(c -> c.server(Server::new)).start(0);
         testIfShutdownIsGraceful(app); // should throw exception
     }
 
@@ -58,7 +58,7 @@ public class TestGracefulShutdown {
     }
 
     private Javalin sharedJavalinConfiguration() {
-        return Javalin.create().disableStartupBanner().port(0)
+        return Javalin.create().disableStartupBanner()
             .get("/immediate-response", context -> context.status(200))
             .get("/delayed-response", context -> Thread.sleep(LONG_WAIT_TIME_IN_MSECS));
     }
diff --git a/src/test/java/io/javalin/TestGzip.kt b/src/test/java/io/javalin/TestGzip.kt
index fe0a9c92..4ab9ace5 100644
--- a/src/test/java/io/javalin/TestGzip.kt
+++ b/src/test/java/io/javalin/TestGzip.kt
@@ -25,7 +25,7 @@ class TestGzip {
             .get("/huge") { ctx -> ctx.result(getSomeObjects(1000).toString()) }
             .get("/tiny") { ctx -> ctx.result(getSomeObjects(10).toString()) }
 
-    val gzipDisabledApp = Javalin.create().disableDynamicGzip()
+    val gzipDisabledApp = Javalin.create().configure { it.dynamicGzip = false}
             .get("/huge") { ctx -> ctx.result(getSomeObjects(1000).toString()) }
             .get("/tiny") { ctx -> ctx.result(getSomeObjects(10).toString()) }
 
diff --git a/src/test/java/io/javalin/TestHttpResponseExceptions.kt b/src/test/java/io/javalin/TestHttpResponseExceptions.kt
index 303c7700..7d768b36 100644
--- a/src/test/java/io/javalin/TestHttpResponseExceptions.kt
+++ b/src/test/java/io/javalin/TestHttpResponseExceptions.kt
@@ -154,7 +154,7 @@ class TestHttpResponseExceptions {
     }
 
     @Test
-    fun `default content type affects http response errors`() = TestUtil.test(Javalin.create().defaultContentType("application/json")) { app, http ->
+    fun `default content type affects http response errors`() = TestUtil.test(Javalin.create().configure { it.defaultContentType = "application/json" }) { app, http ->
         app.get("/content-type") { throw ForbiddenResponse() }
         val response = http.get("/content-type")
         assertThat(response.status).isEqualTo(HttpStatus.FORBIDDEN_403)
diff --git a/src/test/java/io/javalin/TestLogging.kt b/src/test/java/io/javalin/TestLogging.kt
index cadfbb11..e709e449 100644
--- a/src/test/java/io/javalin/TestLogging.kt
+++ b/src/test/java/io/javalin/TestLogging.kt
@@ -23,8 +23,10 @@ class TestLogging {
     fun `debug logging works`() = runTest(Javalin.create().enableDevLogging())
 
     @Test
-    fun `custom logging works`() = runTest(Javalin.create().requestLogger { _, executionTimeMs ->
-        println("That took $executionTimeMs milliseconds")
+    fun `custom logging works`() = runTest(Javalin.create().configure {
+        it.requestLogger { _, executionTimeMs ->
+            println("That took $executionTimeMs milliseconds")
+        }
     })
 
     private fun runTest(app: Javalin) {
@@ -42,9 +44,11 @@ class TestLogging {
     }
 
     private val loggerLog = mutableListOf<String?>()
-    private val bodyLoggingJavalin = Javalin.create().requestLogger { ctx, ms ->
-        loggerLog.add(ctx.resultString())
-        loggerLog.add(ctx.resultString())
+    private val bodyLoggingJavalin = Javalin.create().configure {
+        it.requestLogger { ctx, ms ->
+            loggerLog.add(ctx.resultString())
+            loggerLog.add(ctx.resultString())
+        }
     }
 
     @Test
diff --git a/src/test/java/io/javalin/TestMethodNotAllowed.kt b/src/test/java/io/javalin/TestMethodNotAllowed.kt
index 77bec600..2d01a75a 100644
--- a/src/test/java/io/javalin/TestMethodNotAllowed.kt
+++ b/src/test/java/io/javalin/TestMethodNotAllowed.kt
@@ -7,11 +7,11 @@ import javax.servlet.http.HttpServletResponse
 
 class TestMethodNotAllowed {
 
-    private val preferring405Javalin = Javalin.create()
-            .prefer405over404()
-            .post("/test") { ctx -> ctx.result("Hello world") }
-            .put("/test") { ctx -> ctx.result("Hello world") }
-            .delete("/test") { ctx -> ctx.result("Hello world") }
+    private val preferring405Javalin = Javalin.create().configure { it.prefer405over404 = true }.apply {
+        post("/test") { ctx -> ctx.result("Hello world") }
+        put("/test") { ctx -> ctx.result("Hello world") }
+        delete("/test") { ctx -> ctx.result("Hello world") }
+    }
 
     private val expectedHtml = """
             |Method not allowed
diff --git a/src/test/java/io/javalin/TestRequest.kt b/src/test/java/io/javalin/TestRequest.kt
index 7033b41b..5a4f3624 100644
--- a/src/test/java/io/javalin/TestRequest.kt
+++ b/src/test/java/io/javalin/TestRequest.kt
@@ -265,7 +265,7 @@ class TestRequest {
     }
 
     @Test
-    fun `contextPath() with value works`() = TestUtil.test(Javalin.create().contextPath("/ctx")) { app, http ->
+    fun `contextPath() with value works`() = TestUtil.test(Javalin.create().configure { it.contextPath = "/ctx" }) { app, http ->
         app.get("/") { ctx -> ctx.result(ctx.contextPath()) }
         assertThat(http.getBody("/ctx/")).isEqualTo("/ctx")
     }
diff --git a/src/test/java/io/javalin/TestRequestCache.kt b/src/test/java/io/javalin/TestRequestCache.kt
index cd396659..7568f89b 100644
--- a/src/test/java/io/javalin/TestRequestCache.kt
+++ b/src/test/java/io/javalin/TestRequestCache.kt
@@ -24,7 +24,7 @@ class TestRequestCache {
     }
 
     @Test
-    fun `disabling request-caching works`() = TestUtil.test(Javalin.create().disableRequestCache()) { app, http ->
+    fun `disabling request-caching works`() = TestUtil.test(Javalin.create().configure { it.requestCacheSize = 0 }) { app, http ->
         app.post("/disabled-cache") { ctx ->
             if (ctx.req.inputStream.javaClass.simpleName == "CachedServletInputStream") {
                 throw IllegalStateException("Cache should be disabled.")
diff --git a/src/test/java/io/javalin/TestSinglePageMode.kt b/src/test/java/io/javalin/TestSinglePageMode.kt
index a6f682bd..8410cd53 100644
--- a/src/test/java/io/javalin/TestSinglePageMode.kt
+++ b/src/test/java/io/javalin/TestSinglePageMode.kt
@@ -17,13 +17,27 @@ import java.io.File
 
 class TestSinglePageMode {
 
-    private val rootSinglePageApp_classPath: Javalin by lazy { Javalin.create().enableStaticFiles("/public").enableWebJars().enableSinglePageMode("/", "/public/html.html") }
+    private val rootSinglePageApp_classPath: Javalin by lazy {
+        Javalin.create().configure {
+            it.addStaticFiles("/public")
+            it.addSinglePageRoot("/", "/public/html.html")
+            it.enableWebjars()
+        }
+    }
+
     private val dualSinglePageApp_classPath: Javalin by lazy {
-        Javalin.create().enableStaticFiles("/public")
-                .enableSinglePageMode("/admin", "/public/protected/secret.html")
-                .enableSinglePageMode("/public", "/public/html.html")
+        Javalin.create().configure {
+            it.addStaticFiles("/public")
+            it.addSinglePageRoot("/admin", "/public/protected/secret.html")
+            it.addSinglePageRoot("/public", "/public/html.html")
+        }
+
+    }
+    private val rootSinglePageApp_external: Javalin by lazy {
+        Javalin.create().configure {
+            it.addSinglePageRoot("/", "src/test/external/html.html", Location.EXTERNAL)
+        }
     }
-    private val rootSinglePageApp_external: Javalin by lazy { Javalin.create().enableSinglePageMode("/", "src/test/external/html.html", Location.EXTERNAL) }
 
     @Test
     fun `SinglePageHandler works for HTML requests (classpath)`() = TestUtil.test(rootSinglePageApp_classPath) { _, http ->
@@ -67,12 +81,12 @@ class TestSinglePageMode {
 
     @Test(expected = IllegalArgumentException::class)
     fun `SinglePageHandler throws for non-existent file (classpath)`() {
-        Javalin.create().enableSinglePageMode("/", "/not-a-file.html").start().stop()
+        Javalin.create().configure { it.addSinglePageRoot("/", "/not-a-file.html") }.start().stop()
     }
 
     @Test(expected = IllegalArgumentException::class)
     fun `SinglePageHandler throws for non-existent file (external)`() {
-        Javalin.create().enableSinglePageMode("/", "/not-a-file.html", Location.EXTERNAL).start().stop()
+        Javalin.create().configure { it.addSinglePageRoot("/", "/not-a-file.html", Location.EXTERNAL) }.start().stop()
     }
 
     @Test
@@ -86,7 +100,7 @@ class TestSinglePageMode {
     fun `SinglePageHandler doesn't cache on localhost`() {
         val filePath = "src/test/external/my-special-file.html"
         val file = File(filePath).apply { createNewFile() }.apply { writeText("OLD FILE") }
-        val app = Javalin.create().port(0).enableSinglePageMode("/", filePath, Location.EXTERNAL).start()
+        val app = Javalin.create().configure { it.addSinglePageRoot("/", filePath, Location.EXTERNAL) }.start(0)
         fun getSpaPage() = Unirest.get("http://localhost:${app.port()}/").header(Header.ACCEPT, "text/html").asString().body
         assertThat(getSpaPage()).contains("OLD FILE")
         file.writeText("NEW FILE")
diff --git a/src/test/java/io/javalin/TestStaticDirectorySlash.kt b/src/test/java/io/javalin/TestStaticDirectorySlash.kt
index 5c221892..800bd736 100644
--- a/src/test/java/io/javalin/TestStaticDirectorySlash.kt
+++ b/src/test/java/io/javalin/TestStaticDirectorySlash.kt
@@ -14,12 +14,7 @@ import org.junit.Test
 
 class TestStaticDirectorySlash {
 
-    private val normalJavalin = Javalin.create()
-            .enableStaticFiles("public")
-
-    private val nonIgnoringJavalin = Javalin.create()
-            .enableStaticFiles("public")
-            .dontIgnoreTrailingSlashes()
+    private val normalJavalin = Javalin.create().configure { it.addStaticFiles("public") }
 
     @Test
     fun `normal javalin ignores static directory slashes`() = TestUtil.test(normalJavalin) { _, http ->
@@ -27,22 +22,10 @@ class TestStaticDirectorySlash {
         assertThat(http.getBody("/subpage/")).isEqualTo("TEST") // ok, is directory
     }
 
-    @Test
-    fun `nonIgnoringJavalin (dontIgnoreTrailingSlashes()) doesn't ignore static file slashes`() = TestUtil.test(nonIgnoringJavalin) { _, http ->
-        assertThat(http.getBody("/subpage")).isEqualTo("Not found") // nope, non ignoring and doesnt have slash
-        assertThat(http.getBody("/subpage/")).isEqualTo("TEST") // ok, has slash
-    }
-
     @Test
     fun `normal Javalin serves files but serves directory if it is a directory`() = TestUtil.test(normalJavalin) { _, http ->
         assertThat(http.getBody("/file")).isEqualTo("TESTFILE") // ok, is file = no slash
         assertThat(http.getBody("/file/")).isEqualTo("Not found") // nope, has slash must be directory
     }
 
-    @Test
-    fun `nonIgnoring Javalin serves files but serves directory if it is a directory`() = TestUtil.test(nonIgnoringJavalin) { _, http ->
-        assertThat(http.getBody("/file")).isEqualTo("TESTFILE") // ok, is file = no slash
-        assertThat(http.getBody("/file/")).isEqualTo("Not found") // nope, has slash must be directory
-    }
-
 }
diff --git a/src/test/java/io/javalin/TestStaticFiles.kt b/src/test/java/io/javalin/TestStaticFiles.kt
index 329827f5..28a3888e 100644
--- a/src/test/java/io/javalin/TestStaticFiles.kt
+++ b/src/test/java/io/javalin/TestStaticFiles.kt
@@ -16,14 +16,15 @@ import org.junit.Test
 
 class TestStaticFiles {
 
-    private val defaultStaticResourceApp = Javalin.create().enableStaticFiles("/public") // classpath
-    private val externalStaticResourceApp = Javalin.create().enableStaticFiles("src/test/external/", Location.EXTERNAL)
-    private val multiLocationStaticResourceApp = Javalin.create()
-            .enableStaticFiles("src/test/external/", Location.EXTERNAL)
-            .enableStaticFiles("/public/immutable")
-            .enableStaticFiles("/public/protected")
-            .enableStaticFiles("/public/subdir")
-    private val debugLoggingApp = Javalin.create().enableStaticFiles("/public").enableDevLogging()
+    private val defaultStaticResourceApp = Javalin.create().configure { it.addStaticFiles("/public") } // classpath
+    private val externalStaticResourceApp = Javalin.create().configure { it.addStaticFiles("src/test/external/", Location.EXTERNAL) }
+    private val multiLocationStaticResourceApp = Javalin.create().configure { servlet ->
+        servlet.addStaticFiles("src/test/external/", Location.EXTERNAL)
+        servlet.addStaticFiles("/public/immutable")
+        servlet.addStaticFiles("/public/protected")
+        servlet.addStaticFiles("/public/subdir")
+    }
+    private val devLoggingApp = Javalin.create().configure { it.addStaticFiles("/public") }.enableDevLogging()
 
     @Test
     fun `serving HTML from classpath works`() = TestUtil.test(defaultStaticResourceApp) { _, http ->
@@ -93,7 +94,7 @@ class TestStaticFiles {
     }
 
     @Test
-    fun `content type works in debugmmode`() = TestUtil.test(debugLoggingApp) { _, http ->
+    fun `content type works in debugmmode`() = TestUtil.test(devLoggingApp) { _, http ->
         assertThat(http.get("/html.html").status).isEqualTo(200)
         assertThat(http.get("/html.html").headers.getFirst(Header.CONTENT_TYPE)).contains("text/html")
         assertThat(http.getBody("/html.html")).contains("HTML works")
@@ -102,7 +103,7 @@ class TestStaticFiles {
     }
 
     @Test
-    fun `WebJars available if enabled`() = TestUtil.test(Javalin.create().enableWebJars()) { _, http ->
+    fun `WebJars available if enabled`() = TestUtil.test(Javalin.create().configure { it.enableWebjars() }) { _, http ->
         assertThat(http.get("/webjars/swagger-ui/${OptionalDependency.SWAGGERUI.version}/swagger-ui.css").status).isEqualTo(200)
         assertThat(http.get("/webjars/swagger-ui/${OptionalDependency.SWAGGERUI.version}/swagger-ui.css").headers.getFirst(Header.CONTENT_TYPE)).contains("text/css")
         assertThat(http.get("/webjars/swagger-ui/${OptionalDependency.SWAGGERUI.version}/swagger-ui.css").headers.getFirst(Header.CACHE_CONTROL)).isEqualTo("max-age=31622400")
diff --git a/src/test/java/io/javalin/TestStaticFilesEdgeCases.kt b/src/test/java/io/javalin/TestStaticFilesEdgeCases.kt
index fd0d0d1d..a69f4c07 100644
--- a/src/test/java/io/javalin/TestStaticFilesEdgeCases.kt
+++ b/src/test/java/io/javalin/TestStaticFilesEdgeCases.kt
@@ -14,24 +14,24 @@ class TestStaticFilesEdgeCases {
 
     @Test(expected = RuntimeException::class)
     fun `server doesn't start for non-existent classpath folder`() {
-        Javalin.create().enableStaticFiles("some-fake-folder").start()
+        Javalin.create().configure { it.addStaticFiles("some-fake-folder") }.start()
     }
 
     @Test(expected = RuntimeException::class)
     fun `server doesn't start for non-existent external folder`() {
-        Javalin.create().enableStaticFiles("some-fake-folder", Location.EXTERNAL).start()
+        Javalin.create().configure { it.addStaticFiles("some-fake-folder", Location.EXTERNAL) }.start()
     }
 
     @Test(expected = RuntimeException::class)
     fun `server doesn't start for empty classpath folder`() {
         File("src/test/external/empty").mkdir()
-        Javalin.create().enableStaticFiles("src/test/external/empty", Location.CLASSPATH).start()
+        Javalin.create().configure { it.addStaticFiles("src/test/external/empty", Location.CLASSPATH) }.start()
     }
 
     @Test
     fun `server starts for empty external folder`() {
         File("src/test/external/empty").mkdir()
-        Javalin.create().port(0).enableStaticFiles("src/test/external/empty", Location.EXTERNAL).start().stop()
+        Javalin.create().configure { it.addStaticFiles("src/test/external/empty", Location.EXTERNAL) }.start(0).stop()
     }
 
 }
diff --git a/src/test/java/io/javalin/TestTrailingSlashes.kt b/src/test/java/io/javalin/TestTrailingSlashes.kt
index c48cbcec..838bc0d4 100644
--- a/src/test/java/io/javalin/TestTrailingSlashes.kt
+++ b/src/test/java/io/javalin/TestTrailingSlashes.kt
@@ -15,27 +15,6 @@ import org.junit.Test
 
 class TestTrailingSlashes {
 
-    private val nonIgnoringJavalin = Javalin.create().dontIgnoreTrailingSlashes()
-
-    @Test
-    fun `doesn't ignore when configured (instance)`() = TestUtil.test(nonIgnoringJavalin) { app, http ->
-        app.get("/hello") { ctx -> ctx.result("Hello, slash!") }
-        assertThat(http.getBody("/hello")).isEqualTo("Hello, slash!")
-        assertThat(http.getBody("/hello/")).isEqualTo("Not found")
-    }
-
-    @Test
-    fun `doesn't ignore when configured (ApiBuilder)`() = TestUtil.test(nonIgnoringJavalin) { app, http ->
-        app.routes {
-            path("a") {
-                get { ctx -> ctx.result("a") }
-                get("/") { ctx -> ctx.result("a-slash") }
-            }
-        }
-        assertThat(http.getBody("/a")).isEqualTo("a")
-        assertThat(http.getBody("/a/")).isEqualTo("a-slash")
-    }
-
     @Test
     fun `ignores by default (instance)`() = TestUtil.test { app, http ->
         app.get("/hello") { ctx -> ctx.result("Hello, slash!") }
diff --git a/src/test/java/io/javalin/TestUnixSocketConnector.kt b/src/test/java/io/javalin/TestUnixSocketConnector.kt
index ba19f825..5e8d04f3 100644
--- a/src/test/java/io/javalin/TestUnixSocketConnector.kt
+++ b/src/test/java/io/javalin/TestUnixSocketConnector.kt
@@ -23,15 +23,17 @@ class TestUnixSocketConnector {
         val testPath = "/unixsocket"
         val expectedResultString = "hello unixsocket"
 
-        val unixSocketJavalin = Javalin.create().server {
-            val server = Server()
-            val serverConnector = ServerConnector(server)
-            serverConnector.port = 0
-            server.addConnector(serverConnector)
-            val unixSocketConnector = UnixSocketConnector(server)
-            unixSocketConnector.unixSocket = socketFileName
-            server.addConnector(unixSocketConnector)
-            server
+        val unixSocketJavalin = Javalin.create().configure {
+            it.server {
+                val server = Server()
+                val serverConnector = ServerConnector(server)
+                serverConnector.port = 0
+                server.addConnector(serverConnector)
+                val unixSocketConnector = UnixSocketConnector(server)
+                unixSocketConnector.unixSocket = socketFileName
+                server.addConnector(unixSocketConnector)
+                server
+            }
         }
 
         unixSocketJavalin.get(testPath) { ctx -> ctx.status(200).result(expectedResultString) }
diff --git a/src/test/java/io/javalin/TestWebSocket.kt b/src/test/java/io/javalin/TestWebSocket.kt
index e286f73e..1c0cbd36 100644
--- a/src/test/java/io/javalin/TestWebSocket.kt
+++ b/src/test/java/io/javalin/TestWebSocket.kt
@@ -29,10 +29,12 @@ import java.util.concurrent.atomic.AtomicInteger
  */
 class TestWebSocket {
 
-    private val contextPathJavalin = Javalin.create().contextPath("/websocket")
-    private val javalinWithWsLogger = Javalin.create().wsLogger { ws ->
-        ws.onConnect { ctx -> log.add(ctx.pathParam("param") + " connected") }
-        ws.onClose { ctx -> log.add(ctx.pathParam("param") + " disconnected") }
+    private val contextPathJavalin = Javalin.create().configure { it.wsContextPath = "/websocket" }
+    private val javalinWithWsLogger = Javalin.create().configure {
+        it.wsLogger { ws ->
+            ws.onConnect { ctx -> log.add(ctx.pathParam("param") + " connected") }
+            ws.onClose { ctx -> log.add(ctx.pathParam("param") + " disconnected") }
+        }
     }
     private var log = mutableListOf<String>()
 
@@ -283,21 +285,21 @@ class TestWebSocket {
 
     @Test
     fun `custom WebSocketServletFactory works`() {
-        val server = Server()
+        val newServer = Server()
         var err: Throwable? = Exception("Bang")
         val maxTextSize = 1
         val textToSend = "This text is far too long."
         val expectedMessage = "Text message size [${textToSend.length}] exceeds maximum size [$maxTextSize]"
-        val app = Javalin.create()
-                .wsFactoryConfig { wsFactory ->
-                    wsFactory.policy.maxTextMessageSize = maxTextSize
-                }
-                .ws("/ws") { ws ->
-                    ws.onError { ctx -> err = ctx.error }
-                }
-                .server { server }
-                .port(0)
-                .start()
+        val app = Javalin.create().configure {
+            it.wsFactoryConfig { wsFactory ->
+                wsFactory.policy.maxTextMessageSize = maxTextSize
+            }
+        }
+        app.ws("/ws") { ws ->
+            ws.onError { ctx -> err = ctx.error }
+        }.configure {
+            it.server { newServer }
+        }.start(0)
         val testClient = TestClient(URI.create("ws://localhost:" + app.port() + "/ws"))
 
         doAndSleepWhile({ testClient.connect() }, { !testClient.isOpen })
diff --git a/src/test/java/io/javalin/examples/HelloWorld.java b/src/test/java/io/javalin/examples/HelloWorld.java
index f698988c..0982fac2 100644
--- a/src/test/java/io/javalin/examples/HelloWorld.java
+++ b/src/test/java/io/javalin/examples/HelloWorld.java
@@ -11,7 +11,7 @@ import io.javalin.Javalin;
 
 public class HelloWorld {
     public static void main(String[] args) {
-        Javalin app = Javalin.create().port(7070).start();
+        Javalin app = Javalin.create().start(7070);
         app.get("/", ctx -> ctx.result("Hello World"));
     }
 }
diff --git a/src/test/java/io/javalin/examples/HelloWorldApi.java b/src/test/java/io/javalin/examples/HelloWorldApi.java
index cf8e6fd0..372fda3e 100644
--- a/src/test/java/io/javalin/examples/HelloWorldApi.java
+++ b/src/test/java/io/javalin/examples/HelloWorldApi.java
@@ -15,8 +15,7 @@ public class HelloWorldApi {
 
     public static void main(String[] args) {
         Javalin.create()
-            .port(7070)
-            .start()
+            .start(7070)
             .routes(() -> {
                 get("/hello", ctx -> ctx.result("Hello World"));
                 path("/api", () -> {
diff --git a/src/test/java/io/javalin/examples/HelloWorldAsync.java b/src/test/java/io/javalin/examples/HelloWorldAsync.java
index e467cbb9..f468756a 100644
--- a/src/test/java/io/javalin/examples/HelloWorldAsync.java
+++ b/src/test/java/io/javalin/examples/HelloWorldAsync.java
@@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit;
 
 public class HelloWorldAsync {
     public static void main(String[] args) {
-        Javalin app = Javalin.create().port(7070).start();
+        Javalin app = Javalin.create().start(7070);
         app.get("/", ctx -> {
             CompletableFuture<String> future = new CompletableFuture<>();
             Executors.newSingleThreadScheduledExecutor().schedule(() -> future.complete("Hello World!"), 10, TimeUnit.MILLISECONDS);
diff --git a/src/test/java/io/javalin/examples/HelloWorldAuth.java b/src/test/java/io/javalin/examples/HelloWorldAuth.java
index 2e3823a7..e0074f82 100644
--- a/src/test/java/io/javalin/examples/HelloWorldAuth.java
+++ b/src/test/java/io/javalin/examples/HelloWorldAuth.java
@@ -19,26 +19,24 @@ import static io.javalin.security.SecurityUtil.roles;
 public class HelloWorldAuth {
 
     public static void main(String[] args) {
-        Javalin.create()
-            .port(7070)
-            .start()
-            .accessManager((handler, ctx, permittedRoles) -> {
+        Javalin.create().configure(config -> {
+            config.accessManager((handler, ctx, permittedRoles) -> {
                 String userRole = ctx.queryParam("role");
                 if (userRole != null && permittedRoles.contains(MyRoles.valueOf(userRole))) {
                     handler.handle(ctx);
                 } else {
                     ctx.status(401).result("Unauthorized");
                 }
-            })
-            .routes(() -> {
-                get("/hello", ctx -> ctx.result("Hello World 1"), roles(ROLE_ONE));
-                path("/api", () -> {
-                    get("/test", ctx -> ctx.result("Hello World 2"), roles(ROLE_TWO));
-                    get("/tast", ctx -> ctx.status(200).result("Hello world 3"), roles(ROLE_THREE));
-                    get("/hest", ctx -> ctx.status(200).result("Hello World 4"), roles(ROLE_ONE, ROLE_TWO));
-                    get("/hast", ctx -> ctx.status(200).result("Hello World 5").header("test", "tast"), roles(ROLE_ONE, ROLE_THREE));
-                });
             });
+        }).routes(() -> {
+            get("/hello", ctx -> ctx.result("Hello World 1"), roles(ROLE_ONE));
+            path("/api", () -> {
+                get("/test", ctx -> ctx.result("Hello World 2"), roles(ROLE_TWO));
+                get("/tast", ctx -> ctx.status(200).result("Hello world 3"), roles(ROLE_THREE));
+                get("/hest", ctx -> ctx.status(200).result("Hello World 4"), roles(ROLE_ONE, ROLE_TWO));
+                get("/hast", ctx -> ctx.status(200).result("Hello World 5").header("test", "tast"), roles(ROLE_ONE, ROLE_THREE));
+            });
+        }).start(7070);
     }
 
     enum MyRoles implements Role {
diff --git a/src/test/java/io/javalin/examples/HelloWorldCors.java b/src/test/java/io/javalin/examples/HelloWorldCors.java
index 44c47c89..66a9a549 100644
--- a/src/test/java/io/javalin/examples/HelloWorldCors.java
+++ b/src/test/java/io/javalin/examples/HelloWorldCors.java
@@ -15,10 +15,9 @@ public class HelloWorldCors {
 
     public static void main(String[] args) {
 
-        Javalin corsApp = Javalin.create()
-            .port(7070)
-            .enableCorsForOrigin("http://localhost:7001/", "http://localhost:7002")
-            .start();
+        Javalin corsApp = Javalin.create().configure(config -> {
+            config.enableCorsForOrigin("http://localhost:7001/", "http://localhost:7002");
+        }).start(7070);
 
         corsApp.routes(() -> {
             get(ctx -> ctx.json("Hello Get"));
diff --git a/src/test/java/io/javalin/examples/HelloWorldGson.java b/src/test/java/io/javalin/examples/HelloWorldGson.java
index 0c55e337..05fa2f0e 100644
--- a/src/test/java/io/javalin/examples/HelloWorldGson.java
+++ b/src/test/java/io/javalin/examples/HelloWorldGson.java
@@ -20,7 +20,7 @@ public class HelloWorldGson {
         JavalinJson.setFromJsonMapper(gson::fromJson);
         JavalinJson.setToJsonMapper(gson::toJson);
 
-        Javalin app = Javalin.create().port(7070).start();
+        Javalin app = Javalin.create().start(7070);
         app.get("/", ctx -> ctx.json(Arrays.asList("a", "b", "c")));
 
     }
diff --git a/src/test/java/io/javalin/examples/HelloWorldSecure.java b/src/test/java/io/javalin/examples/HelloWorldSecure.java
index 9d6bbbcc..63751d50 100644
--- a/src/test/java/io/javalin/examples/HelloWorldSecure.java
+++ b/src/test/java/io/javalin/examples/HelloWorldSecure.java
@@ -18,8 +18,8 @@ public class HelloWorldSecure {
     // This is a very basic example, a better one can be found at:
     // https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java#L139-L163
     public static void main(String[] args) {
-        Javalin.create()
-            .server(() -> {
+        Javalin.create().configure(config -> {
+            config.server(() -> {
                 Server server = new Server();
                 ServerConnector sslConnector = new ServerConnector(server, getSslContextFactory());
                 sslConnector.setPort(443);
@@ -27,9 +27,8 @@ public class HelloWorldSecure {
                 connector.setPort(80);
                 server.setConnectors(new Connector[]{sslConnector, connector});
                 return server;
-            })
-            .start()
-            .get("/", ctx -> ctx.result("Hello World")); // valid endpoint for both connectors
+            });
+        }).start().get("/", ctx -> ctx.result("Hello World")); // valid endpoint for both connectors
     }
 
     private static SslContextFactory getSslContextFactory() {
diff --git a/src/test/java/io/javalin/examples/HelloWorldServlet.java b/src/test/java/io/javalin/examples/HelloWorldServlet.java
index c3d6eade..7a649a57 100644
--- a/src/test/java/io/javalin/examples/HelloWorldServlet.java
+++ b/src/test/java/io/javalin/examples/HelloWorldServlet.java
@@ -17,15 +17,17 @@ public class HelloWorldServlet {
 
     public static void main(String[] args) {
         Javalin app = Javalin.create();
-        app.server(() -> {
-            Server server = new Server();
-            ServletContextHandler context = new ServletContextHandler();
-            context.setContextPath("/other-servlet");
-            context.addServlet(TestServlet.class, "/");
-            ContextHandlerCollection handlers = new ContextHandlerCollection();
-            handlers.setHandlers(new Handler[]{context});
-            server.setHandler(handlers);
-            return server;
+        app.configure(config -> {
+            config.server(() -> {
+                Server server = new Server();
+                ServletContextHandler context = new ServletContextHandler();
+                context.setContextPath("/other-servlet");
+                context.addServlet(TestServlet.class, "/");
+                ContextHandlerCollection handlers = new ContextHandlerCollection();
+                handlers.setHandlers(new Handler[]{context});
+                server.setHandler(handlers);
+                return server;
+            });
         });
         app.get("/", ctx -> ctx.result("Hello Javalin World!"));
         app.start(8000);
diff --git a/src/test/java/io/javalin/examples/HelloWorldStaticFiles.java b/src/test/java/io/javalin/examples/HelloWorldStaticFiles.java
index 53c05270..808ed31a 100644
--- a/src/test/java/io/javalin/examples/HelloWorldStaticFiles.java
+++ b/src/test/java/io/javalin/examples/HelloWorldStaticFiles.java
@@ -12,10 +12,9 @@ import io.javalin.Javalin;
 public class HelloWorldStaticFiles {
 
     public static void main(String[] args) {
-        Javalin.create()
-            .port(7070)
-            .enableStaticFiles("/public")
-            .start();
+        Javalin.create().configure(config -> {
+            config.addStaticFiles("/public");
+        }).start(7070);
     }
 
 }
diff --git a/src/test/java/io/javalin/examples/HelloWorldStaticFiles_external.java b/src/test/java/io/javalin/examples/HelloWorldStaticFiles_external.java
index 0e6fdb13..f5f6f2f5 100644
--- a/src/test/java/io/javalin/examples/HelloWorldStaticFiles_external.java
+++ b/src/test/java/io/javalin/examples/HelloWorldStaticFiles_external.java
@@ -12,10 +12,9 @@ import io.javalin.staticfiles.Location;
 public class HelloWorldStaticFiles_external {
 
     public static void main(String[] args) {
-        Javalin.create()
-            .port(7070)
-            .enableStaticFiles("src/test/external/", Location.EXTERNAL)
-            .start();
+        Javalin.create().configure(config -> {
+            config.addStaticFiles("src/test/external/", Location.EXTERNAL);
+        }).start(7070);
     }
 
 }
diff --git a/src/test/java/io/javalin/examples/HelloWorldWebSockets.java b/src/test/java/io/javalin/examples/HelloWorldWebSockets.java
index 65a2175e..1f333d3d 100644
--- a/src/test/java/io/javalin/examples/HelloWorldWebSockets.java
+++ b/src/test/java/io/javalin/examples/HelloWorldWebSockets.java
@@ -12,7 +12,7 @@ import io.javalin.Javalin;
 // see HelloWorldSecure for how to set that up
 public class HelloWorldWebSockets {
     public static void main(String[] args) {
-        Javalin app = Javalin.create().enableDevLogging().port(7070);
+        Javalin app = Javalin.create().enableDevLogging();
         app.ws("/websocket", ws -> {
             ws.onConnect(ctx -> {
                 System.out.println("Connected");
@@ -38,6 +38,6 @@ public class HelloWorldWebSockets {
                 "   setInterval(() => ws.send(\"Repeating request every 2 seconds\"), 2000);\n" +
                 "</script>");
         });
-        app.start();
+        app.start(7070);
     }
 }
diff --git a/src/test/java/io/javalin/performance/StupidAsyncTest.java b/src/test/java/io/javalin/performance/StupidAsyncTest.java
index 557ed74a..9987e21e 100644
--- a/src/test/java/io/javalin/performance/StupidAsyncTest.java
+++ b/src/test/java/io/javalin/performance/StupidAsyncTest.java
@@ -33,9 +33,8 @@ public class StupidAsyncTest {
         QueuedThreadPool threadPool = new QueuedThreadPool(10, 2, 60_000);
 
         Javalin app = Javalin.create()
-            .server(() -> new Server(threadPool))
-            .port(0)
-            .start();
+            .configure(c -> c.server(() -> new Server(threadPool)))
+            .start(0);
 
         HttpUtil http = new HttpUtil(app);
 
diff --git a/src/test/java/io/javalin/performance/StupidPerformanceTest.java b/src/test/java/io/javalin/performance/StupidPerformanceTest.java
index 8ec14445..3127f0b3 100644
--- a/src/test/java/io/javalin/performance/StupidPerformanceTest.java
+++ b/src/test/java/io/javalin/performance/StupidPerformanceTest.java
@@ -43,7 +43,6 @@ public class StupidPerformanceTest {
     public static void setup() {
         // Thread.sleep(7500) // uncomment if running with VisualVM
         app = Javalin.create()
-            .port(0)
             .routes(() -> {
                 before(ctx -> ctx.header("X-BEFORE", "Before"));
                 before(ctx -> ctx.status(200));
@@ -53,7 +52,7 @@ public class StupidPerformanceTest {
                     crud("/messages/:message-id", new GenericController());
                 });
                 after(ctx -> ctx.header("X-AFTER", "After"));
-            }).start();
+            }).start(0);
         origin = "http://localhost:" + app.port();
     }
 
diff --git a/src/test/java/io/javalin/routeoverview/VisualTest.java b/src/test/java/io/javalin/routeoverview/VisualTest.java
index ffd19870..9bd1d0d8 100644
--- a/src/test/java/io/javalin/routeoverview/VisualTest.java
+++ b/src/test/java/io/javalin/routeoverview/VisualTest.java
@@ -28,10 +28,11 @@ public class VisualTest {
 
     public static void main(String[] args) {
         Javalin app = Javalin.create()
-            .contextPath("/context-path")
             .enableRouteOverview("/route-overview")
-            .enableCorsForAllOrigins();
-        app.start();
+            .configure((config) -> {
+                config.contextPath = "/context-path";
+                config.enableCorsForAllOrigins();
+            }).start();
 
         app.get("/", ctx -> ctx.redirect("/context-path/route-overview"));
         app.get("/just-some-path", new HandlerImplementation());
diff --git a/src/test/kotlin/io/javalin/examples/AdvancedServerSentEvent.kt b/src/test/kotlin/io/javalin/examples/AdvancedServerSentEvent.kt
index 7b86d582..5d5adfc0 100644
--- a/src/test/kotlin/io/javalin/examples/AdvancedServerSentEvent.kt
+++ b/src/test/kotlin/io/javalin/examples/AdvancedServerSentEvent.kt
@@ -20,8 +20,10 @@ fun main(args: Array<String>) {
     val statsClients = ConcurrentLinkedQueue<SseClient>()
 
     Javalin.create().apply {
-        enableStaticFiles("/public")
-        server { Server(tp) }
+        configure {
+            it.addStaticFiles("/public")
+            it.server { Server(tp) }
+        }
         get("/") { it.redirect("/sse/sse-example.html") }
         sse("/sse-counter") { client ->
             counterClients.add(client)
diff --git a/src/test/kotlin/io/javalin/examples/HelloWorldAuth.kt b/src/test/kotlin/io/javalin/examples/HelloWorldAuth.kt
index a16e51d8..b54a508b 100644
--- a/src/test/kotlin/io/javalin/examples/HelloWorldAuth.kt
+++ b/src/test/kotlin/io/javalin/examples/HelloWorldAuth.kt
@@ -22,7 +22,7 @@ enum class MyRoles : Role {
 fun main(args: Array<String>) {
 
     val app = Javalin.create().apply {
-        accessManager(::accessManager)
+        configure { it.accessManager(::accessManager) }
     }.start(7070)
 
     app.routes {
diff --git a/src/test/kotlin/io/javalin/examples/HelloWorldCors.kt b/src/test/kotlin/io/javalin/examples/HelloWorldCors.kt
index c412ab04..75bd9655 100644
--- a/src/test/kotlin/io/javalin/examples/HelloWorldCors.kt
+++ b/src/test/kotlin/io/javalin/examples/HelloWorldCors.kt
@@ -12,7 +12,7 @@ import io.javalin.apibuilder.ApiBuilder.*
 fun main(args: Array<String>) {
 
     val corsApp = Javalin.create().apply {
-        enableCorsForOrigin("http://localhost:7001/", "http://localhost:7002")
+        configure { it.enableCorsForOrigin("http://localhost:7001/", "http://localhost:7002") }
     }.start(7070)
 
     corsApp.routes {
diff --git a/src/test/kotlin/io/javalin/examples/HelloWorldSecure.kt b/src/test/kotlin/io/javalin/examples/HelloWorldSecure.kt
index 171aa1c2..27eac23b 100644
--- a/src/test/kotlin/io/javalin/examples/HelloWorldSecure.kt
+++ b/src/test/kotlin/io/javalin/examples/HelloWorldSecure.kt
@@ -15,8 +15,8 @@ import org.eclipse.jetty.util.ssl.SslContextFactory
 // This is a very basic example, a better one can be found at:
 // https://github.com/eclipse/jetty.project/blob/jetty-9.4.x/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java#L139-L163
 fun main(args: Array<String>) {
-    val app = Javalin.create().apply {
-        server {
+    val app = Javalin.create().configure {
+        it.server {
             val server = Server()
             val sslConnector = ServerConnector(server, sslContextFactory())
             sslConnector.port = 443
diff --git a/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles.kt b/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles.kt
index 2c49e202..7671846f 100644
--- a/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles.kt
+++ b/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles.kt
@@ -10,7 +10,7 @@ import io.javalin.Javalin
 
 fun main(args: Array<String>) {
     Javalin.create().apply {
-        enableStaticFiles("/public")
+        configure { it.addStaticFiles("/public")  }
     }.start(7070)
 }
 
diff --git a/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles_external.kt b/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles_external.kt
index a2fcd89d..1ad50fc4 100644
--- a/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles_external.kt
+++ b/src/test/kotlin/io/javalin/examples/HelloWorldStaticFiles_external.kt
@@ -11,6 +11,6 @@ import io.javalin.staticfiles.Location
 
 fun main(args: Array<String>) {
     Javalin.create().apply {
-        enableStaticFiles("src/test/external/", Location.EXTERNAL)
+        configure { it.addStaticFiles("src/test/external/", Location.EXTERNAL) }
     }.start(7070)
 }
diff --git a/src/test/kotlin/io/javalin/examples/HelloWorldSwagger.kt b/src/test/kotlin/io/javalin/examples/HelloWorldSwagger.kt
index aeff38af..1d12fbef 100644
--- a/src/test/kotlin/io/javalin/examples/HelloWorldSwagger.kt
+++ b/src/test/kotlin/io/javalin/examples/HelloWorldSwagger.kt
@@ -17,7 +17,7 @@ import io.javalin.core.util.SwaggerRenderer
 
 fun main(args: Array<String>) {
     Javalin.create().apply {
-        enableWebJars()
+        configure { it.enableWebjars() }
         get("/my-docs", SwaggerRenderer("exampleApiSpec.yaml"))
     }.start(0)
 }