CASSANDRASC-69: Refactor Sidecar configuration

This commit refactors the Sidecar configuration. It separates the configuration
objects from the cluster objects. For example, by default `InstancesConfig` will
be provisioned from the configured `InstanceConfiguration`.

POJOs now represent the configuration from the yaml files. Adding new configurations
entails adding new POJOs and their relationships as well as updating the default
yaml configuration file.

Patch by Francisco Guerrero; Reviewed by Dinesh Joshi, Yifan Cai for CASSANDRASC-69
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 013a356..922e191 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -160,4 +160,3 @@
           requires:
             - java8
             - java11
-
diff --git a/CHANGES.txt b/CHANGES.txt
index 64ecc54..9c330d6 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * Refactor Sidecar configuration (CASSANDRASC-69)
  * Add Client Methods for Obtaining Sidecar and Cassandra Health (CASSANDRASC-70)
  * Publish bytes streamed and written metrics (CASSANDRASC-68)
  * Extract the in-jvm dtest template for use in other projects (CASSANDRASC-55)
diff --git a/README.md b/README.md
index 622a7bb..82c47a8 100644
--- a/README.md
+++ b/README.md
@@ -65,9 +65,9 @@
 Testing
 -------
 
-The test framework is set up to run 4.1 and 5.0 (Trunk) tests (see `TestVersionSupplier.java`) by default.  
+The test framework is set up to run 4.1 and 5.1 (Trunk) tests (see `TestVersionSupplier.java`) by default.
 You can change this via the Java property `cassandra.sidecar.versions_to_test` by supplying a comma-delimited string.
-For example, `-Dcassandra.sidecar.versions_to_test=4.0,4.1,5.0`.
+For example, `-Dcassandra.sidecar.versions_to_test=4.0,4.1,5.1`.
 
 CircleCI Testing
 -----------------
diff --git a/build.gradle b/build.gradle
index 6a4fe1d..abbe555 100644
--- a/build.gradle
+++ b/build.gradle
@@ -21,8 +21,8 @@
 import com.github.spotbugs.SpotBugsTask
 import org.nosphere.apache.rat.RatTask
 
-import java.nio.file.Paths
 import java.nio.file.Files
+import java.nio.file.Paths
 
 buildscript {
     dependencies {
@@ -53,7 +53,7 @@
 }
 
 
-ext.dtestJar = System.getenv("DTEST_JAR") ?: "dtest-5.1.jar" // trunk is currently 5.0.jar - update when trunk moves
+ext.dtestJar = System.getenv("DTEST_JAR") ?: "dtest-5.1.jar" // trunk is currently 5.1.jar - update when trunk moves
 println("Using DTest jar: ${ext.dtestJar}")
 
 // Force checkstyle, rat, and spotBugs to run before test tasks for faster feedback
@@ -177,10 +177,11 @@
     implementation('ch.qos.logback:logback-core:1.2.3')
     implementation('ch.qos.logback:logback-classic:1.2.3')
 
-    implementation(group: 'org.apache.commons', name: 'commons-configuration2', version: '2.7')
+    implementation(group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0')
 
-    runtimeOnly group: 'commons-beanutils', name: 'commons-beanutils', version: '1.9.3'
-    runtimeOnly group: 'org.yaml', name: 'snakeyaml', version: '1.26'
+    // Jackson for yaml-based configuration parsing
+    implementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${project.jacksonVersion}")
+    implementation(group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: "${project.jacksonVersion}")
 
     jolokia 'org.jolokia:jolokia-jvm:1.6.0:agent'
 
@@ -213,7 +214,7 @@
         exclude group: 'junit', module: 'junit'
     }
     integrationTestImplementation(files("dtest-jars/${dtestJar}"))
-    integrationTestImplementation("org.apache.cassandra:dtest-api:0.0.15")
+    integrationTestImplementation("org.apache.cassandra:dtest-api:0.0.16")
     // Needed by the Cassandra dtest framework
     integrationTestImplementation("org.junit.vintage:junit-vintage-engine:${junitVersion}")
 }
@@ -425,6 +426,7 @@
 }
 
 integrationTest.onlyIf { "true" != System.getenv("skipIntegrationTest") }
+compileIntegrationTestJava.onlyIf { "true" != System.getenv("skipIntegrationTest") }
 
 // copyDist gets called on every build
 copyDist.dependsOn installDist, copyJolokia
diff --git a/client/README.md b/client/README.md
index 7594edc..2ab3067 100644
--- a/client/README.md
+++ b/client/README.md
@@ -48,18 +48,18 @@
                                             .userAgent("sample-agent/0.0.1")
                                             .build();
 
-        SidecarConfig sidecarConfig = new SidecarConfig.Builder()
+        SidecarClientConfig clientConfig = new SidecarClientConfig.Builder()
                                       .maxRetries(10)
                                       .retryDelayMillis(200)
                                       .maxRetryDelayMillis(10_000)
                                       .build();
 
-        RetryPolicy defaultRetryPolicy = new ExponentialBackoffRetryPolicy(sidecarConfig.maxRetries(),
-                                                                           sidecarConfig.retryDelayMillis(),
-                                                                           sidecarConfig.maxRetryDelayMillis());
+        RetryPolicy defaultRetryPolicy = new ExponentialBackoffRetryPolicy(clientConfig.maxRetries(),
+                                                                           clientConfig.retryDelayMillis(),
+                                                                           clientConfig.maxRetryDelayMillis());
 
         VertxHttpClient vertxHttpClient = new VertxHttpClient(vertx, httpClientConfig);
         VertxRequestExecutor requestExecutor = new VertxRequestExecutor(vertxHttpClient);
         SimpleSidecarInstancesProvider instancesProvider = new SimpleSidecarInstancesProvider(new ArrayList<>(clusterConfig));
-        return new SidecarClient(instancesProvider, requestExecutor, sidecarConfig, defaultRetryPolicy);
+        return new SidecarClient(instancesProvider, requestExecutor, clientConfig, defaultRetryPolicy);
 ```
diff --git a/client/build.gradle b/client/build.gradle
index 44e359a..6e59530 100644
--- a/client/build.gradle
+++ b/client/build.gradle
@@ -66,7 +66,7 @@
 
 dependencies {
     api(project(':common'))
-    implementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.2')
+    implementation(group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${project.jacksonVersion}")
     implementation("org.slf4j:slf4j-api:${project.slf4jVersion}")
 
     compileOnly('org.jetbrains:annotations:23.0.0')
diff --git a/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClient.java b/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClient.java
index 16b50c2..ec3f3cc 100644
--- a/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClient.java
+++ b/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClient.java
@@ -57,13 +57,13 @@
 
     public SidecarClient(SidecarInstancesProvider instancesProvider,
                          RequestExecutor requestExecutor,
-                         SidecarConfig sidecarConfig,
+                         SidecarClientConfig sidecarClientConfig,
                          RetryPolicy defaultRetryPolicy)
     {
         this.defaultRetryPolicy = defaultRetryPolicy;
-        ignoreConflictRetryPolicy = new IgnoreConflictRetryPolicy(sidecarConfig.maxRetries(),
-                                                                  sidecarConfig.retryDelayMillis(),
-                                                                  sidecarConfig.maxRetryDelayMillis());
+        ignoreConflictRetryPolicy = new IgnoreConflictRetryPolicy(sidecarClientConfig.maxRetries(),
+                                                                  sidecarClientConfig.retryDelayMillis(),
+                                                                  sidecarClientConfig.maxRetryDelayMillis());
         baseBuilder = new RequestContext.Builder()
                       .instanceSelectionPolicy(new RandomInstanceSelectionPolicy(instancesProvider))
                       .retryPolicy(defaultRetryPolicy);
diff --git a/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClientConfig.java b/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClientConfig.java
new file mode 100644
index 0000000..fd138d1
--- /dev/null
+++ b/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClientConfig.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cassandra.sidecar.client;
+
+/**
+ * Encapsulates configurations for the {@link SidecarClient}
+ */
+public interface SidecarClientConfig
+{
+    /**
+     * @return the maximum number of times to retry a request
+     */
+    int maxRetries();
+
+    /**
+     * @return the initial amount of time to wait before retrying a failed request
+     */
+     long retryDelayMillis();
+
+    /**
+     * @return the maximum amount of time to wait before retrying a failed request
+     */
+     long maxRetryDelayMillis();
+}
diff --git a/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarConfig.java b/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClientConfigImpl.java
similarity index 68%
rename from client/src/main/java/org/apache/cassandra/sidecar/client/SidecarConfig.java
rename to client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClientConfigImpl.java
index 6bcc387..3a3a4c9 100644
--- a/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarConfig.java
+++ b/client/src/main/java/org/apache/cassandra/sidecar/client/SidecarClientConfigImpl.java
@@ -19,20 +19,22 @@
 
 package org.apache.cassandra.sidecar.client;
 
+import org.apache.cassandra.sidecar.common.DataObjectBuilder;
+
 /**
  * Encapsulates configurations for the {@link SidecarClient}
  */
-public class SidecarConfig
+public class SidecarClientConfigImpl implements SidecarClientConfig
 {
-    private static final int DEFAULT_MAX_RETRIES = 3;
-    private static final long DEFAULT_RETRY_DELAY_MILLIS = 500L;
-    private static final long DEFAULT_MAX_RETRY_DELAY_MILLIS = 60_000L;
+    public static final int DEFAULT_MAX_RETRIES = 3;
+    public static final long DEFAULT_RETRY_DELAY_MILLIS = 500L;
+    public static final long DEFAULT_MAX_RETRY_DELAY_MILLIS = 60_000L;
 
-    private final int maxRetries;
-    private final long retryDelayMillis;
-    private final long maxRetryDelayMillis;
+    protected final int maxRetries;
+    protected final long retryDelayMillis;
+    protected final long maxRetryDelayMillis;
 
-    private SidecarConfig(Builder builder)
+    private SidecarClientConfigImpl(Builder builder)
     {
         maxRetries = builder.maxRetries;
         retryDelayMillis = builder.retryDelayMillis;
@@ -42,6 +44,7 @@
     /**
      * @return the maximum number of times to retry a request
      */
+    @Override
     public int maxRetries()
     {
         return maxRetries;
@@ -50,6 +53,7 @@
     /**
      * @return the initial amount of time to wait before retrying a failed request
      */
+    @Override
     public long retryDelayMillis()
     {
         return retryDelayMillis;
@@ -58,19 +62,35 @@
     /**
      * @return the maximum amount of time to wait before retrying a failed request
      */
+    @Override
     public long maxRetryDelayMillis()
     {
         return maxRetryDelayMillis;
     }
 
+    public static Builder builder()
+    {
+        return new Builder();
+    }
+
     /**
      * {@code SidecarConfig} builder static inner class.
      */
-    public static final class Builder
+    public static class Builder implements DataObjectBuilder<Builder, SidecarClientConfig>
     {
-        private int maxRetries = DEFAULT_MAX_RETRIES;
-        private long retryDelayMillis = DEFAULT_RETRY_DELAY_MILLIS;
-        private long maxRetryDelayMillis = DEFAULT_MAX_RETRY_DELAY_MILLIS;
+        protected int maxRetries = DEFAULT_MAX_RETRIES;
+        protected long retryDelayMillis = DEFAULT_RETRY_DELAY_MILLIS;
+        protected long maxRetryDelayMillis = DEFAULT_MAX_RETRY_DELAY_MILLIS;
+
+        protected Builder()
+        {
+        }
+
+        @Override
+        public Builder self()
+        {
+            return this;
+        }
 
         /**
          * Sets the {@code maxRetries} and returns a reference to this Builder enabling method chaining.
@@ -80,8 +100,7 @@
          */
         public Builder maxRetries(int maxRetries)
         {
-            this.maxRetries = maxRetries;
-            return this;
+            return update(b -> b.maxRetries = maxRetries);
         }
 
         /**
@@ -92,8 +111,7 @@
          */
         public Builder retryDelayMillis(long retryDelayMillis)
         {
-            this.retryDelayMillis = retryDelayMillis;
-            return this;
+            return update(b -> b.retryDelayMillis = retryDelayMillis);
         }
 
         /**
@@ -104,8 +122,7 @@
          */
         public Builder maxRetryDelayMillis(long maxRetryDelayMillis)
         {
-            this.maxRetryDelayMillis = maxRetryDelayMillis;
-            return this;
+            return update(b -> b.maxRetryDelayMillis = maxRetryDelayMillis);
         }
 
         /**
@@ -113,9 +130,10 @@
          *
          * @return a {@code SidecarConfig} built with parameters of this {@code SidecarConfig.Builder}
          */
-        public SidecarConfig build()
+        @Override
+        public SidecarClientConfig build()
         {
-            return new SidecarConfig(this);
+            return new SidecarClientConfigImpl(this);
         }
     }
 }
diff --git a/client/src/test/java/org/apache/cassandra/sidecar/client/SidecarConfigTest.java b/client/src/test/java/org/apache/cassandra/sidecar/client/SidecarClientConfigTest.java
similarity index 67%
rename from client/src/test/java/org/apache/cassandra/sidecar/client/SidecarConfigTest.java
rename to client/src/test/java/org/apache/cassandra/sidecar/client/SidecarClientConfigTest.java
index c1762f4..a0c7c0b 100644
--- a/client/src/test/java/org/apache/cassandra/sidecar/client/SidecarConfigTest.java
+++ b/client/src/test/java/org/apache/cassandra/sidecar/client/SidecarClientConfigTest.java
@@ -24,14 +24,14 @@
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * Unit tests for {@link SidecarConfig}
+ * Unit tests for {@link SidecarClientConfig}
  */
-class SidecarConfigTest
+class SidecarClientConfigTest
 {
     @Test
     void testDefaults()
     {
-        SidecarConfig config = new SidecarConfig.Builder().build();
+        SidecarClientConfig config = SidecarClientConfigImpl.builder().build();
         assertThat(config.maxRetries()).isEqualTo(3);
         assertThat(config.retryDelayMillis()).isEqualTo(500L);
         assertThat(config.maxRetryDelayMillis()).isEqualTo(60_000L);
@@ -40,31 +40,32 @@
     @Test
     void testMaxRetries()
     {
-        SidecarConfig config = new SidecarConfig.Builder().maxRetries(10).build();
+        SidecarClientConfig config = SidecarClientConfigImpl.builder().maxRetries(10).build();
         assertThat(config.maxRetries()).isEqualTo(10);
     }
 
     @Test
     void testRetryDelayMillis()
     {
-        SidecarConfig config = new SidecarConfig.Builder().retryDelayMillis(100).build();
+        SidecarClientConfig config = SidecarClientConfigImpl.builder().retryDelayMillis(100).build();
         assertThat(config.retryDelayMillis()).isEqualTo(100L);
     }
 
     @Test
     void testMaxRetryDelayMillis()
     {
-        SidecarConfig config = new SidecarConfig.Builder().maxRetryDelayMillis(5_100).build();
+        SidecarClientConfig config = SidecarClientConfigImpl.builder().maxRetryDelayMillis(5_100).build();
         assertThat(config.maxRetryDelayMillis()).isEqualTo(5_100L);
     }
 
     @Test
     void testAllOptions()
     {
-        SidecarConfig config = new SidecarConfig.Builder().maxRetries(10)
-                                                          .retryDelayMillis(100)
-                                                          .maxRetryDelayMillis(5_100)
-                                                          .build();
+        SidecarClientConfig config = SidecarClientConfigImpl.builder()
+                                                            .maxRetries(10)
+                                                            .retryDelayMillis(100)
+                                                            .maxRetryDelayMillis(5_100)
+                                                            .build();
         assertThat(config.maxRetries()).isEqualTo(10);
         assertThat(config.retryDelayMillis()).isEqualTo(100L);
         assertThat(config.maxRetryDelayMillis()).isEqualTo(5_100L);
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java b/common/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
index 2507795..2724cce 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/cluster/InstancesConfigImpl.java
@@ -86,9 +86,9 @@
             catch (UnknownHostException e)
             {
                 NoSuchElementException error = new NoSuchElementException("Instance with host address "
-                                                      + host +
-                                                      " not found, and an error occurred when " +
-                                                      "attempting to resolve its IP address.");
+                                                                          + host +
+                                                                          " not found, and an error occurred when " +
+                                                                          "attempting to resolve its IP address.");
                 error.initCause(e);
                 throw error;
             }
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java b/common/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
index 7aeb6c0..cb75236 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java
@@ -18,15 +18,12 @@
 
 package org.apache.cassandra.sidecar.cluster.instance;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
-import com.google.common.collect.ImmutableList;
-
-import org.apache.cassandra.sidecar.common.CQLSessionProvider;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
-import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
-import org.apache.cassandra.sidecar.common.JmxClient;
-import org.jetbrains.annotations.VisibleForTesting;
+import org.apache.cassandra.sidecar.common.DataObjectBuilder;
 
 /**
  * Local implementation of InstanceMetadata.
@@ -40,34 +37,14 @@
     private final String stagingDir;
     private final CassandraAdapterDelegate delegate;
 
-    public InstanceMetadataImpl(int id,
-                                String host,
-                                int port,
-                                Iterable<String> dataDirs,
-                                String stagingDir,
-                                CQLSessionProvider sessionProvider,
-                                JmxClient jmxClient,
-                                CassandraVersionProvider versionProvider,
-                                String sidecarVersion)
+    protected InstanceMetadataImpl(Builder builder)
     {
-        this(id, host, port, dataDirs, stagingDir,
-             new CassandraAdapterDelegate(versionProvider, sessionProvider, jmxClient, sidecarVersion));
-    }
-
-    @VisibleForTesting
-    public InstanceMetadataImpl(int id,
-                                String host,
-                                int port,
-                                Iterable<String> dataDirs,
-                                String stagingDir,
-                                CassandraAdapterDelegate delegate)
-    {
-        this.id = id;
-        this.host = host;
-        this.port = port;
-        this.dataDirs = ImmutableList.copyOf(dataDirs);
-        this.stagingDir = stagingDir;
-        this.delegate = delegate;
+        id = builder.id;
+        host = builder.host;
+        port = builder.port;
+        dataDirs = Collections.unmodifiableList(builder.dataDirs);
+        stagingDir = builder.stagingDir;
+        delegate = builder.delegate;
     }
 
     @Override
@@ -106,4 +83,118 @@
         return delegate;
     }
 
+    public static Builder builder()
+    {
+        return new Builder();
+    }
+
+    /**
+     * {@code InstanceMetadataImpl} builder static inner class.
+     */
+    public static class Builder implements DataObjectBuilder<Builder, InstanceMetadataImpl>
+    {
+        protected int id;
+        protected String host;
+        protected int port;
+        protected List<String> dataDirs;
+        protected String stagingDir;
+        protected CassandraAdapterDelegate delegate;
+
+        protected Builder()
+        {
+        }
+
+        protected Builder(InstanceMetadataImpl instanceMetadata)
+        {
+            id = instanceMetadata.id;
+            host = instanceMetadata.host;
+            port = instanceMetadata.port;
+            dataDirs = new ArrayList<>(instanceMetadata.dataDirs);
+            stagingDir = instanceMetadata.stagingDir;
+            delegate = instanceMetadata.delegate;
+        }
+
+        @Override
+        public Builder self()
+        {
+            return this;
+        }
+
+        /**
+         * Sets the {@code id} and returns a reference to this Builder enabling method chaining.
+         *
+         * @param id the {@code id} to set
+         * @return a reference to this Builder
+         */
+        public Builder id(int id)
+        {
+            return update(b -> b.id = id);
+        }
+
+        /**
+         * Sets the {@code host} and returns a reference to this Builder enabling method chaining.
+         *
+         * @param host the {@code host} to set
+         * @return a reference to this Builder
+         */
+        public Builder host(String host)
+        {
+            return update(b -> b.host = host);
+        }
+
+        /**
+         * Sets the {@code port} and returns a reference to this Builder enabling method chaining.
+         *
+         * @param port the {@code port} to set
+         * @return a reference to this Builder
+         */
+        public Builder port(int port)
+        {
+            return update(b -> b.port = port);
+        }
+
+        /**
+         * Sets the {@code dataDirs} and returns a reference to this Builder enabling method chaining.
+         *
+         * @param dataDirs the {@code dataDirs} to set
+         * @return a reference to this Builder
+         */
+        public Builder dataDirs(List<String> dataDirs)
+        {
+            return update(b -> b.dataDirs = dataDirs);
+        }
+
+        /**
+         * Sets the {@code stagingDir} and returns a reference to this Builder enabling method chaining.
+         *
+         * @param stagingDir the {@code stagingDir} to set
+         * @return a reference to this Builder
+         */
+        public Builder stagingDir(String stagingDir)
+        {
+            return update(b -> b.stagingDir = stagingDir);
+        }
+
+        /**
+         * Sets the {@code delegate} and returns a reference to this Builder enabling method chaining.
+         *
+         * @param delegate the {@code delegate} to set
+         * @return a reference to this Builder
+         */
+        public Builder delegate(CassandraAdapterDelegate delegate)
+        {
+            return update(b -> b.delegate = delegate);
+        }
+
+        /**
+         * Returns a {@code InstanceMetadataImpl} built from the parameters previously set.
+         *
+         * @return a {@code InstanceMetadataImpl} built with parameters of this {@code InstanceMetadataImpl.Builder}
+         */
+        @Override
+        public InstanceMetadataImpl build()
+        {
+            return new InstanceMetadataImpl(this);
+        }
+    }
 }
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/DataObjectBuilder.java b/common/src/main/java/org/apache/cassandra/sidecar/common/DataObjectBuilder.java
new file mode 100644
index 0000000..e04724d
--- /dev/null
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/DataObjectBuilder.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.common;
+
+import java.util.function.Consumer;
+
+/**
+ * Interface to build data objects
+ *
+ * @param <T> type of builder
+ * @param <R> type of result from build
+ */
+public interface DataObjectBuilder<T extends DataObjectBuilder<?, ?>, R>
+{
+    /**
+     * Self typing
+     *
+     * @return type of implementor class
+     */
+    T self();
+
+    /**
+     * Build into data object of type R
+     *
+     * @return data object type
+     */
+    R build();
+
+    /**
+     * Updates a field in the builder
+     *
+     * @param updater function to mutate fields
+     * @return builder itself for chained invocation
+     */
+    default T update(Consumer<? super T> updater)
+    {
+        updater.accept(self());
+        return self();
+    }
+}
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/YAMLValidationConfiguration.java b/common/src/main/java/org/apache/cassandra/sidecar/common/utils/YAMLValidationConfiguration.java
deleted file mode 100644
index 72f37bf..0000000
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/YAMLValidationConfiguration.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.sidecar.common.utils;
-
-import java.util.Set;
-
-/**
- * An implementation that reads the {@link ValidationConfiguration} from a {@code YAMLConfiguration}.
- */
-public class YAMLValidationConfiguration implements ValidationConfiguration
-{
-    private final Set<String> forbiddenKeyspaces;
-    private final String allowedPatternForDirectory;
-    private final String allowedPatternForComponentName;
-    private final String allowedPatternForRestrictedComponentName;
-
-    public YAMLValidationConfiguration(Set<String> forbiddenKeyspaces,
-                                       String allowedPatternForDirectory,
-                                       String allowedPatternForComponentName,
-                                       String allowedPatternForRestrictedComponentName)
-    {
-        this.forbiddenKeyspaces = forbiddenKeyspaces;
-        this.allowedPatternForDirectory = allowedPatternForDirectory;
-        this.allowedPatternForComponentName = allowedPatternForComponentName;
-        this.allowedPatternForRestrictedComponentName = allowedPatternForRestrictedComponentName;
-    }
-
-    public Set<String> forbiddenKeyspaces()
-    {
-        return forbiddenKeyspaces;
-    }
-
-    public String allowedPatternForDirectory()
-    {
-        return allowedPatternForDirectory;
-    }
-
-    public String allowedPatternForComponentName()
-    {
-        return allowedPatternForComponentName;
-    }
-
-    public String allowedPatternForRestrictedComponentName()
-    {
-        return allowedPatternForRestrictedComponentName;
-    }
-}
diff --git a/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/TestValidationConfiguration.java b/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/TestValidationConfiguration.java
deleted file mode 100644
index 0153ef0..0000000
--- a/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/TestValidationConfiguration.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.sidecar.common;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
-
-/**
- * A {@link ValidationConfiguration} used for unit testing
- */
-public class TestValidationConfiguration implements ValidationConfiguration
-{
-    private static final Set<String> FORBIDDEN_KEYSPACES = new HashSet<>(Arrays.asList("system_schema",
-                                                                                         "system_traces",
-                                                                                         "system_distributed",
-                                                                                         "system",
-                                                                                         "system_auth",
-                                                                                         "system_views",
-                                                                                         "system_virtual_schema"));
-    private static final String ALLOWED_PATTERN_FOR_DIRECTORY = "[a-zA-Z0-9_-]+";
-    private static final String ALLOWED_PATTERN_FOR_COMPONENT_NAME = ALLOWED_PATTERN_FOR_DIRECTORY
-                                                                       + "(.db|.cql|.json|.crc32|TOC.txt)";
-    private static final String ALLOWED_PATTERN_FOR_RESTRICTED_COMPONENT_NAME = ALLOWED_PATTERN_FOR_DIRECTORY
-                                                                                  + "(.db|TOC.txt)";
-
-    public Set<String> forbiddenKeyspaces()
-    {
-        return FORBIDDEN_KEYSPACES;
-    }
-
-    public String allowedPatternForDirectory()
-    {
-        return ALLOWED_PATTERN_FOR_DIRECTORY;
-    }
-
-    public String allowedPatternForComponentName()
-    {
-        return ALLOWED_PATTERN_FOR_COMPONENT_NAME;
-    }
-
-    public String allowedPatternForRestrictedComponentName()
-    {
-        return ALLOWED_PATTERN_FOR_RESTRICTED_COMPONENT_NAME;
-    }
-}
diff --git a/gradle.properties b/gradle.properties
index 60be724..ae298ce 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,3 +21,4 @@
 vertxVersion=4.2.1
 guavaVersion=27.0.1-jre
 slf4jVersion=1.7.36
+jacksonVersion=2.14.3
diff --git a/src/main/dist/conf/sidecar.yaml b/src/main/dist/conf/sidecar.yaml
index 33e4178..2342c87 100644
--- a/src/main/dist/conf/sidecar.yaml
+++ b/src/main/dist/conf/sidecar.yaml
@@ -23,7 +23,11 @@
   - id: 1
     host: localhost1
     port: 9042
-    data_dirs: /ccm/test/node1/data0, /ccm/test/node1/data1
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node1/data0
+      - /ccm/test/node1/data1
     staging_dir: /ccm/test/node1/sstable-staging
     jmx_host: 127.0.0.1
     jmx_port: 7100
@@ -33,7 +37,11 @@
   - id: 2
     host: localhost2
     port: 9042
-    data_dirs: /ccm/test/node2/data0, /ccm/test/node2/data1
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node2/data0
+      - /ccm/test/node2/data1
     staging_dir: /ccm/test/node2/sstable-staging
     jmx_host: 127.0.0.1
     jmx_port: 7200
@@ -43,7 +51,11 @@
   - id: 3
     host: localhost3
     port: 9042
-    data_dirs: /ccm/test/node3/data0, /ccm/test/node3/data1
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node3/data0
+      - /ccm/test/node3/data1
     staging_dir: /ccm/test/node3/sstable-staging
     jmx_host: 127.0.0.1
     jmx_port: 7300
@@ -52,58 +64,58 @@
 #    jmx_role_password:
 
 sidecar:
-  - host: 0.0.0.0
-  - port: 9043
-  - request_idle_timeout_millis: 300000 # this field expects integer value
-  - request_timeout_millis: 300000
-  - throttle:
-    - stream_requests_per_sec: 5000
-    - delay_sec: 5
-    - timeout_sec: 10
-  - sstable_upload:
-    - concurrent_upload_limit: 80
-    - min_free_space_percent: 10
-  - allowable_time_skew_in_minutes: 60
-  - sstable_import:
-    - poll_interval_millis: 100
-    - cache:
-        - expire_after_access_millis: 7200000 # 2 hours
-        - maximum_size: 10000
-  - worker_pools:
-    - service:
-      - name: "sidecar-worker-pool"
-      - size: 20
-      - max_execution_time_millis: 60000 # 60 seconds
-    - internal:
-      - name: "sidecar-internal-worker-pool"
-      - size: 20
-      - max_execution_time_millis: 300000 # 5 minutes
+  host: 0.0.0.0
+  port: 9043
+  request_idle_timeout_millis: 300000 # this field expects integer value
+  request_timeout_millis: 300000
+  throttle:
+    stream_requests_per_sec: 5000
+    delay_sec: 5
+    timeout_sec: 10
+  sstable_upload:
+    concurrent_upload_limit: 80
+    min_free_space_percent: 10
+  allowable_time_skew_in_minutes: 60
+  sstable_import:
+    poll_interval_millis: 100
+    cache:
+      expire_after_access_millis: 7200000 # 2 hours
+      maximum_size: 10000
+  worker_pools:
+    service:
+      name: "sidecar-worker-pool"
+      size: 20
+      max_execution_time_millis: 60000 # 60 seconds
+    internal:
+      name: "sidecar-internal-worker-pool"
+      size: 20
+      max_execution_time_millis: 900000 # 15 minutes
 
 #
 # Enable SSL configuration (Disabled by default)
 #
-#  - ssl:
-#      - enabled: true
-#      - keystore:
-#          - path: "path/to/keystore.p12"
-#          - password: password
-#      - truststore:
-#          - path: "path/to/truststore.p12"
-#          - password: password
+#  ssl:
+#    enabled: true
+#    keystore:
+#      path: "path/to/keystore.p12"
+#      password: password
+#    truststore:
+#      path: "path/to/truststore.p12"
+#      password: password
 
 
 healthcheck:
-  - poll_freq_millis: 30000
+  poll_freq_millis: 30000
 
 cassandra_input_validation:
-  - forbidden_keyspaces:
-      - system_schema
-      - system_traces
-      - system_distributed
-      - system
-      - system_auth
-      - system_views
-      - system_virtual_schema
-  - allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
-  - allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
-  - allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
+  forbidden_keyspaces:
+    - system_schema
+    - system_traces
+    - system_distributed
+    - system
+    - system_auth
+    - system_views
+    - system_virtual_schema
+  allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
+  allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
+  allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
diff --git a/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java b/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
index ca0bf9d..883675b 100644
--- a/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
+++ b/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
@@ -33,7 +33,11 @@
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
 import io.vertx.core.http.HttpServer;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.SidecarConfiguration;
+import org.apache.cassandra.sidecar.config.SslConfiguration;
 import org.apache.cassandra.sidecar.utils.SslUtils;
 
 /**
@@ -46,17 +50,24 @@
     private static final Logger logger = LoggerFactory.getLogger(CassandraSidecarDaemon.class);
     private final Vertx vertx;
     private final HttpServer server;
-    private final Configuration config;
-    private long healthCheckTimerId;
+    private final SidecarConfiguration config;
+    private final InstancesConfig instancesConfig;
     private final ExecutorPools executorPools;
 
+    private long healthCheckTimerId;
+
     @Inject
-    public CassandraSidecarDaemon(Vertx vertx, HttpServer server, Configuration config, ExecutorPools executorPools)
+    public CassandraSidecarDaemon(Vertx vertx,
+                                  HttpServer server,
+                                  SidecarConfiguration config,
+                                  InstancesConfig instancesConfig,
+                                  ExecutorPools executorPools)
     {
         this.vertx = vertx;
         this.server = server;
         this.config = config;
         this.executorPools = executorPools;
+        this.instancesConfig = instancesConfig;
     }
 
     public static void main(String[] args)
@@ -72,14 +83,17 @@
     {
         banner(System.out);
         validate();
-        logger.info("Starting Cassandra Sidecar on {}:{}", config.getHost(), config.getPort());
-        server.listen(config.getPort(), config.getHost())
+
+        ServiceConfiguration service = config.serviceConfiguration();
+
+        logger.info("Starting Cassandra Sidecar on {}:{}", service.host(), service.port());
+        server.listen(service.port(), service.host())
               .onSuccess(p -> {
                   // Run a health check after start up
                   healthCheck();
                   // Configure the periodic timer to run subsequent health checks configured
                   // by the config.getHealthCheckFrequencyMillis() interval
-                  updateHealthChecker(config.getHealthCheckFrequencyMillis());
+                  updateHealthChecker(config.healthCheckConfiguration().checkIntervalMillis());
               });
     }
 
@@ -89,11 +103,10 @@
         List<Future> closingFutures = new ArrayList<>();
         closingFutures.add(server.close());
         executorPools.internal().cancelTimer(healthCheckTimerId);
-        config.getInstancesConfig()
-              .instances()
-              .forEach(instance ->
-                       closingFutures.add(executorPools.internal()
-                                                       .executeBlocking(p -> instance.delegate().close())));
+        instancesConfig.instances()
+                       .forEach(instance ->
+                                closingFutures.add(executorPools.internal()
+                                                                .executeBlocking(p -> instance.delegate().close())));
 
         try
         {
@@ -126,17 +139,18 @@
 
     private void validate()
     {
-        if (config.isSslEnabled())
+        SslConfiguration ssl = config.sslConfiguration();
+        if (ssl != null && ssl.enabled())
         {
             try
             {
-                if (config.getKeyStorePath() == null || config.getKeystorePassword() == null)
+                if (!ssl.isKeystoreConfigured())
                     throw new IllegalArgumentException("keyStorePath and keyStorePassword must be set if ssl enabled");
 
-                SslUtils.validateSslOpts(config.getKeyStorePath(), config.getKeystorePassword());
+                SslUtils.validateSslOpts(ssl.keystore().path(), ssl.keystore().password());
 
-                if (config.getTrustStorePath() != null && config.getTruststorePassword() != null)
-                    SslUtils.validateSslOpts(config.getTrustStorePath(), config.getTruststorePassword());
+                if (ssl.isTruststoreConfigured())
+                    SslUtils.validateSslOpts(ssl.truststore().path(), ssl.truststore().password());
             }
             catch (Exception e)
             {
@@ -166,15 +180,15 @@
     }
 
     /**
-     * Checks the health of every instance configured in the {@link Configuration#getInstancesConfig()}.
+     * Checks the health of every instance configured in the {@link InstancesConfig}.
      * The health check is executed in a blocking thread to prevent the event-loop threads from blocking.
      */
     private void healthCheck()
     {
-        config.getInstancesConfig().instances()
-              .forEach(instanceMetadata ->
-                       executorPools.internal()
-                                    .executeBlocking(promise -> instanceMetadata.delegate().healthCheck()));
+        instancesConfig.instances()
+                       .forEach(instanceMetadata ->
+                                executorPools.internal()
+                                             .executeBlocking(promise -> instanceMetadata.delegate().healthCheck()));
     }
 }
 
diff --git a/src/main/java/org/apache/cassandra/sidecar/Configuration.java b/src/main/java/org/apache/cassandra/sidecar/Configuration.java
deleted file mode 100644
index 6b3dd15..0000000
--- a/src/main/java/org/apache/cassandra/sidecar/Configuration.java
+++ /dev/null
@@ -1,626 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.sidecar;
-
-import javax.annotation.Nullable;
-
-import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
-import org.apache.cassandra.sidecar.config.CacheConfiguration;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
-
-/**
- * Sidecar configuration
- */
-public class Configuration
-{
-    /* Cassandra Instances Config */
-    private final InstancesConfig instancesConfig;
-
-    /* Sidecar's HTTP REST API port */
-    private final int port;
-
-    /* Sidecar's listen address */
-    private final String host;
-
-    /* Healthcheck frequency in millis */
-    private final int healthCheckFrequencyMillis;
-
-    /* SSL related settings */
-    @Nullable
-    private final String keyStorePath;
-
-    @Nullable
-    private final String keyStorePassword;
-
-    @Nullable
-    private final String trustStorePath;
-
-    @Nullable
-    private final String trustStorePassword;
-
-    private final boolean isSslEnabled;
-
-    private final long rateLimitStreamRequestsPerSecond;
-
-    private final long throttleTimeoutInSeconds;
-
-    private final long throttleDelayInSeconds;
-
-    private final int allowableSkewInMinutes;
-
-    private final int requestIdleTimeoutMillis;
-
-    private final long requestTimeoutMillis;
-    private final int ssTableImportPollIntervalMillis;
-
-    private final float minSpacePercentRequiredForUpload;
-
-    private final int concurrentUploadsLimit;
-
-    private final ValidationConfiguration validationConfiguration;
-    private final CacheConfiguration ssTableImportCacheConfiguration;
-    private final WorkerPoolConfiguration serverWorkerPoolConfiguration;
-    private final WorkerPoolConfiguration serverInternalWorkerPoolConfiguration;
-
-    public Configuration(InstancesConfig instancesConfig,
-                         String host,
-                         int port,
-                         int healthCheckFrequencyMillis,
-                         boolean isSslEnabled,
-                         @Nullable String keyStorePath,
-                         @Nullable String keyStorePassword,
-                         @Nullable String trustStorePath,
-                         @Nullable String trustStorePassword,
-                         long rateLimitStreamRequestsPerSecond,
-                         long throttleTimeoutInSeconds,
-                         long throttleDelayInSeconds,
-                         int allowableSkewInMinutes,
-                         int requestIdleTimeoutMillis,
-                         long requestTimeoutMillis,
-                         float minSpacePercentRequiredForUpload,
-                         int concurrentUploadsLimit,
-                         int ssTableImportPollIntervalMillis,
-                         ValidationConfiguration validationConfiguration,
-                         CacheConfiguration ssTableImportCacheConfiguration,
-                         WorkerPoolConfiguration serverWorkerPoolConfiguration,
-                         WorkerPoolConfiguration serverInternalWorkerPoolConfiguration)
-    {
-        this.instancesConfig = instancesConfig;
-        this.host = host;
-        this.port = port;
-        this.healthCheckFrequencyMillis = healthCheckFrequencyMillis;
-        this.ssTableImportPollIntervalMillis = ssTableImportPollIntervalMillis;
-        this.ssTableImportCacheConfiguration = ssTableImportCacheConfiguration;
-
-        this.keyStorePath = keyStorePath;
-        this.keyStorePassword = keyStorePassword;
-        this.trustStorePath = trustStorePath;
-        this.trustStorePassword = trustStorePassword;
-        this.isSslEnabled = isSslEnabled;
-        this.rateLimitStreamRequestsPerSecond = rateLimitStreamRequestsPerSecond;
-        this.throttleTimeoutInSeconds = throttleTimeoutInSeconds;
-        this.throttleDelayInSeconds = throttleDelayInSeconds;
-        this.allowableSkewInMinutes = allowableSkewInMinutes;
-        this.requestIdleTimeoutMillis = requestIdleTimeoutMillis;
-        this.requestTimeoutMillis = requestTimeoutMillis;
-        this.minSpacePercentRequiredForUpload = minSpacePercentRequiredForUpload;
-        this.concurrentUploadsLimit = concurrentUploadsLimit;
-        this.validationConfiguration = validationConfiguration;
-        this.serverWorkerPoolConfiguration = serverWorkerPoolConfiguration;
-        this.serverInternalWorkerPoolConfiguration = serverInternalWorkerPoolConfiguration;
-    }
-
-    /**
-     * Constructs a new configuration object with the given {@link Builder}
-     *
-     * @param builder the configuration builder
-     */
-    protected Configuration(Builder builder)
-    {
-        instancesConfig = builder.instancesConfig;
-        port = builder.port;
-        host = builder.host;
-        healthCheckFrequencyMillis = builder.healthCheckFrequencyMillis;
-        keyStorePath = builder.keyStorePath;
-        keyStorePassword = builder.keyStorePassword;
-        trustStorePath = builder.trustStorePath;
-        trustStorePassword = builder.trustStorePassword;
-        isSslEnabled = builder.isSslEnabled;
-        rateLimitStreamRequestsPerSecond = builder.rateLimitStreamRequestsPerSecond;
-        throttleTimeoutInSeconds = builder.throttleTimeoutInSeconds;
-        throttleDelayInSeconds = builder.throttleDelayInSeconds;
-        allowableSkewInMinutes = builder.allowableSkewInMinutes;
-        requestIdleTimeoutMillis = builder.requestIdleTimeoutMillis;
-        requestTimeoutMillis = builder.requestTimeoutMillis;
-        ssTableImportPollIntervalMillis = builder.ssTableImportPollIntervalMillis;
-        minSpacePercentRequiredForUpload = builder.minSpacePercentRequiredForUploads;
-        concurrentUploadsLimit = builder.concurrentUploadsLimit;
-        validationConfiguration = builder.validationConfiguration;
-        ssTableImportCacheConfiguration = builder.ssTableImportCacheConfiguration;
-        serverWorkerPoolConfiguration = builder.serverWorkerPoolConfiguration;
-        serverInternalWorkerPoolConfiguration = builder.serverInternalWorkerPoolConfiguration;
-    }
-
-    /**
-     * @return the Cassandra Instances config
-     */
-    public InstancesConfig getInstancesConfig()
-    {
-        return instancesConfig;
-    }
-
-    /**
-     * @return the Cassandra validation configuration
-     */
-    public ValidationConfiguration getValidationConfiguration()
-    {
-        return validationConfiguration;
-    }
-
-    /**
-     * @return the listen address for Sidecar
-     */
-    public String getHost()
-    {
-        return host;
-    }
-
-    /**
-     * @return the Sidecar's REST HTTP API port
-     */
-    public Integer getPort()
-    {
-        return port;
-    }
-
-    /**
-     * @return the health check frequency in milliseconds
-     */
-    public int getHealthCheckFrequencyMillis()
-    {
-        return healthCheckFrequencyMillis;
-    }
-
-    /**
-     * @return the SSTable import poll interval in milliseconds
-     */
-    public int getSSTableImportPollIntervalMillis()
-    {
-        return ssTableImportPollIntervalMillis;
-    }
-
-    /**
-     * @return true if SSL is enabled, false otherwise
-     */
-    public boolean isSslEnabled()
-    {
-        return isSslEnabled;
-    }
-
-    /**
-     * @return the Keystore Path
-     */
-    @Nullable
-    public String getKeyStorePath()
-    {
-        return keyStorePath;
-    }
-
-    /**
-     * @return the Keystore password
-     */
-    @Nullable
-    public String getKeystorePassword()
-    {
-        return keyStorePassword;
-    }
-
-    /**
-     * @return the Truststore Path
-     */
-    @Nullable
-    public String getTrustStorePath()
-    {
-        return trustStorePath;
-    }
-
-    /**
-     * @return the Truststore password
-     */
-    @Nullable
-    public String getTruststorePassword()
-    {
-        return trustStorePassword;
-    }
-
-    /**
-     * @return the number of stream requests accepted per second
-     */
-    public long getRateLimitStreamRequestsPerSecond()
-    {
-        return rateLimitStreamRequestsPerSecond;
-    }
-
-    public long getThrottleTimeoutInSeconds()
-    {
-        return throttleTimeoutInSeconds;
-    }
-
-    public long getThrottleDelayInSeconds()
-    {
-        return throttleDelayInSeconds;
-    }
-
-    public int allowableSkewInMinutes()
-    {
-        return allowableSkewInMinutes;
-    }
-
-    public int getRequestIdleTimeoutMillis()
-    {
-        return this.requestIdleTimeoutMillis;
-    }
-
-    public long getRequestTimeoutMillis()
-    {
-        return this.requestTimeoutMillis;
-    }
-
-    public float getMinSpacePercentRequiredForUpload()
-    {
-        return this.minSpacePercentRequiredForUpload;
-    }
-
-    public int getConcurrentUploadsLimit()
-    {
-        return this.concurrentUploadsLimit;
-    }
-
-    public CacheConfiguration ssTableImportCacheConfiguration()
-    {
-        return ssTableImportCacheConfiguration;
-    }
-
-    public WorkerPoolConfiguration serverWorkerPoolConfiguration()
-    {
-        return serverWorkerPoolConfiguration;
-    }
-
-    public WorkerPoolConfiguration serverInternalWorkerPoolConfiguration()
-    {
-        return serverInternalWorkerPoolConfiguration;
-    }
-
-    /**
-     * {@code Configuration} builder static inner class.
-     * @param <T> the type that extends Builder
-     */
-    public static class Builder<T extends Builder<T>>
-    {
-        protected InstancesConfig instancesConfig;
-        protected String host;
-        protected int port;
-        protected int healthCheckFrequencyMillis;
-        protected String keyStorePath;
-        protected String keyStorePassword;
-        protected String trustStorePath;
-        protected String trustStorePassword;
-        protected boolean isSslEnabled;
-        protected long rateLimitStreamRequestsPerSecond;
-        protected long throttleTimeoutInSeconds;
-        protected long throttleDelayInSeconds;
-        protected int allowableSkewInMinutes;
-        protected int requestIdleTimeoutMillis;
-        protected long requestTimeoutMillis;
-        protected int ssTableImportPollIntervalMillis = 100;
-        protected float minSpacePercentRequiredForUploads;
-        protected int concurrentUploadsLimit;
-        protected ValidationConfiguration validationConfiguration;
-        protected CacheConfiguration ssTableImportCacheConfiguration;
-        protected WorkerPoolConfiguration serverWorkerPoolConfiguration;
-        protected WorkerPoolConfiguration serverInternalWorkerPoolConfiguration;
-
-        protected T self()
-        {
-            //noinspection unchecked
-            return (T) this;
-        }
-
-        /**
-         * Sets the {@code instancesConfig} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param instancesConfig the {@code instancesConfig} to set
-         * @return a reference to this Builder
-         */
-        public T setInstancesConfig(InstancesConfig instancesConfig)
-        {
-            this.instancesConfig = instancesConfig;
-            return self();
-        }
-
-        /**
-         * Sets the {@code host} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param host the {@code host} to set
-         * @return a reference to this Builder
-         */
-        public T setHost(String host)
-        {
-            this.host = host;
-            return self();
-        }
-
-        /**
-         * Sets the {@code port} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param port the {@code port} to set
-         * @return a reference to this Builder
-         */
-        public T setPort(int port)
-        {
-            this.port = port;
-            return self();
-        }
-
-        /**
-         * Sets the {@code healthCheckFrequencyMillis} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param freqMillis the {@code healthCheckFrequencyMillis} to set
-         * @return a reference to this Builder
-         */
-        public T setHealthCheckFrequency(int freqMillis)
-        {
-            healthCheckFrequencyMillis = freqMillis;
-            return self();
-        }
-
-        /**
-         * Sets the {@code keyStorePath} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param path the {@code keyStorePath} to set
-         * @return a reference to this Builder
-         */
-        public T setKeyStorePath(String path)
-        {
-            keyStorePath = path;
-            return self();
-        }
-
-        /**
-         * Sets the {@code keyStorePassword} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param password the {@code keyStorePassword} to set
-         * @return a reference to this Builder
-         */
-        public T setKeyStorePassword(String password)
-        {
-            keyStorePassword = password;
-            return self();
-        }
-
-        /**
-         * Sets the {@code trustStorePath} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param path the {@code trustStorePath} to set
-         * @return a reference to this Builder
-         */
-        public T setTrustStorePath(String path)
-        {
-            trustStorePath = path;
-            return self();
-        }
-
-        /**
-         * Sets the {@code trustStorePassword} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param password the {@code trustStorePassword} to set
-         * @return a reference to this Builder
-         */
-        public T setTrustStorePassword(String password)
-        {
-            trustStorePassword = password;
-            return self();
-        }
-
-        /**
-         * Sets the {@code isSslEnabled} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param enabled the {@code isSslEnabled} to set
-         * @return a reference to this Builder
-         */
-        public T setSslEnabled(boolean enabled)
-        {
-            isSslEnabled = enabled;
-            return self();
-        }
-
-        /**
-         * Sets the {@code rateLimitStreamRequestsPerSecond} and returns a reference to this Builder enabling method
-         * chaining.
-         *
-         * @param rateLimitStreamRequestsPerSecond the {@code rateLimitStreamRequestsPerSecond} to set
-         * @return a reference to this Builder
-         */
-        public T setRateLimitStreamRequestsPerSecond(long rateLimitStreamRequestsPerSecond)
-        {
-            this.rateLimitStreamRequestsPerSecond = rateLimitStreamRequestsPerSecond;
-            return self();
-        }
-
-        /**
-         * Sets the {@code throttleTimeoutInSeconds} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param throttleTimeoutInSeconds the {@code throttleTimeoutInSeconds} to set
-         * @return a reference to this Builder
-         */
-        public T setThrottleTimeoutInSeconds(long throttleTimeoutInSeconds)
-        {
-            this.throttleTimeoutInSeconds = throttleTimeoutInSeconds;
-            return self();
-        }
-
-        /**
-         * Sets the {@code throttleDelayInSeconds} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param throttleDelayInSeconds the {@code throttleDelayInSeconds} to set
-         * @return a reference to this Builder
-         */
-        public T setThrottleDelayInSeconds(long throttleDelayInSeconds)
-        {
-            this.throttleDelayInSeconds = throttleDelayInSeconds;
-            return self();
-        }
-
-        /**
-         * Sets the {@code allowableSkewInMinutes} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param allowableSkewInMinutes the {@code allowableSkewInMinutes} to set
-         * @return a reference to this Builder
-         */
-        public T setAllowableSkewInMinutes(int allowableSkewInMinutes)
-        {
-            this.allowableSkewInMinutes = allowableSkewInMinutes;
-            return self();
-        }
-
-        /**
-         * Sets the {@code requestIdleTimeoutMillis} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param requestIdleTimeoutMillis the {@code requestIdleTimeoutMillis} to set
-         * @return a reference to this Builder
-         */
-        public T setRequestIdleTimeoutMillis(int requestIdleTimeoutMillis)
-        {
-            this.requestIdleTimeoutMillis = requestIdleTimeoutMillis;
-            return self();
-        }
-
-        /**
-         * Sets the {@code requestTimeoutMillis} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param requestTimeoutMillis the {@code requestTimeoutMillis} to set
-         * @return a reference to this Builder
-         */
-        public T setRequestTimeoutMillis(long requestTimeoutMillis)
-        {
-            this.requestTimeoutMillis = requestTimeoutMillis;
-            return self();
-        }
-
-        /**
-         * Sets the {@code ssTableImportPollIntervalMillis} and returns a reference to this Builder enabling method
-         * chaining.
-         *
-         * @param ssTableImportPollIntervalMillis the {@code ssTableImportPollIntervalMillis} to set
-         * @return a reference to this Builder
-         */
-        public T setSSTableImportPollIntervalMillis(int ssTableImportPollIntervalMillis)
-        {
-            this.ssTableImportPollIntervalMillis = ssTableImportPollIntervalMillis;
-            return self();
-        }
-
-        /**
-         * Sets the {@code minSpacePercentRequiredForUpload} and returns a reference to this Builder enabling method
-         * chaining.
-         *
-         * @param minSpacePercentRequiredForUploads the {@code minSpacePercentRequiredForUpload} to set
-         * @return a reference to this Builder
-         */
-        public T setMinSpacePercentRequiredForUploads(float minSpacePercentRequiredForUploads)
-        {
-            this.minSpacePercentRequiredForUploads = minSpacePercentRequiredForUploads;
-            return self();
-        }
-
-        /**
-         * Sets the {@code concurrentUploadsLimit} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param concurrentUploadsLimit the {@code concurrentUploadsLimit} to set
-         * @return a reference to this Builder
-         */
-        public T setConcurrentUploadsLimit(int concurrentUploadsLimit)
-        {
-            this.concurrentUploadsLimit = concurrentUploadsLimit;
-            return self();
-        }
-
-        /**
-         * Sets the {@code validationConfiguration} and returns a reference to this Builder enabling method chaining.
-         *
-         * @param validationConfiguration the {@code validationConfiguration} to set
-         * @return a reference to this Builder
-         */
-        public T setValidationConfiguration(ValidationConfiguration validationConfiguration)
-        {
-            this.validationConfiguration = validationConfiguration;
-            return self();
-        }
-
-        /**
-         * Sets the {@code ssTableImportCacheConfiguration} and returns a reference to this Builder enabling method
-         * chaining.
-         *
-         * @param cacheConfiguration the {@code ssTableImportCacheConfiguration} to set
-         * @return a reference to this Builder
-         */
-        public T setSSTableImportCacheConfiguration(CacheConfiguration cacheConfiguration)
-        {
-            ssTableImportCacheConfiguration = cacheConfiguration;
-            return self();
-        }
-
-        /**
-         * Sets the {@code serverWorkerPoolConfiguration} and returns a reference to this Builder enabling method
-         * chaining.
-         *
-         * @param workerPoolConfiguration the {@code serverWorkerPoolConfiguration} to set
-         * @return a reference to this Builder
-         */
-        public T setServerWorkerPoolConfiguration(WorkerPoolConfiguration workerPoolConfiguration)
-        {
-            serverWorkerPoolConfiguration = workerPoolConfiguration;
-            return self();
-        }
-
-        /**
-         * Sets the {@code serverInternalWorkerPoolConfiguration} and returns a reference to this Builder enabling
-         * method chaining.
-         *
-         * @param workerPoolConfiguration the {@code serverInternalWorkerPoolConfiguration} to set
-         * @return a reference to this Builder
-         */
-        public T setServerInternalWorkerPoolConfiguration(WorkerPoolConfiguration workerPoolConfiguration)
-        {
-            serverInternalWorkerPoolConfiguration = workerPoolConfiguration;
-            return self();
-        }
-
-        /**
-         * Returns a {@code Configuration} built from the parameters previously set.
-         *
-         * @return a {@code Configuration} built with parameters of this {@code Configuration.Builder}
-         */
-        public Configuration build()
-        {
-            return new Configuration(this);
-        }
-    }
-}
diff --git a/src/main/java/org/apache/cassandra/sidecar/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
index 66b2c81..09a8955 100644
--- a/src/main/java/org/apache/cassandra/sidecar/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
@@ -20,12 +20,12 @@
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import com.google.common.util.concurrent.SidecarRateLimiter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
@@ -45,11 +45,22 @@
 import io.vertx.ext.web.handler.TimeoutHandler;
 import org.apache.cassandra.sidecar.adapters.base.CassandraFactory;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
+import org.apache.cassandra.sidecar.cluster.InstancesConfigImpl;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
+import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadataImpl;
 import org.apache.cassandra.sidecar.common.ApiEndpointsV1;
+import org.apache.cassandra.sidecar.common.CQLSessionProvider;
+import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
+import org.apache.cassandra.sidecar.common.JmxClient;
 import org.apache.cassandra.sidecar.common.dns.DnsResolver;
 import org.apache.cassandra.sidecar.common.utils.SidecarVersionProvider;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
+import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import org.apache.cassandra.sidecar.config.InstanceConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.SidecarConfiguration;
+import org.apache.cassandra.sidecar.config.SslConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
 import org.apache.cassandra.sidecar.logging.SidecarLoggerHandler;
 import org.apache.cassandra.sidecar.routes.CassandraHealthHandler;
 import org.apache.cassandra.sidecar.routes.FileStreamHandler;
@@ -74,8 +85,6 @@
  */
 public class MainModule extends AbstractModule
 {
-    private static final Logger LOGGER = LoggerFactory.getLogger(MainModule.class);
-
     public static final Map<String, String> OK_STATUS = Collections.singletonMap("status", "OK");
     public static final Map<String, String> NOT_OK_STATUS = Collections.singletonMap("status", "NOT_OK");
 
@@ -91,24 +100,25 @@
 
     @Provides
     @Singleton
-    public HttpServer vertxServer(Vertx vertx, Configuration conf, Router router)
+    public HttpServer vertxServer(Vertx vertx, SidecarConfiguration conf, Router router)
     {
         HttpServerOptions options = new HttpServerOptions().setLogActivity(true);
         options.setIdleTimeoutUnit(TimeUnit.MILLISECONDS)
-               .setIdleTimeout(conf.getRequestIdleTimeoutMillis());
+               .setIdleTimeout(conf.serviceConfiguration().requestIdleTimeoutMillis());
 
-        if (conf.isSslEnabled())
+        SslConfiguration ssl = conf.sslConfiguration();
+        if (ssl != null && ssl.enabled())
         {
             options.setKeyStoreOptions(new JksOptions()
-                                       .setPath(conf.getKeyStorePath())
-                                       .setPassword(conf.getKeystorePassword()))
-                   .setSsl(conf.isSslEnabled());
+                                       .setPath(ssl.keystore().path())
+                                       .setPassword(ssl.keystore().password()))
+                   .setSsl(ssl.enabled());
 
-            if (conf.getTrustStorePath() != null && conf.getTruststorePassword() != null)
+            if (ssl.isTruststoreConfigured())
             {
                 options.setTrustStoreOptions(new JksOptions()
-                                             .setPath(conf.getTrustStorePath())
-                                             .setPassword(conf.getTruststorePassword()));
+                                             .setPath(ssl.truststore().path())
+                                             .setPassword(ssl.truststore().password()));
             }
         }
 
@@ -119,7 +129,7 @@
     @Provides
     @Singleton
     public Router vertxRouter(Vertx vertx,
-                              Configuration conf,
+                              ServiceConfiguration conf,
                               CassandraHealthHandler cassandraHealthHandler,
                               StreamSSTableComponentHandler streamSSTableComponentHandler,
                               FileStreamHandler fileStreamHandler,
@@ -138,7 +148,7 @@
         Router router = Router.router(vertx);
         router.route()
               .handler(loggerHandler)
-              .handler(TimeoutHandler.create(conf.getRequestTimeoutMillis(),
+              .handler(TimeoutHandler.create(conf.requestTimeoutMillis(),
                                              HttpResponseStatus.REQUEST_TIMEOUT.code()));
 
         router.route()
@@ -222,29 +232,45 @@
 
     @Provides
     @Singleton
-    public Configuration configuration(CassandraVersionProvider cassandraVersionProvider,
-                                       SidecarVersionProvider sidecarVersionProvider,
-                                       DnsResolver dnsResolver) throws IOException
+    public SidecarConfiguration sidecarConfiguration() throws IOException
     {
         final String confPath = System.getProperty("sidecar.config", "file://./conf/config.yaml");
-        return YAMLSidecarConfiguration.of(confPath,
-                                           cassandraVersionProvider,
-                                           sidecarVersionProvider.sidecarVersion(),
-                                           dnsResolver);
+        return SidecarConfigurationImpl.readYamlConfiguration(confPath);
     }
 
     @Provides
     @Singleton
-    public InstancesConfig instancesConfig(Configuration configuration)
+    public ServiceConfiguration serviceConfiguration(SidecarConfiguration sidecarConfiguration)
     {
-        return configuration.getInstancesConfig();
+        return sidecarConfiguration.serviceConfiguration();
     }
 
     @Provides
     @Singleton
-    public ValidationConfiguration validationConfiguration(Configuration configuration)
+    public CassandraInputValidationConfiguration validationConfiguration(SidecarConfiguration configuration)
     {
-        return configuration.getValidationConfiguration();
+        return configuration.cassandraInputValidationConfiguration();
+    }
+
+    @Provides
+    @Singleton
+    public InstancesConfig instancesConfig(SidecarConfiguration configuration,
+                                           CassandraVersionProvider cassandraVersionProvider,
+                                           SidecarVersionProvider sidecarVersionProvider,
+                                           DnsResolver dnsResolver)
+    {
+        int healthCheckFrequencyMillis = configuration.healthCheckConfiguration().checkIntervalMillis();
+
+        List<InstanceMetadata> instanceMetadataList =
+        configuration.cassandraInstances()
+                     .stream()
+                     .map(cassandraInstance -> buildInstanceMetadata(cassandraInstance,
+                                                                     cassandraVersionProvider,
+                                                                     healthCheckFrequencyMillis,
+                                                                     sidecarVersionProvider.sidecarVersion()))
+                     .collect(Collectors.toList());
+
+        return new InstancesConfigImpl(instanceMetadataList, dnsResolver);
     }
 
     @Provides
@@ -259,9 +285,10 @@
 
     @Provides
     @Singleton
-    public SidecarRateLimiter streamRequestRateLimiter(Configuration config)
+    public SidecarRateLimiter streamRequestRateLimiter(ServiceConfiguration config)
     {
-        return SidecarRateLimiter.create(config.getRateLimitStreamRequestsPerSecond());
+        return SidecarRateLimiter.create(config.throttleConfiguration()
+                                               .rateLimitStreamRequestsPerSecond());
     }
 
     @Provides
@@ -280,7 +307,7 @@
 
     @Provides
     @Singleton
-    public ErrorHandler errorHandler(Vertx vertx)
+    public ErrorHandler errorHandler()
     {
         return new JsonErrorHandler();
     }
@@ -312,4 +339,42 @@
     {
         return SidecarStats.INSTANCE;
     }
+
+    /**
+     * Builds the {@link InstanceMetadata} from the {@link InstanceConfiguration},
+     * a provided {@code  versionProvider}, and {@code healthCheckFrequencyMillis}.
+     *
+     * @param cassandraInstance          the cassandra instance configuration
+     * @param versionProvider            a Cassandra version provider
+     * @param healthCheckFrequencyMillis the health check frequency configuration in milliseconds
+     * @param sidecarVersion             the version of the Sidecar from the current binary
+     * @return the build instance metadata object
+     */
+    private static InstanceMetadata buildInstanceMetadata(InstanceConfiguration cassandraInstance,
+                                                          CassandraVersionProvider versionProvider,
+                                                          int healthCheckFrequencyMillis,
+                                                          String sidecarVersion)
+    {
+        String host = cassandraInstance.host();
+        int port = cassandraInstance.port();
+
+        CQLSessionProvider session = new CQLSessionProvider(host, port, healthCheckFrequencyMillis);
+        JmxClient jmxClient = new JmxClient(cassandraInstance.jmxHost(),
+                                            cassandraInstance.jmxPort(),
+                                            cassandraInstance.jmxRole(),
+                                            cassandraInstance.jmxRolePassword(),
+                                            cassandraInstance.jmxSslEnabled());
+        CassandraAdapterDelegate delegate = new CassandraAdapterDelegate(versionProvider,
+                                                                         session,
+                                                                         jmxClient,
+                                                                         sidecarVersion);
+        return InstanceMetadataImpl.builder()
+                                   .id(cassandraInstance.id())
+                                   .host(host)
+                                   .port(port)
+                                   .dataDirs(cassandraInstance.dataDirs())
+                                   .stagingDir(cassandraInstance.stagingDir())
+                                   .delegate(delegate)
+                                   .build();
+    }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/YAMLSidecarConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/YAMLSidecarConfiguration.java
deleted file mode 100644
index 1723723..0000000
--- a/src/main/java/org/apache/cassandra/sidecar/YAMLSidecarConfiguration.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.sidecar;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-import java.util.function.UnaryOperator;
-
-import org.apache.commons.configuration2.HierarchicalConfiguration;
-import org.apache.commons.configuration2.YAMLConfiguration;
-import org.apache.commons.configuration2.ex.ConfigurationException;
-import org.apache.commons.configuration2.tree.ImmutableNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import io.vertx.core.VertxOptions;
-import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.cluster.InstancesConfigImpl;
-import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
-import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadataImpl;
-import org.apache.cassandra.sidecar.common.CQLSessionProvider;
-import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
-import org.apache.cassandra.sidecar.common.JmxClient;
-import org.apache.cassandra.sidecar.common.dns.DnsResolver;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
-import org.apache.cassandra.sidecar.common.utils.YAMLValidationConfiguration;
-import org.apache.cassandra.sidecar.config.CacheConfiguration;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
-import org.jetbrains.annotations.Nullable;
-
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.ALLOWABLE_SKEW_IN_MINUTES;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CACHE_EXPIRE_AFTER_ACCESS_MILLIS;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CACHE_MAXIMUM_SIZE;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_ALLOWED_CHARS_FOR_COMPONENT_NAME;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_ALLOWED_CHARS_FOR_DIRECTORY;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_FORBIDDEN_KEYSPACES;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INPUT_VALIDATION;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INSTANCE;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INSTANCES;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INSTANCE_DATA_DIRS;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INSTANCE_HOST;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INSTANCE_ID;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INSTANCE_PORT;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_INSTANCE_STAGING_DIR;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_JMX_HOST;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_JMX_PORT;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_JMX_ROLE;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_JMX_ROLE_PASSWORD;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CASSANDRA_JMX_SSL_ENABLED;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.CONCURRENT_UPLOAD_LIMIT;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.HEALTH_CHECK_INTERVAL;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.HOST;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.KEYSTORE_PASSWORD;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.KEYSTORE_PATH;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.MIN_FREE_SPACE_PERCENT_FOR_UPLOAD;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.PORT;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.REQUEST_IDLE_TIMEOUT_MILLIS;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.REQUEST_TIMEOUT_MILLIS;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.SSL_ENABLED;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.SSTABLE_IMPORT_CACHE_CONFIGURATION;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.SSTABLE_IMPORT_POLL_INTERVAL_MILLIS;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.STREAM_REQUESTS_PER_SEC;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.THROTTLE_DELAY_SEC;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.THROTTLE_TIMEOUT_SEC;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.TRUSTSTORE_PASSWORD;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.TRUSTSTORE_PATH;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.WORKER_POOL_FOR_INTERNAL;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.WORKER_POOL_FOR_SERVICE;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.WORKER_POOL_MAX_EXECUTION_TIME_MILLIS;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.WORKER_POOL_NAME;
-import static org.apache.cassandra.sidecar.utils.SidecarYaml.WORKER_POOL_SIZE;
-
-/**
- * A {@link Configuration} that is built from a YAML configuration file for Sidecar
- */
-public class YAMLSidecarConfiguration extends Configuration
-{
-    private static final Logger logger = LoggerFactory.getLogger(YAMLSidecarConfiguration.class);
-
-    private YAMLSidecarConfiguration(InstancesConfig instancesConfig,
-                                     String host,
-                                     Integer port,
-                                     int healthCheckFrequencyMillis,
-                                     boolean isSslEnabled,
-                                     @Nullable String keyStorePath,
-                                     @Nullable String keyStorePassword,
-                                     @Nullable String trustStorePath,
-                                     @Nullable String trustStorePassword,
-                                     long rateLimitStreamRequestsPerSecond,
-                                     long throttleTimeoutInSeconds,
-                                     long throttleDelayInSeconds,
-                                     int allowableSkewInMinutes,
-                                     int requestIdleTimeoutMillis,
-                                     long requestTimeoutMillis,
-                                     float minFreeSpacePercentRequiredForUpload,
-                                     int concurrentUploadsLimit,
-                                     int ssTableImportPollIntervalMillis,
-                                     ValidationConfiguration validationConfiguration,
-                                     CacheConfiguration ssTableImportCacheConfiguration,
-                                     WorkerPoolConfiguration serverWorkerPoolConfiguration,
-                                     WorkerPoolConfiguration serverInternalWorkerPoolConfiguration)
-    {
-        super(instancesConfig,
-              host,
-              port,
-              healthCheckFrequencyMillis,
-              isSslEnabled,
-              keyStorePath,
-              keyStorePassword,
-              trustStorePath,
-              trustStorePassword,
-              rateLimitStreamRequestsPerSecond,
-              throttleTimeoutInSeconds,
-              throttleDelayInSeconds,
-              allowableSkewInMinutes,
-              requestIdleTimeoutMillis,
-              requestTimeoutMillis,
-              minFreeSpacePercentRequiredForUpload,
-              concurrentUploadsLimit,
-              ssTableImportPollIntervalMillis,
-              validationConfiguration,
-              ssTableImportCacheConfiguration,
-              serverWorkerPoolConfiguration,
-              serverInternalWorkerPoolConfiguration);
-    }
-
-    /**
-     * Returns a new {@link Configuration} built from the provided {@code confPath} YAML file and a
-     * {@code versionProvider}
-     *
-     * @param confPath        the path to the Sidecar YAML configuration file
-     * @param versionProvider a Cassandra version provider
-     * @param sidecarVersion  the version of the Sidecar from the current binary
-     * @param dnsResolver     the DNS resolver to use
-     * @return the {@link YAMLConfiguration} parsed from the YAML file
-     * @throws IOException when reading the configuration from file fails
-     */
-    public static Configuration of(String confPath,
-                                   CassandraVersionProvider versionProvider,
-                                   String sidecarVersion,
-                                   DnsResolver dnsResolver) throws IOException
-    {
-        YAMLConfiguration yamlConf = yamlConfiguration(confPath);
-        int healthCheckFrequencyMillis = yamlConf.getInt(HEALTH_CHECK_INTERVAL, 1000);
-        ValidationConfiguration validationConfiguration = validationConfiguration(yamlConf);
-        InstancesConfig instancesConfig = instancesConfig(yamlConf,
-                                                          versionProvider,
-                                                          healthCheckFrequencyMillis,
-                                                          sidecarVersion, dnsResolver);
-        CacheConfiguration ssTableImportCacheConfiguration = cacheConfig(yamlConf,
-                                                                         SSTABLE_IMPORT_CACHE_CONFIGURATION,
-                                                                         TimeUnit.HOURS.toMillis(2),
-                                                                         10_000);
-        WorkerPoolConfiguration serverWorkerPoolConf = workerPoolConfiguration(yamlConf,
-                                                                               WORKER_POOL_FOR_SERVICE,
-                                                                               "sidecar-worker-pool",
-                                                                               VertxOptions.DEFAULT_WORKER_POOL_SIZE,
-                                                                               TimeUnit.SECONDS.toMillis(60));
-        WorkerPoolConfiguration internalWorkerPoolConf = workerPoolConfiguration(yamlConf,
-                                                                                 WORKER_POOL_FOR_INTERNAL,
-                                                                                 "sidecar-internal-worker-pool",
-                                                                                 VertxOptions.DEFAULT_WORKER_POOL_SIZE,
-                                                                                 TimeUnit.SECONDS.toMillis(60));
-
-        return new YAMLSidecarConfiguration(instancesConfig,
-                                            yamlConf.get(String.class, HOST),
-                                            yamlConf.get(Integer.class, PORT),
-                                            healthCheckFrequencyMillis,
-                                            yamlConf.get(Boolean.class, SSL_ENABLED, false),
-                                            yamlConf.get(String.class, KEYSTORE_PATH, null),
-                                            yamlConf.get(String.class, KEYSTORE_PASSWORD, null),
-                                            yamlConf.get(String.class, TRUSTSTORE_PATH, null),
-                                            yamlConf.get(String.class, TRUSTSTORE_PASSWORD, null),
-                                            yamlConf.getLong(STREAM_REQUESTS_PER_SEC, 5000L),
-                                            yamlConf.getLong(THROTTLE_TIMEOUT_SEC, 10),
-                                            yamlConf.getLong(THROTTLE_DELAY_SEC, 5),
-                                            yamlConf.getInt(ALLOWABLE_SKEW_IN_MINUTES, 60),
-                                            yamlConf.getInt(REQUEST_IDLE_TIMEOUT_MILLIS, 300_000),
-                                            yamlConf.getLong(REQUEST_TIMEOUT_MILLIS, 300_000L),
-                                            yamlConf.getFloat(MIN_FREE_SPACE_PERCENT_FOR_UPLOAD, 10),
-                                            yamlConf.getInt(CONCURRENT_UPLOAD_LIMIT, 80),
-                                            yamlConf.getInt(SSTABLE_IMPORT_POLL_INTERVAL_MILLIS, 100),
-                                            validationConfiguration,
-                                            ssTableImportCacheConfiguration,
-                                            serverWorkerPoolConf,
-                                            internalWorkerPoolConf);
-    }
-
-    /**
-     * Returns an object to read the YAML file from {@code confPath}.
-     *
-     * @param confPath the YAML file that provides the Sidecar {@link Configuration}
-     * @return an object to read the YAML file from {@code confPath}
-     * @throws IOException when reading the configuration from file fails
-     */
-    private static YAMLConfiguration yamlConfiguration(String confPath) throws IOException
-    {
-        logger.info("Reading configuration from {}", confPath);
-
-        try
-        {
-            URL url = new URL(confPath);
-            YAMLConfiguration yamlConf = new YAMLConfiguration();
-            InputStream stream = url.openStream();
-            yamlConf.read(stream);
-            return yamlConf;
-        }
-        catch (ConfigurationException | IOException e)
-        {
-            throw new IOException(String.format("Unable to parse cluster information from file='%s'", confPath), e);
-        }
-    }
-
-    /**
-     * Parses the {@link InstancesConfig} from the {@link YAMLConfiguration yamlConf}, the {@code versionProvider}, and
-     * the {@code healthCheckFrequencyMillis}.
-     *
-     * @param yamlConf                   the object used to parse the YAML file
-     * @param versionProvider            a Cassandra version provider
-     * @param healthCheckFrequencyMillis the health check frequency configuration in milliseconds
-     * @param sidecarVersion             the version of the Sidecar from the current binary
-     * @param dnsResolver                the DNS resolver to use when looking up host IP addresses by name
-     * @return the parsed {@link InstancesConfig} from the {@code yamlConf} object
-     */
-    private static InstancesConfig instancesConfig(YAMLConfiguration yamlConf,
-                                                   CassandraVersionProvider versionProvider,
-                                                   int healthCheckFrequencyMillis,
-                                                   String sidecarVersion,
-                                                   DnsResolver dnsResolver)
-    {
-        /* Since we are supporting handling multiple instances in Sidecar optionally, we prefer reading single instance
-         * data over reading multiple instances section
-         */
-        org.apache.commons.configuration2.Configuration singleInstanceConf = yamlConf.subset(CASSANDRA_INSTANCE);
-        if (singleInstanceConf != null && !singleInstanceConf.isEmpty())
-        {
-            InstanceMetadata instanceMetadata = buildInstanceMetadata(singleInstanceConf,
-                                                                      versionProvider,
-                                                                      healthCheckFrequencyMillis,
-                                                                      sidecarVersion);
-            return new InstancesConfigImpl(instanceMetadata, dnsResolver);
-        }
-
-        List<HierarchicalConfiguration<ImmutableNode>> instances = yamlConf.configurationsAt(CASSANDRA_INSTANCES);
-        final List<InstanceMetadata> instanceMetas = new ArrayList<>();
-        for (HierarchicalConfiguration<ImmutableNode> instance : instances)
-        {
-            InstanceMetadata instanceMetadata = buildInstanceMetadata(instance,
-                                                                      versionProvider,
-                                                                      healthCheckFrequencyMillis,
-                                                                      sidecarVersion);
-            instanceMetas.add(instanceMetadata);
-        }
-        return new InstancesConfigImpl(instanceMetas, dnsResolver);
-    }
-
-    private static CacheConfiguration cacheConfig(YAMLConfiguration yamlConf, String prefix,
-                                                  long defaultExpireAfterAccessMillis, int defaultMaximumSize)
-    {
-        org.apache.commons.configuration2.Configuration cacheConf = yamlConf.subset(prefix);
-        return new CacheConfiguration(
-        cacheConf.getLong(CACHE_EXPIRE_AFTER_ACCESS_MILLIS, defaultExpireAfterAccessMillis),
-        cacheConf.getInt(CACHE_MAXIMUM_SIZE, defaultMaximumSize)
-        );
-    }
-
-    private static WorkerPoolConfiguration workerPoolConfiguration(YAMLConfiguration yamlConf, String prefix,
-                                                                   String defaultName, int defaultSize,
-                                                                   long defaultMaxExecutionTimeMillis)
-    {
-        org.apache.commons.configuration2.Configuration conf = yamlConf.subset(prefix);
-        return new WorkerPoolConfiguration(
-        conf.getString(WORKER_POOL_NAME, defaultName),
-        conf.getInt(WORKER_POOL_SIZE, defaultSize),
-        conf.getLong(WORKER_POOL_MAX_EXECUTION_TIME_MILLIS, defaultMaxExecutionTimeMillis)
-        );
-    }
-
-    /**
-     * Parses the {@link ValidationConfiguration} from the {@link YAMLConfiguration yamlConf}.
-     *
-     * @param yamlConf the object used to parse the YAML file
-     * @return the parsed {@link ValidationConfiguration} from the {@code yamlConf} object
-     */
-    private static ValidationConfiguration validationConfiguration(YAMLConfiguration yamlConf)
-    {
-        org.apache.commons.configuration2.Configuration validation = yamlConf.subset(CASSANDRA_INPUT_VALIDATION);
-        Set<String> forbiddenKeyspaces = new HashSet<>(validation.getList(String.class,
-                                                                          CASSANDRA_FORBIDDEN_KEYSPACES,
-                                                                          Collections.emptyList()));
-        UnaryOperator<String> readString = key -> validation.get(String.class, key);
-        String allowedPatternForDirectory = readString.apply(CASSANDRA_ALLOWED_CHARS_FOR_DIRECTORY);
-        String allowedPatternForComponentName = readString.apply(CASSANDRA_ALLOWED_CHARS_FOR_COMPONENT_NAME);
-        String allowedPatternForRestrictedComponentName = readString
-                                                          .apply(CASSANDRA_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME);
-
-        return new YAMLValidationConfiguration(forbiddenKeyspaces,
-                                               allowedPatternForDirectory,
-                                               allowedPatternForComponentName,
-                                               allowedPatternForRestrictedComponentName);
-    }
-
-    /**
-     * Builds the {@link InstanceMetadata} from the {@link org.apache.commons.configuration2.Configuration},
-     * a provided {@code  versionProvider} and {@code healthCheckFrequencyMillis}.
-     *
-     * @param instance                   the object that allows reading from the YAML file
-     * @param versionProvider            a Cassandra version provider
-     * @param healthCheckFrequencyMillis the health check frequency configuration in milliseconds
-     * @param sidecarVersion             the version of the Sidecar from the current binary
-     * @return the parsed {@link InstanceMetadata} from YAML
-     */
-    private static InstanceMetadata buildInstanceMetadata(org.apache.commons.configuration2.Configuration instance,
-                                                          CassandraVersionProvider versionProvider,
-                                                          int healthCheckFrequencyMillis,
-                                                          String sidecarVersion)
-    {
-        int id = instance.get(Integer.class, CASSANDRA_INSTANCE_ID, 1);
-        String host = instance.get(String.class, CASSANDRA_INSTANCE_HOST);
-        int port = instance.get(Integer.class, CASSANDRA_INSTANCE_PORT);
-        String dataDirs = instance.get(String.class, CASSANDRA_INSTANCE_DATA_DIRS);
-        String stagingDir = instance.get(String.class, CASSANDRA_INSTANCE_STAGING_DIR);
-        String jmxHost = instance.get(String.class, CASSANDRA_JMX_HOST, "127.0.0.1");
-        int jmxPort = instance.get(Integer.class, CASSANDRA_JMX_PORT, 7199);
-        String jmxRole = instance.get(String.class, CASSANDRA_JMX_ROLE, null);
-        String jmxRolePassword = instance.get(String.class, CASSANDRA_JMX_ROLE_PASSWORD, null);
-        boolean jmxSslEnabled = instance.get(Boolean.class, CASSANDRA_JMX_SSL_ENABLED, false);
-
-        CQLSessionProvider session = new CQLSessionProvider(host, port, healthCheckFrequencyMillis);
-        JmxClient jmxClient = new JmxClient(jmxHost, jmxPort, jmxRole, jmxRolePassword, jmxSslEnabled);
-        return new InstanceMetadataImpl(id,
-                                        host,
-                                        port,
-                                        Collections.unmodifiableList(Arrays.asList(dataDirs.split(","))),
-                                        stagingDir,
-                                        session,
-                                        jmxClient,
-                                        versionProvider,
-                                        sidecarVersion);
-    }
-}
diff --git a/src/main/java/org/apache/cassandra/sidecar/concurrent/ExecutorPools.java b/src/main/java/org/apache/cassandra/sidecar/concurrent/ExecutorPools.java
index 97488a1..02576a2 100644
--- a/src/main/java/org/apache/cassandra/sidecar/concurrent/ExecutorPools.java
+++ b/src/main/java/org/apache/cassandra/sidecar/concurrent/ExecutorPools.java
@@ -28,7 +28,7 @@
 import io.vertx.core.Promise;
 import io.vertx.core.Vertx;
 import io.vertx.core.WorkerExecutor;
-import org.apache.cassandra.sidecar.Configuration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
 
 /**
@@ -45,10 +45,10 @@
     private final TaskExecutorPool internalTaskExecutors;
 
     @Inject
-    public ExecutorPools(Vertx vertx, Configuration configuraion)
+    public ExecutorPools(Vertx vertx, ServiceConfiguration configuration)
     {
-        this.taskExecutors = new TaskExecutorPool(vertx, configuraion.serverWorkerPoolConfiguration());
-        this.internalTaskExecutors = new TaskExecutorPool(vertx, configuraion.serverInternalWorkerPoolConfiguration());
+        this.taskExecutors = new TaskExecutorPool(vertx, configuration.serverWorkerPoolConfiguration());
+        this.internalTaskExecutors = new TaskExecutorPool(vertx, configuration.serverInternalWorkerPoolConfiguration());
     }
 
     /**
@@ -86,9 +86,9 @@
         private TaskExecutorPool(Vertx vertx, WorkerPoolConfiguration config)
         {
             this.vertx = vertx;
-            this.workerExecutor = vertx.createSharedWorkerExecutor(config.workerPoolName,
-                                                                   config.workerPoolSize,
-                                                                   config.workerMaxExecutionTimeMillis,
+            this.workerExecutor = vertx.createSharedWorkerExecutor(config.workerPoolName(),
+                                                                   config.workerPoolSize(),
+                                                                   config.workerMaxExecutionTimeMillis(),
                                                                    TimeUnit.MILLISECONDS);
         }
 
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java
index 9d84cde..5238e81 100644
--- a/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java
+++ b/src/main/java/org/apache/cassandra/sidecar/config/CacheConfiguration.java
@@ -21,24 +21,16 @@
 /**
  * Configuration class that encapsulates parameters needed for Caches
  */
-public class CacheConfiguration
+public interface CacheConfiguration
 {
-    private final long expireAfterAccessMillis;
-    private final long maximumSize;
+    /**
+     * @return the configured amount of time in milliseconds after the entry's creation, the most recent
+     * replacement of its value, or its last access has elapsed to be considered an expired entry in the cache
+     */
+    long expireAfterAccessMillis();
 
-    public CacheConfiguration(long expireAfterAccessMillis, long maximumSize)
-    {
-        this.expireAfterAccessMillis = expireAfterAccessMillis;
-        this.maximumSize = maximumSize;
-    }
-
-    public long expireAfterAccessMillis()
-    {
-        return expireAfterAccessMillis;
-    }
-
-    public long maximumSize()
-    {
-        return maximumSize;
-    }
+    /**
+     * @return the maximum number of entries the cache may contain
+     */
+    long maximumSize();
 }
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/CassandraInputValidationConfiguration.java
similarity index 87%
rename from common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationConfiguration.java
rename to src/main/java/org/apache/cassandra/sidecar/config/CassandraInputValidationConfiguration.java
index 36f8b88..f0daca8 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/utils/ValidationConfiguration.java
+++ b/src/main/java/org/apache/cassandra/sidecar/config/CassandraInputValidationConfiguration.java
@@ -16,14 +16,14 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.sidecar.common.utils;
+package org.apache.cassandra.sidecar.config;
 
 import java.util.Set;
 
 /**
- * An interface to provide validation configuration parameters
+ * Encapsulate configuration values for validation properties used for Cassandra inputs
  */
-public interface ValidationConfiguration
+public interface CassandraInputValidationConfiguration
 {
     /**
      * @return a set of forbidden keyspaces
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/HealthCheckConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/HealthCheckConfiguration.java
new file mode 100644
index 0000000..db68807
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/HealthCheckConfiguration.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+/**
+ * Configuration for the health checks
+ */
+public interface HealthCheckConfiguration
+{
+    /**
+     * @return the interval, in milliseconds, in which the health checks will be performed
+     */
+    int checkIntervalMillis();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/InstanceConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/InstanceConfiguration.java
new file mode 100644
index 0000000..39f0c2f
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/InstanceConfiguration.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+import java.util.List;
+
+/**
+ * Encapsulates the basic configuration needed to connect to a single Cassandra instance
+ */
+public interface InstanceConfiguration
+{
+    /**
+     * @return an identifier for the Cassandra instance
+     */
+    int id();
+
+    /**
+     * @return the host address for the Cassandra instance
+     */
+    String host();
+
+    /**
+     * @return the port number for the Cassandra instance
+     */
+    int port();
+
+    /**
+     * @return the username used for connecting to the Cassandra instance
+     */
+    String username();
+
+    /**
+     * @return the password used for connecting to the Cassandra instance
+     */
+    String password();
+
+    /**
+     * @return a list of data directories of cassandra instance
+     */
+    List<String> dataDirs();
+
+    /**
+     * @return staging directory for the uploads of the cassandra instance
+     */
+    String stagingDir();
+
+    /**
+     * @return the host address of the JMX service for the Cassandra instance
+     */
+    String jmxHost();
+
+    /**
+     * @return the port number for the JMX service for the Cassandra instance
+     */
+    int jmxPort();
+
+    /**
+     * @return the port number of the Cassandra instance
+     */
+    boolean jmxSslEnabled();
+
+    /**
+     * @return the name of the JMX role for the JMX service for the Cassandra instance
+     */
+    String jmxRole();
+
+    /**
+     * @return the password for the JMX role for the JMX service for the Cassandra instance
+     */
+    String jmxRolePassword();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/KeyStoreConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/KeyStoreConfiguration.java
new file mode 100644
index 0000000..1fd786d
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/KeyStoreConfiguration.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+/**
+ * Encapsulates key or trust store option configurations
+ */
+public interface KeyStoreConfiguration
+{
+    /**
+     * @return the path to the store
+     */
+    String path();
+
+    /**
+     * @return the password for the store
+     */
+    String password();
+
+    /**
+     * @return the type of the store
+     */
+    String type();
+
+    /**
+     * @return {@code true} if both {@link #path()} and {@link #password()} are provided
+     */
+    default boolean isConfigured()
+    {
+        return path() != null && password() != null;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/SSTableImportConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/SSTableImportConfiguration.java
new file mode 100644
index 0000000..43208dc
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/SSTableImportConfiguration.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+/**
+ * Configuration for the SSTable Import functionality
+ */
+public interface SSTableImportConfiguration
+{
+    /**
+     * @return the interval in milliseconds in which the SSTable Importer will process pending imports
+     */
+    int importIntervalMillis();
+
+    /**
+     * @return the configuration for the cache used for SSTable Import requests
+     */
+    CacheConfiguration cacheConfiguration();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/SSTableUploadConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/SSTableUploadConfiguration.java
new file mode 100644
index 0000000..dd8df66
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/SSTableUploadConfiguration.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+/**
+ * Configuration for SSTable component uploads on this service
+ */
+public interface SSTableUploadConfiguration
+{
+    /**
+     * @return the maximum number of concurrent SSTable component uploads allowed for this service
+     */
+    int concurrentUploadsLimit();
+
+    /**
+     * @return the configured minimum space percentage required for an SSTable component upload
+     */
+    float minimumSpacePercentageRequired();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/ServiceConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/ServiceConfiguration.java
new file mode 100644
index 0000000..8d7af15
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/ServiceConfiguration.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+import java.util.Map;
+
+/**
+ * Configuration for the Sidecar Service and configuration of the REST endpoints in the service
+ */
+public interface ServiceConfiguration
+{
+    String SERVICE_POOL = "service";
+    String INTERNAL_POOL = "internal";
+
+    /**
+     * @return Sidecar's HTTP REST API listen address
+     */
+    String host();
+
+    /**
+     * @return Sidecar's HTTP REST API port
+     */
+    int port();
+
+    /**
+     * Determines if a connection will timeout and be closed if no data is received nor sent within the timeout.
+     * Zero means don't timeout.
+     *
+     * @return the configured idle timeout value
+     */
+    int requestIdleTimeoutMillis();
+
+    /**
+     * @return the amount of time in millis when a response is considered as timed-out after data has not been written
+     */
+    long requestTimeoutMillis();
+
+    /**
+     * @return the maximum time skew allowed between the server and the client
+     */
+    int allowableSkewInMinutes();
+
+    /**
+     * @return the throttling configuration
+     */
+    ThrottleConfiguration throttleConfiguration();
+
+    /**
+     * @return the configuration for SSTable component uploads on this service
+     */
+    SSTableUploadConfiguration ssTableUploadConfiguration();
+
+    /**
+     * @return the configuration for the SSTable Import functionality
+     */
+    SSTableImportConfiguration ssTableImportConfiguration();
+
+    /**
+     * @return the configured worker pools for the service
+     */
+    Map<String, ? extends WorkerPoolConfiguration> workerPoolsConfiguration();
+
+    /**
+     * @return the configuration for the {@link #SERVICE_POOL}
+     */
+    default WorkerPoolConfiguration serverWorkerPoolConfiguration()
+    {
+        return workerPoolsConfiguration().get(SERVICE_POOL);
+    }
+
+    /**
+     * @return the configuration for the {@link #INTERNAL_POOL}
+     */
+    default WorkerPoolConfiguration serverInternalWorkerPoolConfiguration()
+    {
+        return workerPoolsConfiguration().get(INTERNAL_POOL);
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/SidecarConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/SidecarConfiguration.java
new file mode 100644
index 0000000..32f8718
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/SidecarConfiguration.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+import java.util.List;
+
+/**
+ * Configuration for this Sidecar process
+ */
+public interface SidecarConfiguration
+{
+    /**
+     * @return a single configured cassandra instance
+     * @deprecated in favor of configuring multiple instances in the yaml under cassandra_instances
+     */
+    InstanceConfiguration cassandra();
+
+    /**
+     * @return the configured Cassandra instances that this Sidecar manages
+     */
+    List<InstanceConfiguration> cassandraInstances();
+
+    /**
+     * @return the configuration of the REST Services
+     */
+    ServiceConfiguration serviceConfiguration();
+
+    /**
+     * @return the SSL configuration
+     */
+    SslConfiguration sslConfiguration();
+
+    /**
+     * @return the configuration for the health check service
+     */
+    HealthCheckConfiguration healthCheckConfiguration();
+
+    /**
+     * @return the configuration for Cassandra input validation
+     */
+    CassandraInputValidationConfiguration cassandraInputValidationConfiguration();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/SslConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/SslConfiguration.java
new file mode 100644
index 0000000..7a91e97
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/SslConfiguration.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+/**
+ * Encapsulates SSL Configuration
+ */
+public interface SslConfiguration
+{
+    /**
+     * @return {@code true} if SSL is enabled, {@code false} otherwise
+     */
+    boolean enabled();
+
+    /**
+     * @return {@code true} if the keystore is configured, and the {@link KeyStoreConfiguration#path()} and
+     * {@link KeyStoreConfiguration#password()} parameters are provided
+     */
+    default boolean isKeystoreConfigured()
+    {
+        return keystore() != null && keystore().isConfigured();
+    }
+
+    /**
+     * @return the configuration for the keystore
+     */
+    KeyStoreConfiguration keystore();
+
+    /**
+     * @return {@code true} if the truststore is configured, and the {@link KeyStoreConfiguration#path()} and
+     * {@link KeyStoreConfiguration#password()} parameters are provided
+     */
+    default boolean isTruststoreConfigured()
+    {
+        return truststore() != null && truststore().isConfigured();
+    }
+
+    /**
+     * @return the configuration for the truststore
+     */
+    KeyStoreConfiguration truststore();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/ThrottleConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/ThrottleConfiguration.java
new file mode 100644
index 0000000..e4c8af7
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/ThrottleConfiguration.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+/**
+ * The traffic shaping configuration options for the service
+ */
+public interface ThrottleConfiguration
+{
+    long rateLimitStreamRequestsPerSecond();
+
+    long timeoutInSeconds();
+
+    long delayInSeconds();
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/WorkerPoolConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/WorkerPoolConfiguration.java
index 9eb933e..6192068 100644
--- a/src/main/java/org/apache/cassandra/sidecar/config/WorkerPoolConfiguration.java
+++ b/src/main/java/org/apache/cassandra/sidecar/config/WorkerPoolConfiguration.java
@@ -19,21 +19,22 @@
 package org.apache.cassandra.sidecar.config;
 
 /**
- * Values to configure worker pool
+ * Encapsulates configurations for the worker pool
  */
-public class WorkerPoolConfiguration
+public interface WorkerPoolConfiguration
 {
-    public final String workerPoolName;
-    public final int workerPoolSize;
-    // WorkerExecutor logs a warning if the blocking exeuction exceeds the max time configured.
-    // It does not abort the execution. The warning messages look like this.
-    // "Thread xxx has been blocked for yyy ms, time limit is zzz ms"
-    public final long workerMaxExecutionTimeMillis;
+    /**
+     * @return the name of the worker pool
+     */
+    String workerPoolName();
 
-    public WorkerPoolConfiguration(String workerPoolName, int workerPoolSize, long workerMaxExecutionTimeMillis)
-    {
-        this.workerPoolName = workerPoolName;
-        this.workerPoolSize = workerPoolSize;
-        this.workerMaxExecutionTimeMillis = workerMaxExecutionTimeMillis;
-    }
+    /**
+     * @return the size of the worker pool
+     */
+    int workerPoolSize();
+
+    /**
+     * @return the maximum execution time for the worker pool in milliseconds
+     */
+    long workerMaxExecutionTimeMillis();
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImpl.java
new file mode 100644
index 0000000..a56f4a4
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/CacheConfigurationImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import java.util.concurrent.TimeUnit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.CacheConfiguration;
+import org.jetbrains.annotations.VisibleForTesting;
+
+/**
+ * Configuration class that encapsulates parameters needed for Caches
+ */
+public class CacheConfigurationImpl implements CacheConfiguration
+{
+    @JsonProperty("expire_after_access_millis")
+    protected final long expireAfterAccessMillis;
+
+    @JsonProperty("maximum_size")
+    protected final long maximumSize;
+
+    public CacheConfigurationImpl()
+    {
+        this(TimeUnit.HOURS.toMillis(1), 100);
+    }
+
+    @VisibleForTesting
+    public CacheConfigurationImpl(long expireAfterAccessMillis,
+                                  long maximumSize)
+    {
+        this.expireAfterAccessMillis = expireAfterAccessMillis;
+        this.maximumSize = maximumSize;
+    }
+
+    @Override
+    @JsonProperty("expire_after_access_millis")
+    public long expireAfterAccessMillis()
+    {
+        return expireAfterAccessMillis;
+    }
+
+    @Override
+    @JsonProperty("maximum_size")
+    public long maximumSize()
+    {
+        return maximumSize;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/CassandraInputValidationConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/CassandraInputValidationConfigurationImpl.java
new file mode 100644
index 0000000..97d6351
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/CassandraInputValidationConfigurationImpl.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+
+/**
+ * Encapsulate configuration values for validation properties used for Cassandra inputs
+ */
+public class CassandraInputValidationConfigurationImpl implements CassandraInputValidationConfiguration
+{
+    public static final String FORBIDDEN_KEYSPACES_PROPERTY = "forbidden_keyspaces";
+    public static final Set<String> DEFAULT_FORBIDDEN_KEYSPACES =
+    Collections.unmodifiableSet(new HashSet<>(Arrays.asList("system_schema",
+                                                            "system_traces",
+                                                            "system_distributed",
+                                                            "system",
+                                                            "system_auth",
+                                                            "system_views",
+                                                            "system_virtual_schema")));
+    public static final String ALLOWED_CHARS_FOR_DIRECTORY_PROPERTY = "allowed_chars_for_directory";
+    public static final String DEFAULT_ALLOWED_CHARS_FOR_DIRECTORY = "[a-zA-Z0-9_-]+";
+    public static final String ALLOWED_CHARS_FOR_COMPONENT_NAME_PROPERTY = "allowed_chars_for_component_name";
+    public static final String DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME =
+    "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)";
+    public static final String ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME_PROPERTY =
+    "allowed_chars_for_restricted_component_name";
+    public static final String DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME = "[a-zA-Z0-9_-]+(.db|TOC.txt)";
+
+    @JsonProperty(FORBIDDEN_KEYSPACES_PROPERTY)
+    protected final Set<String> forbiddenKeyspaces;
+
+    @JsonProperty(value = ALLOWED_CHARS_FOR_DIRECTORY_PROPERTY, defaultValue = DEFAULT_ALLOWED_CHARS_FOR_DIRECTORY)
+    protected final String allowedPatternForDirectory;
+
+    @JsonProperty(value = ALLOWED_CHARS_FOR_COMPONENT_NAME_PROPERTY,
+    defaultValue = DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME)
+    protected final String allowedPatternForComponentName;
+
+    @JsonProperty(value = ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME_PROPERTY,
+    defaultValue = DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME)
+    protected final String allowedPatternForRestrictedComponentName;
+
+    public CassandraInputValidationConfigurationImpl()
+    {
+        this(DEFAULT_FORBIDDEN_KEYSPACES,
+             DEFAULT_ALLOWED_CHARS_FOR_DIRECTORY,
+             DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME,
+             DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME);
+    }
+
+    public CassandraInputValidationConfigurationImpl(Set<String> forbiddenKeyspaces,
+                                                     String allowedPatternForDirectory,
+                                                     String allowedPatternForComponentName,
+                                                     String allowedPatternForRestrictedComponentName)
+    {
+        this.forbiddenKeyspaces = forbiddenKeyspaces;
+        this.allowedPatternForDirectory = allowedPatternForDirectory;
+        this.allowedPatternForComponentName = allowedPatternForComponentName;
+        this.allowedPatternForRestrictedComponentName = allowedPatternForRestrictedComponentName;
+    }
+
+    /**
+     * @return a set of forbidden keyspaces
+     */
+    @Override
+    @JsonProperty(FORBIDDEN_KEYSPACES_PROPERTY)
+    public Set<String> forbiddenKeyspaces()
+    {
+        return forbiddenKeyspaces;
+    }
+
+    /**
+     * @return a regular expression for an allowed pattern for directory names
+     * (i.e. keyspace directory name or table directory name)
+     */
+    @Override
+    @JsonProperty(value = ALLOWED_CHARS_FOR_DIRECTORY_PROPERTY, defaultValue = DEFAULT_ALLOWED_CHARS_FOR_DIRECTORY)
+    public String allowedPatternForDirectory()
+    {
+        return allowedPatternForDirectory;
+    }
+
+    /**
+     * @return a regular expression for an allowed pattern for component names
+     */
+    @Override
+    @JsonProperty(value = ALLOWED_CHARS_FOR_COMPONENT_NAME_PROPERTY,
+    defaultValue = DEFAULT_ALLOWED_CHARS_FOR_COMPONENT_NAME)
+    public String allowedPatternForComponentName()
+    {
+        return allowedPatternForComponentName;
+    }
+
+    /**
+     * @return a regular expression to an allowed pattern for a subset of component names
+     */
+    @Override
+    @JsonProperty(value = ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME_PROPERTY,
+    defaultValue = DEFAULT_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME)
+    public String allowedPatternForRestrictedComponentName()
+    {
+        return allowedPatternForRestrictedComponentName;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/HealthCheckConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/HealthCheckConfigurationImpl.java
new file mode 100644
index 0000000..bf96cd8
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/HealthCheckConfigurationImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.HealthCheckConfiguration;
+import org.jetbrains.annotations.VisibleForTesting;
+
+/**
+ * Configuration for the health checks
+ */
+public class HealthCheckConfigurationImpl implements HealthCheckConfiguration
+{
+    public static final String POLL_FREQ_MILLIS_PROPERTY = "poll_freq_millis";
+    public static final int DEFAULT_CHECK_INTERVAL_MILLIS = 30000;
+
+    @JsonProperty(value = POLL_FREQ_MILLIS_PROPERTY, defaultValue = DEFAULT_CHECK_INTERVAL_MILLIS + "")
+    protected final int checkIntervalMillis;
+
+    public HealthCheckConfigurationImpl()
+    {
+        this(DEFAULT_CHECK_INTERVAL_MILLIS);
+    }
+
+    @VisibleForTesting
+    public HealthCheckConfigurationImpl(int checkIntervalMillis)
+    {
+        this.checkIntervalMillis = checkIntervalMillis;
+    }
+
+    /**
+     * @return the interval, in milliseconds, in which the health checks will be performed
+     */
+    @Override
+    @JsonProperty(value = POLL_FREQ_MILLIS_PROPERTY, defaultValue = DEFAULT_CHECK_INTERVAL_MILLIS + "")
+    public int checkIntervalMillis()
+    {
+        return checkIntervalMillis;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/InstanceConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/InstanceConfigurationImpl.java
new file mode 100644
index 0000000..9da5ccf
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/InstanceConfigurationImpl.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.InstanceConfiguration;
+
+/**
+ * Encapsulates the basic configuration needed to connect to a single Cassandra instance
+ */
+public class InstanceConfigurationImpl implements InstanceConfiguration
+{
+    @JsonProperty("id")
+    protected final int id;
+
+    @JsonProperty("host")
+    protected final String host;
+
+    @JsonProperty("port")
+    protected final int port;
+
+    @JsonProperty("username")
+    protected final String username;
+
+    @JsonProperty("password")
+    protected final String password;
+
+    @JsonProperty("data_dirs")
+    protected final List<String> dataDirs;
+
+    @JsonProperty("staging_dir")
+    protected final String stagingDir;
+
+    @JsonProperty("jmx_host")
+    protected final String jmxHost;
+
+    @JsonProperty("jmx_port")
+    protected final int jmxPort;
+
+    @JsonProperty("jmx_ssl_enabled")
+    protected final boolean jmxSslEnabled;
+
+    @JsonProperty("jmx_role")
+    protected final String jmxRole;
+
+    @JsonProperty("jmx_role_password")
+    protected final String jmxRolePassword;
+
+    public InstanceConfigurationImpl()
+    {
+        this.id = 0;
+        this.host = null;
+        this.port = 9042;
+        this.username = null;
+        this.password = null;
+        this.dataDirs = null;
+        this.stagingDir = null;
+        this.jmxHost = null;
+        this.jmxPort = 0;
+        this.jmxSslEnabled = false;
+        this.jmxRole = null;
+        this.jmxRolePassword = null;
+    }
+
+    protected InstanceConfigurationImpl(int id,
+                                        String host,
+                                        int port,
+                                        String username,
+                                        String password,
+                                        List<String> dataDirs,
+                                        String stagingDir,
+                                        String jmxHost,
+                                        int jmxPort,
+                                        boolean jmxSslEnabled,
+                                        String jmxRole,
+                                        String jmxRolePassword)
+    {
+        this.id = id;
+        this.host = host;
+        this.port = port;
+        this.username = username;
+        this.password = password;
+        this.dataDirs = Collections.unmodifiableList(dataDirs);
+        this.stagingDir = stagingDir;
+        this.jmxHost = jmxHost;
+        this.jmxPort = jmxPort;
+        this.jmxSslEnabled = jmxSslEnabled;
+        this.jmxRole = jmxRole;
+        this.jmxRolePassword = jmxRolePassword;
+    }
+
+    /**
+     * @return an identifier for the Cassandra instance
+     */
+    @Override
+    @JsonProperty("id")
+    public int id()
+    {
+        return id;
+    }
+
+    /**
+     * @return the host address for the Cassandra instance
+     */
+    @Override
+    @JsonProperty("host")
+    public String host()
+    {
+        return host;
+    }
+
+    /**
+     * @return the port number for the Cassandra instance
+     */
+    @Override
+    @JsonProperty("port")
+    public int port()
+    {
+        return port;
+    }
+
+    /**
+     * @return the username used for connecting to the Cassandra instance
+     */
+    @Override
+    @JsonProperty("username")
+    public String username()
+    {
+        return username;
+    }
+
+    /**
+     * @return the password used for connecting to the Cassandra instance
+     */
+    @Override
+    @JsonProperty("password")
+    public String password()
+    {
+        return password;
+    }
+
+    /**
+     * @return a list of data directories of cassandra instance
+     */
+    @Override
+    @JsonProperty("data_dirs")
+    public List<String> dataDirs()
+    {
+        return dataDirs;
+    }
+
+    /**
+     * @return staging directory for the uploads of the cassandra instance
+     */
+    @Override
+    @JsonProperty("staging_dir")
+    public String stagingDir()
+    {
+        return stagingDir;
+    }
+
+    /**
+     * @return the host address of the JMX service for the Cassandra instance
+     */
+    @Override
+    @JsonProperty("jmx_host")
+    public String jmxHost()
+    {
+        return jmxHost;
+    }
+
+    /**
+     * @return the port number for the JMX service for the Cassandra instance
+     */
+    @Override
+    @JsonProperty("jmx_port")
+    public int jmxPort()
+    {
+        return jmxPort;
+    }
+
+    /**
+     * @return the port number of the Cassandra instance
+     */
+    @Override
+    @JsonProperty("jmx_ssl_enabled")
+    public boolean jmxSslEnabled()
+    {
+        return jmxSslEnabled;
+    }
+
+    /**
+     * @return the name of the JMX role for the JMX service for the Cassandra instance
+     */
+    @Override
+    @JsonProperty("jmx_role")
+    public String jmxRole()
+    {
+        return jmxRole;
+    }
+
+    /**
+     * @return the password for the JMX role for the JMX service for the Cassandra instance
+     */
+    @Override
+    @JsonProperty("jmx_role_password")
+    public String jmxRolePassword()
+    {
+        return jmxRolePassword;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/KeyStoreConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/KeyStoreConfigurationImpl.java
new file mode 100644
index 0000000..450f1eb
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/KeyStoreConfigurationImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.KeyStoreConfiguration;
+
+/**
+ * Encapsulates key or trust store option configurations
+ */
+public class KeyStoreConfigurationImpl implements KeyStoreConfiguration
+{
+    public static final String DEFAULT_TYPE = "JKS";
+
+    @JsonProperty("path")
+    protected final String path;
+
+    @JsonProperty("password")
+    protected final String password;
+
+    @JsonProperty(value = "type", defaultValue = DEFAULT_TYPE)
+    protected final String type;
+
+    public KeyStoreConfigurationImpl()
+    {
+        this(null, null, DEFAULT_TYPE);
+    }
+
+    public KeyStoreConfigurationImpl(String path, String password)
+    {
+        this(path, password, DEFAULT_TYPE);
+    }
+
+    public KeyStoreConfigurationImpl(String path, String password, String type)
+    {
+        this.path = path;
+        this.password = password;
+        this.type = type;
+    }
+
+    /**
+     * @return the path to the store
+     */
+    @Override
+    @JsonProperty("path")
+    public String path()
+    {
+        return path;
+    }
+
+    /**
+     * @return the password for the store
+     */
+    @Override
+    @JsonProperty("password")
+    public String password()
+    {
+        return password;
+    }
+
+    /**
+     * @return the type of the store
+     */
+    @Override
+    @JsonProperty("type")
+    public String type()
+    {
+        return type;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/SSTableImportConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SSTableImportConfigurationImpl.java
new file mode 100644
index 0000000..c82d09d
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SSTableImportConfigurationImpl.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import java.util.concurrent.TimeUnit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.CacheConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableImportConfiguration;
+
+/**
+ * Configuration for the SSTable Import functionality
+ */
+public class SSTableImportConfigurationImpl implements SSTableImportConfiguration
+{
+    public static final String POLL_INTERVAL_MILLIS_PROPERTY = "poll_interval_millis";
+    public static final int DEFAULT_POLL_INTERVAL_MILLIS = 100;
+    public static final String CACHE_PROPERTY = "cache";
+    protected static final CacheConfiguration DEFAULT_CACHE_CONFIGURATION =
+    new CacheConfigurationImpl(TimeUnit.HOURS.toMillis(2), 10_000);
+
+    @JsonProperty(value = POLL_INTERVAL_MILLIS_PROPERTY, defaultValue = DEFAULT_POLL_INTERVAL_MILLIS + "")
+    protected final int importIntervalMillis;
+
+    @JsonProperty(value = CACHE_PROPERTY)
+    protected final CacheConfiguration cacheConfiguration;
+
+    public SSTableImportConfigurationImpl()
+    {
+        this(DEFAULT_POLL_INTERVAL_MILLIS, DEFAULT_CACHE_CONFIGURATION);
+    }
+
+    public SSTableImportConfigurationImpl(CacheConfiguration cacheConfiguration)
+    {
+        this(DEFAULT_POLL_INTERVAL_MILLIS, cacheConfiguration);
+    }
+
+    public SSTableImportConfigurationImpl(int importIntervalMillis)
+    {
+        this(importIntervalMillis, DEFAULT_CACHE_CONFIGURATION);
+    }
+
+    public SSTableImportConfigurationImpl(int importIntervalMillis,
+                                          CacheConfiguration cacheConfiguration)
+    {
+        this.importIntervalMillis = importIntervalMillis;
+        this.cacheConfiguration = cacheConfiguration;
+    }
+
+    /**
+     * @return the interval in milliseconds in which the SSTable Importer will process pending imports
+     */
+    @Override
+    @JsonProperty(value = POLL_INTERVAL_MILLIS_PROPERTY, defaultValue = DEFAULT_POLL_INTERVAL_MILLIS + "")
+    public int importIntervalMillis()
+    {
+        return importIntervalMillis;
+    }
+
+    /**
+     * @return the configuration for the cache used for SSTable Import requests
+     */
+    @Override
+    @JsonProperty(value = CACHE_PROPERTY)
+    public CacheConfiguration cacheConfiguration()
+    {
+        return cacheConfiguration;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/SSTableUploadConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SSTableUploadConfigurationImpl.java
new file mode 100644
index 0000000..cea66cc
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SSTableUploadConfigurationImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.SSTableUploadConfiguration;
+
+/**
+ * Configuration for SSTable component uploads on this service
+ */
+public class SSTableUploadConfigurationImpl implements SSTableUploadConfiguration
+{
+    public static final String CONCURRENT_UPLOAD_LIMIT_PROPERTY = "concurrent_upload_limit";
+    public static final int DEFAULT_CONCURRENT_UPLOAD_LIMIT = 80;
+    public static final String MIN_FREE_SPACE_PERCENT_PROPERTY = "min_free_space_percent";
+    public static final float DEFAULT_MIN_FREE_SPACE_PERCENT = 10;
+
+    @JsonProperty(value = CONCURRENT_UPLOAD_LIMIT_PROPERTY, defaultValue = DEFAULT_CONCURRENT_UPLOAD_LIMIT + "")
+    protected final int concurrentUploadsLimit;
+
+    @JsonProperty(value = MIN_FREE_SPACE_PERCENT_PROPERTY, defaultValue = DEFAULT_MIN_FREE_SPACE_PERCENT + "")
+    protected final float minimumSpacePercentageRequired;
+
+    public SSTableUploadConfigurationImpl()
+    {
+        this(DEFAULT_CONCURRENT_UPLOAD_LIMIT, DEFAULT_MIN_FREE_SPACE_PERCENT);
+    }
+
+    public SSTableUploadConfigurationImpl(int concurrentUploadsLimit)
+    {
+        this(concurrentUploadsLimit, DEFAULT_MIN_FREE_SPACE_PERCENT);
+    }
+
+    public SSTableUploadConfigurationImpl(float minimumSpacePercentageRequired)
+    {
+        this(DEFAULT_CONCURRENT_UPLOAD_LIMIT, minimumSpacePercentageRequired);
+    }
+
+    public SSTableUploadConfigurationImpl(int concurrentUploadsLimit,
+                                          float minimumSpacePercentageRequired)
+    {
+        this.concurrentUploadsLimit = concurrentUploadsLimit;
+        this.minimumSpacePercentageRequired = minimumSpacePercentageRequired;
+    }
+
+    /**
+     * @return the maximum number of concurrent SSTable component uploads allowed for this service
+     */
+    @Override
+    @JsonProperty(value = CONCURRENT_UPLOAD_LIMIT_PROPERTY, defaultValue = DEFAULT_CONCURRENT_UPLOAD_LIMIT + "")
+    public int concurrentUploadsLimit()
+    {
+        return concurrentUploadsLimit;
+    }
+
+    /**
+     * @return the configured minimum space percentage required for an SSTable component upload
+     */
+    @Override
+    @JsonProperty(value = MIN_FREE_SPACE_PERCENT_PROPERTY, defaultValue = DEFAULT_MIN_FREE_SPACE_PERCENT + "")
+    public float minimumSpacePercentageRequired()
+    {
+        return minimumSpacePercentageRequired;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/ServiceConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/ServiceConfigurationImpl.java
new file mode 100644
index 0000000..0716fbc
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/ServiceConfigurationImpl.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.SSTableImportConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableUploadConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.ThrottleConfiguration;
+import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+
+/**
+ * Configuration for the Sidecar Service and configuration of the REST endpoints in the service
+ */
+public class ServiceConfigurationImpl implements ServiceConfiguration
+{
+    public static final String HOST_PROPERTY = "host";
+    public static final String DEFAULT_HOST = "0.0.0.0";
+    public static final String PORT_PROPERTY = "port";
+    public static final int DEFAULT_PORT = 9043;
+    public static final String REQUEST_IDLE_TIMEOUT_MILLIS_PROPERTY = "request_idle_timeout_millis";
+    public static final int DEFAULT_REQUEST_IDLE_TIMEOUT_MILLIS = 300000;
+    public static final String REQUEST_TIMEOUT_MILLIS_PROPERTY = "request_timeout_millis";
+    public static final long DEFAULT_REQUEST_TIMEOUT_MILLIS = 300000L;
+    public static final String ALLOWABLE_SKEW_IN_MINUTES_PROPERTY = "allowable_time_skew_in_minutes";
+    public static final int DEFAULT_ALLOWABLE_SKEW_IN_MINUTES = 60;
+    public static final String THROTTLE_PROPERTY = "throttle";
+    public static final String SSTABLE_UPLOAD_PROPERTY = "sstable_upload";
+    public static final String SSTABLE_IMPORT_PROPERTY = "sstable_import";
+    public static final String WORKER_POOLS_PROPERTY = "worker_pools";
+    protected static final Map<String, WorkerPoolConfiguration> DEFAULT_WORKER_POOLS_CONFIGURATION
+    = Collections.unmodifiableMap(new HashMap<String, WorkerPoolConfiguration>()
+    {{
+        put(SERVICE_POOL, new WorkerPoolConfigurationImpl("sidecar-worker-pool", 20,
+                                                          TimeUnit.SECONDS.toMillis(60)));
+
+        put(INTERNAL_POOL, new WorkerPoolConfigurationImpl("sidecar-internal-worker-pool", 20,
+                                                           TimeUnit.MINUTES.toMillis(15)));
+    }});
+
+
+    @JsonProperty(value = HOST_PROPERTY, defaultValue = DEFAULT_HOST)
+    protected final String host;
+
+    @JsonProperty(value = PORT_PROPERTY, defaultValue = DEFAULT_PORT + "")
+    protected final int port;
+
+    @JsonProperty(value = REQUEST_IDLE_TIMEOUT_MILLIS_PROPERTY, defaultValue = DEFAULT_REQUEST_IDLE_TIMEOUT_MILLIS + "")
+    protected final int requestIdleTimeoutMillis;
+
+    @JsonProperty(value = REQUEST_TIMEOUT_MILLIS_PROPERTY, defaultValue = DEFAULT_REQUEST_TIMEOUT_MILLIS + "")
+    protected final long requestTimeoutMillis;
+
+    @JsonProperty(value = ALLOWABLE_SKEW_IN_MINUTES_PROPERTY, defaultValue = DEFAULT_ALLOWABLE_SKEW_IN_MINUTES + "")
+    protected final int allowableSkewInMinutes;
+
+    @JsonProperty(value = THROTTLE_PROPERTY, required = true)
+    protected final ThrottleConfiguration throttleConfiguration;
+
+    @JsonProperty(value = SSTABLE_UPLOAD_PROPERTY, required = true)
+    protected final SSTableUploadConfiguration ssTableUploadConfiguration;
+
+    @JsonProperty(value = SSTABLE_IMPORT_PROPERTY, required = true)
+    protected final SSTableImportConfiguration ssTableImportConfiguration;
+
+    @JsonProperty(value = WORKER_POOLS_PROPERTY, required = true)
+    protected final Map<String, ? extends WorkerPoolConfiguration> workerPoolsConfiguration;
+
+    public ServiceConfigurationImpl()
+    {
+        this(DEFAULT_HOST,
+             DEFAULT_PORT,
+             DEFAULT_REQUEST_IDLE_TIMEOUT_MILLIS,
+             DEFAULT_REQUEST_TIMEOUT_MILLIS,
+             DEFAULT_ALLOWABLE_SKEW_IN_MINUTES,
+             new ThrottleConfigurationImpl(),
+             new SSTableUploadConfigurationImpl(),
+             new SSTableImportConfigurationImpl(),
+             DEFAULT_WORKER_POOLS_CONFIGURATION);
+    }
+
+    public ServiceConfigurationImpl(SSTableImportConfiguration ssTableImportConfiguration)
+    {
+        this(DEFAULT_HOST,
+             DEFAULT_PORT,
+             DEFAULT_REQUEST_IDLE_TIMEOUT_MILLIS,
+             DEFAULT_REQUEST_TIMEOUT_MILLIS,
+             DEFAULT_ALLOWABLE_SKEW_IN_MINUTES,
+             new ThrottleConfigurationImpl(),
+             new SSTableUploadConfigurationImpl(),
+             ssTableImportConfiguration,
+             DEFAULT_WORKER_POOLS_CONFIGURATION);
+    }
+
+    public ServiceConfigurationImpl(String host,
+                                    ThrottleConfiguration throttleConfiguration,
+                                    SSTableUploadConfiguration ssTableUploadConfiguration)
+    {
+        this(host,
+             DEFAULT_PORT,
+             DEFAULT_REQUEST_IDLE_TIMEOUT_MILLIS,
+             DEFAULT_REQUEST_TIMEOUT_MILLIS,
+             DEFAULT_ALLOWABLE_SKEW_IN_MINUTES,
+             throttleConfiguration,
+             ssTableUploadConfiguration,
+             new SSTableImportConfigurationImpl(),
+             DEFAULT_WORKER_POOLS_CONFIGURATION);
+    }
+
+    public ServiceConfigurationImpl(int requestIdleTimeoutMillis,
+                                    long requestTimeoutMillis,
+                                    SSTableUploadConfiguration ssTableUploadConfiguration)
+    {
+
+        this(DEFAULT_HOST,
+             DEFAULT_PORT,
+             requestIdleTimeoutMillis,
+             requestTimeoutMillis,
+             DEFAULT_ALLOWABLE_SKEW_IN_MINUTES,
+             new ThrottleConfigurationImpl(),
+             ssTableUploadConfiguration,
+             new SSTableImportConfigurationImpl(),
+             DEFAULT_WORKER_POOLS_CONFIGURATION);
+    }
+
+    public ServiceConfigurationImpl(String host,
+                                    int port,
+                                    int requestIdleTimeoutMillis,
+                                    long requestTimeoutMillis,
+                                    int allowableSkewInMinutes,
+                                    ThrottleConfiguration throttleConfiguration,
+                                    SSTableUploadConfiguration ssTableUploadConfiguration,
+                                    SSTableImportConfiguration ssTableImportConfiguration,
+                                    Map<String, ? extends WorkerPoolConfiguration> workerPoolsConfiguration)
+    {
+        this.host = host;
+        this.port = port;
+        this.requestIdleTimeoutMillis = requestIdleTimeoutMillis;
+        this.requestTimeoutMillis = requestTimeoutMillis;
+        this.allowableSkewInMinutes = allowableSkewInMinutes;
+        this.throttleConfiguration = throttleConfiguration;
+        this.ssTableUploadConfiguration = ssTableUploadConfiguration;
+        this.ssTableImportConfiguration = ssTableImportConfiguration;
+        if (workerPoolsConfiguration == null || workerPoolsConfiguration.isEmpty())
+        {
+            this.workerPoolsConfiguration = DEFAULT_WORKER_POOLS_CONFIGURATION;
+        }
+        else
+        {
+            this.workerPoolsConfiguration = workerPoolsConfiguration;
+        }
+    }
+
+    /**
+     * Sidecar's HTTP REST API listen address
+     */
+    @Override
+    @JsonProperty(value = HOST_PROPERTY, defaultValue = DEFAULT_HOST)
+    public String host()
+    {
+        return host;
+    }
+
+    /**
+     * @return Sidecar's HTTP REST API port
+     */
+    @Override
+    @JsonProperty(value = PORT_PROPERTY, defaultValue = DEFAULT_PORT + "")
+    public int port()
+    {
+        return port;
+    }
+
+    /**
+     * Determines if a connection will timeout and be closed if no data is received nor sent within the timeout.
+     * Zero means don't timeout.
+     *
+     * @return the configured idle timeout value
+     */
+    @Override
+    @JsonProperty(value = REQUEST_IDLE_TIMEOUT_MILLIS_PROPERTY, defaultValue = DEFAULT_REQUEST_IDLE_TIMEOUT_MILLIS + "")
+    public int requestIdleTimeoutMillis()
+    {
+        return requestIdleTimeoutMillis;
+    }
+
+    /**
+     * Determines if a response will timeout if the response has not been written after a certain time.
+     */
+    @Override
+    @JsonProperty(value = REQUEST_TIMEOUT_MILLIS_PROPERTY, defaultValue = DEFAULT_REQUEST_TIMEOUT_MILLIS + "")
+    public long requestTimeoutMillis()
+    {
+        return requestTimeoutMillis;
+    }
+
+    /**
+     * @return the maximum time skew allowed between the server and the client
+     */
+    @Override
+    @JsonProperty(value = ALLOWABLE_SKEW_IN_MINUTES_PROPERTY, defaultValue = DEFAULT_ALLOWABLE_SKEW_IN_MINUTES + "")
+    public int allowableSkewInMinutes()
+    {
+        return allowableSkewInMinutes;
+    }
+
+    /**
+     * @return the throttling configuration
+     */
+    @Override
+    @JsonProperty(value = THROTTLE_PROPERTY, required = true)
+    public ThrottleConfiguration throttleConfiguration()
+    {
+        return throttleConfiguration;
+    }
+
+    /**
+     * @return the configuration for SSTable component uploads on this service
+     */
+    @Override
+    @JsonProperty(value = SSTABLE_UPLOAD_PROPERTY, required = true)
+    public SSTableUploadConfiguration ssTableUploadConfiguration()
+    {
+        return ssTableUploadConfiguration;
+    }
+
+    /**
+     * @return the configuration for the SSTable Import functionality
+     */
+    @Override
+    @JsonProperty(value = SSTABLE_IMPORT_PROPERTY, required = true)
+    public SSTableImportConfiguration ssTableImportConfiguration()
+    {
+        return ssTableImportConfiguration;
+    }
+
+    /**
+     * @return the configured worker pools for the service
+     */
+    @Override
+    @JsonProperty(value = WORKER_POOLS_PROPERTY, required = true)
+    public Map<String, ? extends WorkerPoolConfiguration> workerPoolsConfiguration()
+    {
+        return workerPoolsConfiguration;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/SidecarConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SidecarConfigurationImpl.java
new file mode 100644
index 0000000..48b2c10
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SidecarConfigurationImpl.java
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import org.apache.cassandra.sidecar.config.CacheConfiguration;
+import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import org.apache.cassandra.sidecar.config.HealthCheckConfiguration;
+import org.apache.cassandra.sidecar.config.InstanceConfiguration;
+import org.apache.cassandra.sidecar.config.KeyStoreConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableImportConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableUploadConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.SidecarConfiguration;
+import org.apache.cassandra.sidecar.config.SslConfiguration;
+import org.apache.cassandra.sidecar.config.ThrottleConfiguration;
+import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+
+/**
+ * Configuration for this Sidecar process
+ */
+public class SidecarConfigurationImpl implements SidecarConfiguration
+{
+    @Deprecated
+    @JsonProperty(value = "cassandra")
+    protected final InstanceConfiguration cassandraInstance;
+
+    @JsonProperty(value = "cassandra_instances")
+    protected final List<InstanceConfiguration> cassandraInstances;
+
+    @JsonProperty(value = "sidecar", required = true)
+    protected final ServiceConfiguration serviceConfiguration;
+
+    @JsonProperty("ssl")
+    protected final SslConfiguration sslConfiguration;
+
+    @JsonProperty("healthcheck")
+    protected final HealthCheckConfiguration healthCheckConfiguration;
+
+    @JsonProperty("cassandra_input_validation")
+    protected final CassandraInputValidationConfiguration cassandraInputValidationConfiguration;
+
+    public SidecarConfigurationImpl()
+    {
+        this(Collections.emptyList(),
+             new ServiceConfigurationImpl(),
+             null /* sslConfiguration */,
+             new HealthCheckConfigurationImpl(),
+             new CassandraInputValidationConfigurationImpl());
+    }
+
+    public SidecarConfigurationImpl(ServiceConfiguration serviceConfiguration)
+    {
+        this(Collections.emptyList(),
+             serviceConfiguration,
+             null /* sslConfiguration */,
+             new HealthCheckConfigurationImpl(),
+             new CassandraInputValidationConfigurationImpl());
+    }
+
+    public SidecarConfigurationImpl(ServiceConfiguration serviceConfiguration,
+                                    SslConfiguration sslConfiguration,
+                                    HealthCheckConfiguration healthCheckConfiguration)
+    {
+        this(Collections.emptyList(),
+             serviceConfiguration,
+             sslConfiguration,
+             healthCheckConfiguration,
+             new CassandraInputValidationConfigurationImpl());
+    }
+
+    public SidecarConfigurationImpl(List<InstanceConfiguration> cassandraInstances,
+                                    ServiceConfiguration serviceConfiguration,
+                                    SslConfiguration sslConfiguration,
+                                    HealthCheckConfiguration healthCheckConfiguration,
+                                    CassandraInputValidationConfiguration cassandraInputValidationConfiguration)
+    {
+        this.cassandraInstance = null;
+        this.cassandraInstances = Collections.unmodifiableList(cassandraInstances);
+        this.serviceConfiguration = serviceConfiguration;
+        this.sslConfiguration = sslConfiguration;
+        this.healthCheckConfiguration = healthCheckConfiguration;
+        this.cassandraInputValidationConfiguration = cassandraInputValidationConfiguration;
+    }
+
+    /**
+     * @return a single configured cassandra instance
+     * @deprecated in favor of configuring multiple instances in the yaml under cassandra_instances
+     */
+    @Override
+    @JsonProperty(value = "cassandra")
+    @Deprecated
+    public InstanceConfiguration cassandra()
+    {
+        return cassandraInstance;
+    }
+
+    /**
+     * @return the configured Cassandra instances that this Sidecar manages
+     */
+    @Override
+    @JsonProperty(value = "cassandra_instances")
+    public List<InstanceConfiguration> cassandraInstances()
+    {
+        if (cassandraInstance != null)
+        {
+            return Collections.singletonList(cassandraInstance);
+        }
+        else if (cassandraInstances != null && !cassandraInstances.isEmpty())
+        {
+            return Collections.unmodifiableList(cassandraInstances);
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * @return the configuration of the REST Services
+     */
+    @Override
+    @JsonProperty(value = "sidecar", required = true)
+    public ServiceConfiguration serviceConfiguration()
+    {
+        return serviceConfiguration;
+    }
+
+    /**
+     * @return the SSL configuration
+     */
+    @Override
+    @JsonProperty("ssl")
+    public SslConfiguration sslConfiguration()
+    {
+        return sslConfiguration;
+    }
+
+    /**
+     * @return the configuration for the health check service
+     */
+    @Override
+    @JsonProperty("healthcheck")
+    public HealthCheckConfiguration healthCheckConfiguration()
+    {
+        return healthCheckConfiguration;
+    }
+
+    /**
+     * @return the configuration for Cassandra input validation
+     */
+    @Override
+    @JsonProperty("cassandra_input_validation")
+    public CassandraInputValidationConfiguration cassandraInputValidationConfiguration()
+    {
+        return cassandraInputValidationConfiguration;
+    }
+
+    public static SidecarConfigurationImpl readYamlConfiguration(String yamlConfigurationPath) throws IOException
+    {
+        return readYamlConfiguration(Paths.get(yamlConfigurationPath));
+    }
+
+    public static SidecarConfigurationImpl readYamlConfiguration(Path yamlConfigurationPath) throws IOException
+    {
+        SimpleModule simpleModule = new SimpleModule()
+                                    .addAbstractTypeMapping(CacheConfiguration.class,
+                                                            CacheConfigurationImpl.class)
+                                    .addAbstractTypeMapping(CassandraInputValidationConfiguration.class,
+                                                            CassandraInputValidationConfigurationImpl.class)
+                                    .addAbstractTypeMapping(HealthCheckConfiguration.class,
+                                                            HealthCheckConfigurationImpl.class)
+                                    .addAbstractTypeMapping(InstanceConfiguration.class,
+                                                            InstanceConfigurationImpl.class)
+                                    .addAbstractTypeMapping(KeyStoreConfiguration.class,
+                                                            KeyStoreConfigurationImpl.class)
+                                    .addAbstractTypeMapping(SSTableImportConfiguration.class,
+                                                            SSTableImportConfigurationImpl.class)
+                                    .addAbstractTypeMapping(SSTableUploadConfiguration.class,
+                                                            SSTableUploadConfigurationImpl.class)
+                                    .addAbstractTypeMapping(ServiceConfiguration.class,
+                                                            ServiceConfigurationImpl.class)
+                                    .addAbstractTypeMapping(SidecarConfiguration.class,
+                                                            SidecarConfigurationImpl.class)
+                                    .addAbstractTypeMapping(SslConfiguration.class,
+                                                            SslConfigurationImpl.class)
+                                    .addAbstractTypeMapping(ThrottleConfiguration.class,
+                                                            ThrottleConfigurationImpl.class)
+                                    .addAbstractTypeMapping(WorkerPoolConfiguration.class,
+                                                            WorkerPoolConfigurationImpl.class);
+
+        ObjectMapper mapper = new ObjectMapper(new YAMLFactory())
+                              .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
+                              .configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true)
+                              .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+                              .registerModule(simpleModule);
+
+        return mapper.readValue(yamlConfigurationPath.toFile(), SidecarConfigurationImpl.class);
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/SslConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SslConfigurationImpl.java
new file mode 100644
index 0000000..49ef5f6
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/SslConfigurationImpl.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.KeyStoreConfiguration;
+import org.apache.cassandra.sidecar.config.SslConfiguration;
+
+/**
+ * Encapsulates SSL Configuration
+ */
+public class SslConfigurationImpl implements SslConfiguration
+{
+    @JsonProperty("enabled")
+    protected final boolean enabled;
+
+    @JsonProperty("keystore")
+    protected final KeyStoreConfiguration keystore;
+
+    @JsonProperty("truststore")
+    protected final KeyStoreConfiguration truststore;
+
+    public SslConfigurationImpl()
+    {
+        this(false, null, null);
+    }
+
+    public SslConfigurationImpl(boolean enabled,
+                                KeyStoreConfiguration keystore,
+                                KeyStoreConfiguration truststore)
+    {
+        this.enabled = enabled;
+        this.keystore = keystore;
+        this.truststore = truststore;
+    }
+
+    /**
+     * @return {@code true} if SSL is enabled, {@code false} otherwise
+     */
+    @Override
+    @JsonProperty("enabled")
+    public boolean enabled()
+    {
+        return enabled;
+    }
+
+    /**
+     * @return {@code true} if the keystore is configured, and the {@link KeyStoreConfiguration#path()} and
+     * {@link KeyStoreConfiguration#password()} parameters are provided
+     */
+    @Override
+    public boolean isKeystoreConfigured()
+    {
+        return keystore != null && keystore.isConfigured();
+    }
+
+    /**
+     * @return the configuration for the keystore
+     */
+    @Override
+    @JsonProperty("keystore")
+    public KeyStoreConfiguration keystore()
+    {
+        return keystore;
+    }
+
+    /**
+     * @return {@code true} if the truststore is configured, and the {@link KeyStoreConfiguration#path()} and
+     * {@link KeyStoreConfiguration#password()} parameters are provided
+     */
+    @Override
+    public boolean isTruststoreConfigured()
+    {
+        return truststore != null && truststore.isConfigured();
+    }
+
+    /**
+     * @return the configuration for the truststore
+     */
+    @Override
+    @JsonProperty("truststore")
+    public KeyStoreConfiguration truststore()
+    {
+        return truststore;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/ThrottleConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/ThrottleConfigurationImpl.java
new file mode 100644
index 0000000..b4b840f
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/ThrottleConfigurationImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.ThrottleConfiguration;
+
+/**
+ * The traffic shaping configuration options for the service
+ */
+public class ThrottleConfigurationImpl implements ThrottleConfiguration
+{
+    public static final long DEFAULT_STREAM_REQUESTS_PER_SEC = 5000;
+    public static final long DEFAULT_TIMEOUT_SEC = 10;
+    public static final long DEFAULT_DELAY_SEC = 5;
+    public static final String STREAM_REQUESTS_PER_SEC_PROPERTY = "stream_requests_per_sec";
+    public static final String TIMEOUT_SEC_PROPERTY = "timeout_sec";
+    public static final String DELAY_SEC_PROPERTY = "delay_sec";
+
+    @JsonProperty(value = STREAM_REQUESTS_PER_SEC_PROPERTY, defaultValue = DEFAULT_STREAM_REQUESTS_PER_SEC + "")
+    protected final long rateLimitStreamRequestsPerSecond;
+    @JsonProperty(value = TIMEOUT_SEC_PROPERTY, defaultValue = DEFAULT_TIMEOUT_SEC + "")
+    protected final long timeoutInSeconds;
+    @JsonProperty(value = DELAY_SEC_PROPERTY, defaultValue = DEFAULT_DELAY_SEC + "")
+    protected final long delayInSeconds;
+
+    public ThrottleConfigurationImpl()
+    {
+        this(DEFAULT_STREAM_REQUESTS_PER_SEC,
+             DEFAULT_TIMEOUT_SEC,
+             DEFAULT_DELAY_SEC);
+    }
+
+    public ThrottleConfigurationImpl(long rateLimitStreamRequestsPerSecond)
+    {
+        this(rateLimitStreamRequestsPerSecond,
+             DEFAULT_TIMEOUT_SEC,
+             DEFAULT_DELAY_SEC);
+    }
+
+    public ThrottleConfigurationImpl(long rateLimitStreamRequestsPerSecond,
+                                     long timeoutInSeconds,
+                                     long delayInSeconds)
+    {
+        this.rateLimitStreamRequestsPerSecond = rateLimitStreamRequestsPerSecond;
+        this.timeoutInSeconds = timeoutInSeconds;
+        this.delayInSeconds = delayInSeconds;
+    }
+
+    @Override
+    @JsonProperty(value = STREAM_REQUESTS_PER_SEC_PROPERTY, defaultValue = DEFAULT_STREAM_REQUESTS_PER_SEC + "")
+    public long rateLimitStreamRequestsPerSecond()
+    {
+        return rateLimitStreamRequestsPerSecond;
+    }
+
+    @Override
+    @JsonProperty(value = TIMEOUT_SEC_PROPERTY, defaultValue = DEFAULT_TIMEOUT_SEC + "")
+    public long timeoutInSeconds()
+    {
+        return timeoutInSeconds;
+    }
+
+    @Override
+    @JsonProperty(value = DELAY_SEC_PROPERTY, defaultValue = DEFAULT_DELAY_SEC + "")
+    public long delayInSeconds()
+    {
+        return delayInSeconds;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/WorkerPoolConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/WorkerPoolConfigurationImpl.java
new file mode 100644
index 0000000..cb597a5
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/WorkerPoolConfigurationImpl.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config.yaml;
+
+import java.util.concurrent.TimeUnit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+
+/**
+ * Encapsulates configurations for the worker pool
+ */
+public class WorkerPoolConfigurationImpl implements WorkerPoolConfiguration
+{
+    @JsonProperty("name")
+    protected final String workerPoolName;
+
+    @JsonProperty("size")
+    protected final int workerPoolSize;
+
+    // WorkerExecutor logs a warning if the blocking execution exceeds the max time configured.
+    // It does not abort the execution. The warning messages look like this.
+    // "Thread xxx has been blocked for yyy ms, time limit is zzz ms"
+    @JsonProperty("max_execution_time_millis")
+    protected final long workerMaxExecutionTimeMillis;
+
+    public WorkerPoolConfigurationImpl()
+    {
+        this(null, 20, TimeUnit.SECONDS.toMillis(60));
+    }
+
+    public WorkerPoolConfigurationImpl(String workerPoolName,
+                                       int workerPoolSize,
+                                       long workerMaxExecutionTimeMillis)
+    {
+        this.workerPoolName = workerPoolName;
+        this.workerPoolSize = workerPoolSize;
+        this.workerMaxExecutionTimeMillis = workerMaxExecutionTimeMillis;
+    }
+
+    /**
+     * @return the name of the worker pool
+     */
+    @Override
+    @JsonProperty("name")
+    public String workerPoolName()
+    {
+        return workerPoolName;
+    }
+
+    /**
+     * @return the size of the worker pool
+     */
+    @Override
+    @JsonProperty("size")
+    public int workerPoolSize()
+    {
+        return workerPoolSize;
+    }
+
+    /**
+     * @return the maximum execution time for the worker pool in milliseconds
+     */
+    @Override
+    @JsonProperty("max_execution_time_millis")
+    public long workerMaxExecutionTimeMillis()
+    {
+        return workerMaxExecutionTimeMillis;
+    }
+}
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
index 12cd48a..7cd7dc2 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/AbstractHandler.java
@@ -33,8 +33,8 @@
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.common.data.QualifiedTableName;
 import org.apache.cassandra.sidecar.common.exceptions.JmxAuthenticationException;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 
 import static org.apache.cassandra.sidecar.utils.HttpExceptions.wrapHttpException;
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthHandler.java
index 8530015..0366ea8 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/CassandraHealthHandler.java
@@ -27,8 +27,8 @@
 import io.vertx.core.net.SocketAddress;
 import io.vertx.ext.web.RoutingContext;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 
 import static org.apache.cassandra.sidecar.MainModule.NOT_OK_STATUS;
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/RingHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/RingHandler.java
index 7c13e93..f5a78b8 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/RingHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/RingHandler.java
@@ -30,9 +30,9 @@
 import io.vertx.ext.web.RoutingContext;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.StorageOperations;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 import org.apache.cassandra.sidecar.data.RingRequest;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 
 import static org.apache.cassandra.sidecar.utils.HttpExceptions.cassandraServiceUnavailable;
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/SchemaHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/SchemaHandler.java
index 56adc14..b6b7be3 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/SchemaHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/SchemaHandler.java
@@ -28,9 +28,9 @@
 import io.vertx.ext.web.RoutingContext;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.data.SchemaResponse;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 import org.apache.cassandra.sidecar.data.SchemaRequest;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 
 import static org.apache.cassandra.sidecar.utils.HttpExceptions.cassandraServiceUnavailable;
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/SnapshotsHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/SnapshotsHandler.java
index f9e82a1..bf9f106 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/SnapshotsHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/SnapshotsHandler.java
@@ -35,17 +35,17 @@
 import io.vertx.core.net.SocketAddress;
 import io.vertx.ext.web.RoutingContext;
 import io.vertx.ext.web.handler.HttpException;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.StorageOperations;
 import org.apache.cassandra.sidecar.common.data.ListSnapshotFilesResponse;
 import org.apache.cassandra.sidecar.common.exceptions.NodeBootstrappingException;
 import org.apache.cassandra.sidecar.common.exceptions.SnapshotAlreadyExistsException;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 import org.apache.cassandra.sidecar.data.SnapshotRequest;
 import org.apache.cassandra.sidecar.snapshots.SnapshotDirectory;
 import org.apache.cassandra.sidecar.snapshots.SnapshotPathBuilder;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 import org.apache.cassandra.sidecar.utils.RequestUtils;
 
@@ -84,11 +84,11 @@
 {
     private static final String INCLUDE_SECONDARY_INDEX_FILES = "includeSecondaryIndexFiles";
     private final SnapshotPathBuilder builder;
-    private final Configuration configuration;
+    private final ServiceConfiguration configuration;
 
     @Inject
     public SnapshotsHandler(SnapshotPathBuilder builder,
-                            Configuration configuration,
+                            ServiceConfiguration configuration,
                             InstanceMetadataFetcher metadataFetcher,
                             CassandraInputValidator validator,
                             ExecutorPools executorPools)
@@ -197,7 +197,7 @@
                                                     List<SnapshotPathBuilder.SnapshotFile> fileList)
     {
         ListSnapshotFilesResponse response = new ListSnapshotFilesResponse();
-        int sidecarPort = configuration.getPort();
+        int sidecarPort = configuration.port();
         SnapshotDirectory directory = SnapshotDirectory.of(snapshotDirectory);
         int dataDirectoryIndex = dataDirectoryIndex(host, directory.dataDirectory);
         int offset = snapshotDirectory.length() + 1;
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandler.java
index c33c769..6d3abdf 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandler.java
@@ -28,10 +28,10 @@
 import io.vertx.core.http.HttpServerRequest;
 import io.vertx.core.net.SocketAddress;
 import io.vertx.ext.web.RoutingContext;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 import org.apache.cassandra.sidecar.data.StreamSSTableComponentRequest;
 import org.apache.cassandra.sidecar.snapshots.SnapshotPathBuilder;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 
 import static org.apache.cassandra.sidecar.utils.HttpExceptions.wrapHttpException;
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandler.java
index 75f8049..bba6c67 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandler.java
@@ -31,11 +31,11 @@
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.TableOperations;
 import org.apache.cassandra.sidecar.common.data.SSTableImportResponse;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 import org.apache.cassandra.sidecar.data.SSTableImportRequest;
 import org.apache.cassandra.sidecar.routes.AbstractHandler;
 import org.apache.cassandra.sidecar.utils.CacheFactory;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 import org.apache.cassandra.sidecar.utils.SSTableImporter;
 import org.apache.cassandra.sidecar.utils.SSTableUploadsPathBuilder;
diff --git a/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandler.java b/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandler.java
index 4a55101..d14d5b1 100644
--- a/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandler.java
+++ b/src/main/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandler.java
@@ -31,16 +31,17 @@
 import io.vertx.core.http.HttpServerRequest;
 import io.vertx.core.net.SocketAddress;
 import io.vertx.ext.web.RoutingContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.data.SSTableUploadResponse;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ConcurrencyLimiter;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.config.SSTableUploadConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 import org.apache.cassandra.sidecar.data.SSTableUploadRequest;
 import org.apache.cassandra.sidecar.routes.AbstractHandler;
 import org.apache.cassandra.sidecar.stats.SSTableStats;
 import org.apache.cassandra.sidecar.stats.SidecarStats;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.utils.InstanceMetadataFetcher;
 import org.apache.cassandra.sidecar.utils.SSTableUploader;
 import org.apache.cassandra.sidecar.utils.SSTableUploadsPathBuilder;
@@ -55,7 +56,7 @@
 public class SSTableUploadHandler extends AbstractHandler<SSTableUploadRequest>
 {
     private final FileSystem fs;
-    private final Configuration configuration;
+    private final SSTableUploadConfiguration configuration;
     private final SSTableUploader uploader;
     private final SSTableUploadsPathBuilder uploadPathBuilder;
     private final ConcurrencyLimiter limiter;
@@ -64,18 +65,18 @@
     /**
      * Constructs a handler with the provided params.
      *
-     * @param vertx             the vertx instance
-     * @param configuration     configuration object holding config details of Sidecar
-     * @param metadataFetcher   the interface to retrieve metadata
-     * @param uploader          a class that uploads the components
-     * @param uploadPathBuilder a class that provides SSTableUploads directories
-     * @param executorPools     executor pools for blocking executions
-     * @param validator         a validator instance to validate Cassandra-specific input
-     * @param sidecarStats      an interface holding all stats related to main sidecar process
+     * @param vertx                the vertx instance
+     * @param serviceConfiguration configuration object holding config details of Sidecar
+     * @param metadataFetcher      the interface to retrieve metadata
+     * @param uploader             a class that uploads the components
+     * @param uploadPathBuilder    a class that provides SSTableUploads directories
+     * @param executorPools        executor pools for blocking executions
+     * @param validator            a validator instance to validate Cassandra-specific input
+     * @param sidecarStats         an interface holding all stats related to main sidecar process
      */
     @Inject
     protected SSTableUploadHandler(Vertx vertx,
-                                   Configuration configuration,
+                                   ServiceConfiguration serviceConfiguration,
                                    InstanceMetadataFetcher metadataFetcher,
                                    SSTableUploader uploader,
                                    SSTableUploadsPathBuilder uploadPathBuilder,
@@ -85,10 +86,10 @@
     {
         super(metadataFetcher, executorPools, validator);
         this.fs = vertx.fileSystem();
-        this.configuration = configuration;
+        this.configuration = serviceConfiguration.ssTableUploadConfiguration();
         this.uploader = uploader;
         this.uploadPathBuilder = uploadPathBuilder;
-        this.limiter = new ConcurrencyLimiter(configuration::getConcurrentUploadsLimit);
+        this.limiter = new ConcurrencyLimiter(configuration::concurrentUploadsLimit);
         this.stats = sidecarStats.ssTableStats();
     }
 
@@ -204,14 +205,14 @@
 
     /**
      * Ensures there is sufficient space available as per configured in the
-     * {@link Configuration#getMinSpacePercentRequiredForUpload()}.
+     * {@link SSTableUploadConfiguration#minimumSpacePercentageRequired()}.
      *
      * @param uploadDirectory the directory where the SSTables are uploaded
      * @return a succeeded future if there is sufficient space available, or failed future otherwise
      */
     private Future<String> ensureSufficientSpaceAvailable(String uploadDirectory)
     {
-        float minimumPercentageRequired = configuration.getMinSpacePercentRequiredForUpload();
+        float minimumPercentageRequired = configuration.minimumSpacePercentageRequired();
         if (minimumPercentageRequired == 0)
         {
             return Future.succeededFuture(uploadDirectory);
diff --git a/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java b/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
index 413dbab..9f5d9d3 100644
--- a/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
+++ b/src/main/java/org/apache/cassandra/sidecar/snapshots/SnapshotPathBuilder.java
@@ -25,6 +25,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.util.AbstractMap;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
@@ -34,7 +35,6 @@
 import java.util.stream.Stream;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Pair;
 
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -44,11 +44,11 @@
 import io.vertx.core.Vertx;
 import io.vertx.core.file.FileProps;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 import org.apache.cassandra.sidecar.data.SnapshotRequest;
 import org.apache.cassandra.sidecar.data.StreamSSTableComponentRequest;
 import org.apache.cassandra.sidecar.utils.BaseFileSystem;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 
 /**
  * This class builds the snapshot path on a given host validating that it exists
@@ -419,12 +419,11 @@
                        .onFailure(promise::fail)
                        .onSuccess(ar -> {
                            String directory = IntStream.range(0, fileList.size())
-                                                       .mapToObj(i -> Pair.of(fileList.get(i),
-                                                                              ar.<FileProps>resultAt(i)))
-                                                       .filter(pair -> pair.getRight().isDirectory())
-                                                       .max(Comparator.comparingLong(pair -> pair.getRight()
+                                                       .mapToObj(i -> pair(fileList.get(i), ar.<FileProps>resultAt(i)))
+                                                       .filter(pair -> pair.getValue().isDirectory())
+                                                       .max(Comparator.comparingLong(pair -> pair.getValue()
                                                                                                  .lastModifiedTime()))
-                                                       .map(Pair::getLeft)
+                                                       .map(AbstractMap.SimpleEntry::getKey)
                                                        .orElse(null);
 
                            if (directory == null)
@@ -454,4 +453,9 @@
             this.size = size;
         }
     }
+
+    private static <K, V> AbstractMap.SimpleEntry<K, V> pair(K key, V value)
+    {
+        return new AbstractMap.SimpleEntry<>(key, value);
+    }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/BaseFileSystem.java b/src/main/java/org/apache/cassandra/sidecar/utils/BaseFileSystem.java
index fc0b532..07a1910 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/BaseFileSystem.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/BaseFileSystem.java
@@ -29,7 +29,6 @@
 import io.vertx.core.file.FileProps;
 import io.vertx.core.file.FileSystem;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 
 /**
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java b/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java
index 1cd39ae..eaa00ff 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/CacheFactory.java
@@ -32,8 +32,8 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import io.vertx.core.Future;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.config.CacheConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 import org.jetbrains.annotations.VisibleForTesting;
 
 /**
@@ -47,15 +47,16 @@
     private final Cache<SSTableImporter.ImportOptions, Future<Void>> ssTableImportCache;
 
     @Inject
-    public CacheFactory(Configuration configuration, SSTableImporter ssTableImporter)
+    public CacheFactory(ServiceConfiguration configuration, SSTableImporter ssTableImporter)
     {
         this(configuration, ssTableImporter, Ticker.systemTicker());
     }
 
     @VisibleForTesting
-    CacheFactory(Configuration configuration, SSTableImporter ssTableImporter, Ticker ticker)
+    CacheFactory(ServiceConfiguration configuration, SSTableImporter ssTableImporter, Ticker ticker)
     {
-        this.ssTableImportCache = initSSTableImportCache(configuration.ssTableImportCacheConfiguration(),
+        this.ssTableImportCache = initSSTableImportCache(configuration.ssTableImportConfiguration()
+                                                                      .cacheConfiguration(),
                                                          ssTableImporter, ticker);
     }
 
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java b/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
index f91dd6f..dc4bf8c 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/CassandraInputValidator.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.sidecar.common.utils;
+package org.apache.cassandra.sidecar.utils;
 
 import java.io.File;
 import java.util.Objects;
@@ -25,7 +25,10 @@
 import com.google.inject.Singleton;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.ext.web.handler.HttpException;
+import org.apache.cassandra.sidecar.config.CassandraInputValidationConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.CassandraInputValidationConfigurationImpl;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.VisibleForTesting;
 
 /**
  * Miscellaneous methods used for validation.
@@ -33,7 +36,7 @@
 @Singleton
 public class CassandraInputValidator
 {
-    private final ValidationConfiguration validationConfiguration;
+    private final CassandraInputValidationConfiguration validationConfiguration;
 
     /**
      * Constructs a new object with the provided {@code validationConfiguration}
@@ -41,11 +44,17 @@
      * @param validationConfiguration a validation configuration
      */
     @Inject
-    public CassandraInputValidator(ValidationConfiguration validationConfiguration)
+    public CassandraInputValidator(CassandraInputValidationConfiguration validationConfiguration)
     {
         this.validationConfiguration = validationConfiguration;
     }
 
+    @VisibleForTesting
+    public CassandraInputValidator()
+    {
+        this(new CassandraInputValidationConfigurationImpl());
+    }
+
     /**
      * Validates that the {@code keyspace} is not {@code null}, that it contains valid characters, and that it's
      * not a forbidden keyspace.
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java b/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java
index 369d238..358767b 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/FileStreamer.java
@@ -21,7 +21,6 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.concurrent.TimeUnit;
-import static java.util.concurrent.TimeUnit.MICROSECONDS;
 
 import com.google.common.util.concurrent.SidecarRateLimiter;
 import org.slf4j.Logger;
@@ -32,14 +31,16 @@
 import io.vertx.core.Future;
 import io.vertx.core.Promise;
 import io.vertx.ext.web.handler.HttpException;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.common.exceptions.RangeException;
 import org.apache.cassandra.sidecar.common.utils.HttpRange;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.ThrottleConfiguration;
 import org.apache.cassandra.sidecar.models.HttpResponse;
 import org.apache.cassandra.sidecar.stats.SSTableStats;
 import org.apache.cassandra.sidecar.stats.SidecarStats;
 
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
 import static io.netty.handler.codec.http.HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE;
 import static io.netty.handler.codec.http.HttpResponseStatus.TOO_MANY_REQUESTS;
 
@@ -53,16 +54,18 @@
     private static final long DEFAULT_RATE_LIMIT_STREAM_REQUESTS_PER_SECOND = Long.MAX_VALUE;
 
     private final ExecutorPools executorPools;
-    private final Configuration config;
+    private final ThrottleConfiguration config;
     private final SidecarRateLimiter rateLimiter;
     private final SSTableStats stats;
 
     @Inject
-    public FileStreamer(ExecutorPools executorPools, Configuration config, SidecarRateLimiter rateLimiter,
+    public FileStreamer(ExecutorPools executorPools,
+                        ServiceConfiguration config,
+                        SidecarRateLimiter rateLimiter,
                         SidecarStats stats)
     {
         this.executorPools = executorPools;
-        this.config = config;
+        this.config = config.throttleConfiguration();
         this.rateLimiter = rateLimiter;
         this.stats = stats.ssTableStats();
     }
@@ -162,7 +165,7 @@
             promise.fail(new HttpException(TOO_MANY_REQUESTS.code(), "Retry exhausted"));
         }
         else if ((microsToWait = rateLimiter.queryEarliestAvailable(0L))
-                 < TimeUnit.SECONDS.toMicros(config.getThrottleDelayInSeconds()))
+                 < TimeUnit.SECONDS.toMicros(config.delayInSeconds()))
         {
             microsToWait = Math.max(0, microsToWait);
 
@@ -187,7 +190,7 @@
      */
     private boolean isRateLimited()
     {
-        return config.getRateLimitStreamRequestsPerSecond() != DEFAULT_RATE_LIMIT_STREAM_REQUESTS_PER_SECOND;
+        return config.rateLimitStreamRequestsPerSecond() != DEFAULT_RATE_LIMIT_STREAM_REQUESTS_PER_SECOND;
     }
 
     /**
@@ -196,7 +199,7 @@
      */
     private boolean checkRetriesExhausted(Instant startTime)
     {
-        return startTime.plus(Duration.ofSeconds(config.getThrottleTimeoutInSeconds()))
+        return startTime.plus(Duration.ofSeconds(config.timeoutInSeconds()))
                         .isBefore(Instant.now());
     }
 
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/SSTableImporter.java b/src/main/java/org/apache/cassandra/sidecar/utils/SSTableImporter.java
index b962de0..a950acc 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/SSTableImporter.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/SSTableImporter.java
@@ -18,14 +18,15 @@
 
 package org.apache.cassandra.sidecar.utils;
 
+import java.util.AbstractMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.apache.commons.lang3.tuple.Pair;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -36,10 +37,10 @@
 import io.vertx.core.Promise;
 import io.vertx.core.Vertx;
 import io.vertx.ext.web.handler.HttpException;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.TableOperations;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.VisibleForTesting;
 
@@ -78,7 +79,7 @@
     @Inject
     SSTableImporter(Vertx vertx,
                     InstanceMetadataFetcher metadataFetcher,
-                    Configuration configuration,
+                    ServiceConfiguration configuration,
                     ExecutorPools executorPools,
                     SSTableUploadsPathBuilder uploadPathBuilder)
     {
@@ -88,7 +89,8 @@
         this.uploadPathBuilder = uploadPathBuilder;
         this.importQueuePerHost = new ConcurrentHashMap<>();
         executorPools.internal()
-                     .setPeriodic(configuration.getSSTableImportPollIntervalMillis(), this::processPendingImports);
+                     .setPeriodic(configuration.ssTableImportConfiguration().importIntervalMillis(),
+                                  this::processPendingImports);
     }
 
     /**
@@ -102,7 +104,7 @@
     {
         Promise<Void> promise = Promise.promise();
         importQueuePerHost.computeIfAbsent(key(options), this::initializeQueue)
-                          .offer(Pair.of(promise, options));
+                          .offer(new AbstractMap.SimpleEntry<>(promise, options));
         return promise.future();
     }
 
@@ -119,7 +121,7 @@
         boolean removed = false;
         if (queue != null)
         {
-            removed = queue.removeIf(tuple -> options.equals(tuple.getRight()));
+            removed = queue.removeIf(tuple -> options.equals(tuple.getValue()));
         }
 
         LOGGER.debug("Cancel import for options={} was {}removed", options, removed ? "" : "not ");
@@ -196,24 +198,26 @@
      */
     private void drainImportQueue(ImportQueue queue)
     {
+        int successCount = 0, failureCount = 0;
         while (!queue.isEmpty())
         {
-            Pair<Promise<Void>, ImportOptions> pair = queue.poll();
-            Promise<Void> promise = pair.getLeft();
-            ImportOptions options = pair.getRight();
+            LOGGER.info("Starting SSTable import session");
+            AbstractMap.SimpleEntry<Promise<Void>, ImportOptions> pair = queue.poll();
+            Promise<Void> promise = pair.getKey();
+            ImportOptions options = pair.getValue();
 
             CassandraAdapterDelegate cassandra = metadataFetcher.delegate(options.host);
             TableOperations tableOperations = cassandra.tableOperations();
 
             if (tableOperations == null)
             {
-                promise.fail(new HttpException(HttpResponseStatus.SERVICE_UNAVAILABLE.code(),
-                                               "Cassandra service is unavailable"));
+                promise.fail(HttpExceptions.cassandraServiceUnavailable());
             }
             else
             {
                 try
                 {
+                    long startTime = System.nanoTime();
                     List<String> failedDirectories =
                     tableOperations.importNewSSTables(options.keyspace,
                                                       options.tableName,
@@ -225,24 +229,39 @@
                                                       options.invalidateCaches,
                                                       options.extendedVerify,
                                                       options.copyData);
+                    long serviceTimeNanos = System.nanoTime() - startTime;
                     if (!failedDirectories.isEmpty())
                     {
+                        failureCount++;
+                        LOGGER.error("Failed to import SSTables with options={}, serviceTimeMillis={}, " +
+                                     "failedDirectories={}", options, TimeUnit.NANOSECONDS.toMillis(serviceTimeNanos),
+                                     failedDirectories);
                         promise.fail(new HttpException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
                                                        "Failed to import from directories: " + failedDirectories));
                     }
                     else
                     {
+                        successCount++;
+                        LOGGER.info("Successfully imported SSTables with options={}, serviceTimeMillis={}",
+                                    options, TimeUnit.NANOSECONDS.toMillis(serviceTimeNanos));
                         promise.complete();
                         cleanup(options);
                     }
                 }
                 catch (Exception exception)
                 {
+                    failureCount++;
                     LOGGER.error("Failed to import SSTables with options={}", options, exception);
                     promise.fail(exception);
                 }
             }
         }
+
+        if (successCount > 0 || failureCount > 0)
+        {
+            LOGGER.info("Finished SSTable import session with successCount={}, failureCount={}",
+                        successCount, failureCount);
+        }
     }
 
     /**
@@ -269,7 +288,7 @@
      * A {@link ConcurrentLinkedQueue} that allows for locking the queue while operating on it. The queue
      * must be unlocked once the operations are complete.
      */
-    static class ImportQueue extends ConcurrentLinkedQueue<Pair<Promise<Void>, ImportOptions>>
+    static class ImportQueue extends ConcurrentLinkedQueue<AbstractMap.SimpleEntry<Promise<Void>, ImportOptions>>
     {
         private final AtomicBoolean isQueueInUse = new AtomicBoolean(false);
 
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/SSTableUploadsPathBuilder.java b/src/main/java/org/apache/cassandra/sidecar/utils/SSTableUploadsPathBuilder.java
index 9a26a54..07df6e6 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/SSTableUploadsPathBuilder.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/SSTableUploadsPathBuilder.java
@@ -31,7 +31,6 @@
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.common.data.SSTableUploads;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
 import org.apache.cassandra.sidecar.data.SSTableUploadRequest;
 
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/SidecarYaml.java b/src/main/java/org/apache/cassandra/sidecar/utils/SidecarYaml.java
deleted file mode 100644
index 96e0cfc..0000000
--- a/src/main/java/org/apache/cassandra/sidecar/utils/SidecarYaml.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.sidecar.utils;
-
-/**
- * 1. Stores the property keys used to retrieve information from sidecar.yaml file.
- * 2. Utilities that help to read yaml
- */
-public class SidecarYaml
-{
-    public static final String HOST = "sidecar.host";
-    public static final String PORT = "sidecar.port";
-    public static final String HEALTH_CHECK_INTERVAL = "healthcheck.poll_freq_millis";
-    public static final String KEYSTORE_PATH = "sidecar.ssl.keystore.path";
-    public static final String KEYSTORE_PASSWORD = "sidecar.ssl.keystore.password";
-    public static final String TRUSTSTORE_PATH = "sidecar.ssl.truststore.path";
-    public static final String TRUSTSTORE_PASSWORD = "sidecar.ssl.truststore.password";
-    public static final String SSL_ENABLED = "sidecar.ssl.enabled";
-    public static final String STREAM_REQUESTS_PER_SEC = "sidecar.throttle.stream_requests_per_sec";
-    public static final String THROTTLE_TIMEOUT_SEC = "sidecar.throttle.timeout_sec";
-    public static final String THROTTLE_DELAY_SEC = "sidecar.throttle.delay_sec";
-    public static final String ALLOWABLE_SKEW_IN_MINUTES = "sidecar.allowable_time_skew_in_minutes";
-    public static final String REQUEST_IDLE_TIMEOUT_MILLIS = "sidecar.request_idle_timeout_millis";
-    public static final String REQUEST_TIMEOUT_MILLIS = "sidecar.request_timeout_millis";
-    public static final String MIN_FREE_SPACE_PERCENT_FOR_UPLOAD = "sidecar.sstable_upload.min_free_space_percent";
-    public static final String CONCURRENT_UPLOAD_LIMIT = "sidecar.sstable_upload.concurrent_upload_limit";
-    public static final String SSTABLE_IMPORT_POLL_INTERVAL_MILLIS = "sidecar.sstable_import.poll_interval_millis";
-    public static final String SSTABLE_IMPORT_CACHE_CONFIGURATION = "sidecar.sstable_import.cache";
-
-    // v1 cassandra instance key constants
-    public static final String CASSANDRA_INSTANCE = "cassandra";
-
-    // v2 cassandra instances key constants
-    public static final String CASSANDRA_INSTANCES = "cassandra_instances";
-    public static final String CASSANDRA_INSTANCE_ID = "id";
-    public static final String CASSANDRA_INSTANCE_HOST = "host";
-    public static final String CASSANDRA_INSTANCE_PORT = "port";
-    public static final String CASSANDRA_INSTANCE_DATA_DIRS = "data_dirs";
-    public static final String CASSANDRA_INSTANCE_STAGING_DIR = "staging_dir";
-    public static final String CASSANDRA_JMX_HOST = "jmx_host";
-    public static final String CASSANDRA_JMX_PORT = "jmx_port";
-    public static final String CASSANDRA_JMX_ROLE = "jmx_role";
-    public static final String CASSANDRA_JMX_ROLE_PASSWORD = "jmx_role_password";
-    public static final String CASSANDRA_JMX_SSL_ENABLED = "jmx_ssl_enabled";
-
-    // validation for cassandra inputs
-    public static final String CASSANDRA_INPUT_VALIDATION = "cassandra_input_validation";
-    public static final String CASSANDRA_FORBIDDEN_KEYSPACES = "forbidden_keyspaces";
-    public static final String CASSANDRA_ALLOWED_CHARS_FOR_DIRECTORY = "allowed_chars_for_directory";
-    public static final String CASSANDRA_ALLOWED_CHARS_FOR_COMPONENT_NAME = "allowed_chars_for_component_name";
-    public static final String CASSANDRA_ALLOWED_CHARS_FOR_RESTRICTED_COMPONENT_NAME =
-    "allowed_chars_for_restricted_component_name";
-
-    // cache configuration
-    public static final String CACHE_EXPIRE_AFTER_ACCESS_MILLIS = "expire_after_access_millis";
-    public static final String CACHE_MAXIMUM_SIZE = "maximum_size";
-
-    // worker pools configuration
-    private static final String WORKER_POOL_PREFIX = "sidecar.worker_pools.";
-    public static final String WORKER_POOL_FOR_SERVICE = WORKER_POOL_PREFIX + "service";
-    public static final String WORKER_POOL_FOR_INTERNAL = WORKER_POOL_PREFIX + "interal";
-    public static final String WORKER_POOL_NAME = "name";
-    public static final String WORKER_POOL_SIZE = "size";
-    public static final String WORKER_POOL_MAX_EXECUTION_TIME_MILLIS = "max_execution_time_millis";
-}
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/TimeSkewInfo.java b/src/main/java/org/apache/cassandra/sidecar/utils/TimeSkewInfo.java
index 574d1b4..dae816f 100644
--- a/src/main/java/org/apache/cassandra/sidecar/utils/TimeSkewInfo.java
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/TimeSkewInfo.java
@@ -20,8 +20,8 @@
 
 import javax.inject.Inject;
 
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.common.data.TimeSkewResponse;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 
 /**
  * Determine the time skew info for clients' queries.
@@ -29,10 +29,10 @@
 public class TimeSkewInfo
 {
     private final TimeProvider timeProvider;
-    private final Configuration configuration;
+    private final ServiceConfiguration configuration;
 
     @Inject
-    public TimeSkewInfo(TimeProvider timeProvider, Configuration configuration)
+    public TimeSkewInfo(TimeProvider timeProvider, ServiceConfiguration configuration)
     {
         this.timeProvider = timeProvider;
         this.configuration = configuration;
diff --git a/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestBase.java b/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestBase.java
index 42c4a74..5ed8268 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestBase.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestBase.java
@@ -44,6 +44,7 @@
 import io.vertx.core.http.HttpServer;
 import io.vertx.ext.web.client.WebClient;
 import io.vertx.junit5.VertxTestContext;
+import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.common.data.QualifiedTableName;
 import org.apache.cassandra.sidecar.common.dns.DnsResolver;
@@ -62,7 +63,7 @@
     protected Logger logger = LoggerFactory.getLogger(this.getClass());
     protected Vertx vertx;
     protected HttpServer server;
-    protected Configuration config;
+    protected InstancesConfig instancesConfig;
 
     protected static final String TEST_KEYSPACE = "testkeyspace";
     private static final String TEST_TABLE_PREFIX = "testtable";
@@ -76,14 +77,14 @@
         Injector injector = Guice.createInjector(Modules
                                                  .override(new MainModule())
                                                  .with(new IntegrationTestModule(this.sidecarTestContext)));
+        instancesConfig = injector.getInstance(InstancesConfig.class);
         server = injector.getInstance(HttpServer.class);
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
 
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), config.getHost(), context.succeeding(p -> {
-            config.getInstancesConfig().instances()
-                  .forEach(instanceMetadata -> instanceMetadata.delegate().healthCheck());
+        server.listen(server.actualPort(), "127.0.0.1", context.succeeding(p -> {
+            instancesConfig.instances()
+                           .forEach(instanceMetadata -> instanceMetadata.delegate().healthCheck());
             context.completeNow();
         }));
 
diff --git a/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java b/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java
index f936952..9be4ead 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/IntegrationTestModule.java
@@ -22,10 +22,14 @@
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
-import org.apache.cassandra.sidecar.config.CacheConfiguration;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableUploadConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.SidecarConfiguration;
+import org.apache.cassandra.sidecar.config.ThrottleConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.SSTableUploadConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.ThrottleConfigurationImpl;
 import org.apache.cassandra.sidecar.testing.CassandraSidecarTestContext;
 
 /**
@@ -49,31 +53,13 @@
 
     @Provides
     @Singleton
-    public Configuration configuration(InstancesConfig instancesConfig,
-                                       ValidationConfiguration validationConfiguration)
+    public SidecarConfiguration configuration()
     {
-        WorkerPoolConfiguration workPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                           30000);
-        return new Configuration.Builder()
-               .setInstancesConfig(instancesConfig)
-               .setHost("127.0.0.1")
-               .setPort(9043)
-               .setRateLimitStreamRequestsPerSecond(1000L)
-               .setValidationConfiguration(validationConfiguration)
-               .setRequestIdleTimeoutMillis(300_000)
-               .setRequestTimeoutMillis(300_000L)
-               .setConcurrentUploadsLimit(80)
-               .setMinSpacePercentRequiredForUploads(0)
-               .setSSTableImportCacheConfiguration(new CacheConfiguration(60_000, 100))
-               .setServerWorkerPoolConfiguration(workPoolConf)
-               .setServerInternalWorkerPoolConfiguration(workPoolConf)
-               .build();
-    }
-
-    @Provides
-    @Singleton
-    public ValidationConfiguration validationConfiguration()
-    {
-        return new TestValidationConfiguration();
+        ThrottleConfiguration throttleConfiguration = new ThrottleConfigurationImpl(1000L);
+        SSTableUploadConfiguration ssTableUploadConfiguration = new SSTableUploadConfigurationImpl(0F);
+        ServiceConfiguration serviceConfiguration = new ServiceConfigurationImpl("127.0.0.1",
+                                                                                 throttleConfiguration,
+                                                                                 ssTableUploadConfiguration);
+        return new SidecarConfigurationImpl(serviceConfiguration);
     }
 }
diff --git a/src/test/integration/org/apache/cassandra/sidecar/routes/GossipInfoHandlerIntegrationTest.java b/src/test/integration/org/apache/cassandra/sidecar/routes/GossipInfoHandlerIntegrationTest.java
index 4ad7a48..9fd8fb1 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/routes/GossipInfoHandlerIntegrationTest.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/routes/GossipInfoHandlerIntegrationTest.java
@@ -41,7 +41,7 @@
     {
         String testRoute = "/api/v1/cassandra/gossip";
         testWithClient(context, client -> {
-            client.get(config.getPort(), "127.0.0.1", testRoute)
+            client.get(server.actualPort(), "127.0.0.1", testRoute)
                   .expect(ResponsePredicate.SC_OK)
                   .send(context.succeeding(response -> {
                       GossipInfoResponse gossipResponse = response.bodyAsJson(GossipInfoResponse.class);
diff --git a/src/test/integration/org/apache/cassandra/sidecar/routes/RingHandlerIntegrationTest.java b/src/test/integration/org/apache/cassandra/sidecar/routes/RingHandlerIntegrationTest.java
index d2b06a2..b277a32 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/routes/RingHandlerIntegrationTest.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/routes/RingHandlerIntegrationTest.java
@@ -52,7 +52,7 @@
     {
         String testRoute = "/api/v1/cassandra/ring";
         testWithClient(context, client -> {
-            client.get(config.getPort(), "127.0.0.1", testRoute)
+            client.get(server.actualPort(), "127.0.0.1", testRoute)
                   .expect(ResponsePredicate.SC_OK)
                   .send(context.succeeding(response -> {
                       assertRingResponseOK(response, sidecarTestContext);
@@ -90,7 +90,7 @@
     {
         String testRoute = "/api/v1/cassandra/ring/keyspaces/" + keyspace;
         testWithClient(context, client -> {
-            client.get(config.getPort(), "127.0.0.1", testRoute)
+            client.get(server.actualPort(), "127.0.0.1", testRoute)
                   .send(context.succeeding(verifier));
         });
     }
diff --git a/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java b/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java
index 26b99f6..c6d7b6e 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/routes/SnapshotsHandlerIntegrationTest.java
@@ -46,7 +46,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/keyspaces/non-existent/tables/testtable/snapshots/my-snapshot";
-        client.put(config.getPort(), "127.0.0.1", testRoute)
+        client.put(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_NOT_FOUND)
               .send(context.succeedingThenComplete());
         // wait until the test completes
@@ -61,7 +61,7 @@
 
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/keyspaces/testkeyspace/tables/non-existent/snapshots/my-snapshot";
-        client.put(config.getPort(), "127.0.0.1", testRoute)
+        client.put(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_NOT_FOUND)
               .send(context.succeedingThenComplete());
         // wait until the test completes
@@ -78,7 +78,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = String.format("/api/v1/keyspaces/%s/tables/%s/snapshots/my-snapshot",
                                          TEST_KEYSPACE, table);
-        client.put(config.getPort(), "127.0.0.1", testRoute)
+        client.put(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_OK)
               .send(context.succeeding(response ->
                                        context.verify(() -> {
@@ -86,7 +86,7 @@
 
                                            // creating the snapshot with the same name will return
                                            // a 409 (Conflict) status code
-                                           client.put(config.getPort(), "127.0.0.1", testRoute)
+                                           client.put(server.actualPort(), "127.0.0.1", testRoute)
                                                  .expect(ResponsePredicate.SC_CONFLICT)
                                                  .send(context.succeedingThenComplete());
                                        })));
@@ -104,7 +104,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = String.format("/api/v1/keyspaces/%s/tables/%s/snapshots/my-snapshot",
                                          TEST_KEYSPACE, table);
-        client.put(config.getPort(), "127.0.0.1", testRoute)
+        client.put(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_OK)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
@@ -169,7 +169,7 @@
                                          TEST_KEYSPACE, table, snapshotName);
 
         // first create the snapshot
-        client.put(config.getPort(), "127.0.0.1", testRoute)
+        client.put(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_OK)
               .send(context.succeeding(
               createResponse ->
@@ -183,7 +183,7 @@
                   assertThat(found).isNotEmpty();
 
                   // then delete the snapshot
-                  client.delete(config.getPort(), "127.0.0.1", testRoute)
+                  client.delete(server.actualPort(), "127.0.0.1", testRoute)
                         .expect(ResponsePredicate.SC_OK)
                         .send(context.succeeding(
                         deleteResponse ->
@@ -216,7 +216,7 @@
     private void assertNotFoundOnDeleteSnapshot(VertxTestContext context, String testRoute) throws InterruptedException
     {
         WebClient client = WebClient.create(vertx);
-        client.delete(config.getPort(), "127.0.0.1", testRoute)
+        client.delete(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_NOT_FOUND)
               .send(context.succeedingThenComplete());
         // wait until test completes
diff --git a/src/test/integration/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerIntegrationTest.java b/src/test/integration/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerIntegrationTest.java
index 56b6587..118e575 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerIntegrationTest.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerIntegrationTest.java
@@ -130,7 +130,7 @@
         String testRoute = "/api/v1/uploads/" + uploadId + "/keyspaces/" + tableName.keyspace()
                            + "/tables/" + tableName.tableName() + "/import";
         sendRequest(vertxTestContext,
-                    () -> client.put(config.getPort(), "127.0.0.1", testRoute),
+                    () -> client.put(server.actualPort(), "127.0.0.1", testRoute),
                     vertxTestContext.succeeding(response -> vertxTestContext.verify(() -> {
                         assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
                         assertThat(queryValues(sidecarTestContext, tableName))
diff --git a/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java b/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java
index 52b4851..d4a21a5 100644
--- a/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java
+++ b/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java
@@ -39,6 +39,7 @@
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadataImpl;
 import org.apache.cassandra.sidecar.common.CQLSessionProvider;
+import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
 import org.apache.cassandra.sidecar.common.JmxClient;
 import org.apache.cassandra.sidecar.common.SimpleCassandraVersion;
@@ -122,19 +123,22 @@
             // Use the parent of the first data directory as the staging directory
             Path dataDirParentPath = Paths.get(dataDirectories[0]).getParent();
             assertThat(dataDirParentPath).isNotNull()
-                      .exists();
+                                         .exists();
             Path stagingPath = dataDirParentPath.resolve("staging");
             String uploadsStagingDirectory = stagingPath.toFile().getAbsolutePath();
             Files.createDirectories(stagingPath);
-            metadata.add(new InstanceMetadataImpl(i + 1,
-                                                  config.broadcastAddress().getAddress().getHostAddress(),
-                                                  nativeTransportPort,
-                                                  Arrays.asList(dataDirectories),
-                                                  uploadsStagingDirectory,
-                                                  sessionProvider,
-                                                  jmxClient,
-                                                  versionProvider,
-                                                  "1.0-TEST"));
+            CassandraAdapterDelegate delegate = new CassandraAdapterDelegate(versionProvider,
+                                                                             sessionProvider,
+                                                                             jmxClient,
+                                                                             "1.0-TEST");
+            metadata.add(InstanceMetadataImpl.builder()
+                                             .id(i + 1)
+                                             .host(config.broadcastAddress().getAddress().getHostAddress())
+                                             .port(nativeTransportPort)
+                                             .dataDirs(Arrays.asList(dataDirectories))
+                                             .stagingDir(uploadsStagingDirectory)
+                                             .delegate(delegate)
+                                             .build());
         }
         return new InstancesConfigImpl(metadata, dnsResolver);
     }
diff --git a/src/test/java/org/apache/cassandra/sidecar/LoggerHandlerInjectionTest.java b/src/test/java/org/apache/cassandra/sidecar/LoggerHandlerInjectionTest.java
index e3facb0..50ae53e 100644
--- a/src/test/java/org/apache/cassandra/sidecar/LoggerHandlerInjectionTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/LoggerHandlerInjectionTest.java
@@ -59,7 +59,6 @@
 public class LoggerHandlerInjectionTest
 {
     private Vertx vertx;
-    private Configuration config;
     private final Logger logger = mock(Logger.class);
     private HttpServer server;
     private FakeLoggerHandler loggerHandler;
@@ -73,14 +72,13 @@
                                                         .with(binder -> binder.bind(LoggerHandler.class)
                                                                               .toInstance(loggerHandler)));
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
         Router router = injector.getInstance(Router.class);
 
         router.get("/fake-route").handler(promise -> promise.json("done"));
 
         VertxTestContext context = new VertxTestContext();
         server = injector.getInstance(HttpServer.class);
-        server.listen(config.getPort(), context.succeedingThenComplete());
+        server.listen(0, context.succeedingThenComplete());
 
         context.awaitCompletion(5, TimeUnit.SECONDS);
     }
@@ -110,7 +108,7 @@
             verify(logger, times(1)).info("{}", HttpResponseStatus.OK.code());
             testContext.completeNow();
         });
-        client.get(config.getPort(), "localhost", "/fake-route")
+        client.get(server.actualPort(), "localhost", "/fake-route")
               .as(BodyCodec.string()).ssl(false)
               .send(testContext.succeeding(responseVerifier));
     }
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestModule.java b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
index 9b6b2f9..3452b88 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
@@ -34,11 +34,18 @@
 import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
 import org.apache.cassandra.sidecar.common.MockCassandraFactory;
 import org.apache.cassandra.sidecar.common.NodeSettings;
-import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
 import org.apache.cassandra.sidecar.common.dns.DnsResolver;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
-import org.apache.cassandra.sidecar.config.CacheConfiguration;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.HealthCheckConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableUploadConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.SidecarConfiguration;
+import org.apache.cassandra.sidecar.config.SslConfiguration;
+import org.apache.cassandra.sidecar.config.ThrottleConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.HealthCheckConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.SSTableUploadConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.ThrottleConfigurationImpl;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -57,32 +64,25 @@
 
     @Provides
     @Singleton
-    public Configuration configuration(InstancesConfig instancesConfig)
+    public SidecarConfiguration configuration()
     {
-        return abstractConfig(instancesConfig);
+        return abstractConfig();
     }
 
-    protected Configuration abstractConfig(InstancesConfig instancesConfig)
+    protected SidecarConfigurationImpl abstractConfig()
     {
-        WorkerPoolConfiguration workPoolConf = new WorkerPoolConfiguration("test-pool", 10, 30000);
-        return new Configuration.Builder<>()
-               .setInstancesConfig(instancesConfig)
-               .setHost("127.0.0.1")
-               .setPort(6475)
-               .setHealthCheckFrequency(1000)
-               .setSslEnabled(false)
-               .setRateLimitStreamRequestsPerSecond(1)
-               .setThrottleDelayInSeconds(5)
-               .setThrottleTimeoutInSeconds(10)
-               .setAllowableSkewInMinutes(60)
-               .setRequestIdleTimeoutMillis(300_000)
-               .setRequestTimeoutMillis(300_000L)
-               .setConcurrentUploadsLimit(80)
-               .setMinSpacePercentRequiredForUploads(0)
-               .setSSTableImportCacheConfiguration(new CacheConfiguration(60_000, 100))
-               .setServerWorkerPoolConfiguration(workPoolConf)
-               .setServerInternalWorkerPoolConfiguration(workPoolConf)
-               .build();
+        return abstractConfig(null);
+    }
+
+    protected SidecarConfigurationImpl abstractConfig(SslConfiguration sslConfiguration)
+    {
+        ThrottleConfiguration throttleConfiguration = new ThrottleConfigurationImpl(1, 10, 5);
+        SSTableUploadConfiguration uploadConfiguration = new SSTableUploadConfigurationImpl(0F);
+        ServiceConfiguration serviceConfiguration = new ServiceConfigurationImpl("127.0.0.1",
+                                                                                 throttleConfiguration,
+                                                                                 uploadConfiguration);
+        HealthCheckConfiguration healthCheckConfiguration = new HealthCheckConfigurationImpl(1000);
+        return new SidecarConfigurationImpl(serviceConfiguration, sslConfiguration, healthCheckConfiguration);
     }
 
     @Provides
@@ -116,7 +116,7 @@
         if (isUp)
         {
             when(delegate.nodeSettings()).thenReturn(new NodeSettings(
-                    "testVersion", "testPartitioner", Collections.singletonMap("version", "testSidecar")));
+            "testVersion", "testPartitioner", Collections.singletonMap("version", "testSidecar")));
         }
         when(delegate.isUp()).thenReturn(isUp);
         when(instanceMeta.delegate()).thenReturn(delegate);
@@ -143,11 +143,4 @@
         builder.add(new MockCassandraFactory());
         return builder.build();
     }
-
-    @Provides
-    @Singleton
-    public ValidationConfiguration validationConfiguration()
-    {
-        return new TestValidationConfiguration();
-    }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
index bef0208..0539a6f 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
@@ -24,9 +24,10 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.config.CacheConfiguration;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.SslConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.KeyStoreConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.SslConfigurationImpl;
 
 import static org.apache.cassandra.sidecar.common.ResourceUtils.writeResourceToPath;
 
@@ -45,7 +46,7 @@
     }
 
     @Override
-    public Configuration abstractConfig(InstancesConfig instancesConfig)
+    public SidecarConfigurationImpl abstractConfig()
     {
         ClassLoader classLoader = TestSslModule.class.getClassLoader();
         Path keyStorePath = writeResourceToPath(classLoader, certPath, "certs/test.p12");
@@ -63,27 +64,13 @@
             logger.error("Trust Store file not found in path={}", trustStorePath);
         }
 
-        WorkerPoolConfiguration workerPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                             30000);
+        SslConfiguration sslConfiguration =
+        new SslConfigurationImpl(true,
+                                 new KeyStoreConfigurationImpl(keyStorePath.toAbsolutePath().toString(),
+                                                               keyStorePassword),
+                                 new KeyStoreConfigurationImpl(trustStorePath.toAbsolutePath().toString(),
+                                                               trustStorePassword));
 
-        return new Configuration.Builder<>()
-               .setInstancesConfig(instancesConfig)
-               .setHost("127.0.0.1")
-               .setPort(6475)
-               .setHealthCheckFrequency(1000)
-               .setKeyStorePath(keyStorePath.toAbsolutePath().toString())
-               .setKeyStorePassword(keyStorePassword)
-               .setTrustStorePath(trustStorePath.toAbsolutePath().toString())
-               .setTrustStorePassword(trustStorePassword)
-               .setSslEnabled(true)
-               .setRateLimitStreamRequestsPerSecond(1)
-               .setRequestIdleTimeoutMillis(300_000)
-               .setRequestTimeoutMillis(300_000L)
-               .setConcurrentUploadsLimit(80)
-               .setMinSpacePercentRequiredForUploads(0)
-               .setSSTableImportCacheConfiguration(new CacheConfiguration(60_000, 100))
-               .setServerWorkerPoolConfiguration(workerPoolConf)
-               .setServerInternalWorkerPoolConfiguration(workerPoolConf)
-               .build();
+        return super.abstractConfig(sslConfiguration);
     }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/ThrottleTest.java b/src/test/java/org/apache/cassandra/sidecar/ThrottleTest.java
index f939d76..ca055cd 100644
--- a/src/test/java/org/apache/cassandra/sidecar/ThrottleTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/ThrottleTest.java
@@ -53,7 +53,6 @@
     private static final Logger logger = LoggerFactory.getLogger(ThrottleTest.class);
     private Vertx vertx;
     private HttpServer server;
-    private Configuration config;
 
     @BeforeEach
     void setUp() throws InterruptedException
@@ -62,10 +61,9 @@
         Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(new TestModule()));
         server = injector.getInstance(HttpServer.class);
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
 
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), context.succeedingThenComplete());
+        server.listen(0, context.succeedingThenComplete());
 
         context.awaitCompletion(5, SECONDS);
     }
@@ -85,8 +83,10 @@
     @Test
     void testStreamRequestsThrottled() throws Exception
     {
-        String testRoute = "/keyspaces/TestKeyspace/tables/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b/snapshots" +
-                "/TestSnapshot/components/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
+        String testRoute = "/keyspaces/TestKeyspace" +
+                           "/tables/TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b" +
+                           "/snapshots/TestSnapshot" +
+                           "/components/TestKeyspace-TestTable-54ea95ce-bba2-4e0a-a9be-e428e5d7160b-Data.db";
 
         for (int i = 0; i < 20; i++)
         {
@@ -107,21 +107,21 @@
     private void unblockingClientRequest(String route)
     {
         WebClient client = WebClient.create(vertx);
-        client.get(config.getPort(), "localhost", "/api/v1" + route)
-                .as(BodyCodec.buffer())
-                .send(resp ->
-                {
-                    // do nothing
-                });
+        client.get(server.actualPort(), "localhost", "/api/v1" + route)
+              .as(BodyCodec.buffer())
+              .send(resp ->
+                    {
+                        // do nothing
+                    });
     }
 
     private HttpResponse blockingClientRequest(String route) throws ExecutionException, InterruptedException
     {
         WebClient client = WebClient.create(vertx);
         CompletableFuture<HttpResponse> future = new CompletableFuture<>();
-        client.get(config.getPort(), "localhost", "/api/v1" + route)
-                .as(BodyCodec.buffer())
-                .send(resp -> future.complete(resp.result()));
+        client.get(server.actualPort(), "localhost", "/api/v1" + route)
+              .as(BodyCodec.buffer())
+              .send(resp -> future.complete(resp.result()));
         return future.get();
     }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/YAMLSidecarConfigurationTest.java b/src/test/java/org/apache/cassandra/sidecar/YAMLSidecarConfigurationTest.java
deleted file mode 100644
index 90f4033..0000000
--- a/src/test/java/org/apache/cassandra/sidecar/YAMLSidecarConfigurationTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.cassandra.sidecar;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.Objects;
-
-import org.junit.jupiter.api.Test;
-
-import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
-import org.apache.cassandra.sidecar.common.CassandraVersionProvider;
-import org.apache.cassandra.sidecar.common.dns.DnsResolver;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-
-/**
- * Tests reading Sidecar {@link Configuration} from YAML files
- */
-class YAMLSidecarConfigurationTest
-{
-    private static final String SIDECAR_VERSION = "unknown";
-
-    private final CassandraVersionProvider versionProvider = mock(CassandraVersionProvider.class);
-
-    @Test
-    public void testSidecarConfiguration() throws IOException
-    {
-        String confPath1 = confPath("sidecar_multiple_instances.yaml");
-        Configuration multipleInstancesConfig = YAMLSidecarConfiguration.of(confPath1,
-                                                                            versionProvider,
-                                                                            SIDECAR_VERSION,
-                                                                            DnsResolver.DEFAULT);
-        validateSidecarConfiguration(multipleInstancesConfig);
-
-        String confPath = confPath("sidecar_single_instance.yaml");
-        Configuration singleInstanceConfig = YAMLSidecarConfiguration.of(confPath,
-                                                                  versionProvider,
-                                                                  SIDECAR_VERSION,
-                                                                  DnsResolver.DEFAULT);
-        validateSidecarConfiguration(singleInstanceConfig);
-    }
-
-    @Test
-    public void testLegacySidecarYAMLFormatWithSingleInstance() throws IOException
-    {
-        String confPath = confPath("sidecar_single_instance.yaml");
-        Configuration configuration = YAMLSidecarConfiguration.of(confPath,
-                                                                  versionProvider,
-                                                                  SIDECAR_VERSION,
-                                                                  DnsResolver.DEFAULT);
-        InstancesConfig instancesConfig = configuration.getInstancesConfig();
-        assertThat(instancesConfig.instances().size()).isEqualTo(1);
-        InstanceMetadata instanceMetadata = instancesConfig.instances().get(0);
-        assertThat(instanceMetadata.host()).isEqualTo("localhost");
-        assertThat(instanceMetadata.port()).isEqualTo(9042);
-    }
-
-    @Test
-    public void testReadAllowableTimeSkew() throws IOException
-    {
-        String confPath1 = confPath("sidecar_single_instance.yaml");
-        Configuration configuration = YAMLSidecarConfiguration.of(confPath1,
-                                                                  versionProvider,
-                                                                  SIDECAR_VERSION,
-                                                                  DnsResolver.DEFAULT);
-        assertThat(configuration.allowableSkewInMinutes()).isEqualTo(89);
-
-        String confPath = confPath("sidecar_custom_allowable_time_skew.yaml");
-        configuration = YAMLSidecarConfiguration.of(confPath,
-                                                    versionProvider,
-                                                    SIDECAR_VERSION,
-                                                    DnsResolver.DEFAULT);
-        assertThat(configuration.allowableSkewInMinutes()).isEqualTo(1);
-    }
-
-    @Test
-    public void testReadingSingleInstanceSectionOverMultipleInstances() throws IOException
-    {
-        String confPath = confPath("sidecar_with_single_multiple_instances.yaml");
-        Configuration configuration = YAMLSidecarConfiguration.of(confPath,
-                                                                  versionProvider,
-                                                                  SIDECAR_VERSION,
-                                                                  DnsResolver.DEFAULT);
-        InstancesConfig instancesConfig = configuration.getInstancesConfig();
-        assertThat(instancesConfig.instances().size()).isEqualTo(1);
-        InstanceMetadata instanceMetadata = instancesConfig.instances().get(0);
-        assertThat(instanceMetadata.host()).isEqualTo("localhost");
-        assertThat(instanceMetadata.port()).isEqualTo(9042);
-    }
-
-    @Test
-    public void testReadingMultipleInstances() throws IOException
-    {
-        String confPath = confPath("sidecar_multiple_instances.yaml");
-        Configuration configuration = YAMLSidecarConfiguration.of(confPath,
-                                                                  versionProvider,
-                                                                  SIDECAR_VERSION,
-                                                                  DnsResolver.DEFAULT);
-        InstancesConfig instancesConfig = configuration.getInstancesConfig();
-        assertThat(instancesConfig.instances().size()).isEqualTo(2);
-    }
-
-    @Test
-    public void testReadingCassandraInputValidation() throws IOException
-    {
-        String confPath = confPath("sidecar_validation_configuration.yaml");
-        Configuration configuration = YAMLSidecarConfiguration.of(confPath,
-                                                                  versionProvider,
-                                                                  SIDECAR_VERSION,
-                                                                  DnsResolver.DEFAULT);
-        ValidationConfiguration validationConfiguration = configuration.getValidationConfiguration();
-
-        assertThat(validationConfiguration.forbiddenKeyspaces()).contains("a", "b", "c");
-        assertThat(validationConfiguration.allowedPatternForDirectory()).isEqualTo("[a-z]+");
-        assertThat(validationConfiguration.allowedPatternForComponentName())
-        .isEqualTo("(.db|.cql|.json|.crc32|TOC.txt)");
-        assertThat(validationConfiguration.allowedPatternForRestrictedComponentName())
-        .isEqualTo("(.db|TOC.txt)");
-    }
-
-    @Test
-    public void testUploadsConfiguration() throws IOException
-    {
-        String confPath = confPath("sidecar_multiple_instances.yaml");
-        Configuration configuration = YAMLSidecarConfiguration.of(confPath,
-                                                                  versionProvider,
-                                                                  SIDECAR_VERSION,
-                                                                  DnsResolver.DEFAULT);
-
-        assertThat(configuration.getConcurrentUploadsLimit()).isEqualTo(80);
-        assertThat(configuration.getMinSpacePercentRequiredForUpload()).isEqualTo(10);
-    }
-
-    private void validateSidecarConfiguration(Configuration config)
-    {
-        assertThat(config.getHost()).isEqualTo("0.0.0.0");
-        assertThat(config.getPort()).isEqualTo(9043);
-        assertThat(config.getRequestIdleTimeoutMillis()).isEqualTo(500_000);
-        assertThat(config.getRequestTimeoutMillis()).isEqualTo(1_200_000L);
-        assertThat(config.getRateLimitStreamRequestsPerSecond()).isEqualTo(80);
-        assertThat(config.getThrottleDelayInSeconds()).isEqualTo(7);
-        assertThat(config.getThrottleTimeoutInSeconds()).isEqualTo(21);
-        assertThat(config.allowableSkewInMinutes()).isEqualTo(89);
-        assertThat(config.getSSTableImportPollIntervalMillis()).isEqualTo(50);
-        assertThat(config.ssTableImportCacheConfiguration()).isNotNull();
-        assertThat(config.ssTableImportCacheConfiguration().expireAfterAccessMillis()).isEqualTo(1000L);
-        assertThat(config.ssTableImportCacheConfiguration().maximumSize()).isEqualTo(100);
-    }
-
-    private String confPath(String resourceName)
-    {
-        URL resource = getClass().getClassLoader().getResource(resourceName);
-        return Objects.requireNonNull(resource).toString();
-    }
-}
diff --git a/src/test/java/org/apache/cassandra/sidecar/concurrent/ExecutorPoolsTest.java b/src/test/java/org/apache/cassandra/sidecar/concurrent/ExecutorPoolsTest.java
index 8831030..e4aebed 100644
--- a/src/test/java/org/apache/cassandra/sidecar/concurrent/ExecutorPoolsTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/concurrent/ExecutorPoolsTest.java
@@ -24,19 +24,15 @@
 import java.util.concurrent.TimeUnit;
 
 import com.google.common.util.concurrent.Uninterruptibles;
-
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import io.vertx.core.Vertx;
-import org.apache.cassandra.sidecar.Configuration;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 /**
  * Test {@link ExecutorPools}
@@ -45,18 +41,12 @@
 {
     private ExecutorPools pools;
     private Vertx vertx;
-    private Configuration mockConfiguration;
 
     @BeforeEach
     public void before()
     {
         vertx = Vertx.vertx();
-        mockConfiguration = mock(Configuration.class);
-        WorkerPoolConfiguration workerPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                             TimeUnit.SECONDS.toMillis(30));
-        when(mockConfiguration.serverWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        when(mockConfiguration.serverInternalWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        pools = new ExecutorPools(vertx, mockConfiguration);
+        pools = new ExecutorPools(vertx, new ServiceConfigurationImpl());
     }
 
     @AfterEach
@@ -84,8 +74,11 @@
         class IntWrapper
         {
             int i = 0;
+
             void increment()
-            { i += 1; }
+            {
+                i += 1;
+            }
         }
 
         ExecutorPools.TaskExecutorPool pool = pools.internal();
diff --git a/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java b/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
new file mode 100644
index 0000000..8ca2bde
--- /dev/null
+++ b/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.config;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
+
+import static org.apache.cassandra.sidecar.common.ResourceUtils.writeResourceToPath;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests reading Sidecar {@link SidecarConfiguration} from YAML files
+ */
+class SidecarConfigurationTest
+{
+    @TempDir
+    private Path configPath;
+
+    @Test
+    void testSidecarConfiguration() throws IOException
+    {
+        Path yamlPath = yaml("config/sidecar_multiple_instances.yaml");
+        SidecarConfiguration config = SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+        validateMultipleInstancesSidecarConfiguration(config, false);
+    }
+
+    @Test
+    void testLegacySidecarYAMLFormatWithSingleInstance() throws IOException
+    {
+        Path yamlPath = yaml("config/sidecar_single_instance.yaml");
+        SidecarConfiguration config = SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+        validateSingleInstanceSidecarConfiguration(config);
+    }
+
+    @Test
+    void testReadAllowableTimeSkew() throws IOException
+    {
+        Path yamlPath = yaml("config/sidecar_custom_allowable_time_skew.yaml");
+        SidecarConfiguration config = SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+        assertThat(config.serviceConfiguration()).isNotNull();
+        assertThat(config.serviceConfiguration().allowableSkewInMinutes()).isEqualTo(1);
+    }
+
+    @Test
+    void testReadingSingleInstanceSectionOverMultipleInstances() throws IOException
+    {
+        Path yamlPath = yaml("config/sidecar_with_single_multiple_instances.yaml");
+        SidecarConfiguration configuration = SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+        assertThat(configuration.cassandraInstances()).isNotNull().hasSize(1);
+
+        InstanceConfiguration i1 = configuration.cassandraInstances().get(0);
+        assertThat(i1.host()).isEqualTo("localhost");
+        assertThat(i1.port()).isEqualTo(9042);
+    }
+
+    @Test
+    void testReadingCassandraInputValidation() throws IOException
+    {
+        Path yamlPath = yaml("config/sidecar_validation_configuration.yaml");
+        SidecarConfiguration configuration = SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+        CassandraInputValidationConfiguration validationConfiguration =
+        configuration.cassandraInputValidationConfiguration();
+
+        assertThat(validationConfiguration.forbiddenKeyspaces()).contains("a", "b", "c");
+        assertThat(validationConfiguration.allowedPatternForDirectory()).isEqualTo("[a-z]+");
+        assertThat(validationConfiguration.allowedPatternForComponentName())
+        .isEqualTo("(.db|.cql|.json|.crc32|TOC.txt)");
+        assertThat(validationConfiguration.allowedPatternForRestrictedComponentName())
+        .isEqualTo("(.db|TOC.txt)");
+    }
+
+    @Test
+    void testUploadsConfiguration() throws IOException
+    {
+        Path yamlPath = yaml("config/sidecar_multiple_instances.yaml");
+        SidecarConfiguration config = SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+
+        assertThat(config.serviceConfiguration()).isNotNull();
+        SSTableUploadConfiguration uploadConfiguration = config.serviceConfiguration()
+                                                               .ssTableUploadConfiguration();
+        assertThat(uploadConfiguration).isNotNull();
+
+        assertThat(uploadConfiguration.concurrentUploadsLimit()).isEqualTo(80);
+        assertThat(uploadConfiguration.minimumSpacePercentageRequired()).isEqualTo(10);
+    }
+
+    @Test
+    void testSslConfiguration() throws IOException
+    {
+        Path yamlPath = yaml("config/sidecar_ssl.yaml");
+        SidecarConfiguration config = SidecarConfigurationImpl.readYamlConfiguration(yamlPath);
+        validateMultipleInstancesSidecarConfiguration(config, true);
+    }
+
+    void validateSingleInstanceSidecarConfiguration(SidecarConfiguration config)
+    {
+        assertThat(config.cassandraInstances()).isNotNull().hasSize(1);
+
+        InstanceConfiguration i1 = config.cassandraInstances().get(0);
+
+        // instance 1
+        assertThat(i1.id()).isEqualTo(0);
+        assertThat(i1.host()).isEqualTo("localhost");
+        assertThat(i1.port()).isEqualTo(9042);
+        assertThat(i1.username()).isEqualTo("cassandra");
+        assertThat(i1.password()).isEqualTo("cassandra");
+        assertThat(i1.dataDirs()).containsExactly("/ccm/test/node1/data0", "/ccm/test/node1/data1");
+        assertThat(i1.stagingDir()).isEqualTo("/ccm/test/node1/sstable-staging");
+        assertThat(i1.jmxHost()).isEqualTo("127.0.0.1");
+        assertThat(i1.jmxPort()).isEqualTo(7199);
+        assertThat(i1.jmxSslEnabled()).isTrue();
+        assertThat(i1.jmxRole()).isEqualTo("controlRole");
+        assertThat(i1.jmxRolePassword()).isEqualTo("controlPassword");
+
+        // service configuration
+        validateDefaultServiceConfiguration(config.serviceConfiguration());
+
+        // ssl configuration
+        assertThat(config.sslConfiguration()).isNull();
+
+        // health check configuration
+        assertThat(config.healthCheckConfiguration()).isNotNull();
+        assertThat(config.healthCheckConfiguration().checkIntervalMillis()).isEqualTo(30_000);
+
+        // cassandra input validation configuration
+        validateDefaultCassandraInputValidationConfiguration(config.cassandraInputValidationConfiguration());
+    }
+
+    void validateMultipleInstancesSidecarConfiguration(SidecarConfiguration config, boolean withSslConfiguration)
+    {
+        // instances configuration
+        assertThat(config.cassandraInstances()).isNotNull().hasSize(3);
+
+        InstanceConfiguration i1 = config.cassandraInstances().get(0);
+        InstanceConfiguration i2 = config.cassandraInstances().get(1);
+        InstanceConfiguration i3 = config.cassandraInstances().get(2);
+
+        // instance 1
+        assertThat(i1.id()).isEqualTo(1);
+        assertThat(i1.host()).isEqualTo("localhost1");
+        assertThat(i1.port()).isEqualTo(9042);
+        assertThat(i1.username()).isEqualTo("cassandra");
+        assertThat(i1.password()).isEqualTo("cassandra");
+        assertThat(i1.dataDirs()).containsExactly("/ccm/test/node1/data0", "/ccm/test/node1/data1");
+        assertThat(i1.stagingDir()).isEqualTo("/ccm/test/node1/sstable-staging");
+        assertThat(i1.jmxHost()).isEqualTo("127.0.0.1");
+        assertThat(i1.jmxPort()).isEqualTo(7100);
+        assertThat(i1.jmxSslEnabled()).isFalse();
+
+        // instance 2
+        assertThat(i2.id()).isEqualTo(2);
+        assertThat(i2.host()).isEqualTo("localhost2");
+        assertThat(i2.port()).isEqualTo(9042);
+        assertThat(i2.username()).isEqualTo("cassandra");
+        assertThat(i2.password()).isEqualTo("cassandra");
+        assertThat(i2.dataDirs()).containsExactly("/ccm/test/node2/data0", "/ccm/test/node2/data1");
+        assertThat(i2.stagingDir()).isEqualTo("/ccm/test/node2/sstable-staging");
+        assertThat(i2.jmxHost()).isEqualTo("127.0.0.1");
+        assertThat(i2.jmxPort()).isEqualTo(7200);
+        assertThat(i2.jmxSslEnabled()).isFalse();
+
+        // instance 3
+        assertThat(i3.id()).isEqualTo(3);
+        assertThat(i3.host()).isEqualTo("localhost3");
+        assertThat(i3.port()).isEqualTo(9042);
+        assertThat(i3.username()).isEqualTo("cassandra");
+        assertThat(i3.password()).isEqualTo("cassandra");
+        assertThat(i3.dataDirs()).containsExactly("/ccm/test/node3/data0", "/ccm/test/node3/data1");
+        assertThat(i3.stagingDir()).isEqualTo("/ccm/test/node3/sstable-staging");
+        assertThat(i3.jmxHost()).isEqualTo("127.0.0.1");
+        assertThat(i3.jmxPort()).isEqualTo(7300);
+        assertThat(i3.jmxSslEnabled()).isFalse();
+
+        // service configuration
+        validateDefaultServiceConfiguration(config.serviceConfiguration());
+
+        // ssl configuration
+        if (withSslConfiguration)
+        {
+            validateDefaultSslConfiguration(config.sslConfiguration());
+        }
+        else
+        {
+            assertThat(config.sslConfiguration()).isNull();
+        }
+
+        // health check configuration
+        assertThat(config.healthCheckConfiguration()).isNotNull();
+        assertThat(config.healthCheckConfiguration().checkIntervalMillis()).isEqualTo(30_000);
+
+        // cassandra input validation configuration
+        validateDefaultCassandraInputValidationConfiguration(config.cassandraInputValidationConfiguration());
+    }
+
+    void validateDefaultServiceConfiguration(ServiceConfiguration serviceConfiguration)
+    {
+        assertThat(serviceConfiguration).isNotNull();
+        assertThat(serviceConfiguration.host()).isEqualTo("0.0.0.0");
+        assertThat(serviceConfiguration.port()).isEqualTo(9043);
+        assertThat(serviceConfiguration.requestIdleTimeoutMillis()).isEqualTo(300000);
+        assertThat(serviceConfiguration.requestTimeoutMillis()).isEqualTo(300000);
+        assertThat(serviceConfiguration.allowableSkewInMinutes()).isEqualTo(60);
+
+        // service configuration throttling
+        ThrottleConfiguration throttle = serviceConfiguration.throttleConfiguration();
+
+        assertThat(throttle).isNotNull();
+        assertThat(throttle.rateLimitStreamRequestsPerSecond()).isEqualTo(5000);
+        assertThat(throttle.delayInSeconds()).isEqualTo(5);
+        assertThat(throttle.timeoutInSeconds()).isEqualTo(10);
+    }
+
+    void validateDefaultCassandraInputValidationConfiguration(CassandraInputValidationConfiguration config)
+    {
+        assertThat(config).isNotNull();
+        assertThat(config.forbiddenKeyspaces()).containsExactlyInAnyOrder("system_schema",
+                                                                          "system_traces",
+                                                                          "system_distributed",
+                                                                          "system",
+                                                                          "system_auth",
+                                                                          "system_views",
+                                                                          "system_virtual_schema");
+        assertThat(config.allowedPatternForDirectory()).isEqualTo("[a-zA-Z0-9_-]+");
+        assertThat(config.allowedPatternForComponentName())
+        .isEqualTo("[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)");
+        assertThat(config.allowedPatternForRestrictedComponentName()).isEqualTo("[a-zA-Z0-9_-]+(.db|TOC.txt)");
+    }
+
+    void validateDefaultSslConfiguration(SslConfiguration config)
+    {
+        assertThat(config).isNotNull();
+        assertThat(config.enabled()).isTrue();
+        assertThat(config.keystore()).isNotNull();
+        assertThat(config.keystore().path()).isEqualTo("path/to/keystore.p12");
+        assertThat(config.keystore().password()).isEqualTo("password");
+        assertThat(config.truststore()).isNotNull();
+        assertThat(config.truststore().path()).isEqualTo("path/to/truststore.p12");
+        assertThat(config.truststore().password()).isEqualTo("password");
+    }
+
+    private Path yaml(String resourceName)
+    {
+        ClassLoader classLoader = this.getClass().getClassLoader();
+        return writeResourceToPath(classLoader, configPath, resourceName);
+    }
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/data/RingRequestTest.java b/src/test/java/org/apache/cassandra/sidecar/data/RingRequestTest.java
index b62a56f..90a140a 100644
--- a/src/test/java/org/apache/cassandra/sidecar/data/RingRequestTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/data/RingRequestTest.java
@@ -18,14 +18,8 @@
 
 package org.apache.cassandra.sidecar.data;
 
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
-
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
@@ -33,19 +27,6 @@
  */
 class RingRequestTest
 {
-    @BeforeEach
-    void setup()
-    {
-        Guice.createInjector(new AbstractModule()
-        {
-            protected void configure()
-            {
-                bind(ValidationConfiguration.class).to(TestValidationConfiguration.class);
-                requestStaticInjection(RingRequest.class);
-            }
-        });
-    }
-
     @Test
     void testEmptyConstructor()
     {
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/GossipInfoHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/GossipInfoHandlerTest.java
index f2dfdbe..d7f8559 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/GossipInfoHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/GossipInfoHandlerTest.java
@@ -42,7 +42,6 @@
 import io.vertx.ext.web.client.predicate.ResponsePredicate;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.MainModule;
 import org.apache.cassandra.sidecar.TestModule;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
@@ -64,7 +63,6 @@
 {
     static final Logger LOGGER = LoggerFactory.getLogger(GossipInfoHandlerTest.class);
     Vertx vertx;
-    Configuration config;
     HttpServer server;
 
     @SuppressWarnings("DataFlowIssue")
@@ -78,9 +76,8 @@
                                                .with(testOverride));
         vertx = injector.getInstance(Vertx.class);
         server = injector.getInstance(HttpServer.class);
-        config = injector.getInstance(Configuration.class);
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), config.getHost(), context.succeedingThenComplete());
+        server.listen(0, "127.0.0.1", context.succeedingThenComplete());
         context.awaitCompletion(5, TimeUnit.SECONDS);
     }
 
@@ -101,7 +98,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/cassandra/gossip";
-        client.get(config.getPort(), config.getHost(), testRoute)
+        client.get(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_OK)
               .send(context.succeeding(response -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
@@ -113,9 +110,9 @@
                   assertThat(gossipInfo.heartbeat()).isEqualTo("242");
                   assertThat(gossipInfo.load()).isEqualTo("88971.0");
                   assertThat(gossipInfo.statusWithPort())
-                      .isEqualTo("NORMAL,-9223372036854775808");
+                  .isEqualTo("NORMAL,-9223372036854775808");
                   assertThat(gossipInfo.sstableVersions())
-                            .isEqualTo(Collections.singletonList("big-nb"));
+                  .isEqualTo(Collections.singletonList("big-nb"));
                   assertThat(gossipInfo.tokens()).isEqualTo("<hidden>");
                   context.completeNow();
               }));
@@ -149,11 +146,11 @@
     }
 
     private static final String SAMPLE_GOSSIP_INFO =
-        "/127.0.0.1:7000\n" +
-        "  generation:1668100877\n" +
-        "  heartbeat:242\n" +
-        "  LOAD:211:88971.0\n" +
-        "  STATUS_WITH_PORT:19:NORMAL,-9223372036854775808\n" +
-        "  SSTABLE_VERSIONS:6:big-nb\n" +
-        "  TOKENS:18:<hidden>";
+    "/127.0.0.1:7000\n" +
+    "  generation:1668100877\n" +
+    "  heartbeat:242\n" +
+    "  LOAD:211:88971.0\n" +
+    "  STATUS_WITH_PORT:19:NORMAL,-9223372036854775808\n" +
+    "  SSTABLE_VERSIONS:6:big-nb\n" +
+    "  TOKENS:18:<hidden>";
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/SchemaHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/SchemaHandlerTest.java
index 3b98fdb..82c6909 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/SchemaHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/SchemaHandlerTest.java
@@ -47,7 +47,6 @@
 import io.vertx.ext.web.client.predicate.ResponsePredicate;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.MainModule;
 import org.apache.cassandra.sidecar.TestModule;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
@@ -69,7 +68,6 @@
 {
     static final Logger LOGGER = LoggerFactory.getLogger(SchemaHandlerTest.class);
     Vertx vertx;
-    Configuration config;
     HttpServer server;
     @TempDir
     File dataDir0;
@@ -87,9 +85,8 @@
                                                             .with(new SchemaHandlerTestModule())));
         vertx = injector.getInstance(Vertx.class);
         server = injector.getInstance(HttpServer.class);
-        config = injector.getInstance(Configuration.class);
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), config.getHost(), context.succeedingThenComplete());
+        server.listen(0, "127.0.0.1", context.succeedingThenComplete());
         context.awaitCompletion(5, TimeUnit.SECONDS);
     }
 
@@ -110,7 +107,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/schema/keyspaces";
-        client.get(config.getPort(), config.getHost(), testRoute)
+        client.get(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_OK)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
@@ -127,7 +124,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/schema/keyspaces/testKeyspace";
-        client.get(config.getPort(), config.getHost(), testRoute)
+        client.get(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_OK)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
@@ -144,7 +141,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/schema/keyspaces/nonExistent";
-        client.get(config.getPort(), config.getHost(), testRoute)
+        client.get(server.actualPort(), "127.0.0.1", testRoute)
               .expect(ResponsePredicate.SC_NOT_FOUND)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(NOT_FOUND.code());
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/SnapshotsHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/SnapshotsHandlerTest.java
index 226d943..6d04f94 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/SnapshotsHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/SnapshotsHandlerTest.java
@@ -46,7 +46,6 @@
 import io.vertx.ext.web.client.WebClient;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.MainModule;
 import org.apache.cassandra.sidecar.TestModule;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
@@ -69,7 +68,6 @@
     private static final Logger logger = LoggerFactory.getLogger(SnapshotsHandlerTest.class);
     private Vertx vertx;
     private HttpServer server;
-    private Configuration config;
     @TempDir
     File temporaryFolder;
 
@@ -81,10 +79,9 @@
                                                                      .with(new ListSnapshotTestModule())));
         server = injector.getInstance(HttpServer.class);
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
 
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), config.getHost(), context.succeedingThenComplete());
+        server.listen(0, "localhost", context.succeedingThenComplete());
 
         context.awaitCompletion(5, TimeUnit.SECONDS);
         SnapshotUtils.initializeTmpDirectory(temporaryFolder);
@@ -110,7 +107,7 @@
         ListSnapshotFilesResponse.FileInfo fileInfoExpected =
         new ListSnapshotFilesResponse.FileInfo(11,
                                                "localhost",
-                                               6475,
+                                               9043,
                                                0,
                                                "snapshot1",
                                                "keyspace1",
@@ -119,14 +116,14 @@
         ListSnapshotFilesResponse.FileInfo fileInfoNotExpected =
         new ListSnapshotFilesResponse.FileInfo(11,
                                                "localhost",
-                                               6475,
+                                               9043,
                                                0,
                                                "snapshot1",
                                                "keyspace1",
                                                "table1-1234",
                                                "2.db");
 
-        client.get(config.getPort(), "localhost", testRoute)
+        client.get(server.actualPort(), "localhost", testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
                   ListSnapshotFilesResponse resp = response.bodyAsJson(ListSnapshotFilesResponse.class);
@@ -146,7 +143,7 @@
         List<ListSnapshotFilesResponse.FileInfo> fileInfoExpected = Arrays.asList(
         new ListSnapshotFilesResponse.FileInfo(11,
                                                "localhost",
-                                               6475,
+                                               9043,
                                                0,
                                                "snapshot1",
                                                "keyspace1",
@@ -154,7 +151,7 @@
                                                "1.db"),
         new ListSnapshotFilesResponse.FileInfo(0,
                                                "localhost",
-                                               6475,
+                                               9043,
                                                0,
                                                "snapshot1",
                                                "keyspace1",
@@ -171,7 +168,7 @@
                                                "table1-1234",
                                                "2.db");
 
-        client.get(config.getPort(), "localhost", testRoute)
+        client.get(server.actualPort(), "localhost", testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
                   ListSnapshotFilesResponse resp = response.bodyAsJson(ListSnapshotFilesResponse.class);
@@ -186,7 +183,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/keyspaces/keyspace1/tables/table1-1234/snapshots/snapshotInvalid";
-        client.get(config.getPort(), "localhost", testRoute)
+        client.get(server.actualPort(), "localhost", testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(NOT_FOUND.code());
                   assertThat(response.statusMessage()).isEqualTo(NOT_FOUND.reasonPhrase());
@@ -199,7 +196,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/i_❤_u/tables/table/snapshots/snapshot";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -215,7 +212,7 @@
         VertxTestContext context = new VertxTestContext();
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + forbiddenKeyspace + "/tables/table/snapshots/snapshot";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -228,7 +225,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/ks/tables/i_❤_u/snapshots/snapshot";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandlerTest.java
index 721abab..b769280 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/StreamSSTableComponentHandlerTest.java
@@ -40,7 +40,6 @@
 import io.vertx.ext.web.codec.BodyCodec;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.MainModule;
 import org.apache.cassandra.sidecar.TestModule;
 
@@ -64,7 +63,6 @@
 
     private Vertx vertx;
     private HttpServer server;
-    private Configuration config;
 
     @BeforeEach
     void setUp() throws InterruptedException
@@ -72,10 +70,9 @@
         Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(new TestModule()));
         server = injector.getInstance(HttpServer.class);
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
 
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), context.succeedingThenComplete());
+        server.listen(0, "localhost", context.succeedingThenComplete());
 
         context.awaitCompletion(5, TimeUnit.SECONDS);
     }
@@ -98,7 +95,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .as(BodyCodec.buffer())
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
@@ -112,7 +109,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/i_❤_u/tables/table/snapshots/snapshot/components/component-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -129,7 +126,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + forbiddenKeyspace + "/tables/table/snapshots/snapshot" +
                            "/components/component-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(FORBIDDEN.code());
                   assertThat(response.statusMessage()).isEqualTo(FORBIDDEN.reasonPhrase());
@@ -143,7 +140,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/random/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(NOT_FOUND.code());
                   context.completeNow();
@@ -156,7 +153,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/random/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(NOT_FOUND.code());
                   context.completeNow();
@@ -169,7 +166,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/system/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(FORBIDDEN.code());
                   assertThat(response.statusMessage()).isEqualTo(FORBIDDEN.reasonPhrase());
@@ -183,7 +180,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/k*s/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -197,7 +194,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data...db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -211,7 +208,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Digest.crc32d";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -224,7 +221,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/i_❤_u/snapshots/snap/components/component-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -240,7 +237,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/TestTable/snapshots/" +
                            invalidFileName + "/components/component-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(BAD_REQUEST.code());
                   assertThat(response.statusMessage()).isEqualTo(BAD_REQUEST.reasonPhrase());
@@ -256,7 +253,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots/snap/components/" +
                            invalidComponentName;
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(FORBIDDEN.code());
                   assertThat(response.statusMessage()).isEqualTo(FORBIDDEN.reasonPhrase());
@@ -270,7 +267,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/TestTable/snapshots/TestSnapshot/components" +
                            "/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=0-")
               .as(BodyCodec.buffer())
               .send(context.succeeding(response -> context.verify(() -> {
@@ -286,7 +283,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=4-3")
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(REQUESTED_RANGE_NOT_SATISFIABLE.code());
@@ -300,7 +297,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=5-9")
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(REQUESTED_RANGE_NOT_SATISFIABLE.code());
@@ -314,7 +311,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=5-")
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(REQUESTED_RANGE_NOT_SATISFIABLE.code());
@@ -328,7 +325,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=0-999999")
               .as(BodyCodec.buffer())
               .send(context.succeeding(response -> context.verify(() -> {
@@ -344,7 +341,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=0-2") // 3 bytes streamed
               .as(BodyCodec.buffer())
               .send(context.succeeding(response -> context.verify(() -> {
@@ -360,7 +357,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=-2") // last 2 bytes streamed
               .as(BodyCodec.buffer())
               .send(context.succeeding(response -> context.verify(() -> {
@@ -376,7 +373,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bytes=-5")
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
@@ -393,7 +390,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/snapshots" +
                            "/TestSnapshot/components/" + TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute)
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute)
               .putHeader("Range", "bits=0-2")
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(REQUESTED_RANGE_NOT_SATISFIABLE.code());
@@ -408,7 +405,7 @@
         String testRoute = "/keyspaces/" + TEST_KEYSPACE + "/tables/" + TEST_TABLE + "/" +
                            "snapshots/TestSnapshot/components/" +
                            TEST_KEYSPACE + "-" + TEST_TABLE + "-Data.db";
-        client.get(config.getPort(), "localhost", "/api/v1" + testRoute + "?instanceId=2")
+        client.get(server.actualPort(), "localhost", "/api/v1" + testRoute + "?instanceId=2")
               .as(BodyCodec.buffer())
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/TimeSkewInfoHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/TimeSkewInfoHandlerTest.java
index 8209a8d..ddda2f0 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/TimeSkewInfoHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/TimeSkewInfoHandlerTest.java
@@ -38,7 +38,6 @@
 import io.vertx.ext.web.codec.BodyCodec;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.MainModule;
 import org.apache.cassandra.sidecar.TestModule;
 import org.apache.cassandra.sidecar.common.data.TimeSkewResponse;
@@ -57,7 +56,6 @@
     private static final long TEST_TIMESTAMP = 12345L;
     private Vertx vertx;
     private HttpServer server;
-    private Configuration config;
 
     @BeforeEach
     public void setUp() throws InterruptedException
@@ -67,9 +65,8 @@
                                                         .with(new TestModule(), customTimeProvider));
         this.vertx = injector.getInstance(Vertx.class);
         this.server = injector.getInstance(HttpServer.class);
-        this.config = injector.getInstance(Configuration.class);
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), context.succeedingThenComplete());
+        server.listen(0, "127.0.0.1", context.succeedingThenComplete());
 
         context.awaitCompletion(5, TimeUnit.SECONDS);
     }
@@ -91,7 +88,7 @@
     {
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/time-skew";
-        client.get(config.getPort(), "localhost", testRoute)
+        client.get(server.actualPort(), "127.0.0.1", testRoute)
               .as(BodyCodec.buffer())
               .send(context.succeeding(response -> context.verify(() -> {
                   assertThat(response.statusCode()).isEqualTo(OK.code());
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java
index 4642faa..b06b027 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/cassandra/NodeSettingsHandlerTest.java
@@ -38,7 +38,6 @@
 import io.vertx.ext.web.codec.BodyCodec;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.MainModule;
 import org.apache.cassandra.sidecar.TestModule;
 import org.apache.cassandra.sidecar.common.NodeSettings;
@@ -53,7 +52,6 @@
     private static final String URI_WITH_INSTANCE_ID = NODE_SETTINGS_ROUTE + "?instanceId=%s";
 
     private Vertx vertx;
-    private Configuration config;
     private HttpServer server;
 
     @BeforeEach
@@ -62,10 +60,9 @@
         Injector injector = Guice.createInjector(Modules.override(new MainModule()).with(new TestModule()));
         server = injector.getInstance(HttpServer.class);
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
 
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), config.getHost(), context.succeedingThenComplete());
+        server.listen(0, "localhost", context.succeedingThenComplete());
 
         context.awaitCompletion(5, TimeUnit.SECONDS);
     }
@@ -86,7 +83,7 @@
     public void validRequestWithoutInstanceId(VertxTestContext context)
     {
         WebClient client = WebClient.create(vertx);
-        client.get(config.getPort(), "localhost", NODE_SETTINGS_ROUTE)
+        client.get(server.actualPort(), "localhost", NODE_SETTINGS_ROUTE)
               .as(BodyCodec.buffer())
               .send(resp -> {
                   assertThat(resp.result().statusCode()).isEqualTo(HttpResponseStatus.OK.code());
@@ -102,7 +99,7 @@
     public void validRequestWithInstanceId(VertxTestContext context)
     {
         WebClient client = WebClient.create(vertx);
-        client.get(config.getPort(), "localhost", String.format(URI_WITH_INSTANCE_ID, "1"))
+        client.get(server.actualPort(), "localhost", String.format(URI_WITH_INSTANCE_ID, "1"))
               .as(BodyCodec.buffer())
               .send(resp -> {
                   assertThat(resp.result().statusCode()).isEqualTo(HttpResponseStatus.OK.code());
@@ -118,7 +115,7 @@
     public void validRequestWithInvalidInstanceId(VertxTestContext context)
     {
         WebClient client = WebClient.create(vertx);
-        client.get(config.getPort(), "localhost", String.format(URI_WITH_INSTANCE_ID, "10"))
+        client.get(server.actualPort(), "localhost", String.format(URI_WITH_INSTANCE_ID, "10"))
               .as(BodyCodec.buffer())
               .send(resp -> {
                   assertThat(resp.result().statusCode()).isEqualTo(HttpResponseStatus.NOT_FOUND.code());
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/BaseUploadsHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/BaseUploadsHandlerTest.java
index be9b684..ed8ef81 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/BaseUploadsHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/BaseUploadsHandlerTest.java
@@ -47,13 +47,14 @@
 import io.vertx.core.http.HttpServer;
 import io.vertx.ext.web.client.WebClient;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.MainModule;
 import org.apache.cassandra.sidecar.TestModule;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
-import org.apache.cassandra.sidecar.config.CacheConfiguration;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableUploadConfiguration;
+import org.apache.cassandra.sidecar.config.SidecarConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
 import org.apache.cassandra.sidecar.snapshots.SnapshotUtils;
 
 import static org.apache.cassandra.sidecar.snapshots.SnapshotUtils.mockInstancesConfig;
@@ -71,28 +72,32 @@
     protected Vertx vertx;
     protected HttpServer server;
     protected WebClient client;
-    protected Configuration config;
     protected CassandraAdapterDelegate mockDelegate;
-    protected Configuration mockConfiguration;
+    protected SidecarConfiguration sidecarConfiguration;
     @TempDir
     protected File temporaryFolder;
+    protected SSTableUploadConfiguration mockSSTableUploadConfiguration;
 
     @BeforeEach
     void setup() throws InterruptedException
     {
         mockDelegate = mock(CassandraAdapterDelegate.class);
-        mockConfiguration = mock(Configuration.class);
-        TestModuleOverride testModuleOverride = new TestModuleOverride(mockDelegate, mockConfiguration);
+        TestModule testModule = new TestModule();
+        mockSSTableUploadConfiguration = mock(SSTableUploadConfiguration.class);
+        when(mockSSTableUploadConfiguration.concurrentUploadsLimit()).thenReturn(3);
+        when(mockSSTableUploadConfiguration.minimumSpacePercentageRequired()).thenReturn(0F);
+        sidecarConfiguration =
+        new SidecarConfigurationImpl(new ServiceConfigurationImpl(500, 1000L, mockSSTableUploadConfiguration));
+        TestModuleOverride testModuleOverride = new TestModuleOverride(mockDelegate);
         Injector injector = Guice.createInjector(Modules.override(new MainModule())
-                                                        .with(Modules.override(new TestModule())
+                                                        .with(Modules.override(testModule)
                                                                      .with(testModuleOverride)));
         server = injector.getInstance(HttpServer.class);
         vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
         client = WebClient.create(vertx);
 
         VertxTestContext context = new VertxTestContext();
-        server.listen(config.getPort(), config.getHost(), context.succeedingThenComplete());
+        server.listen(0, "localhost", context.succeedingThenComplete());
 
         Metadata mockMetadata = mock(Metadata.class);
         KeyspaceMetadata mockKeyspaceMetadata = mock(KeyspaceMetadata.class);
@@ -154,12 +159,10 @@
     class TestModuleOverride extends AbstractModule
     {
         private final CassandraAdapterDelegate delegate;
-        private final Configuration mockConfiguration;
 
-        TestModuleOverride(CassandraAdapterDelegate delegate, Configuration mockConfiguration)
+        TestModuleOverride(CassandraAdapterDelegate delegate)
         {
             this.delegate = delegate;
-            this.mockConfiguration = mockConfiguration;
         }
 
         @Provides
@@ -178,27 +181,9 @@
 
         @Singleton
         @Provides
-        public Configuration abstractConfig(InstancesConfig instancesConfig)
+        public SidecarConfiguration configuration()
         {
-            when(mockConfiguration.getInstancesConfig()).thenReturn(instancesConfig);
-            when(mockConfiguration.getHost()).thenReturn("127.0.0.1");
-            when(mockConfiguration.getPort()).thenReturn(6475);
-            when(mockConfiguration.getHealthCheckFrequencyMillis()).thenReturn(1000);
-            when(mockConfiguration.isSslEnabled()).thenReturn(false);
-            when(mockConfiguration.getRateLimitStreamRequestsPerSecond()).thenReturn(1L);
-            when(mockConfiguration.getThrottleDelayInSeconds()).thenReturn(5L);
-            when(mockConfiguration.getThrottleTimeoutInSeconds()).thenReturn(10L);
-            when(mockConfiguration.getRequestIdleTimeoutMillis()).thenReturn(500);
-            when(mockConfiguration.getRequestTimeoutMillis()).thenReturn(1000L);
-            when(mockConfiguration.getSSTableImportPollIntervalMillis()).thenReturn(100);
-            when(mockConfiguration.ssTableImportCacheConfiguration()).thenReturn(new CacheConfiguration(60_000, 100));
-            when(mockConfiguration.getConcurrentUploadsLimit()).thenReturn(3);
-            when(mockConfiguration.getMinSpacePercentRequiredForUpload()).thenReturn(0F);
-            WorkerPoolConfiguration workerPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                                 TimeUnit.SECONDS.toMillis(30));
-            when(mockConfiguration.serverWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-            when(mockConfiguration.serverInternalWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-            return mockConfiguration;
+            return sidecarConfiguration;
         }
     }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableCleanupHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableCleanupHandlerTest.java
index 2baa8a8..890f5d5 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableCleanupHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableCleanupHandlerTest.java
@@ -47,7 +47,7 @@
 
         Path uploadRoot = createStagedUploadFiles(uploadId);
 
-        client.delete(config.getPort(), "localhost", testRoute)
+        client.delete(server.actualPort(), "localhost", testRoute)
               .expect(ResponsePredicate.SC_OK)
               .send(context.succeeding(response -> {
                   context.verify(() -> {
@@ -61,7 +61,7 @@
     @Test
     void testCleanupUploadBadRequest(VertxTestContext context)
     {
-        client.delete(config.getPort(), "localhost", "/api/v1/uploads/1234")
+        client.delete(server.actualPort(), "localhost", "/api/v1/uploads/1234")
               .expect(ResponsePredicate.SC_BAD_REQUEST)
               .send(context.succeeding(response -> context.verify(() -> {
                   JsonObject error = response.bodyAsJsonObject();
@@ -81,7 +81,7 @@
         UUID uploadId = UUID.randomUUID();
         String testRoute = String.format("/api/v1/uploads/%s", uploadId);
 
-        client.delete(config.getPort(), "localhost", testRoute)
+        client.delete(server.actualPort(), "localhost", testRoute)
               .expect(ResponsePredicate.SC_NOT_FOUND)
               .send(context.succeeding(response -> context.completeNow()));
     }
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerTest.java
index e73fa53..2c04242 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableImportHandlerTest.java
@@ -59,7 +59,7 @@
     @Test
     void testInvalidUploadId(VertxTestContext context)
     {
-        client.put(config.getPort(), "localhost", "/api/v1/uploads/1234/keyspaces/ks/tables/tbl/import")
+        client.put(server.actualPort(), "localhost", "/api/v1/uploads/1234/keyspaces/ks/tables/tbl/import")
               .expect(ResponsePredicate.SC_BAD_REQUEST)
               .send(context.succeeding(response -> context.verify(() -> {
                   JsonObject error = response.bodyAsJsonObject();
@@ -75,7 +75,8 @@
     void testInvalidKeyspace(VertxTestContext context)
     {
         UUID uploadId = UUID.randomUUID();
-        client.put(config.getPort(), "localhost", "/api/v1/uploads/" + uploadId + "/keyspaces/_n$ks_/tables/tbl/import")
+        client.put(server.actualPort(), "localhost", "/api/v1/uploads/"
+                                                     + uploadId + "/keyspaces/_n$ks_/tables/tbl/import")
               .expect(ResponsePredicate.SC_BAD_REQUEST)
               .send(context.succeeding(response -> context.verify(() -> {
                   JsonObject error = response.bodyAsJsonObject();
@@ -90,7 +91,7 @@
     void testInvalidTable(VertxTestContext context)
     {
         UUID uploadId = UUID.randomUUID();
-        client.put(config.getPort(), "localhost",
+        client.put(server.actualPort(), "localhost",
                    "/api/v1/uploads/" + uploadId + "/keyspaces/ks/tables/_n$t_valid_/import")
               .expect(ResponsePredicate.SC_BAD_REQUEST)
               .send(context.succeeding(response -> context.verify(() -> {
@@ -122,7 +123,8 @@
         UUID uploadId = UUID.randomUUID();
         createStagedUploadFiles(uploadId);
 
-        client.put(config.getPort(), "localhost", "/api/v1/uploads/" + uploadId + "/keyspaces/ks/tables/table/import")
+        client.put(server.actualPort(), "localhost", "/api/v1/uploads/"
+                                                     + uploadId + "/keyspaces/ks/tables/table/import")
               .expect(ResponsePredicate.SC_SERVICE_UNAVAILABLE)
               .send(context.succeedingThenComplete());
     }
@@ -189,7 +191,7 @@
 
         String requestURI = "/api/v1/uploads/" + uploadId + "/keyspaces/ks/tables/table/import";
         sendRequest(context,
-                    () -> client.put(config.getPort(), "localhost", requestURI)
+                    () -> client.put(server.actualPort(), "localhost", requestURI)
                                 .addQueryParam("resetLevel", "false"),
                     context.succeeding(response -> context.verify(() -> {
                         assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
@@ -224,7 +226,7 @@
 
         String requestURI = "/api/v1/uploads/" + uploadId + "/keyspaces/ks/tables/table/import";
         sendRequest(context,
-                    () -> client.put(config.getPort(), "localhost", requestURI)
+                    () -> client.put(server.actualPort(), "localhost", requestURI)
                                 .addQueryParam("clearRepaired", "false"),
                     context.succeeding(response -> context.verify(() -> {
                         assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
@@ -259,7 +261,7 @@
 
         String requestURI = "/api/v1/uploads/" + uploadId + "/keyspaces/ks/tables/table/import";
         sendRequest(context,
-                    () -> client.put(config.getPort(), "localhost", requestURI)
+                    () -> client.put(server.actualPort(), "localhost", requestURI)
                                 .addQueryParam("verifySSTables", "false"),
                     context.succeeding(response -> context.verify(() -> {
                         assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
@@ -293,7 +295,7 @@
 
         String requestURI = "/api/v1/uploads/" + uploadId + "/keyspaces/ks/tables/table/import";
         sendRequest(context,
-                    () -> client.put(config.getPort(), "localhost", requestURI)
+                    () -> client.put(server.actualPort(), "localhost", requestURI)
                                 .addQueryParam("extendedVerify", "false"),
                     context.succeeding(response -> context.verify(() -> {
                         assertThat(response.statusCode()).isEqualTo(HttpResponseStatus.OK.code());
@@ -326,7 +328,7 @@
     throws InterruptedException
     {
         sendRequest(context,
-                    () -> client.put(config.getPort(), "localhost", requestURI),
+                    () -> client.put(server.actualPort(), "localhost", requestURI),
                     context.succeeding(response -> context.verify(() -> {
                         validator.accept(response);
                         context.completeNow();
diff --git a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java
index f4a5767..64f2986 100644
--- a/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/routes/sstableuploads/SSTableUploadHandlerTest.java
@@ -152,7 +152,7 @@
     @Test
     public void testFreeSpacePercentCheckNotPassed(VertxTestContext context) throws IOException
     {
-        when(mockConfiguration.getMinSpacePercentRequiredForUpload()).thenReturn(100F);
+        when(mockSSTableUploadConfiguration.minimumSpacePercentageRequired()).thenReturn(100F);
 
         UUID uploadId = UUID.randomUUID();
         sendUploadRequestAndVerify(context, uploadId, "ks", "tbl", "without-md5.db", "",
@@ -163,7 +163,7 @@
     @Test
     public void testConcurrentUploadLimitExceeded(VertxTestContext context) throws IOException
     {
-        when(mockConfiguration.getConcurrentUploadsLimit()).thenReturn(0);
+        when(mockSSTableUploadConfiguration.concurrentUploadsLimit()).thenReturn(0);
 
         UUID uploadId = UUID.randomUUID();
         sendUploadRequestAndVerify(context, uploadId, "ks", "tbl", "without-md5.db", "",
@@ -174,7 +174,7 @@
     @Test
     public void testPermitCleanup(VertxTestContext context) throws IOException, InterruptedException
     {
-        when(mockConfiguration.getConcurrentUploadsLimit()).thenReturn(1);
+        when(mockSSTableUploadConfiguration.concurrentUploadsLimit()).thenReturn(1);
 
         UUID uploadId = UUID.randomUUID();
         CountDownLatch latch = new CountDownLatch(1);
@@ -242,7 +242,7 @@
         WebClient client = WebClient.create(vertx);
         String testRoute = "/api/v1/uploads/" + uploadId + "/keyspaces/" + keyspace
                            + "/tables/" + tableName + "/components/" + targetFileName;
-        HttpRequest<Buffer> req = client.put(config.getPort(), "localhost", testRoute);
+        HttpRequest<Buffer> req = client.put(server.actualPort(), "localhost", testRoute);
         if (!expectedMd5.isEmpty())
         {
             req.putHeader(HttpHeaderNames.CONTENT_MD5.toString(), expectedMd5);
diff --git a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java
index 74d847e..a020fb5 100644
--- a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotSearchTest.java
@@ -36,20 +36,15 @@
 import io.vertx.core.Vertx;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
-import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 
 import static org.apache.cassandra.sidecar.snapshots.SnapshotUtils.mockInstancesConfig;
 import static org.apache.cassandra.sidecar.snapshots.SnapshotUtils.snapshot1Instance1Files;
 import static org.apache.cassandra.sidecar.snapshots.SnapshotUtils.snapshot1Instance2Files;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 /**
  * Tests for searching snapshots
@@ -71,14 +66,8 @@
         SnapshotUtils.initializeTmpDirectory(temporaryFolder);
         InstancesConfig mockInstancesConfig = mockInstancesConfig(rootDir);
 
-        ValidationConfiguration validationConfiguration = new TestValidationConfiguration();
-        CassandraInputValidator validator = new CassandraInputValidator(validationConfiguration);
-        Configuration configuration = mock(Configuration.class);
-        WorkerPoolConfiguration workerPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                             TimeUnit.SECONDS.toMillis(30));
-        when(configuration.serverWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        when(configuration.serverInternalWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        ExecutorPools executorPools = new ExecutorPools(vertx, configuration);
+        CassandraInputValidator validator = new CassandraInputValidator();
+        ExecutorPools executorPools = new ExecutorPools(vertx, new ServiceConfigurationImpl());
         instance = new SnapshotPathBuilder(vertx, mockInstancesConfig, validator, executorPools);
     }
 
diff --git a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java
index 7dc89de..c0f7af7 100644
--- a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java
+++ b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java
@@ -117,18 +117,22 @@
             delegate2 = new CassandraAdapterDelegate(versionProvider, cqlSessionProvider2, null, null);
         }
 
-        InstanceMetadataImpl localhost = new InstanceMetadataImpl(1,
-                                                                  "localhost",
-                                                                  9043,
-                                                                  Collections.singletonList(rootPath + "/d1"),
-                                                                  makeStagingDir(rootPath),
-                                                                  delegate1);
-        InstanceMetadataImpl localhost2 = new InstanceMetadataImpl(2,
-                                                                   "localhost2",
-                                                                   9043,
-                                                                   Collections.singletonList(rootPath + "/d2"),
-                                                                   makeStagingDir(rootPath),
-                                                                   delegate2);
+        InstanceMetadataImpl localhost = InstanceMetadataImpl.builder()
+                                                             .id(1)
+                                                             .host("localhost")
+                                                             .port(9043)
+                                                             .dataDirs(Collections.singletonList(rootPath + "/d1"))
+                                                             .stagingDir(makeStagingDir(rootPath))
+                                                             .delegate(delegate1)
+                                                             .build();
+        InstanceMetadataImpl localhost2 = InstanceMetadataImpl.builder()
+                                                              .id(2)
+                                                              .host("localhost2")
+                                                              .port(9043)
+                                                              .dataDirs(Collections.singletonList(rootPath + "/d2"))
+                                                              .stagingDir(makeStagingDir(rootPath))
+                                                              .delegate(delegate2)
+                                                              .build();
         List<InstanceMetadata> instanceMetas = Arrays.asList(localhost, localhost2);
         return new InstancesConfigImpl(instanceMetas, DnsResolver.DEFAULT);
     }
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java
index 235cd4a..f4ecb6c 100644
--- a/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/CacheFactoryTest.java
@@ -31,12 +31,15 @@
 
 import com.github.benmanes.caffeine.cache.Cache;
 import io.vertx.core.Future;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.config.CacheConfiguration;
+import org.apache.cassandra.sidecar.config.SSTableImportConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.CacheConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.SSTableImportConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 /**
  * Unit tests for the {@link CacheFactory} class
@@ -52,14 +55,16 @@
     void setup()
     {
         fakeTicker = new FakeTicker();
-        CacheConfiguration mockSSTableImportCacheConfiguration = mock(CacheConfiguration.class);
-        when(mockSSTableImportCacheConfiguration.expireAfterAccessMillis())
-        .thenReturn(SSTABLE_IMPORT_EXPIRE_AFTER_ACCESS_MILLIS); // 2 hours
-        when(mockSSTableImportCacheConfiguration.maximumSize()).thenReturn(SSTABLE_IMPORT_CACHE_MAX_SIZE);
-        Configuration mockConfiguration = mock(Configuration.class);
-        when(mockConfiguration.ssTableImportCacheConfiguration()).thenReturn(mockSSTableImportCacheConfiguration);
+
+        CacheConfiguration ssTableImportCacheConfiguration =
+        new CacheConfigurationImpl(SSTABLE_IMPORT_EXPIRE_AFTER_ACCESS_MILLIS, // 2 hours
+                                   SSTABLE_IMPORT_CACHE_MAX_SIZE);
+
+        SSTableImportConfiguration ssTableImportConfiguration =
+        new SSTableImportConfigurationImpl(ssTableImportCacheConfiguration);
+        ServiceConfiguration serviceConfiguration = new ServiceConfigurationImpl(ssTableImportConfiguration);
         SSTableImporter mockSSTableImporter = mock(SSTableImporter.class);
-        cacheFactory = new CacheFactory(mockConfiguration, mockSSTableImporter, fakeTicker::read);
+        cacheFactory = new CacheFactory(serviceConfiguration, mockSSTableImporter, fakeTicker::read);
     }
 
     @Test
@@ -76,8 +81,8 @@
         Void result1 = ssTableImportCacheEntry(cache, type1Options1, mock(Void.class));
         Void type2Result1 = ssTableImportCacheEntry(cache, type2Options1, mock(Void.class));
 
-        assertThat(result1).isNotNull();
-        assertThat(result1).isNotSameAs(type2Result1);
+        assertThat(result1).isNotNull()
+                           .isNotSameAs(type2Result1);
 
         // advance ticker 1 minute
         fakeTicker.advance(1, TimeUnit.MINUTES);
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java
index 3ef258d..78e7105 100644
--- a/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/CassandraInputValidatorTest.java
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.cassandra.sidecar.common;
+package org.apache.cassandra.sidecar.utils;
 
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -24,7 +24,6 @@
 
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.ext.web.handler.HttpException;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
@@ -38,7 +37,7 @@
     @BeforeEach
     void setup()
     {
-        instance = new CassandraInputValidator(new TestValidationConfiguration());
+        instance = new CassandraInputValidator();
     }
 
     private void testCommon_invalidCharacters(String testName)
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/SSTableImporterTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/SSTableImporterTest.java
index a20ca22..ce690ba 100644
--- a/src/test/java/org/apache/cassandra/sidecar/utils/SSTableImporterTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/SSTableImporterTest.java
@@ -19,7 +19,6 @@
 package org.apache.cassandra.sidecar.utils;
 
 import java.util.Collections;
-import java.util.concurrent.TimeUnit;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -31,11 +30,12 @@
 import io.vertx.ext.web.handler.HttpException;
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.common.CassandraAdapterDelegate;
 import org.apache.cassandra.sidecar.common.TableOperations;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.SSTableImportConfigurationImpl;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -49,24 +49,25 @@
 {
     private Vertx vertx;
     private InstanceMetadataFetcher mockMetadataFetcher;
-    private Configuration mockConfiguration;
     private TableOperations mockTableOperations1;
     private ExecutorPools executorPools;
     private SSTableUploadsPathBuilder mockUploadPathBuilder;
     private SSTableImporter importer;
+    private ServiceConfiguration serviceConfiguration;
 
     @BeforeEach
     public void setup() throws InterruptedException
     {
         vertx = Vertx.vertx();
+        serviceConfiguration = new ServiceConfigurationImpl(new SSTableImportConfigurationImpl(10));
+
         mockMetadataFetcher = mock(InstanceMetadataFetcher.class);
-        mockConfiguration = mock(Configuration.class);
         CassandraAdapterDelegate mockCassandraAdapterDelegate1 = mock(CassandraAdapterDelegate.class);
         CassandraAdapterDelegate mockCassandraAdapterDelegate2 = mock(CassandraAdapterDelegate.class);
         CassandraAdapterDelegate mockCassandraAdapterDelegate3 = mock(CassandraAdapterDelegate.class);
         mockTableOperations1 = mock(TableOperations.class);
         TableOperations mockTableOperations2 = mock(TableOperations.class);
-        when(mockConfiguration.getSSTableImportPollIntervalMillis()).thenReturn(10);
+
         when(mockMetadataFetcher.delegate("localhost")).thenReturn(mockCassandraAdapterDelegate1);
         when(mockMetadataFetcher.delegate("127.0.0.2")).thenReturn(mockCassandraAdapterDelegate2);
         when(mockMetadataFetcher.delegate("127.0.0.3")).thenReturn(mockCassandraAdapterDelegate3);
@@ -81,11 +82,7 @@
         when(mockTableOperations2.importNewSSTables("ks", "tbl", "/dir", true, true,
                                                     true, true, true, true, false))
         .thenThrow(new RuntimeException("Exception during import"));
-        WorkerPoolConfiguration workerPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                             TimeUnit.SECONDS.toMillis(30));
-        when(mockConfiguration.serverWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        when(mockConfiguration.serverInternalWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        executorPools = new ExecutorPools(vertx, mockConfiguration);
+        executorPools = new ExecutorPools(vertx, serviceConfiguration);
         mockUploadPathBuilder = mock(SSTableUploadsPathBuilder.class);
 
         // since we are not actually creating any files for the test, we need to handle cleanup such that we don't
@@ -95,7 +92,7 @@
         when(mockUploadPathBuilder.resolveUploadIdDirectory(anyString(), anyString()))
         .thenReturn(Future.failedFuture("fake-path"));
         when(mockUploadPathBuilder.isValidDirectory("fake-path")).thenReturn(Future.failedFuture("skip cleanup"));
-        importer = new SSTableImporter(vertx, mockMetadataFetcher, mockConfiguration, executorPools,
+        importer = new SSTableImporter(vertx, mockMetadataFetcher, serviceConfiguration, executorPools,
                                        mockUploadPathBuilder);
     }
 
@@ -204,8 +201,9 @@
     @Test
     void testCancelImportSucceeds(VertxTestContext context)
     {
-        when(mockConfiguration.getSSTableImportPollIntervalMillis()).thenReturn(500);
-        SSTableImporter importer = new SSTableImporter(vertx, mockMetadataFetcher, mockConfiguration, executorPools,
+        serviceConfiguration = new ServiceConfigurationImpl(new SSTableImportConfigurationImpl(500));
+
+        SSTableImporter importer = new SSTableImporter(vertx, mockMetadataFetcher, serviceConfiguration, executorPools,
                                                        mockUploadPathBuilder);
         SSTableImporter.ImportOptions options = new SSTableImporter.ImportOptions.Builder()
                                                 .host("localhost")
diff --git a/src/test/java/org/apache/cassandra/sidecar/utils/TimeSkewInfoTest.java b/src/test/java/org/apache/cassandra/sidecar/utils/TimeSkewInfoTest.java
index 0058e07..ce714f6 100644
--- a/src/test/java/org/apache/cassandra/sidecar/utils/TimeSkewInfoTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/utils/TimeSkewInfoTest.java
@@ -20,7 +20,7 @@
 
 import org.junit.jupiter.api.Test;
 
-import org.apache.cassandra.sidecar.Configuration;
+import org.apache.cassandra.sidecar.config.ServiceConfiguration;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -36,7 +36,7 @@
     {
         long currentTime = 12345L;
         TimeProvider timeProvider = () -> currentTime;
-        Configuration config = mock(Configuration.class);
+        ServiceConfiguration config = mock(ServiceConfiguration.class);
         TimeSkewInfo info = new TimeSkewInfo(timeProvider, config);
         assertThat(info.timeSkewResponse().currentTime).isEqualTo(currentTime);
     }
@@ -44,7 +44,7 @@
     @Test
     public void returnsMaxSkewInMinutes()
     {
-        Configuration config = mock(Configuration.class);
+        ServiceConfiguration config = mock(ServiceConfiguration.class);
         when(config.allowableSkewInMinutes()).thenReturn(60);
         TimeSkewInfo info = new TimeSkewInfo(TimeProvider.DEFAULT_TIME_PROVIDER, config);
         assertThat(info.timeSkewResponse().allowableSkewInMinutes).isEqualTo(60);
diff --git a/src/test/resources/config/sidecar_custom_allowable_time_skew.yaml b/src/test/resources/config/sidecar_custom_allowable_time_skew.yaml
new file mode 100644
index 0000000..d584a21
--- /dev/null
+++ b/src/test/resources/config/sidecar_custom_allowable_time_skew.yaml
@@ -0,0 +1,19 @@
+#
+# Cassandra SideCar configuration file
+#
+cassandra:
+  host: localhost
+  port: 9042
+  data_dirs: /cassandra/d1/data, /cassandra/d2/data
+  jmx_host: 127.0.0.1
+  jmx_port: 7199
+  jmx_role: controlRole
+  jmx_role_password: controlPassword
+  jmx_ssl_enabled: true
+
+sidecar:
+  host: 0.0.0.0
+  port: 1234
+  request_idle_timeout_millis: 500000 # this field expects integer value
+  request_timeout_millis: 1200000
+  allowable_time_skew_in_minutes: 1
diff --git a/src/test/resources/config/sidecar_multiple_instances.yaml b/src/test/resources/config/sidecar_multiple_instances.yaml
new file mode 100644
index 0000000..4388cea
--- /dev/null
+++ b/src/test/resources/config/sidecar_multiple_instances.yaml
@@ -0,0 +1,121 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Cassandra SideCar configuration file
+#
+cassandra_instances:
+  - id: 1
+    host: localhost1
+    port: 9042
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node1/data0
+      - /ccm/test/node1/data1
+    staging_dir: /ccm/test/node1/sstable-staging
+    jmx_host: 127.0.0.1
+    jmx_port: 7100
+    jmx_ssl_enabled: false
+  #    jmx_role:
+  #    jmx_role_password:
+  - id: 2
+    host: localhost2
+    port: 9042
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node2/data0
+      - /ccm/test/node2/data1
+    staging_dir: /ccm/test/node2/sstable-staging
+    jmx_host: 127.0.0.1
+    jmx_port: 7200
+    jmx_ssl_enabled: false
+  #    jmx_role:
+  #    jmx_role_password:
+  - id: 3
+    host: localhost3
+    port: 9042
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node3/data0
+      - /ccm/test/node3/data1
+    staging_dir: /ccm/test/node3/sstable-staging
+    jmx_host: 127.0.0.1
+    jmx_port: 7300
+    jmx_ssl_enabled: false
+#    jmx_role:
+#    jmx_role_password:
+
+sidecar:
+  host: 0.0.0.0
+  port: 9043
+  request_idle_timeout_millis: 300000 # this field expects integer value
+  request_timeout_millis: 300000
+  throttle:
+    stream_requests_per_sec: 5000
+    delay_sec: 5
+    timeout_sec: 10
+  sstable_upload:
+    concurrent_upload_limit: 80
+    min_free_space_percent: 10
+  allowable_time_skew_in_minutes: 60
+  sstable_import:
+    poll_interval_millis: 100
+    cache:
+      expire_after_access_millis: 7200000 # 2 hours
+      maximum_size: 10000
+  worker_pools:
+    service:
+      name: "sidecar-worker-pool"
+      size: 20
+      max_execution_time_millis: 60000 # 60 seconds
+    internal:
+      name: "sidecar-internal-worker-pool"
+      size: 20
+      max_execution_time_millis: 300000 # 5 minutes
+
+#
+# Enable SSL configuration (Disabled by default)
+#
+#  ssl:
+#    enabled: true
+#    keystore:
+#      path: "path/to/keystore.p12"
+#      password: password
+#    truststore:
+#      path: "path/to/truststore.p12"
+#      password: password
+
+
+healthcheck:
+  poll_freq_millis: 30000
+
+cassandra_input_validation:
+  forbidden_keyspaces:
+    - system_schema
+    - system_traces
+    - system_distributed
+    - system
+    - system_auth
+    - system_views
+    - system_virtual_schema
+  allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
+  allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
+  allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
diff --git a/src/test/resources/config/sidecar_single_instance.yaml b/src/test/resources/config/sidecar_single_instance.yaml
new file mode 100644
index 0000000..8948660
--- /dev/null
+++ b/src/test/resources/config/sidecar_single_instance.yaml
@@ -0,0 +1,74 @@
+#
+# Cassandra SideCar configuration file
+#
+cassandra:
+  host: localhost
+  port: 9042
+  username: cassandra
+  password: cassandra
+  data_dirs:
+    - /ccm/test/node1/data0
+    - /ccm/test/node1/data1
+  staging_dir: /ccm/test/node1/sstable-staging
+  jmx_host: 127.0.0.1
+  jmx_port: 7199
+  jmx_role: controlRole
+  jmx_role_password: controlPassword
+  jmx_ssl_enabled: true
+
+sidecar:
+  host: 0.0.0.0
+  port: 9043
+  request_idle_timeout_millis: 300000 # this field expects integer value
+  request_timeout_millis: 300000
+  throttle:
+    stream_requests_per_sec: 5000
+    delay_sec: 5
+    timeout_sec: 10
+  sstable_upload:
+    concurrent_upload_limit: 80
+    min_free_space_percent: 10
+  allowable_time_skew_in_minutes: 60
+  sstable_import:
+    poll_interval_millis: 100
+    cache:
+      expire_after_access_millis: 7200000 # 2 hours
+      maximum_size: 10000
+  worker_pools:
+    service:
+      name: "sidecar-worker-pool"
+      size: 20
+      max_execution_time_millis: 60000 # 60 seconds
+    internal:
+      name: "sidecar-internal-worker-pool"
+      size: 20
+      max_execution_time_millis: 300000 # 5 minutes
+
+#
+# Enable SSL configuration (Disabled by default)
+#
+#  ssl:
+#    enabled: true
+#    keystore:
+#      path: "path/to/keystore.p12"
+#      password: password
+#    truststore:
+#      path: "path/to/truststore.p12"
+#      password: password
+
+
+healthcheck:
+  poll_freq_millis: 30000
+
+cassandra_input_validation:
+  forbidden_keyspaces:
+    - system_schema
+    - system_traces
+    - system_distributed
+    - system
+    - system_auth
+    - system_views
+    - system_virtual_schema
+  allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
+  allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
+  allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
diff --git a/src/test/resources/config/sidecar_ssl.yaml b/src/test/resources/config/sidecar_ssl.yaml
new file mode 100644
index 0000000..fe03502
--- /dev/null
+++ b/src/test/resources/config/sidecar_ssl.yaml
@@ -0,0 +1,121 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Cassandra SideCar configuration file
+#
+cassandra_instances:
+  - id: 1
+    host: localhost1
+    port: 9042
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node1/data0
+      - /ccm/test/node1/data1
+    staging_dir: /ccm/test/node1/sstable-staging
+    jmx_host: 127.0.0.1
+    jmx_port: 7100
+    jmx_ssl_enabled: false
+  #    jmx_role:
+  #    jmx_role_password:
+  - id: 2
+    host: localhost2
+    port: 9042
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node2/data0
+      - /ccm/test/node2/data1
+    staging_dir: /ccm/test/node2/sstable-staging
+    jmx_host: 127.0.0.1
+    jmx_port: 7200
+    jmx_ssl_enabled: false
+  #    jmx_role:
+  #    jmx_role_password:
+  - id: 3
+    host: localhost3
+    port: 9042
+    username: cassandra
+    password: cassandra
+    data_dirs:
+      - /ccm/test/node3/data0
+      - /ccm/test/node3/data1
+    staging_dir: /ccm/test/node3/sstable-staging
+    jmx_host: 127.0.0.1
+    jmx_port: 7300
+    jmx_ssl_enabled: false
+#    jmx_role:
+#    jmx_role_password:
+
+sidecar:
+  host: 0.0.0.0
+  port: 9043
+  request_idle_timeout_millis: 300000 # this field expects integer value
+  request_timeout_millis: 300000
+  throttle:
+    stream_requests_per_sec: 5000
+    delay_sec: 5
+    timeout_sec: 10
+  sstable_upload:
+    concurrent_upload_limit: 80
+    min_free_space_percent: 10
+  allowable_time_skew_in_minutes: 60
+  sstable_import:
+    poll_interval_millis: 100
+    cache:
+      expire_after_access_millis: 7200000 # 2 hours
+      maximum_size: 10000
+  worker_pools:
+    service:
+      name: "sidecar-worker-pool"
+      size: 20
+      max_execution_time_millis: 60000 # 60 seconds
+    internal:
+      name: "sidecar-internal-worker-pool"
+      size: 20
+      max_execution_time_millis: 300000 # 5 minutes
+
+#
+# Enable SSL configuration (Disabled by default)
+#
+ssl:
+  enabled: true
+  keystore:
+    path: "path/to/keystore.p12"
+    password: password
+  truststore:
+    path: "path/to/truststore.p12"
+    password: password
+
+
+healthcheck:
+  poll_freq_millis: 30000
+
+cassandra_input_validation:
+  forbidden_keyspaces:
+    - system_schema
+    - system_traces
+    - system_distributed
+    - system
+    - system_auth
+    - system_views
+    - system_virtual_schema
+  allowed_chars_for_directory: "[a-zA-Z0-9_-]+"
+  allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)"
+  allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)"
diff --git a/src/test/resources/config/sidecar_validation_configuration.yaml b/src/test/resources/config/sidecar_validation_configuration.yaml
new file mode 100644
index 0000000..63ffe86
--- /dev/null
+++ b/src/test/resources/config/sidecar_validation_configuration.yaml
@@ -0,0 +1,34 @@
+#
+# Cassandra SideCar configuration file
+#
+cassandra:
+  host: localhost
+  port: 9042
+  data_dirs: /cassandra/d1/data, /cassandra/d2/data
+  jmx_host: 127.0.0.1
+  jmx_port: 7199
+  jmx_role: controlRole
+  jmx_role_password: controlPassword
+  jmx_ssl_enabled: true
+
+sidecar:
+  host: 0.0.0.0
+  port: 1234
+  request_idle_timeout_millis: 500000 # this field expects integer value
+  request_timeout_millis: 1200000
+  throttle:
+    stream_requests_per_sec: 80
+    delay_sec: 7
+    timeout_sec: 21
+  allowable_time_skew_in_minutes: 89
+  sstable_import:
+    poll_interval_millis: 50
+
+cassandra_input_validation:
+  forbidden_keyspaces:
+    - a
+    - b
+    - c
+  allowed_chars_for_directory: "[a-z]+"
+  allowed_chars_for_component_name: "(.db|.cql|.json|.crc32|TOC.txt)"
+  allowed_chars_for_restricted_component_name: "(.db|TOC.txt)"
diff --git a/src/test/resources/sidecar_with_single_multiple_instances.yaml b/src/test/resources/config/sidecar_with_single_multiple_instances.yaml
similarity index 66%
rename from src/test/resources/sidecar_with_single_multiple_instances.yaml
rename to src/test/resources/config/sidecar_with_single_multiple_instances.yaml
index 7ea5c25..ec98f7d 100644
--- a/src/test/resources/sidecar_with_single_multiple_instances.yaml
+++ b/src/test/resources/config/sidecar_with_single_multiple_instances.yaml
@@ -2,14 +2,14 @@
 # Cassandra SideCar configuration file
 #
 cassandra:
-  - host: localhost
-  - port: 9042
-  - data_dirs: /cassandra/d1/data, /cassandra/d2/data
-  - jmx_host: 127.0.0.1
-  - jmx_port: 7199
-  - jmx_role: controlRole
-  - jmx_role_password: controlPassword
-  - jmx_ssl_enabled: true
+  host: localhost
+  port: 9042
+  data_dirs: /cassandra/d1/data, /cassandra/d2/data
+  jmx_host: 127.0.0.1
+  jmx_port: 7199
+  jmx_role: controlRole
+  jmx_role_password: controlPassword
+  jmx_ssl_enabled: true
 
 cassandra_instances:
   - id: 1
@@ -32,13 +32,13 @@
     jmx_ssl_enabled: true
 
 sidecar:
-  - host: 0.0.0.0
-  - port: 9043
-  - throttle:
-      - stream_requests_per_sec: 5000
-      - delay_sec: 5
-      - timeout_sec: 10
-  - allowable_time_skew_in_minutes: 60
+  host: 0.0.0.0
+  port: 9043
+  throttle:
+    stream_requests_per_sec: 5000
+    delay_sec: 5
+    timeout_sec: 10
+  allowable_time_skew_in_minutes: 60
 
 #
 # Enable SSL configuration (Disabled by default)
@@ -54,4 +54,4 @@
 
 
 healthcheck:
-  - poll_freq_millis: 30000
+  poll_freq_millis: 30000
diff --git a/src/test/resources/sidecar_custom_allowable_time_skew.yaml b/src/test/resources/sidecar_custom_allowable_time_skew.yaml
deleted file mode 100644
index 8ba4520..0000000
--- a/src/test/resources/sidecar_custom_allowable_time_skew.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# Cassandra SideCar configuration file
-#
-cassandra:
-  - host: localhost
-  - port: 9042
-  - data_dirs: /cassandra/d1/data, /cassandra/d2/data
-  - jmx_host: 127.0.0.1
-  - jmx_port: 7199
-  - jmx_role: controlRole
-  - jmx_role_password: controlPassword
-  - jmx_ssl_enabled: true
-
-sidecar:
-  - host: 0.0.0.0
-  - port: 1234
-  - request_idle_timeout_millis: 500000 # this field expects integer value
-  - request_timeout_millis: 1200000
-  - throttle:
-      - stream_requests_per_sec: 80
-      - delay_sec: 7
-      - timeout_sec: 21
-  - allowable_time_skew_in_minutes: 1
-  - sstable_import:
-      - poll_interval_millis: 50
diff --git a/src/test/resources/sidecar_multiple_instances.yaml b/src/test/resources/sidecar_multiple_instances.yaml
deleted file mode 100644
index 460d0fb..0000000
--- a/src/test/resources/sidecar_multiple_instances.yaml
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Cassandra SideCar configuration file
-#
-cassandra_instances:
-  - id: 1
-    host: localhost1
-    port: 9042
-    data_dirs: /cassandra/d1/data, /cassandra/d2/data
-    jmx_host: 127.0.0.1
-    jmx_port: 7100
-    jmx_ssl_enabled: false
-  #    jmx_role:
-  #    jmx_role_password:
-  - id: 2
-    host: localhost2
-    port: 9042
-    data_dirs: /cassandra/d3/data, /cassandra/d4/data
-    jmx_host: 127.0.0.1
-    jmx_port: 7200
-    jmx_ssl_enabled: false
-#    jmx_role:
-#    jmx_role_password:
-
-sidecar:
-  - host: 0.0.0.0
-  - port: 9043
-  - request_idle_timeout_millis: 500000 # this field expects integer value
-  - request_timeout_millis: 1200000
-  - throttle:
-      - stream_requests_per_sec: 80
-      - delay_sec: 7
-      - timeout_sec: 21
-  - sstable_upload:
-      - concurrent_upload_limit: 80
-      - min_free_space_percent: 10
-  - allowable_time_skew_in_minutes: 89
-  - sstable_import:
-      - poll_interval_millis: 50
-      - cache:
-        - expire_after_access_millis: 1000
-        - maximum_size: 100
-
-#
-# Enable SSL configuration (Disabled by default)
-#
-#  - ssl:
-#      - enabled: true
-#      - keystore:
-#          - path: "path/to/keystore.p12"
-#          - password: password
-#      - truststore:
-#          - path: "path/to/truststore.p12"
-#          - password: password
-
-
-healthcheck:
-  - poll_freq_millis: 30000
diff --git a/src/test/resources/sidecar_single_instance.yaml b/src/test/resources/sidecar_single_instance.yaml
deleted file mode 100644
index 90d5e0e..0000000
--- a/src/test/resources/sidecar_single_instance.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-#
-# Cassandra SideCar configuration file
-#
-cassandra:
-  - host: localhost
-  - port: 9042
-  - data_dirs: /cassandra/d1/data, /cassandra/d2/data
-  - jmx_host: 127.0.0.1
-  - jmx_port: 7199
-  - jmx_role: controlRole
-  - jmx_role_password: controlPassword
-  - jmx_ssl_enabled: true
-
-sidecar:
-  - host: 0.0.0.0
-  - port: 9043
-  - request_idle_timeout_millis: 500000 # this field expects integer value
-  - request_timeout_millis: 1200000
-  - throttle:
-      - stream_requests_per_sec: 80
-      - delay_sec: 7
-      - timeout_sec: 21
-  - allowable_time_skew_in_minutes: 89
-  - sstable_import:
-      - poll_interval_millis: 50
-      - cache:
-          - expire_after_access_millis: 1000
-          - maximum_size: 100
-
-#
-# Enable SSL configuration (Disabled by default)
-#
-#  - ssl:
-#      - enabled: true
-#      - keystore:
-#          - path: "path/to/keystore.p12"
-#          - password: password
-#      - truststore:
-#          - path: "path/to/truststore.p12"
-#          - password: password
-
-
-healthcheck:
-  - poll_freq_millis: 30000
diff --git a/src/test/resources/sidecar_validation_configuration.yaml b/src/test/resources/sidecar_validation_configuration.yaml
deleted file mode 100644
index 11b0714..0000000
--- a/src/test/resources/sidecar_validation_configuration.yaml
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Cassandra SideCar configuration file
-#
-cassandra:
-  - host: localhost
-  - port: 9042
-  - data_dirs: /cassandra/d1/data, /cassandra/d2/data
-  - jmx_host: 127.0.0.1
-  - jmx_port: 7199
-  - jmx_role: controlRole
-  - jmx_role_password: controlPassword
-  - jmx_ssl_enabled: true
-
-sidecar:
-  - host: 0.0.0.0
-  - port: 1234
-  - request_idle_timeout_millis: 500000 # this field expects integer value
-  - request_timeout_millis: 1200000
-  - throttle:
-      - stream_requests_per_sec: 80
-      - delay_sec: 7
-      - timeout_sec: 21
-  - allowable_time_skew_in_minutes: 89
-  - sstable_import:
-      - poll_interval_millis: 50
-
-cassandra_input_validation:
-  - forbidden_keyspaces:
-      - a
-      - b
-      - c
-  - allowed_chars_for_directory: "[a-z]+"
-  - allowed_chars_for_component_name: "(.db|.cql|.json|.crc32|TOC.txt)"
-  - allowed_chars_for_restricted_component_name: "(.db|TOC.txt)"
diff --git a/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java b/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
index fb0ab8c..e2dbd9b 100644
--- a/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
+++ b/src/testFixtures/java/org/apache/cassandra/sidecar/snapshots/AbstractSnapshotPathBuilderTest.java
@@ -32,23 +32,18 @@
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.core.Future;
 import io.vertx.core.Vertx;
 import io.vertx.ext.web.handler.HttpException;
 import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.Configuration;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
-import org.apache.cassandra.sidecar.common.TestValidationConfiguration;
-import org.apache.cassandra.sidecar.common.utils.CassandraInputValidator;
-import org.apache.cassandra.sidecar.common.utils.ValidationConfiguration;
 import org.apache.cassandra.sidecar.concurrent.ExecutorPools;
-import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
+import org.apache.cassandra.sidecar.config.yaml.ServiceConfigurationImpl;
 import org.apache.cassandra.sidecar.data.SnapshotRequest;
 import org.apache.cassandra.sidecar.data.StreamSSTableComponentRequest;
+import org.apache.cassandra.sidecar.utils.CassandraInputValidator;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -76,15 +71,7 @@
     @BeforeEach
     protected void setup() throws IOException
     {
-        ValidationConfiguration validationConfiguration = new TestValidationConfiguration();
-        validator = new CassandraInputValidator(validationConfiguration);
-        Guice.createInjector(new AbstractModule()
-        {
-            protected void configure()
-            {
-                bind(ValidationConfiguration.class).toInstance(validationConfiguration);
-            }
-        });
+        validator = new CassandraInputValidator();
 
         InstancesConfig mockInstancesConfig = mock(InstancesConfig.class);
         InstanceMetadata mockInstanceMeta = mock(InstanceMetadata.class);
@@ -145,12 +132,7 @@
                            "/snapshots/this_is_a_valid_snapshot_name_i_❤_u/nb-203-big-TOC.txt").createNewFile();
 
         vertx = Vertx.vertx();
-        Configuration configuration = mock(Configuration.class);
-        WorkerPoolConfiguration workerPoolConf = new WorkerPoolConfiguration("test-pool", 10,
-                                                                             TimeUnit.SECONDS.toMillis(30));
-        when(configuration.serverWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        when(configuration.serverInternalWorkerPoolConfiguration()).thenReturn(workerPoolConf);
-        executorPools = new ExecutorPools(vertx, configuration);
+        executorPools = new ExecutorPools(vertx, new ServiceConfigurationImpl());
         instance = initialize(vertx, mockInstancesConfig, executorPools);
     }
 
diff --git a/vertx-client-shaded/build.gradle b/vertx-client-shaded/build.gradle
index 7690614..1133246 100644
--- a/vertx-client-shaded/build.gradle
+++ b/vertx-client-shaded/build.gradle
@@ -46,7 +46,7 @@
 
 dependencies {
     shadow(group: 'org.slf4j', name: 'slf4j-api', version: "${project.slf4jVersion}")
-    shadow(group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.14.2')
+    shadow(group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "${project.jacksonVersion}")
     api(project(':vertx-client'))
 
     testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1'
diff --git a/vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxSidecarClientTest.java b/vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxSidecarClientTest.java
index 557cc67..f7c1e03 100644
--- a/vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxSidecarClientTest.java
+++ b/vertx-client/src/test/java/org/apache/cassandra/sidecar/client/VertxSidecarClientTest.java
@@ -40,18 +40,19 @@
                                             .userAgent("cassandra-sidecar-test/0.0.1")
                                             .build();
 
-        SidecarConfig sidecarConfig = new SidecarConfig.Builder().maxRetries(instances.size())
-                                                                 .retryDelayMillis(50)
-                                                                 .maxRetryDelayMillis(100)
-                                                                 .build();
+        SidecarClientConfig sidecarClientConfig = SidecarClientConfigImpl.builder()
+                                                                         .maxRetries(instances.size())
+                                                                         .retryDelayMillis(50)
+                                                                         .maxRetryDelayMillis(100)
+                                                                         .build();
 
-        RetryPolicy defaultRetryPolicy = new ExponentialBackoffRetryPolicy(sidecarConfig.maxRetries(),
-                                                                           sidecarConfig.retryDelayMillis(),
-                                                                           sidecarConfig.maxRetryDelayMillis());
+        RetryPolicy defaultRetryPolicy = new ExponentialBackoffRetryPolicy(sidecarClientConfig.maxRetries(),
+                                                                           sidecarClientConfig.retryDelayMillis(),
+                                                                           sidecarClientConfig.maxRetryDelayMillis());
 
         VertxHttpClient vertxHttpClient = new VertxHttpClient(vertx, httpClientConfig);
         VertxRequestExecutor requestExecutor = new VertxRequestExecutor(vertxHttpClient);
         SimpleSidecarInstancesProvider instancesProvider = new SimpleSidecarInstancesProvider(instances);
-        return new SidecarClient(instancesProvider, requestExecutor, sidecarConfig, defaultRetryPolicy);
+        return new SidecarClient(instancesProvider, requestExecutor, sidecarClientConfig, defaultRetryPolicy);
     }
 }