diff --git a/CHANGES.txt b/CHANGES.txt
index 072fab1f9..9c10df67f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,6 @@
+4.13.0 (Sep 13, 2024)
+- Added support for Kerberos Proxy authentication.
+
 4.12.1 (Jun 10, 2024)
 - Fixed deadlock for virtual thread in Push Manager and SSE Client.
 
diff --git a/client/pom.xml b/client/pom.xml
index b8d94bba7..2c1892ca2 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.split.client</groupId>
         <artifactId>java-client-parent</artifactId>
-        <version>4.12.1</version>
+        <version>4.13.0</version>
     </parent>
     <artifactId>java-client</artifactId>
     <packaging>jar</packaging>
@@ -64,6 +64,7 @@
                                     <include>io.split.schemas:*</include>
                                     <include>io.codigo.grammar:*</include>
                                     <include>org.apache.httpcomponents.*</include>
+                                    <include>org.apache.hc.*</include>
                                     <include>com.google.*</include>
                                     <include>org.yaml:snakeyaml:*</include>
 
@@ -238,5 +239,17 @@
             <version>4.0.3</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-module-junit4</artifactId>
+            <version>1.7.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito</artifactId>
+            <version>1.7.4</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/client/src/main/java/io/split/client/RequestDecorator.java b/client/src/main/java/io/split/client/RequestDecorator.java
index 1572463ef..33059e617 100644
--- a/client/src/main/java/io/split/client/RequestDecorator.java
+++ b/client/src/main/java/io/split/client/RequestDecorator.java
@@ -2,16 +2,12 @@
 
 import io.split.client.dtos.RequestContext;
 
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.Header;
 
 import java.util.HashSet;
-import java.util.HashMap;
 import java.util.Map;
 import java.util.Arrays;
-import java.util.ArrayList;
 import java.util.Set;
-import java.util.List;
+import java.util.stream.Collectors;
 
 public final class RequestDecorator {
     CustomHeaderDecorator _headerDecorator;
@@ -36,42 +32,16 @@ public RequestDecorator(CustomHeaderDecorator headerDecorator) {
                 : headerDecorator;
     }
 
-    public HttpRequest decorateHeaders(HttpRequest request) {
+    public RequestContext decorateHeaders(RequestContext request) {
         try {
-            Map<String, List<String>> headers = _headerDecorator
-                    .getHeaderOverrides(new RequestContext(convertToMap(request.getHeaders())));
-            for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
-                if (isHeaderAllowed(entry.getKey())) {
-                    List<String> values = entry.getValue();
-                    for (int i = 0; i < values.size(); i++) {
-                        if (i == 0) {
-                            request.setHeader(entry.getKey(), values.get(i));
-                        } else {
-                            request.addHeader(entry.getKey(), values.get(i));
-                        }
-                    }
-                }
-            }
+            return new RequestContext(_headerDecorator.getHeaderOverrides(request)
+                    .entrySet()
+                    .stream()
+                    .filter(e -> !forbiddenHeaders.contains(e.getKey().toLowerCase()))
+                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
         } catch (Exception e) {
             throw new IllegalArgumentException(
                     String.format("Problem adding custom headers to request decorator: %s", e), e);
         }
-
-        return request;
-    }
-
-    private boolean isHeaderAllowed(String headerName) {
-        return !forbiddenHeaders.contains(headerName.toLowerCase());
-    }
-
-    private Map<String, List<String>> convertToMap(Header[] to_convert) {
-        Map<String, List<String>> to_return = new HashMap<String, List<String>>();
-        for (Integer i = 0; i < to_convert.length; i++) {
-            if (!to_return.containsKey(to_convert[i].getName())) {
-                to_return.put(to_convert[i].getName(), new ArrayList<String>());
-            }
-            to_return.get(to_convert[i].getName()).add(to_convert[i].getValue());
-        }
-        return to_return;
     }
 }
diff --git a/client/src/main/java/io/split/client/SplitClientConfig.java b/client/src/main/java/io/split/client/SplitClientConfig.java
index 2f29c1719..8787c1069 100644
--- a/client/src/main/java/io/split/client/SplitClientConfig.java
+++ b/client/src/main/java/io/split/client/SplitClientConfig.java
@@ -4,6 +4,7 @@
 import io.split.client.impressions.ImpressionsManager;
 import io.split.client.utils.FileTypeEnum;
 import io.split.integrations.IntegrationsConfig;
+import io.split.service.CustomHttpModule;
 import io.split.storages.enums.OperationMode;
 import io.split.storages.enums.StorageMode;
 import org.apache.hc.core5.http.HttpHost;
@@ -91,7 +92,7 @@ public class SplitClientConfig {
     private final HashSet<String> _flagSetsFilter;
     private final int _invalidSets;
     private final CustomHeaderDecorator _customHeaderDecorator;
-
+    private final CustomHttpModule _alternativeHTTPModule;
 
     public static Builder builder() {
         return new Builder();
@@ -148,7 +149,8 @@ private SplitClientConfig(String endpoint,
                               ThreadFactory threadFactory,
                               HashSet<String> flagSetsFilter,
                               int invalidSets,
-                              CustomHeaderDecorator customHeaderDecorator) {
+                              CustomHeaderDecorator customHeaderDecorator,
+                              CustomHttpModule alternativeHTTPModule) {
         _endpoint = endpoint;
         _eventsEndpoint = eventsEndpoint;
         _featuresRefreshRate = pollForFeatureChangesEveryNSeconds;
@@ -201,6 +203,7 @@ private SplitClientConfig(String endpoint,
         _flagSetsFilter = flagSetsFilter;
         _invalidSets = invalidSets;
         _customHeaderDecorator = customHeaderDecorator;
+        _alternativeHTTPModule = alternativeHTTPModule;
 
         Properties props = new Properties();
         try {
@@ -409,6 +412,7 @@ public CustomHeaderDecorator customHeaderDecorator() {
         return _customHeaderDecorator;
     }
 
+    public CustomHttpModule alternativeHTTPModule() { return _alternativeHTTPModule; }
     public static final class Builder {
 
         private String _endpoint = SDK_ENDPOINT;
@@ -466,6 +470,7 @@ public static final class Builder {
         private HashSet<String> _flagSetsFilter = new HashSet<>();
         private int _invalidSetsCount = 0;
         private CustomHeaderDecorator _customHeaderDecorator = null;
+        private CustomHttpModule _alternativeHTTPModule = null;
 
         public Builder() {
         }
@@ -960,6 +965,17 @@ public Builder customHeaderDecorator(CustomHeaderDecorator customHeaderDecorator
             return this;
         }
 
+        /**
+         * Alternative Http Client
+         *
+         * @param alternativeHTTPModule
+         * @return this builder
+         */
+        public Builder alternativeHTTPModule(CustomHttpModule alternativeHTTPModule) {
+            _alternativeHTTPModule = alternativeHTTPModule;
+            return this;
+        }
+
         /**
          * Thread Factory
          *
@@ -971,7 +987,7 @@ public Builder threadFactory(ThreadFactory threadFactory) {
             return this;
         }
 
-        public SplitClientConfig build() {
+        private void verifyRates() {
             if (_featuresRefreshRate < 5 ) {
                 throw new IllegalArgumentException("featuresRefreshRate must be >= 5: " + _featuresRefreshRate);
             }
@@ -980,15 +996,6 @@ public SplitClientConfig build() {
                 throw new IllegalArgumentException("segmentsRefreshRate must be >= 30: " + _segmentsRefreshRate);
             }
 
-            switch (_impressionsMode) {
-                case OPTIMIZED:
-                    _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 300 : Math.max(60, _impressionsRefreshRate);
-                    break;
-                case DEBUG:
-                    _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 60 : _impressionsRefreshRate;
-                    break;
-            }
-
             if (_eventSendIntervalInMillis < 1000) {
                 throw new IllegalArgumentException("_eventSendIntervalInMillis must be >= 1000: " + _eventSendIntervalInMillis);
             }
@@ -996,19 +1003,12 @@ public SplitClientConfig build() {
             if (_metricsRefreshRate < 30) {
                 throw new IllegalArgumentException("metricsRefreshRate must be >= 30: " + _metricsRefreshRate);
             }
-
-            if (_impressionsQueueSize <=0 ) {
-                throw new IllegalArgumentException("impressionsQueueSize must be > 0: " + _impressionsQueueSize);
-            }
-
-            if (_connectionTimeout <= 0) {
-                throw new IllegalArgumentException("connectionTimeOutInMs must be > 0: " + _connectionTimeout);
-            }
-
-            if (_readTimeout <= 0) {
-                throw new IllegalArgumentException("readTimeout must be > 0: " + _readTimeout);
+            if(_telemetryRefreshRate < 60) {
+                throw new IllegalStateException("_telemetryRefreshRate must be >= 60");
             }
+        }
 
+        private void verifyEndPoints() {
             if (_endpoint == null) {
                 throw new IllegalArgumentException("endpoint must not be null");
             }
@@ -1021,18 +1021,6 @@ public SplitClientConfig build() {
                 throw new IllegalArgumentException("If endpoint is set, you must also set the events endpoint");
             }
 
-            if (_numThreadsForSegmentFetch <= 0) {
-                throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
-            }
-
-            if (_authRetryBackoffBase <= 0) {
-                throw new IllegalArgumentException("authRetryBackoffBase: must be >= 1");
-            }
-
-            if (_streamingReconnectBackoffBase <= 0) {
-                throw new IllegalArgumentException("streamingReconnectBackoffBase: must be >= 1");
-            }
-
             if (_authServiceURL == null) {
                 throw new IllegalArgumentException("authServiceURL must not be null");
             }
@@ -1044,22 +1032,26 @@ public SplitClientConfig build() {
             if (_telemetryURl == null) {
                 throw new IllegalArgumentException("telemetryURl must not be null");
             }
+        }
 
-            if (_onDemandFetchRetryDelayMs <= 0) {
-                throw new IllegalStateException("streamingRetryDelay must be > 0");
+        private void verifyAllModes() {
+            switch (_impressionsMode) {
+                case OPTIMIZED:
+                    _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 300 : Math.max(60, _impressionsRefreshRate);
+                    break;
+                case DEBUG:
+                    _impressionsRefreshRate = (_impressionsRefreshRate <= 0) ? 60 : _impressionsRefreshRate;
+                    break;
+                case NONE:
+                    break;
             }
 
-            if(_onDemandFetchMaxRetries <= 0) {
-                throw new IllegalStateException("_onDemandFetchMaxRetries must be > 0");
+            if (_impressionsQueueSize <=0 ) {
+                throw new IllegalArgumentException("impressionsQueueSize must be > 0: " + _impressionsQueueSize);
             }
-
             if(_storageMode == null) {
                 _storageMode = StorageMode.MEMORY;
             }
-            
-            if(_telemetryRefreshRate < 60) {
-                throw new IllegalStateException("_telemetryRefreshRate must be >= 60");
-            }
 
             if(OperationMode.CONSUMER.equals(_operationMode)){
                 if(_customStorageWrapper == null) {
@@ -1067,8 +1059,56 @@ public SplitClientConfig build() {
                 }
                 _storageMode = StorageMode.PLUGGABLE;
             }
+        }
+
+        private void verifyNetworkParams() {
+            if (_connectionTimeout <= 0) {
+                throw new IllegalArgumentException("connectionTimeOutInMs must be > 0: " + _connectionTimeout);
+            }
+
+            if (_readTimeout <= 0) {
+                throw new IllegalArgumentException("readTimeout must be > 0: " + _readTimeout);
+            }
+            if (_authRetryBackoffBase <= 0) {
+                throw new IllegalArgumentException("authRetryBackoffBase: must be >= 1");
+            }
+
+            if (_streamingReconnectBackoffBase <= 0) {
+                throw new IllegalArgumentException("streamingReconnectBackoffBase: must be >= 1");
+            }
+
+            if (_onDemandFetchRetryDelayMs <= 0) {
+                throw new IllegalStateException("streamingRetryDelay must be > 0");
+            }
+
+            if(_onDemandFetchMaxRetries <= 0) {
+                throw new IllegalStateException("_onDemandFetchMaxRetries must be > 0");
+            }
+        }
+
+        private void verifyAlternativeClient() {
+            if (_alternativeHTTPModule != null && _streamingEnabled) {
+                throw new IllegalArgumentException("Streaming feature is not supported with Alternative HTTP Client");
+            }
+        }
+
+        public SplitClientConfig build() {
+
+            verifyRates();
+
+            verifyAllModes();
+
+            verifyEndPoints();
+
+            verifyNetworkParams();
+
+            verifyAlternativeClient();
+
+            if (_numThreadsForSegmentFetch <= 0) {
+                throw new IllegalArgumentException("Number of threads for fetching segments MUST be greater than zero");
+            }
 
-            return new SplitClientConfig(
+                return new SplitClientConfig(
                     _endpoint,
                     _eventsEndpoint,
                     _featuresRefreshRate,
@@ -1120,7 +1160,8 @@ public SplitClientConfig build() {
                     _threadFactory,
                     _flagSetsFilter,
                     _invalidSetsCount,
-                    _customHeaderDecorator);
+                    _customHeaderDecorator,
+                    _alternativeHTTPModule);
         }
     }
 }
\ No newline at end of file
diff --git a/client/src/main/java/io/split/client/SplitFactoryBuilder.java b/client/src/main/java/io/split/client/SplitFactoryBuilder.java
index c2271ec4f..2b48fb0d3 100644
--- a/client/src/main/java/io/split/client/SplitFactoryBuilder.java
+++ b/client/src/main/java/io/split/client/SplitFactoryBuilder.java
@@ -2,6 +2,7 @@
 
 import io.split.inputValidation.ApiKeyValidator;
 import io.split.grammar.Treatments;
+import io.split.service.SplitHttpClient;
 import io.split.storages.enums.StorageMode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/client/src/main/java/io/split/client/SplitFactoryImpl.java b/client/src/main/java/io/split/client/SplitFactoryImpl.java
index a783b1445..3102d3e17 100644
--- a/client/src/main/java/io/split/client/SplitFactoryImpl.java
+++ b/client/src/main/java/io/split/client/SplitFactoryImpl.java
@@ -57,8 +57,9 @@
 import io.split.engine.segments.SegmentChangeFetcher;
 import io.split.engine.segments.SegmentSynchronizationTaskImp;
 import io.split.integrations.IntegrationsConfig;
-import io.split.service.SplitHttpClient;
 import io.split.service.SplitHttpClientImpl;
+import io.split.service.SplitHttpClient;
+
 import io.split.storages.SegmentCache;
 import io.split.storages.SegmentCacheConsumer;
 import io.split.storages.SegmentCacheProducer;
@@ -83,6 +84,7 @@
 import io.split.telemetry.synchronizer.TelemetryInMemorySubmitter;
 import io.split.telemetry.synchronizer.TelemetrySyncTask;
 import io.split.telemetry.synchronizer.TelemetrySynchronizer;
+
 import org.apache.hc.client5.http.auth.AuthScope;
 import org.apache.hc.client5.http.auth.Credentials;
 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
@@ -102,7 +104,6 @@
 import org.apache.hc.core5.ssl.SSLContexts;
 import org.apache.hc.core5.util.TimeValue;
 import org.apache.hc.core5.util.Timeout;
-import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import pluggable.CustomStorageWrapper;
 
@@ -111,16 +112,16 @@
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
 
 import static io.split.client.utils.SplitExecutorFactory.buildExecutorService;
 
 public class SplitFactoryImpl implements SplitFactory {
-    private static final Logger _log = LoggerFactory.getLogger(SplitFactory.class);
+    private static final org.slf4j.Logger _log = LoggerFactory.getLogger(SplitFactoryImpl.class);
     private static final String LEGACY_LOG_MESSAGE = "The sdk initialize in localhost mode using Legacy file. The splitFile or "
             +
             "inputStream doesn't add it to the config.";
@@ -155,15 +156,16 @@ public class SplitFactoryImpl implements SplitFactory {
     private final SplitSynchronizationTask _splitSynchronizationTask;
     private final EventsTask _eventsTask;
     private final SyncManager _syncManager;
-    private final SplitHttpClient _splitHttpClient;
+    private SplitHttpClient _splitHttpClient;
     private final UserStorageWrapper _userStorageWrapper;
     private final ImpressionsSender _impressionsSender;
     private final URI _rootTarget;
     private final URI _eventsRootTarget;
     private final UniqueKeysTracker _uniqueKeysTracker;
+    private RequestDecorator _requestDecorator;
 
     // Constructor for standalone mode
-    public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException {
+    public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyntaxException, IOException {
         _userStorageWrapper = null;
         _operationMode = config.operationMode();
         _startTime = System.currentTimeMillis();
@@ -186,9 +188,13 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
         // SDKReadinessGates
         _gates = new SDKReadinessGates();
 
+        _requestDecorator = new RequestDecorator(config.customHeaderDecorator());
         // HttpClient
-        RequestDecorator requestDecorator = new RequestDecorator(config.customHeaderDecorator());
-        _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, requestDecorator);
+        if (config.alternativeHTTPModule() == null) {
+            _splitHttpClient = buildSplitHttpClient(apiToken, config, _sdkMetadata, _requestDecorator);
+        } else {
+            _splitHttpClient = config.alternativeHTTPModule().createClient(apiToken, _sdkMetadata, _requestDecorator);
+        }
 
         // Roots
         _rootTarget = URI.create(config.endpoint());
@@ -234,7 +240,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
         EventsSender eventsSender = EventsSender.create(_splitHttpClient, _eventsRootTarget, _telemetryStorageProducer);
         _eventsTask = EventsTask.create(config.eventSendIntervalInMillis(), eventsStorage, eventsSender,
                 config.getThreadFactory());
-        _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory());
+        _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
+                config.getThreadFactory());
 
         // Evaluator
         _evaluator = new EvaluatorImp(splitCache, segmentCache);
@@ -257,7 +264,8 @@ public SplitFactoryImpl(String apiToken, SplitClientConfig config) throws URISyn
         // SyncManager
         SplitTasks splitTasks = SplitTasks.build(_splitSynchronizationTask, _segmentSynchronizationTaskImp,
                 _impressionsManager, _eventsTask, _telemetrySyncTask, _uniqueKeysTracker);
-        SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata), requestDecorator);
+        SplitAPI splitAPI = SplitAPI.build(_splitHttpClient, buildSSEdHttpClient(apiToken, config, _sdkMetadata),
+                _requestDecorator);
 
         _syncManager = SyncManagerImp.build(splitTasks, _splitFetcher, splitCache, splitAPI,
                 segmentCache, _gates, _telemetryStorageProducer, _telemetrySynchronizer, config, splitParser,
@@ -328,8 +336,10 @@ protected SplitFactoryImpl(String apiToken, SplitClientConfig config, CustomStor
         _evaluator = new EvaluatorImp(userCustomSplitAdapterConsumer, userCustomSegmentAdapterConsumer);
         _impressionsSender = PluggableImpressionSender.create(customStorageWrapper);
         _uniqueKeysTracker = createUniqueKeysTracker(config);
-        _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer, userCustomImpressionAdapterProducer);
-        _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer, config.getThreadFactory());
+        _impressionsManager = buildImpressionsManager(config, userCustomImpressionAdapterConsumer,
+                userCustomImpressionAdapterProducer);
+        _telemetrySyncTask = new TelemetrySyncTask(config.getTelemetryRefreshRate(), _telemetrySynchronizer,
+                config.getThreadFactory());
 
         SplitTasks splitTasks = SplitTasks.build(null, null,
                 _impressionsManager, null, _telemetrySyncTask, _uniqueKeysTracker);
@@ -491,7 +501,7 @@ public boolean isDestroyed() {
         return isTerminated;
     }
 
-    private static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
+    protected static SplitHttpClient buildSplitHttpClient(String apiToken, SplitClientConfig config,
             SDKMetadata sdkMetadata, RequestDecorator requestDecorator)
             throws URISyntaxException {
         SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
diff --git a/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java b/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java
index a5474cf5b..259ed0794 100644
--- a/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java
+++ b/client/src/main/java/io/split/client/dtos/SplitHttpResponse.java
@@ -1,7 +1,7 @@
 package io.split.client.dtos;
 
-import java.util.Map;
-import org.apache.hc.core5.http.Header;
+import java.util.List;
+
 /**
  * A structure for returning http call results information
  */
@@ -11,15 +11,42 @@ public class SplitHttpResponse {
     private final String _body;
     private final Header[] _responseHeaders;
 
+    public static class Header {
+        private String _name;
+        private List<String> _values;
+
+        public Header(String name, List<String> values) {
+            _name = name;
+            _values = values;
+        }
+
+        public String getName() {
+            return _name;
+        }
+
+        public List<String> getValues() {
+            return _values;
+        }
+    };
+
     public SplitHttpResponse(Integer statusCode, String statusMessage, String body, Header[] headers) {
         _statusCode = statusCode;
         _statusMessage = statusMessage;
         _body = body;
         _responseHeaders = headers;
     }
+
+    public SplitHttpResponse(Integer statusCode, String statusMessage, String body, List<Header> headers) {
+        _statusCode = statusCode;
+        _statusMessage = statusMessage;
+        _body = body;
+        _responseHeaders = headers.toArray(new Header[0]);
+    }
+
     public Integer statusCode() {
         return _statusCode;
     }
+
     public String statusMessage() {
         return _statusMessage;
     }
diff --git a/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java b/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java
index 06df64cc4..35c0f57f2 100644
--- a/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java
+++ b/client/src/main/java/io/split/client/impressions/HttpImpressionsSender.java
@@ -4,6 +4,7 @@
 import io.split.client.dtos.ImpressionCount;
 import io.split.client.dtos.SplitHttpResponse;
 import io.split.client.dtos.TestImpressions;
+import io.split.client.utils.Json;
 import io.split.client.utils.Utils;
 
 import io.split.service.SplitHttpClient;
@@ -11,7 +12,6 @@
 import io.split.telemetry.domain.enums.LastSynchronizationRecordsEnum;
 import io.split.telemetry.domain.enums.ResourceEnum;
 import io.split.telemetry.storage.TelemetryRuntimeProducer;
-import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,10 +67,12 @@ private HttpImpressionsSender(SplitHttpClient client, URI impressionBulkTarget,
     public void postImpressionsBulk(List<TestImpressions> impressions) {
         long initTime = System.currentTimeMillis();
         try {
-            HttpEntity entity = Utils.toJsonEntity(impressions);
-            Map<String, List<String>> additionalHeaders = Collections.singletonMap(IMPRESSIONS_MODE_HEADER,
-                    Collections.singletonList(_mode.toString()));
-            SplitHttpResponse response = _client.post(_impressionBulkTarget, entity, additionalHeaders);
+            Map<String, List<String>> additionalHeaders = new HashMap<>();
+            additionalHeaders.put(IMPRESSIONS_MODE_HEADER, Collections.singletonList(_mode.toString()));
+            additionalHeaders.put("Content-Type", Collections.singletonList("application/json"));
+
+            SplitHttpResponse response = _client.post(_impressionBulkTarget, Json.toJson(impressions),
+                    additionalHeaders);
 
             if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
                 _telemetryRuntimeProducer.recordSyncError(ResourceEnum.IMPRESSION_SYNC, response.statusCode());
@@ -95,8 +97,12 @@ public void postCounters(HashMap<ImpressionCounter.Key, Integer> raw) {
         }
 
         try {
+
+            Map<String, List<String>> additionalHeaders = new HashMap<>();
+            additionalHeaders.put("Content-Type", Collections.singletonList("application/json"));
+
             SplitHttpResponse response = _client.post(_impressionCountTarget,
-                    Utils.toJsonEntity(ImpressionCount.fromImpressionCounterData(raw)),
+                    Json.toJson(ImpressionCount.fromImpressionCounterData(raw)),
                     null);
 
             if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
diff --git a/client/src/main/java/io/split/client/utils/ApacheRequestDecorator.java b/client/src/main/java/io/split/client/utils/ApacheRequestDecorator.java
new file mode 100644
index 000000000..c64d9d46c
--- /dev/null
+++ b/client/src/main/java/io/split/client/utils/ApacheRequestDecorator.java
@@ -0,0 +1,43 @@
+package io.split.client.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpRequest;
+
+import io.split.client.RequestDecorator;
+import io.split.client.dtos.RequestContext;
+
+public class ApacheRequestDecorator {
+
+    public static HttpRequest decorate(HttpRequest request, RequestDecorator decorator) {
+
+        RequestContext ctx = new RequestContext(convertToMap(request.getHeaders()));
+        for (Map.Entry<String, List<String>> entry : decorator.decorateHeaders(ctx).headers().entrySet()) {
+            List<String> values = entry.getValue();
+            for (int i = 0; i < values.size(); i++) {
+                if (i == 0) {
+                    request.setHeader(entry.getKey(), values.get(i));
+                } else {
+                    request.addHeader(entry.getKey(), values.get(i));
+                }
+            }
+        }
+
+        return request;
+    }
+
+    private static Map<String, List<String>> convertToMap(Header[] to_convert) {
+        Map<String, List<String>> to_return = new HashMap<String, List<String>>();
+        for (Integer i = 0; i < to_convert.length; i++) {
+            if (!to_return.containsKey(to_convert[i].getName())) {
+                to_return.put(to_convert[i].getName(), new ArrayList<String>());
+            }
+            to_return.get(to_convert[i].getName()).add(to_convert[i].getValue());
+        }
+        return to_return;
+    }
+}
diff --git a/client/src/main/java/io/split/engine/common/PushManagerImp.java b/client/src/main/java/io/split/engine/common/PushManagerImp.java
index 3c15481fd..653249308 100644
--- a/client/src/main/java/io/split/engine/common/PushManagerImp.java
+++ b/client/src/main/java/io/split/engine/common/PushManagerImp.java
@@ -84,6 +84,7 @@ public static PushManagerImp build(Synchronizer synchronizer,
                 telemetryRuntimeProducer, flagSetsFilter);
         Worker<SegmentQueueDto> segmentWorker = new SegmentsWorkerImp(synchronizer);
         PushStatusTracker pushStatusTracker = new PushStatusTrackerImp(statusMessages, telemetryRuntimeProducer);
+
         return new PushManagerImp(new AuthApiClientImp(authUrl, splitAPI.getHttpClient(), telemetryRuntimeProducer),
                 EventSourceClientImp.build(streamingUrl, featureFlagsWorker, segmentWorker, pushStatusTracker, splitAPI.getSseHttpClient(),
                         telemetryRuntimeProducer, threadFactory, splitAPI.getRequestDecorator()),
diff --git a/client/src/main/java/io/split/engine/sse/client/SSEClient.java b/client/src/main/java/io/split/engine/sse/client/SSEClient.java
index 37cc6dac9..aac6f5566 100644
--- a/client/src/main/java/io/split/engine/sse/client/SSEClient.java
+++ b/client/src/main/java/io/split/engine/sse/client/SSEClient.java
@@ -2,6 +2,7 @@
 
 import com.google.common.base.Strings;
 import io.split.client.RequestDecorator;
+import io.split.client.utils.ApacheRequestDecorator;
 import io.split.telemetry.domain.StreamingEvent;
 import io.split.telemetry.domain.enums.StreamEventsEnum;
 import io.split.telemetry.storage.TelemetryRuntimeProducer;
@@ -64,11 +65,11 @@ private enum ConnectionState {
     private final TelemetryRuntimeProducer _telemetryRuntimeProducer;
 
     public SSEClient(Function<RawEvent, Void> eventCallback,
-                     Function<StatusMessage, Void> statusCallback,
-                     CloseableHttpClient client,
-                     TelemetryRuntimeProducer telemetryRuntimeProducer,
-                     ThreadFactory threadFactory,
-                     RequestDecorator requestDecorator) {
+            Function<StatusMessage, Void> statusCallback,
+            CloseableHttpClient client,
+            TelemetryRuntimeProducer telemetryRuntimeProducer,
+            ThreadFactory threadFactory,
+            RequestDecorator requestDecorator) {
         _eventCallback = eventCallback;
         _statusCallback = statusCallback;
         _client = client;
@@ -96,7 +97,7 @@ public boolean open(URI uri) {
                 }
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
-                if(e.getMessage() == null){
+                if (e.getMessage() == null) {
                     _log.info("The thread was interrupted while opening SSEClient");
                     return false;
                 }
@@ -152,31 +153,41 @@ private void connectAndLoop(URI uri, CountDownLatch signal) {
                     _log.debug(exc.getMessage());
                     if (SOCKET_CLOSED_MESSAGE.equals(exc.getMessage())) { // Connection closed by us
                         _statusCallback.apply(StatusMessage.FORCED_STOP);
-                        _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
-                                StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+                        _telemetryRuntimeProducer.recordStreamingEvents(
+                                new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+                                        StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(),
+                                        System.currentTimeMillis()));
                         return;
                     }
                     // Connection closed by server
                     _statusCallback.apply(StatusMessage.RETRYABLE_ERROR);
-                    _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
-                            StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+                    _telemetryRuntimeProducer
+                            .recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+                                    StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(),
+                                    System.currentTimeMillis()));
                     return;
                 } catch (IOException exc) { // Other type of connection error
-                    if(!_forcedStop.get()) {
+                    if (!_forcedStop.get()) {
                         _log.debug(String.format("SSE connection ended abruptly: %s. Retying", exc.getMessage()));
-                        _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
-                                StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+                        _telemetryRuntimeProducer.recordStreamingEvents(
+                                new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+                                        StreamEventsEnum.SseConnectionErrorValues.REQUESTED_CONNECTION_ERROR.getValue(),
+                                        System.currentTimeMillis()));
                         _statusCallback.apply(StatusMessage.RETRYABLE_ERROR);
                         return;
                     }
 
-                    _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
-                            StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+                    _telemetryRuntimeProducer
+                            .recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+                                    StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(),
+                                    System.currentTimeMillis()));
                 }
             }
         } catch (Exception e) { // Any other error non related to the connection disables streaming altogether
-            _telemetryRuntimeProducer.recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
-                    StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(), System.currentTimeMillis()));
+            _telemetryRuntimeProducer
+                    .recordStreamingEvents(new StreamingEvent(StreamEventsEnum.SSE_CONNECTION_ERROR.getType(),
+                            StreamEventsEnum.SseConnectionErrorValues.NON_REQUESTED_CONNECTION_ERROR.getValue(),
+                            System.currentTimeMillis()));
             _log.warn(e.getMessage(), e);
             _statusCallback.apply(StatusMessage.NONRETRYABLE_ERROR);
         } finally {
@@ -194,12 +205,13 @@ private void connectAndLoop(URI uri, CountDownLatch signal) {
 
     private boolean establishConnection(URI uri, CountDownLatch signal) {
         HttpGet request = new HttpGet(uri);
-        request = (HttpGet) _requestDecorator.decorateHeaders(request);
+        request = (HttpGet) ApacheRequestDecorator.decorate(request, _requestDecorator);
         _ongoingRequest.set(request);
         try {
             _ongoingResponse.set(_client.execute(_ongoingRequest.get()));
             if (_ongoingResponse.get().getCode() != 200) {
-                _log.error(String.format("Establishing connection, code error: %s. The url is %s", _ongoingResponse.get().getCode(), uri.toURL()));
+                _log.error(String.format("Establishing connection, code error: %s. The url is %s",
+                        _ongoingResponse.get().getCode(), uri.toURL()));
                 return false;
             }
             _state.set(ConnectionState.OPEN);
@@ -236,4 +248,4 @@ private void handleMessage(String message) {
         RawEvent e = RawEvent.fromString(message);
         _eventCallback.apply(e);
     }
-}
\ No newline at end of file
+}
diff --git a/client/src/main/java/io/split/service/CustomHttpModule.java b/client/src/main/java/io/split/service/CustomHttpModule.java
new file mode 100644
index 000000000..001648fb3
--- /dev/null
+++ b/client/src/main/java/io/split/service/CustomHttpModule.java
@@ -0,0 +1,11 @@
+package io.split.service;
+
+import io.split.client.RequestDecorator;
+import io.split.client.utils.SDKMetadata;
+
+import java.io.IOException;
+
+public interface CustomHttpModule {
+    public SplitHttpClient createClient(String apiToken, SDKMetadata sdkMetadata, RequestDecorator decorator)
+            throws IOException;
+}
diff --git a/client/src/main/java/io/split/service/HttpPostImp.java b/client/src/main/java/io/split/service/HttpPostImp.java
index e5baa001b..b33bf2103 100644
--- a/client/src/main/java/io/split/service/HttpPostImp.java
+++ b/client/src/main/java/io/split/service/HttpPostImp.java
@@ -1,15 +1,18 @@
 package io.split.service;
 
 import io.split.client.dtos.SplitHttpResponse;
-import io.split.client.utils.Utils;
+import io.split.client.utils.Json;
 import io.split.telemetry.domain.enums.HttpParamsWrapper;
 import io.split.telemetry.storage.TelemetryRuntimeProducer;
-import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
@@ -25,16 +28,19 @@ public HttpPostImp(SplitHttpClient client, TelemetryRuntimeProducer telemetryRun
 
     public void post(URI uri, Object object, String posted, HttpParamsWrapper httpParamsWrapper) {
         long initTime = System.currentTimeMillis();
-        HttpEntity entity = Utils.toJsonEntity(object);
 
         try {
-            SplitHttpResponse response = _client.post(uri, entity, null);
+            Map<String, List<String>> headers = new HashMap<>();
+            headers.put("Content-Type", Collections.singletonList("application/json"));
+            SplitHttpResponse response = _client.post(uri, Json.toJson(object), headers);
             if (response.statusCode() < HttpStatus.SC_OK || response.statusCode() >= HttpStatus.SC_MULTIPLE_CHOICES) {
                 _telemetryRuntimeProducer.recordSyncError(httpParamsWrapper.getResourceEnum(), response.statusCode());
                 return;
             }
-            _telemetryRuntimeProducer.recordSyncLatency(httpParamsWrapper.getHttpLatenciesEnum(), System.currentTimeMillis() - initTime);
-            _telemetryRuntimeProducer.recordSuccessfulSync(httpParamsWrapper.getLastSynchronizationRecordsEnum(), System.currentTimeMillis());
+            _telemetryRuntimeProducer.recordSyncLatency(httpParamsWrapper.getHttpLatenciesEnum(),
+                    System.currentTimeMillis() - initTime);
+            _telemetryRuntimeProducer.recordSuccessfulSync(httpParamsWrapper.getLastSynchronizationRecordsEnum(),
+                    System.currentTimeMillis());
         } catch (Throwable t) {
             _logger.warn("Exception when posting " + posted + object, t);
         }
diff --git a/client/src/main/java/io/split/service/SplitHttpClient.java b/client/src/main/java/io/split/service/SplitHttpClient.java
index 1c88bcd4e..899fcf56b 100644
--- a/client/src/main/java/io/split/service/SplitHttpClient.java
+++ b/client/src/main/java/io/split/service/SplitHttpClient.java
@@ -3,8 +3,6 @@
 import io.split.engine.common.FetchOptions;
 import io.split.client.dtos.SplitHttpResponse;
 
-import org.apache.hc.core5.http.HttpEntity;
-
 import java.io.Closeable;
 import java.io.IOException;
 import java.net.URI;
@@ -30,6 +28,6 @@ public interface SplitHttpClient extends Closeable {
      * @return The response structure SplitHttpResponse
      */
     public SplitHttpResponse post(URI uri,
-            HttpEntity entity,
+            String entity,
             Map<String, List<String>> additionalHeaders) throws IOException;
 }
diff --git a/client/src/main/java/io/split/service/SplitHttpClientImpl.java b/client/src/main/java/io/split/service/SplitHttpClientImpl.java
index 64ca3a55c..7f0674411 100644
--- a/client/src/main/java/io/split/service/SplitHttpClientImpl.java
+++ b/client/src/main/java/io/split/service/SplitHttpClientImpl.java
@@ -1,6 +1,7 @@
 package io.split.service;
 
 import io.split.client.RequestDecorator;
+import io.split.client.utils.ApacheRequestDecorator;
 import io.split.client.utils.SDKMetadata;
 import io.split.client.utils.Utils;
 import io.split.engine.common.FetchOptions;
@@ -9,9 +10,10 @@
 import org.apache.hc.client5.http.classic.methods.HttpPost;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
-import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.HttpEntities;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import java.io.IOException;
@@ -19,8 +21,11 @@
 import java.net.URISyntaxException;
 import org.apache.hc.core5.http.HttpRequest;
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 public final class SplitHttpClientImpl implements SplitHttpClient {
 
@@ -72,7 +77,7 @@ public SplitHttpResponse get(URI uri, FetchOptions options, Map<String, List<Str
                 request.setHeader(HEADER_CACHE_CONTROL_NAME, HEADER_CACHE_CONTROL_VALUE);
             }
 
-            _requestDecorator.decorateHeaders(request);
+            request = (HttpGet) ApacheRequestDecorator.decorate(request, _requestDecorator);
 
             response = _client.execute(request);
 
@@ -87,10 +92,14 @@ public SplitHttpResponse get(URI uri, FetchOptions options, Map<String, List<Str
                         response.getReasonPhrase()));
                 statusMessage = response.getReasonPhrase();
             }
+
             return new SplitHttpResponse(response.getCode(),
                     statusMessage,
                     EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8),
-                    response.getHeaders());
+                    Arrays.stream(response.getHeaders()).map(
+                            h -> new SplitHttpResponse.Header(h.getName(), Collections.singletonList(h.getValue())))
+                            .collect(Collectors.toList()));
+            // response.getHeaders());
         } catch (Exception e) {
             throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e);
         } finally {
@@ -98,7 +107,7 @@ public SplitHttpResponse get(URI uri, FetchOptions options, Map<String, List<Str
         }
     }
 
-    public SplitHttpResponse post(URI uri, HttpEntity entity, Map<String, List<String>> additionalHeaders)
+    public SplitHttpResponse post(URI uri, String body, Map<String, List<String>> additionalHeaders)
             throws IOException {
 
         CloseableHttpResponse response = null;
@@ -112,8 +121,8 @@ public SplitHttpResponse post(URI uri, HttpEntity entity, Map<String, List<Strin
                     }
                 }
             }
-            request.setEntity(entity);
-            request = (HttpPost) _requestDecorator.decorateHeaders(request);
+            request.setEntity(HttpEntities.create(body, ContentType.APPLICATION_JSON));
+            request = (HttpPost) ApacheRequestDecorator.decorate(request, _requestDecorator);
 
             response = _client.execute(request);
 
@@ -123,7 +132,10 @@ public SplitHttpResponse post(URI uri, HttpEntity entity, Map<String, List<Strin
                 _log.warn(String.format("Response status was: %s. Reason: %s", response.getCode(),
                         response.getReasonPhrase()));
             }
-            return new SplitHttpResponse(response.getCode(), statusMessage, "", response.getHeaders());
+            return new SplitHttpResponse(response.getCode(), statusMessage, "",
+                    Arrays.stream(response.getHeaders()).map(
+                            h -> new SplitHttpResponse.Header(h.getName(), Collections.singletonList(h.getValue())))
+                            .collect(Collectors.toList()));
         } catch (Exception e) {
             throw new IOException(String.format("Problem in http post operation: %s", e), e);
         } finally {
diff --git a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java
index abcc551fe..0a154f7d4 100644
--- a/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java
+++ b/client/src/test/java/io/split/client/LocalhostSplitFactoryYamlTest.java
@@ -2,6 +2,7 @@
 
 import io.split.client.utils.LocalhostUtils;
 import io.split.grammar.Treatments;
+import io.split.service.SplitHttpClient;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
diff --git a/client/src/test/java/io/split/client/SplitFactoryImplTest.java b/client/src/test/java/io/split/client/SplitFactoryImplTest.java
index 2d548f9e6..f2f7e3efc 100644
--- a/client/src/test/java/io/split/client/SplitFactoryImplTest.java
+++ b/client/src/test/java/io/split/client/SplitFactoryImplTest.java
@@ -11,10 +11,11 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
+import static org.mockito.Mockito.when;
 import pluggable.CustomStorageWrapper;
 
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -22,6 +23,7 @@
 import java.lang.reflect.Modifier;
 import java.net.URISyntaxException;
 
+
 public class SplitFactoryImplTest extends TestCase {
     public static final String API_KEY ="29013ionasdasd09u";
     public static final String ENDPOINT = "https://sdk.split-stage.io";
@@ -135,7 +137,7 @@ public void testFactoryConsumerInstantiation() throws Exception {
         CustomStorageWrapper customStorageWrapper = Mockito.mock(CustomStorageWrapper.class);
         UserStorageWrapper userStorageWrapper = Mockito.mock(UserStorageWrapper.class);
         TelemetrySynchronizer telemetrySynchronizer = Mockito.mock(TelemetrySynchronizer.class);
-        Mockito.when(userStorageWrapper.connect()).thenReturn(true);
+        when(userStorageWrapper.connect()).thenReturn(true);
 
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .enableDebug()
@@ -173,7 +175,7 @@ public void testFactoryConsumerInstantiation() throws Exception {
     public void testFactoryConsumerInstantiationRetryReadiness() throws Exception {
         CustomStorageWrapper customStorageWrapper = Mockito.mock(CustomStorageWrapper.class);
         UserStorageWrapper userStorageWrapper = Mockito.mock(UserStorageWrapper.class);
-        Mockito.when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
+        when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .enableDebug()
                 .impressionsMode(ImpressionsManager.Mode.DEBUG)
@@ -202,7 +204,7 @@ public void testFactoryConsumerInstantiationRetryReadiness() throws Exception {
     public void testFactoryConsumerDestroy() throws NoSuchFieldException, URISyntaxException, IllegalAccessException {
         CustomStorageWrapper customStorageWrapper = Mockito.mock(CustomStorageWrapper.class);
         UserStorageWrapper userStorageWrapper = Mockito.mock(UserStorageWrapper.class);
-        Mockito.when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
+        when(userStorageWrapper.connect()).thenReturn(false).thenReturn(true);
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .enableDebug()
                 .impressionsMode(ImpressionsManager.Mode.DEBUG)
@@ -228,7 +230,7 @@ public void testFactoryConsumerDestroy() throws NoSuchFieldException, URISyntaxE
     }
 
     @Test
-    public void testLocalhostLegacy() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+    public void testLocalhostLegacy() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .setBlockUntilReadyTimeout(10000)
                 .build();
@@ -241,7 +243,7 @@ public void testLocalhostLegacy() throws URISyntaxException, NoSuchMethodExcepti
     }
 
     @Test
-    public void testLocalhostYaml() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+    public void testLocalhostYaml() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .splitFile("src/test/resources/split.yaml")
                 .setBlockUntilReadyTimeout(10000)
@@ -255,7 +257,7 @@ public void testLocalhostYaml() throws URISyntaxException, NoSuchMethodException
     }
 
     @Test
-    public void testLocalhosJson() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+    public void testLocalhosJson() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .splitFile("src/test/resources/split_init.json")
                 .setBlockUntilReadyTimeout(10000)
@@ -270,7 +272,7 @@ public void testLocalhosJson() throws URISyntaxException, NoSuchMethodException,
 
     @Test
     public void testLocalhostYamlInputStream() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
-            IllegalAccessException, FileNotFoundException {
+            IllegalAccessException, IOException {
         InputStream inputStream = new FileInputStream("src/test/resources/split.yaml");
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .splitFile(inputStream, FileTypeEnum.YAML)
@@ -286,7 +288,7 @@ public void testLocalhostYamlInputStream() throws URISyntaxException, NoSuchMeth
 
     @Test
     public void testLocalhosJsonInputStream() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
-            IllegalAccessException, FileNotFoundException {
+            IllegalAccessException, IOException {
         InputStream inputStream = new FileInputStream("src/test/resources/split_init.json");
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .splitFile(inputStream, FileTypeEnum.JSON)
@@ -301,7 +303,7 @@ public void testLocalhosJsonInputStream() throws URISyntaxException, NoSuchMetho
     }
 
     @Test
-    public void testLocalhosJsonInputStreamNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+    public void testLocalhosJsonInputStreamNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .splitFile(null, FileTypeEnum.JSON)
                 .setBlockUntilReadyTimeout(10000)
@@ -316,7 +318,7 @@ public void testLocalhosJsonInputStreamNull() throws URISyntaxException, NoSuchM
 
     @Test
     public void testLocalhosJsonInputStreamAndFileTypeNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
-            IllegalAccessException, FileNotFoundException {
+            IllegalAccessException, IOException {
         InputStream inputStream = new FileInputStream("src/test/resources/split_init.json");
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .splitFile(inputStream, null)
@@ -332,7 +334,7 @@ public void testLocalhosJsonInputStreamAndFileTypeNull() throws URISyntaxExcepti
 
     @Test
     public void testLocalhosJsonInputStreamNullAndFileTypeNull() throws URISyntaxException, NoSuchMethodException, InvocationTargetException,
-            IllegalAccessException {
+            IllegalAccessException, IOException {
         SplitClientConfig splitClientConfig = SplitClientConfig.builder()
                 .splitFile(null, null)
                 .setBlockUntilReadyTimeout(10000)
diff --git a/client/src/test/java/io/split/client/RequestDecoratorTest.java b/client/src/test/java/io/split/client/utils/ApacheRequestDecoratorTest.java
similarity index 80%
rename from client/src/test/java/io/split/client/RequestDecoratorTest.java
rename to client/src/test/java/io/split/client/utils/ApacheRequestDecoratorTest.java
index 62868eb40..5d5971bb8 100644
--- a/client/src/test/java/io/split/client/RequestDecoratorTest.java
+++ b/client/src/test/java/io/split/client/utils/ApacheRequestDecoratorTest.java
@@ -1,32 +1,31 @@
-package io.split.client;
+package io.split.client.utils;
 
+import io.split.client.CustomHeaderDecorator;
+import io.split.client.RequestDecorator;
+import io.split.client.dtos.RequestContext;
 import org.apache.hc.client5.http.classic.methods.HttpGet;
 import org.apache.hc.client5.http.classic.methods.HttpPost;
 import org.apache.hc.core5.http.Header;
 import org.apache.hc.core5.http.ProtocolException;
 import org.junit.Assert;
 import org.junit.Test;
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.Is.is;
-
-import io.split.client.dtos.RequestContext;
 
 import java.util.List;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.Map;
 
-public class RequestDecoratorTest {
+public class ApacheRequestDecoratorTest {
 
     @Test
     public void testNoOp() {
-        RequestDecorator decorator = new RequestDecorator(null);
+        ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
+        RequestDecorator requestDecorator = new RequestDecorator(null);
         HttpGet request = new HttpGet("http://anyhost");
-        request  = (HttpGet) decorator.decorateHeaders(request);
+
+        request  = (HttpGet) apacheRequestDecorator.decorate(request, requestDecorator);
         Assert.assertEquals(0, request.getHeaders().length);
         request.addHeader("myheader", "value");
-        request  = (HttpGet) decorator.decorateHeaders(request);
+        request  = (HttpGet) apacheRequestDecorator.decorate(request, requestDecorator);
         Assert.assertEquals(1, request.getHeaders().length);
     }
 
@@ -45,9 +44,11 @@ public Map<String, List<String>> getHeaderOverrides(RequestContext context) {
         }
         MyCustomHeaders myHeaders = new MyCustomHeaders();
         RequestDecorator decorator = new RequestDecorator(myHeaders);
+        ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
+
         HttpGet request = new HttpGet("http://anyhost");
         request.addHeader("first", "myfirstheader");
-        request  = (HttpGet) decorator.decorateHeaders(request);
+        request  = (HttpGet) apacheRequestDecorator.decorate(request, decorator);
 
         Assert.assertEquals(4, request.getHeaders().length);
         Assert.assertEquals("1", request.getHeader("first").getValue());
@@ -59,7 +60,7 @@ public Map<String, List<String>> getHeaderOverrides(RequestContext context) {
 
         HttpPost request2 = new HttpPost("http://anyhost");
         request2.addHeader("myheader", "value");
-        request2  = (HttpPost) decorator.decorateHeaders(request2);
+        request2  = (HttpPost) apacheRequestDecorator.decorate(request2, decorator);
         Assert.assertEquals(5, request2.getHeaders().length);
     }
 
@@ -88,8 +89,9 @@ public Map<String, List<String>> getHeaderOverrides(RequestContext context) {
         }
         MyCustomHeaders myHeaders = new MyCustomHeaders();
         RequestDecorator decorator = new RequestDecorator(myHeaders);
+        ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
         HttpGet request = new HttpGet("http://anyhost");
-        request  = (HttpGet) decorator.decorateHeaders(request);
+        request  = (HttpGet) apacheRequestDecorator.decorate(request, decorator);
         Assert.assertEquals(1, request.getHeaders().length);
         Assert.assertEquals(null, request.getHeader("SplitSDKVersion"));
     }
@@ -105,7 +107,8 @@ public Map<String, List<String>> getHeaderOverrides(RequestContext context) {
         }
         MyCustomHeaders myHeaders = new MyCustomHeaders();
         RequestDecorator decorator = new RequestDecorator(myHeaders);
+        ApacheRequestDecorator apacheRequestDecorator = new ApacheRequestDecorator();
         HttpGet request = new HttpGet("http://anyhost");
-        request  = (HttpGet) decorator.decorateHeaders(request);
+        request  = (HttpGet) apacheRequestDecorator.decorate(request, decorator);
     }
 }
\ No newline at end of file
diff --git a/client/src/test/java/io/split/service/HttpSplitClientTest.java b/client/src/test/java/io/split/service/HttpSplitClientTest.java
index 946775f39..4d18a080d 100644
--- a/client/src/test/java/io/split/service/HttpSplitClientTest.java
+++ b/client/src/test/java/io/split/service/HttpSplitClientTest.java
@@ -16,7 +16,7 @@
 import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
 import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.Header;
+//import org.apache.hc.core5.http.Header;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -57,9 +57,9 @@ public void testGetWithSpecialCharacters() throws URISyntaxException, Invocation
         HttpUriRequest request = captor.getValue();
         assertThat(request.getFirstHeader("AdditionalHeader").getValue(), is(equalTo("add")));
 
-        Header[] headers = splitHttpResponse.responseHeaders();
+        SplitHttpResponse.Header[] headers = splitHttpResponse.responseHeaders();
         assertThat(headers[0].getName(), is(equalTo("Via")));
-        assertThat(headers[0].getValue(), is(equalTo("HTTP/1.1 m_proxy_rio1")));
+        assertThat(headers[0].getValues().get(0), is(equalTo("HTTP/1.1 m_proxy_rio1")));
         Assert.assertNotNull(change);
         Assert.assertEquals(1, change.splits.size());
         Assert.assertNotNull(change.splits.get(0));
@@ -122,7 +122,7 @@ public void testPost() throws URISyntaxException, IOException, IllegalAccessExce
 
         Map<String, List<String>> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode",
                 Collections.singletonList("OPTIMIZED"));
-        SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget, Utils.toJsonEntity(toSend),
+        SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget, Json.toJson(toSend),
                 additionalHeaders);
 
         // Capture outgoing request and validate it
@@ -152,7 +152,7 @@ public void testPosttNoExceptionOnHttpErrorCode() throws URISyntaxException, Inv
 
         SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, decorator, "qwerty", metadata());
         SplitHttpResponse splitHttpResponse = splitHtpClient.post(rootTarget,
-                Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
+                Json.toJson(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
         Assert.assertEquals(500, (long) splitHttpResponse.statusCode());
 
     }
@@ -165,7 +165,7 @@ public void testPosttException() throws URISyntaxException, InvocationTargetExce
                 HttpStatus.SC_INTERNAL_SERVER_ERROR);
 
         SplitHttpClient splitHtpClient = SplitHttpClientImpl.create(httpClientMock, null, "qwerty", metadata());
-        splitHtpClient.post(rootTarget, Utils.toJsonEntity(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
+        splitHtpClient.post(rootTarget, Json.toJson(Arrays.asList(new String[] { "A", "B", "C", "D" })), null);
     }
 
     private SDKMetadata metadata() {
diff --git a/client/src/test/resources/org/powermock/extensions/configuration.properties b/client/src/test/resources/org/powermock/extensions/configuration.properties
new file mode 100644
index 000000000..a8ebaeba3
--- /dev/null
+++ b/client/src/test/resources/org/powermock/extensions/configuration.properties
@@ -0,0 +1 @@
+powermock.global-ignore=jdk.internal.reflect.*,javax.net.ssl.*
\ No newline at end of file
diff --git a/okhttp-modules/pom.xml b/okhttp-modules/pom.xml
new file mode 100644
index 000000000..529869307
--- /dev/null
+++ b/okhttp-modules/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>java-client-parent</artifactId>
+        <groupId>io.split.client</groupId>
+        <version>4.13.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <version>4.13.0</version>
+    <artifactId>okhttp-modules</artifactId>
+    <packaging>jar</packaging>
+    <name>http-modules</name>
+    <description>Alternative Http Modules</description>
+
+    <profiles>
+        <profile>
+            <id>release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.sonatype.plugins</groupId>
+                        <artifactId>nexus-staging-maven-plugin</artifactId>
+                        <version>1.6.3</version>
+                        <extensions>true</extensions>
+                        <configuration>
+                            <skipNexusStagingDeployMojo>false</skipNexusStagingDeployMojo>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+    <dependencies>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>logging-interceptor</artifactId>
+            <version>4.12.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.split.client</groupId>
+            <artifactId>java-client</artifactId>
+            <version>4.13.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents.client5</groupId>
+            <artifactId>httpclient5</artifactId>
+            <version>5.0.3</version>
+        </dependency>
+        <!-- Test deps -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-module-junit4</artifactId>
+            <version>1.7.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.powermock</groupId>
+            <artifactId>powermock-api-mockito</artifactId>
+            <version>1.7.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>mockwebserver</artifactId>
+            <version>4.8.0</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+</project>
diff --git a/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/HTTPKerberosAuthInterceptor.java b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/HTTPKerberosAuthInterceptor.java
new file mode 100644
index 000000000..26bd23ea5
--- /dev/null
+++ b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/HTTPKerberosAuthInterceptor.java
@@ -0,0 +1,273 @@
+package io.split.httpmodules.okhttp;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Date;
+import java.util.Set;
+import java.util.Base64;
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.Principal;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.Subject;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.kerberos.KerberosTicket;
+
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.Authenticator;
+import okhttp3.Route;
+
+/**
+ *
+ * An HTTP Request interceptor that modifies the request headers to enable
+ * Kerberos authentication. It appends the Kerberos authentication token to the
+ * 'Authorization' request header for Kerberos authentication
+ *
+ *  Copyright 2024 MarkLogic Corporation
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ *
+ */
+public class HTTPKerberosAuthInterceptor implements Authenticator {
+    String host;
+    Map<String,String> krbOptions;
+    LoginContext loginContext;
+  public HTTPKerberosAuthInterceptor(String host, Map<String,String> krbOptions) throws IOException {
+    this.host = host;
+    this.krbOptions = krbOptions;
+    try {
+      buildSubjectCredentials();
+    } catch (LoginException e) {
+      throw new IOException(e.getMessage(), e);
+    }
+  }
+
+  /**
+   * Class to create Kerberos Configuration object which specifies the Kerberos
+   * Login Module to be used for authentication.
+   *
+   */
+  protected static class KerberosLoginConfiguration extends Configuration {
+    Map<String,String> krbOptions = null;
+
+    public KerberosLoginConfiguration() {}
+
+    KerberosLoginConfiguration(Map<String,String> krbOptions) {
+
+      this.krbOptions = krbOptions;
+    }
+    @Override
+    public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+      if (krbOptions == null) {
+        throw new IllegalStateException("Cannot create AppConfigurationEntry without Kerberos Options");
+      }
+      return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
+          AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, krbOptions) };
+    }
+  }
+
+  /**
+   * This method checks the validity of the TGT in the cache and build the
+   * Subject inside the LoginContext using Krb5LoginModule and the TGT cached by
+   * the Kerberos client. It assumes that a valid TGT is already present in the
+   * kerberos client's cache.
+   *
+   * @throws LoginException
+   */
+  protected void buildSubjectCredentials() throws LoginException {
+    Subject subject = new Subject();
+    /**
+     * We are not getting the TGT from KDC here. The actual TGT is got from the
+     * KDC using kinit or equivalent but we use the cached TGT in order to build
+     * the LoginContext and populate the TGT inside the Subject using
+     * Krb5LoginModule
+     */
+
+    LoginContext lc = getLoginContext(subject);
+    lc.login();
+    loginContext = lc;
+  }
+
+  protected LoginContext getLoginContext(Subject subject) throws LoginException {
+    return new LoginContext("Krb5LoginContext", subject, null,
+            (krbOptions != null) ? new KerberosLoginConfiguration(krbOptions) : new KerberosLoginConfiguration());
+  }
+  /**
+   * This method is responsible for getting the client principal name from the
+   * subject's principal set
+   *
+   * @return String the Kerberos principal name populated in the subject
+   * @throws IllegalStateException if there is more than 0 or more than 1
+   *           principal is present
+   */
+  protected String getClientPrincipalName() {
+    final Set<Principal> principalSet = getContextSubject().getPrincipals();
+    if (principalSet.size() != 1)
+      throw new IllegalStateException(
+          "Only one principal is expected. Found 0 or more than one principals :" + principalSet);
+    return principalSet.iterator().next().getName();
+  }
+
+  protected Subject getContextSubject() {
+    Subject subject = loginContext.getSubject();
+    if (subject == null)
+      throw new IllegalStateException("Kerberos login context without subject");
+    return subject;
+  }
+
+  protected CreateAuthorizationHeaderAction getAuthorizationHeaderAction(String clientPrincipal,
+                                                                         String serverPrincipalName) {
+    return new CreateAuthorizationHeaderAction(clientPrincipal,
+            serverPrincipalName);
+  }
+
+  /**
+   * This method builds the Authorization header for Kerberos. It
+   * generates a request token based on the service ticket, client principal name and
+   * time-stamp
+   *
+   * @param serverPrincipalName
+   *            the name registered with the KDC of the service for which we
+   *            need to authenticate
+   * @return the HTTP Authorization header token
+   */
+  protected String buildAuthorizationHeader(String serverPrincipalName) throws LoginException, PrivilegedActionException {
+    /*
+     * Get the principal from the Subject's private credentials and populate the
+     * client and server principal name for the GSS API
+     */
+    final String clientPrincipal = getClientPrincipalName();
+    final CreateAuthorizationHeaderAction action = getAuthorizationHeaderAction(clientPrincipal,
+      serverPrincipalName);
+
+    /*
+     * Check if the TGT in the Subject's private credentials are valid. If
+     * valid, then we use the TGT in the Subject's private credentials. If not,
+     * we build the Subject's private credentials again from valid TGT in the
+     * Kerberos client cache.
+     */
+    Set<Object> privateCreds = getContextSubject().getPrivateCredentials();
+    for (Object privateCred : privateCreds) {
+      if (privateCred instanceof KerberosTicket) {
+        String serverPrincipalTicketName = ((KerberosTicket) privateCred).getServer().getName();
+        if ((serverPrincipalTicketName.startsWith("krbtgt"))
+          && ((KerberosTicket) privateCred).getEndTime().compareTo(new Date()) < 0) {
+          buildSubjectCredentials();
+          break;
+        }
+      }
+    }
+
+    /*
+     * Subject.doAs takes in the Subject context and the action to be run as
+     * arguments. This method executes the action as the Subject given in the
+     * argument. We do this in order to provide the Subject's context so that we
+     * reuse the service ticket which will be populated in the Subject rather
+     * than getting the service ticket from the KDC for each request. The GSS
+     * API populates the service ticket in the Subject and reuses it
+     *
+     */
+    Subject.doAs(loginContext.getSubject(), action);
+    return action.getNegotiateToken();
+  }
+
+  /**
+   * Creates a privileged action which will be executed as the Subject using
+   * Subject.doAs() method. We do this in order to create a context of the user
+   * who has the service ticket and reuse this context for subsequent requests
+   */
+  protected static class CreateAuthorizationHeaderAction implements PrivilegedExceptionAction {
+    String clientPrincipalName;
+    String serverPrincipalName;
+
+    private StringBuilder outputToken = new StringBuilder();
+
+    protected CreateAuthorizationHeaderAction(final String clientPrincipalName, final String serverPrincipalName) {
+      this.clientPrincipalName = clientPrincipalName;
+      this.serverPrincipalName = serverPrincipalName;
+    }
+
+    protected String getNegotiateToken() {
+      return outputToken.toString();
+    }
+
+    /*
+     * Here GSS API takes care of getting the service ticket from the Subject
+     * cache or by using the TGT information populated in the subject which is
+     * done by buildSubjectCredentials method. The service ticket received is
+     * populated in the subject's private credentials along with the TGT
+     * information since we will be executing this method as the Subject. For
+     * subsequent requests, the cached service ticket will be re-used. For this
+     * to work the System property javax.security.auth.useSubjectCredsOnly must
+     * be set to true.
+     */
+    @Override
+    public Object run() throws KerberosAuthException {
+      try {
+        Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2");
+        Oid krb5PrincipalNameType = new Oid("1.2.840.113554.1.2.2.1");
+        final GSSManager manager = GSSManager.getInstance();
+        final GSSName clientName = manager.createName(clientPrincipalName, krb5PrincipalNameType);
+        final GSSCredential clientCred = manager.createCredential(clientName, 8 * 3600, krb5Mechanism,
+            GSSCredential.INITIATE_ONLY);
+        final GSSName serverName = manager.createName(serverPrincipalName, krb5PrincipalNameType);
+
+        final GSSContext context = manager.createContext(serverName, krb5Mechanism, clientCred,
+            GSSContext.DEFAULT_LIFETIME);
+        byte[] inToken = new byte[0];
+        byte[] outToken = context.initSecContext(inToken, 0, inToken.length);
+        if (outToken == null) {
+          throw new IOException("could not initialize the security context");
+        }
+        context.requestMutualAuth(true);
+        outputToken.append(new String(Base64.getEncoder().encode(outToken)));
+        context.dispose();
+      } catch (GSSException | IOException exception) {
+        throw new KerberosAuthException(exception.getMessage(), exception);
+      }
+      return null;
+    }
+  }
+
+  /*
+   * The server principal name which we pass as an argument to
+   * buildAuthorizationHeader method would always start with 'HTTP/' because we
+   * create the principal name for the Marklogic server starting with 'HTTP/'
+   * followed by the host name as mentioned in the <a href=
+   * "http://docs.marklogic.com/guide/security/external-auth#id_13835"> External
+   * Security Guide</a>.
+   */
+  @Override public Request authenticate(Route route, Response response) throws IOException {
+    String authValue;
+    try {
+      authValue = "Negotiate " + buildAuthorizationHeader("HTTP/" + host);
+    } catch (Exception e) {
+      throw new IOException(e.getMessage(), e);
+    }
+
+    return response.request().newBuilder()
+            .header("Proxy-authorization", authValue)
+            .build();
+  }
+}
diff --git a/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/KerberosAuthException.java b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/KerberosAuthException.java
new file mode 100644
index 000000000..06fa2672f
--- /dev/null
+++ b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/KerberosAuthException.java
@@ -0,0 +1,10 @@
+package io.split.httpmodules.okhttp;
+
+public class KerberosAuthException extends Exception {
+    public KerberosAuthException(String message) {
+        super(message);
+    }
+    public KerberosAuthException(String message, Throwable exception) {
+        super(message, exception);
+    }
+}
diff --git a/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpClientImpl.java b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpClientImpl.java
new file mode 100644
index 000000000..65a59921f
--- /dev/null
+++ b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpClientImpl.java
@@ -0,0 +1,247 @@
+package io.split.httpmodules.okhttp;
+
+import io.split.client.RequestDecorator;
+import io.split.client.dtos.SplitHttpResponse;
+import io.split.client.utils.SDKMetadata;
+import io.split.engine.common.FetchOptions;
+import io.split.service.SplitHttpClient;
+
+import okhttp3.Authenticator;
+import okhttp3.OkHttpClient;
+import okhttp3.logging.HttpLoggingInterceptor;
+import okhttp3.MediaType;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class OkHttpClientImpl implements SplitHttpClient {
+    protected OkHttpClient httpClient;
+    private static final Logger _log = LoggerFactory.getLogger(OkHttpClientImpl.class);
+    private static final String HEADER_CACHE_CONTROL_NAME = "Cache-Control";
+    private static final String HEADER_CACHE_CONTROL_VALUE = "no-cache";
+    private static final String HEADER_API_KEY = "Authorization";
+    private static final String HEADER_CLIENT_KEY = "SplitSDKClientKey";
+    private static final String HEADER_CLIENT_MACHINE_NAME = "SplitSDKMachineName";
+    private static final String HEADER_CLIENT_MACHINE_IP = "SplitSDKMachineIP";
+    private static final String HEADER_CLIENT_VERSION = "SplitSDKVersion";
+    private String _apikey;
+    protected SDKMetadata _metadata;
+    private final RequestDecorator _decorator;
+
+    public OkHttpClientImpl(String apiToken, SDKMetadata sdkMetadata,
+            Proxy proxy, String proxyAuthKerberosPrincipalName, boolean debugEnabled,
+            int readTimeout, int connectionTimeout, RequestDecorator decorator) throws IOException {
+        _apikey = apiToken;
+        _metadata = sdkMetadata;
+        _decorator = decorator;
+        setHttpClient(proxy, proxyAuthKerberosPrincipalName, debugEnabled,
+                readTimeout, connectionTimeout);
+
+    }
+    protected void setHttpClient(Proxy proxy, String proxyAuthKerberosPrincipalName, boolean debugEnabled,
+                                 int readTimeout, int connectionTimeout) throws IOException {
+        httpClient = initializeClient(proxy, proxyAuthKerberosPrincipalName, debugEnabled,
+                readTimeout, connectionTimeout);
+    }
+    protected OkHttpClient initializeClient(Proxy proxy, String proxyAuthKerberosPrincipalName, boolean debugEnabled,
+            int readTimeout, int connectionTimeout) throws IOException {
+        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
+        if (debugEnabled) {
+            logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);
+        } else {
+            logging.setLevel(HttpLoggingInterceptor.Level.NONE);
+        }
+
+        Map<String, String> kerberosOptions = new HashMap<>();
+        kerberosOptions.put("com.sun.security.auth.module.Krb5LoginModule", "required");
+        kerberosOptions.put("refreshKrb5Config", "false");
+        kerberosOptions.put("doNotPrompt", "false");
+        kerberosOptions.put("useTicketCache", "true");
+
+        Authenticator proxyAuthenticator = getProxyAuthenticator(proxyAuthKerberosPrincipalName, kerberosOptions);
+
+        return new okhttp3.OkHttpClient.Builder()
+                .proxy(proxy)
+                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
+                .connectTimeout(connectionTimeout, TimeUnit.MILLISECONDS)
+                .addInterceptor(logging)
+                .proxyAuthenticator(proxyAuthenticator)
+                .build();
+    }
+
+    public HTTPKerberosAuthInterceptor getProxyAuthenticator(String proxyKerberosPrincipalName,
+            Map<String, String> kerberosOptions) throws IOException {
+        return new HTTPKerberosAuthInterceptor(proxyKerberosPrincipalName, kerberosOptions);
+    }
+
+    @Override
+    public SplitHttpResponse get(URI uri, FetchOptions options, Map<String, List<String>> additionalHeaders) {
+        try {
+            okhttp3.Request.Builder requestBuilder = getRequestBuilder();
+            requestBuilder.url(uri.toString());
+            Map<String, List<String>> headers = mergeHeaders(buildBasicHeaders(), additionalHeaders);
+            Map<String, List<String>> decorateHeaders = OkHttpRequestDecorator.decorate(headers, _decorator);
+            Map<String, List<String>> finalHeaders;
+            if (decorateHeaders.isEmpty()) {
+                finalHeaders = headers;
+            } else {
+                finalHeaders = decorateHeaders;
+            }
+            for (Map.Entry<String, List<String>> e : finalHeaders.entrySet()) {
+                for (String headerValue : e.getValue()) {
+                    requestBuilder.addHeader(e.getKey(), headerValue);
+                }
+            }
+
+            if (options.cacheControlHeadersEnabled()) {
+                requestBuilder.addHeader(HEADER_CACHE_CONTROL_NAME, HEADER_CACHE_CONTROL_VALUE);
+            }
+
+            Request request = requestBuilder.build();
+            _log.debug(String.format("Request Headers: %s", request.headers()));
+
+            Response response = httpClient.newCall(request).execute();
+
+            int responseCode = response.code();
+
+            _log.debug(String.format("[GET] %s. Status code: %s",
+                    request.url().toString(),
+                    responseCode));
+
+            String statusMessage = "";
+            if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
+                _log.warn(String.format("Response status was: %s. Reason: %s", responseCode,
+                        response.message()));
+                statusMessage = response.message();
+            }
+
+            String responseBody = response.body().string();
+            response.close();
+
+            return new SplitHttpResponse(responseCode,
+                    statusMessage,
+                    responseBody,
+                    getResponseHeaders(response));
+        } catch (Exception e) {
+            throw new IllegalStateException(String.format("Problem in http get operation: %s", e), e);
+        }
+    }
+
+    @Override
+    public SplitHttpResponse post(URI url, String entity,
+            Map<String, List<String>> additionalHeaders) {
+        try {
+            okhttp3.Request.Builder requestBuilder = getRequestBuilder();
+            requestBuilder.url(url.toString());
+            Map<String, List<String>> headers = mergeHeaders(buildBasicHeaders(), additionalHeaders);
+            Map<String, List<String>> decorateHeaders = OkHttpRequestDecorator.decorate(headers, _decorator);
+            Map<String, List<String>> finalHeaders;
+            if (decorateHeaders.isEmpty()) {
+                finalHeaders = headers;
+            } else {
+                finalHeaders = decorateHeaders;
+            }
+            for (Map.Entry<String, List<String>> e : finalHeaders.entrySet()) {
+                for (String headerValue : e.getValue()) {
+                    requestBuilder.addHeader(e.getKey(), headerValue);
+                }
+            }
+            requestBuilder.addHeader("Accept-Encoding", "gzip");
+            requestBuilder.addHeader("Content-Type", "application/json");
+            RequestBody postBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), entity);
+            requestBuilder.post(postBody);
+
+            Request request = requestBuilder.build();
+            _log.debug(String.format("Request Headers: %s", request.headers()));
+
+            Response response = httpClient.newCall(request).execute();
+
+            int responseCode = response.code();
+
+            _log.debug(String.format("[GET] %s. Status code: %s",
+                    request.url().toString(),
+                    responseCode));
+
+            String statusMessage = "";
+            if (responseCode < HttpURLConnection.HTTP_OK || responseCode >= HttpURLConnection.HTTP_MULT_CHOICE) {
+                _log.warn(String.format("Response status was: %s. Reason: %s", responseCode,
+                        response.message()));
+                statusMessage = response.message();
+            }
+            response.close();
+
+            return new SplitHttpResponse(responseCode, statusMessage, "", getResponseHeaders(response));
+        } catch (Exception e) {
+            throw new IllegalStateException(String.format("Problem in http post operation: %s", e), e);
+        }
+    }
+
+    protected okhttp3.Request.Builder getRequestBuilder() {
+        return new okhttp3.Request.Builder();
+    }
+
+    protected Request getRequest(okhttp3.Request.Builder requestBuilder) {
+        return requestBuilder.build();
+    }
+
+    protected Map<String, List<String>> buildBasicHeaders() {
+        Map<String, List<String>> h = new HashMap<>();
+        h.put(HEADER_API_KEY, Collections.singletonList("Bearer " + _apikey));
+        h.put(HEADER_CLIENT_VERSION, Collections.singletonList(_metadata.getSdkVersion()));
+        h.put(HEADER_CLIENT_MACHINE_IP, Collections.singletonList(_metadata.getMachineIp()));
+        h.put(HEADER_CLIENT_MACHINE_NAME, Collections.singletonList(_metadata.getMachineName()));
+        h.put(HEADER_CLIENT_KEY, Collections.singletonList(_apikey.length() > 4
+                ? _apikey.substring(_apikey.length() - 4)
+                : _apikey));
+        return h;
+    }
+
+    protected Map<String, List<String>> mergeHeaders(Map<String, List<String>> headers,
+            Map<String, List<String>> toAdd) {
+        if (toAdd == null || toAdd.size() == 0) {
+            return headers;
+        }
+
+        for (Map.Entry<String, List<String>> entry : toAdd.entrySet()) {
+            headers.put(entry.getKey(), entry.getValue());
+//            headers.computeIfPresent(entry.getKey(),
+//                    (k, oldValue) -> Stream.concat(oldValue.stream(), entry.getValue().stream())
+//                            .collect(Collectors.toList()));
+        }
+
+        return headers;
+    }
+
+    protected SplitHttpResponse.Header[] getResponseHeaders(Response response) {
+        List<SplitHttpResponse.Header> responseHeaders = new ArrayList<>();
+        Map<String, List<String>> map = response.headers().toMultimap();
+        for (Map.Entry<String, List<String>> entry : map.entrySet()) {
+            if (entry.getKey() != null) {
+                responseHeaders.add(new SplitHttpResponse.Header(entry.getKey(), entry.getValue()));
+            }
+        }
+        return responseHeaders.toArray(new SplitHttpResponse.Header[0]);
+    }
+
+    @Override
+    public void close() throws IOException {
+        httpClient.dispatcher().executorService().shutdown();
+    }
+
+}
diff --git a/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpModule.java b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpModule.java
new file mode 100644
index 000000000..9f512874d
--- /dev/null
+++ b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpModule.java
@@ -0,0 +1,195 @@
+package io.split.httpmodules.okhttp;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+
+import io.split.client.RequestDecorator;
+import io.split.client.utils.SDKMetadata;
+import io.split.service.CustomHttpModule;
+
+public class OkHttpModule implements CustomHttpModule {
+    private static final int DEFAULT_CONNECTION_TIMEOUT = 15000;
+    private static final int DEFAULT_READ_TIMEOUT = 15000;
+    private final Boolean _debugEnabled;
+    private final Integer _connectionTimeout;
+    private final Integer _readTimeout;
+    private final Proxy _proxy;
+    private final ProxyAuthScheme _proxyAuthScheme;
+    private final String _proxyAuthKerberosPrincipalName;
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private OkHttpModule(ProxyAuthScheme proxyAuthScheme,
+            String proxyAuthKerberosPrincipalName,
+            Proxy proxy,
+            Integer connectionTimeout,
+            Integer readTimeout,
+            Boolean debugEnabled) {
+        _proxyAuthScheme = proxyAuthScheme;
+        _proxyAuthKerberosPrincipalName = proxyAuthKerberosPrincipalName;
+        _proxy = proxy;
+        _connectionTimeout = connectionTimeout;
+        _readTimeout = readTimeout;
+        _debugEnabled = debugEnabled;
+    }
+
+    @Override
+    public OkHttpClientImpl createClient(String apiToken, SDKMetadata sdkMetadata, RequestDecorator decorator)
+            throws IOException {
+        return new OkHttpClientImpl(apiToken, sdkMetadata,
+                _proxy, _proxyAuthKerberosPrincipalName, _debugEnabled,
+                _readTimeout, _connectionTimeout, decorator);
+    }
+
+    public Proxy proxy() {
+        return _proxy;
+    }
+
+    public ProxyAuthScheme proxyAuthScheme() {
+        return _proxyAuthScheme;
+    }
+
+    public String proxyKerberosPrincipalName() {
+        return _proxyAuthKerberosPrincipalName;
+    }
+
+    public Integer connectionTimeout() {
+        return _connectionTimeout;
+    }
+
+    public Boolean debugEnabled() {
+        return _debugEnabled;
+    }
+
+    public Integer readTimeout() {
+        return _readTimeout;
+    }
+
+    public static final class Builder {
+        private Integer _connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
+        private Integer _readTimeout = DEFAULT_READ_TIMEOUT;
+        private String _proxyHost = "localhost";
+        private int _proxyPort = -1;
+        private ProxyAuthScheme _proxyAuthScheme = null;
+        private String _proxyAuthKerberosPrincipalName = null;
+        private Boolean _debugEnabled = false;
+
+        public Builder() {
+        }
+
+        public Builder debugEnabled() {
+            _debugEnabled = true;
+            return this;
+        }
+
+        /**
+         * The host location of the proxy. Default is localhost.
+         *
+         * @param proxyHost location of the proxy
+         * @return this builder
+         */
+        public Builder proxyHost(String proxyHost) {
+            _proxyHost = proxyHost;
+            return this;
+        }
+
+        /**
+         * The port of the proxy. Default is -1.
+         *
+         * @param proxyPort port for the proxy
+         * @return this builder
+         */
+        public Builder proxyPort(int proxyPort) {
+            _proxyPort = proxyPort;
+            return this;
+        }
+
+        Proxy proxy() {
+            if (_proxyPort != -1) {
+                return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(_proxyHost, _proxyPort));
+            }
+            // Default is no proxy.
+            return null;
+        }
+
+        /**
+         * Authentication Scheme
+         *
+         * @param proxyAuthScheme
+         * @return this builder
+         */
+        public Builder proxyAuthScheme(ProxyAuthScheme proxyAuthScheme) {
+            _proxyAuthScheme = proxyAuthScheme;
+            return this;
+        }
+
+        /**
+         * Kerberos Principal Account Name
+         *
+         * @param proxyAuthKerberosPrincipalName
+         * @return this builder
+         */
+        public Builder proxyAuthKerberosPrincipalName(String proxyAuthKerberosPrincipalName) {
+            _proxyAuthKerberosPrincipalName = proxyAuthKerberosPrincipalName;
+            return this;
+        }
+
+        /**
+         * HTTP Connection Timeout
+         *
+         * @param connectionTimeout
+         * @return this builder
+         */
+        public Builder connectionTimeout(int connectionTimeout) {
+            _connectionTimeout = connectionTimeout;
+            return this;
+        }
+
+        /**
+         * HTTP Read Timeout
+         *
+         * @param readTimeout
+         * @return this builder
+         */
+        public Builder readTimeout(int readTimeout) {
+            _readTimeout = readTimeout;
+            return this;
+        }
+
+        private void verifyAuthScheme() {
+            if (_proxyAuthScheme == ProxyAuthScheme.KERBEROS) {
+                if (proxy() == null) {
+                    throw new IllegalStateException("Kerberos mode require Proxy parameters.");
+                }
+                if (_proxyAuthKerberosPrincipalName == null) {
+                    throw new IllegalStateException("Kerberos mode require Kerberos Principal Name.");
+                }
+            }
+        }
+
+        private void verifyTimeouts() {
+            if (_connectionTimeout <= 0) {
+                _connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
+            }
+            if (_readTimeout <= 0) {
+                _readTimeout = DEFAULT_READ_TIMEOUT;
+            }
+        }
+
+        public OkHttpModule build() {
+            verifyTimeouts();
+            verifyAuthScheme();
+
+            return new OkHttpModule(
+                    _proxyAuthScheme,
+                    _proxyAuthKerberosPrincipalName,
+                    proxy(),
+                    _connectionTimeout,
+                    _readTimeout,
+                    _debugEnabled);
+        }
+    }
+}
diff --git a/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpRequestDecorator.java b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpRequestDecorator.java
new file mode 100644
index 000000000..efe9b8077
--- /dev/null
+++ b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/OkHttpRequestDecorator.java
@@ -0,0 +1,15 @@
+package io.split.httpmodules.okhttp;
+
+import java.util.List;
+import java.util.Map;
+
+import io.split.client.RequestDecorator;
+import io.split.client.dtos.RequestContext;
+
+class OkHttpRequestDecorator {
+
+    public static Map<String, List<String>> decorate(Map<String, List<String>> headers,
+            RequestDecorator decorator) {
+        return decorator.decorateHeaders(new RequestContext(headers)).headers();
+    }
+}
diff --git a/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/ProxyAuthScheme.java b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/ProxyAuthScheme.java
new file mode 100644
index 000000000..4340829a2
--- /dev/null
+++ b/okhttp-modules/src/main/java/io/split/httpmodules/okhttp/ProxyAuthScheme.java
@@ -0,0 +1,5 @@
+package io.split.httpmodules.okhttp;
+
+public enum ProxyAuthScheme {
+    KERBEROS
+}
diff --git a/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/HTTPKerberosAuthIntercepterTest.java b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/HTTPKerberosAuthIntercepterTest.java
new file mode 100644
index 000000000..2103abd1c
--- /dev/null
+++ b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/HTTPKerberosAuthIntercepterTest.java
@@ -0,0 +1,111 @@
+package io.split.httpmodules.okhttp;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+import static org.powermock.api.mockito.PowerMockito.*;
+
+import java.security.PrivilegedActionException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(HTTPKerberosAuthInterceptor.class)
+public class HTTPKerberosAuthIntercepterTest {
+    @Test
+    public void testBasicFlow() throws Exception {
+        System.setProperty("java.security.krb5.conf", "src/test/resources/krb5.conf");
+
+        HTTPKerberosAuthInterceptor kerberosAuthInterceptor = mock(HTTPKerberosAuthInterceptor.class);
+        LoginContext loginContext = PowerMockito.mock(LoginContext.class);
+        when(kerberosAuthInterceptor.getLoginContext(any())).thenReturn((loginContext));
+
+        doCallRealMethod().when(kerberosAuthInterceptor).buildSubjectCredentials();
+        kerberosAuthInterceptor.buildSubjectCredentials();
+        verify(loginContext, times(1)).login();
+
+        Subject subject = new Subject();
+        when(loginContext.getSubject()).thenReturn(subject);
+        doCallRealMethod().when(kerberosAuthInterceptor).getContextSubject();
+        kerberosAuthInterceptor.getContextSubject();
+        verify(loginContext, times(1)).getSubject();
+
+        subject.getPrincipals().add(new KerberosPrincipal("bilal"));
+        subject.getPublicCredentials().add(new KerberosPrincipal("name"));
+        subject.getPrivateCredentials().add(new KerberosPrincipal("name"));
+
+        doCallRealMethod().when(kerberosAuthInterceptor).getClientPrincipalName();
+        assertThat(kerberosAuthInterceptor.getClientPrincipalName(), is(equalTo("bilal@ATHENA.MIT.EDU"))) ;
+        verify(loginContext, times(2)).getSubject();
+
+        when(kerberosAuthInterceptor.buildAuthorizationHeader(any())).thenReturn("secured-token");
+        okhttp3.Request originalRequest = new okhttp3.Request.Builder().url("http://somthing").build();
+        okhttp3.Response response = new okhttp3.Response.Builder().code(200).request(originalRequest).
+                protocol(okhttp3.Protocol.HTTP_1_1).message("ok").build();
+        doCallRealMethod().when(kerberosAuthInterceptor).authenticate(null, response);
+        okhttp3.Request request =  kerberosAuthInterceptor.authenticate(null, response);
+        assertThat(request.headers("Proxy-authorization"), is(equalTo(Arrays.asList("Negotiate secured-token"))));
+    }
+
+    @Test
+    public void testKerberosLoginConfiguration() {
+        Map<String, String> kerberosOptions = new HashMap<String, String>();
+        kerberosOptions.put("com.sun.security.auth.module.Krb5LoginModule", "required");
+        kerberosOptions.put("refreshKrb5Config", "false");
+        kerberosOptions.put("doNotPrompt", "false");
+        kerberosOptions.put("useTicketCache", "true");
+
+        HTTPKerberosAuthInterceptor.KerberosLoginConfiguration kerberosConfig = new HTTPKerberosAuthInterceptor.KerberosLoginConfiguration(kerberosOptions);
+        AppConfigurationEntry[] appConfig = kerberosConfig.getAppConfigurationEntry("");
+        assertThat("com.sun.security.auth.module.Krb5LoginModule", is(equalTo(appConfig[0].getLoginModuleName())));
+        assertThat(AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, is(equalTo(appConfig[0].getControlFlag())));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testKerberosLoginConfigurationException() {
+        HTTPKerberosAuthInterceptor.KerberosLoginConfiguration kerberosConfig = new HTTPKerberosAuthInterceptor.KerberosLoginConfiguration();
+        AppConfigurationEntry[] appConfig = kerberosConfig.getAppConfigurationEntry("");
+    }
+
+    @Test
+    public void testBuildAuthorizationHeader() throws LoginException, PrivilegedActionException {
+        System.setProperty("java.security.krb5.conf", "src/test/resources/krb5.conf");
+
+        HTTPKerberosAuthInterceptor kerberosAuthInterceptor = mock(HTTPKerberosAuthInterceptor.class);
+        HTTPKerberosAuthInterceptor.CreateAuthorizationHeaderAction ahh = mock(HTTPKerberosAuthInterceptor.CreateAuthorizationHeaderAction.class);
+        when(ahh.getNegotiateToken()).thenReturn("secret-token");
+        when(kerberosAuthInterceptor.getAuthorizationHeaderAction(any(), any())).thenReturn(ahh);
+
+        LoginContext loginContext = PowerMockito.mock(LoginContext.class);
+        doCallRealMethod().when(kerberosAuthInterceptor).buildAuthorizationHeader("bilal");
+        Subject subject = new Subject();
+        when(loginContext.getSubject()).thenReturn(subject);
+        when(kerberosAuthInterceptor.getContextSubject()).thenReturn(subject);
+        when(kerberosAuthInterceptor.getLoginContext(subject)).thenReturn((loginContext));
+        doCallRealMethod().when(kerberosAuthInterceptor).buildSubjectCredentials();
+        kerberosAuthInterceptor.buildSubjectCredentials();
+
+        subject.getPrincipals().add(new KerberosPrincipal("bilal"));
+        subject.getPublicCredentials().add(new KerberosPrincipal("name"));
+        subject.getPrivateCredentials().add(new KerberosPrincipal("name"));
+        doCallRealMethod().when(kerberosAuthInterceptor).getClientPrincipalName();
+
+        assertThat("secret-token", is(equalTo(kerberosAuthInterceptor.buildAuthorizationHeader("bilal"))));
+    }
+}
diff --git a/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/OkHttpClientImplTest.java b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/OkHttpClientImplTest.java
new file mode 100644
index 000000000..88d93333a
--- /dev/null
+++ b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/OkHttpClientImplTest.java
@@ -0,0 +1,429 @@
+package io.split.httpmodules.okhttp;
+
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.reflect.Whitebox;
+
+import io.split.client.CustomHeaderDecorator;
+import io.split.client.RequestDecorator;
+import io.split.client.dtos.*;
+import io.split.client.impressions.Impression;
+import io.split.client.utils.Json;
+import io.split.client.utils.SDKMetadata;
+import io.split.client.dtos.SplitHttpResponse.Header;
+import io.split.engine.common.FetchOptions;
+
+import okhttp3.OkHttpClient;
+import okhttp3.OkHttpClient.*;
+import okhttp3.HttpUrl;
+import okhttp3.Headers;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.*;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.HttpURLConnection;
+import java.util.*;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.mockito.Matchers.any;
+import static org.powermock.api.mockito.PowerMockito.mock;
+
+public class OkHttpClientImplTest {
+
+    @Test
+    public void testGetWithSpecialCharacters() throws IOException, InterruptedException {
+        MockWebServer server = new MockWebServer();
+        BufferedReader br = new BufferedReader(new FileReader("src/test/resources/split-change-special-characters.json"));
+        String body;
+        try {
+            StringBuilder sb = new StringBuilder();
+            String line = br.readLine();
+
+            while (line != null) {
+                sb.append(line);
+                sb.append(System.lineSeparator());
+                line = br.readLine();
+            }
+            body = sb.toString();
+        } finally {
+            br.close();
+        }
+
+        server.enqueue(new MockResponse().setBody(body).addHeader("via", "HTTP/1.1 s_proxy_rio1"));
+        server.start();
+        HttpUrl baseUrl = server.url("/v1/");
+        URI rootTarget = baseUrl.uri();
+
+        OkHttpClientImpl okHttpClientImpl = mock(OkHttpClientImpl.class);
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        PowerMockito.doReturn(client).when(okHttpClientImpl).initializeClient(null, "bilal", false,
+                0, 0);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).setHttpClient(null, "bilal", false,
+                0, 0);
+        okHttpClientImpl.setHttpClient(null, "bilal", false,
+        0, 0);
+
+        Map<String, List<String>> additionalHeaders = Collections.singletonMap("AdditionalHeader",
+                Collections.singletonList("add"));
+        FetchOptions fetchOptions = new FetchOptions.Builder().cacheControlHeaders(true).build();
+        RequestDecorator requestDecorator = new RequestDecorator(null);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).get(rootTarget, fetchOptions, additionalHeaders);
+        okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
+        requestBuilder.url(rootTarget.toString());
+        PowerMockito.doReturn(requestBuilder).when(okHttpClientImpl).getRequestBuilder();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).buildBasicHeaders();
+        Whitebox.setInternalState(okHttpClientImpl, "_metadata", metadata());
+        Whitebox.setInternalState(okHttpClientImpl, "_apikey", "qwerty");
+        Whitebox.setInternalState(okHttpClientImpl, "_decorator", requestDecorator);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).mergeHeaders(buildBasicHeaders(), additionalHeaders);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getResponseHeaders(any());
+        PowerMockito.doReturn(requestBuilder.build()).when(okHttpClientImpl).getRequest(requestBuilder);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getRequest(requestBuilder);
+
+        SplitHttpResponse splitHttpResponse = okHttpClientImpl.get(rootTarget, fetchOptions, additionalHeaders);
+
+        RecordedRequest request = server.takeRequest();
+        server.shutdown();
+        Headers requestHeaders = request.getHeaders();
+
+        assertThat(splitHttpResponse.statusCode(), is(equalTo(HttpURLConnection.HTTP_OK)));
+        Assert.assertEquals("/v1/", request.getPath());
+        assertThat(requestHeaders.get("Authorization"), is(equalTo("Bearer qwerty"))) ;
+        assertThat(requestHeaders.get("SplitSDKClientKey"), is(equalTo("erty")));
+        assertThat(requestHeaders.get("SplitSDKVersion"), is(equalTo("java-1.2.3")));
+        assertThat(requestHeaders.get("SplitSDKMachineIP"), is(equalTo("1.2.3.4")));
+        assertThat(requestHeaders.get("SplitSDKMachineName"), is(equalTo("someIP")));
+        assertThat(requestHeaders.get("AdditionalHeader"), is(equalTo("add")));
+
+        SplitChange change = Json.fromJson(splitHttpResponse.body(), SplitChange.class);
+
+        Header[] headers = splitHttpResponse.responseHeaders();
+        assertThat(headers[1].getName(), is(equalTo("via")));
+        assertThat(headers[1].getValues().get(0), is(equalTo("HTTP/1.1 s_proxy_rio1")));
+        assertThat(splitHttpResponse.statusCode(), is(equalTo(200)));
+        Assert.assertNotNull(change);
+        Assert.assertEquals(1, change.splits.size());
+        Assert.assertNotNull(change.splits.get(0));
+
+        Split split = change.splits.get(0);
+        Map<String, String> configs = split.configurations;
+        Assert.assertEquals(2, configs.size());
+        Assert.assertEquals("{\"test\": \"blue\",\"grüne Straße\": 13}", configs.get("on"));
+        Assert.assertEquals("{\"test\": \"blue\",\"size\": 15}", configs.get("off"));
+        Assert.assertEquals(2, split.sets.size());
+        okHttpClientImpl.close();
+    }
+
+    @Test
+    public void testGetErrors() throws IOException, InterruptedException {
+        MockWebServer server = new MockWebServer();
+        server.enqueue(new MockResponse().setBody("").setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR));
+        server.start();
+        HttpUrl baseUrl = server.url("/v1/");
+        URI rootTarget = baseUrl.uri();
+
+        OkHttpClientImpl okHttpClientImpl = mock(OkHttpClientImpl.class);
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        PowerMockito.doReturn(client).when(okHttpClientImpl).initializeClient(null, "bilal", false,
+                0, 0);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).setHttpClient(null, "bilal", false,
+                0, 0);
+        okHttpClientImpl.setHttpClient(null, "bilal", false,
+                0, 0);
+
+        Map<String, List<String>> additionalHeaders = Collections.singletonMap("AdditionalHeader",
+                Collections.singletonList("add"));
+        FetchOptions fetchOptions = new FetchOptions.Builder().cacheControlHeaders(true).build();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).get(rootTarget, fetchOptions, additionalHeaders);
+        okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
+        requestBuilder.url(rootTarget.toString());
+        PowerMockito.doReturn(requestBuilder).when(okHttpClientImpl).getRequestBuilder();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).buildBasicHeaders();
+        Whitebox.setInternalState(okHttpClientImpl, "_metadata", metadata());
+        Whitebox.setInternalState(okHttpClientImpl, "_apikey", "qwerty");
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).mergeHeaders(buildBasicHeaders(), additionalHeaders);
+        RequestDecorator requestDecorator = new RequestDecorator(null);
+        Whitebox.setInternalState(okHttpClientImpl, "_decorator", requestDecorator);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getResponseHeaders(any());
+
+        SplitHttpResponse splitHttpResponse = okHttpClientImpl.get(rootTarget,
+                fetchOptions, additionalHeaders);
+
+        RecordedRequest request = server.takeRequest();
+        server.shutdown();
+        assertThat(splitHttpResponse.statusCode(), is(equalTo(HttpURLConnection.HTTP_INTERNAL_ERROR)));
+        okHttpClientImpl.close();
+    }
+
+    @Test
+    public void testGetParameters() throws IOException, InterruptedException {
+        class MyCustomHeaders implements CustomHeaderDecorator {
+            public MyCustomHeaders() {}
+            @Override
+            public Map<String, List<String>> getHeaderOverrides(RequestContext context) {
+                Map<String, List<String>> additionalHeaders = context.headers();
+                additionalHeaders.put("first", Arrays.asList("1"));
+                additionalHeaders.put("second", Arrays.asList("2.1", "2.2"));
+                additionalHeaders.put("third", Arrays.asList("3"));
+                return additionalHeaders;
+            }
+        }
+        MockWebServer server = new MockWebServer();
+        BufferedReader br = new BufferedReader(new FileReader("src/test/resources/split-change-special-characters.json"));
+        String body;
+        try {
+            StringBuilder sb = new StringBuilder();
+            String line = br.readLine();
+
+            while (line != null) {
+                sb.append(line);
+                sb.append(System.lineSeparator());
+                line = br.readLine();
+            }
+            body = sb.toString();
+        } finally {
+            br.close();
+        }
+
+        server.enqueue(new MockResponse().setBody(body).addHeader("via", "HTTP/1.1 s_proxy_rio1"));
+        server.start();
+        HttpUrl baseUrl = server.url("/splitChanges?since=1234567");
+        URI rootTarget = baseUrl.uri();
+        RequestDecorator requestDecorator = new RequestDecorator(new MyCustomHeaders());
+
+        OkHttpClientImpl okHttpClientImpl = mock(OkHttpClientImpl.class);
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        PowerMockito.doReturn(client).when(okHttpClientImpl).initializeClient(null, "bilal", false,
+                0, 0);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).setHttpClient(null, "bilal", false,
+                0, 0);
+        okHttpClientImpl.setHttpClient(null, "bilal", false,
+                0, 0);
+
+        FetchOptions fetchOptions = new FetchOptions.Builder().cacheControlHeaders(true).build();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).get(rootTarget, fetchOptions, null);
+        okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
+        requestBuilder.url(rootTarget.toString());
+        PowerMockito.doReturn(requestBuilder).when(okHttpClientImpl).getRequestBuilder();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).buildBasicHeaders();
+        Whitebox.setInternalState(okHttpClientImpl, "_metadata", metadata());
+        Whitebox.setInternalState(okHttpClientImpl, "_apikey", "qwerty");
+        Whitebox.setInternalState(okHttpClientImpl, "_decorator", requestDecorator);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).mergeHeaders(buildBasicHeaders(), null);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getResponseHeaders(any());
+        FetchOptions options = new FetchOptions.Builder().cacheControlHeaders(true).build();
+
+        SplitHttpResponse splitHttpResponse = okHttpClientImpl.get(rootTarget, options, null);
+
+        RecordedRequest request = server.takeRequest();
+        server.shutdown();
+        Headers requestHeaders = request.getHeaders();
+
+        assertThat(requestHeaders.get("Cache-Control"), is(equalTo("no-cache")));
+        assertThat(requestHeaders.get("first"), is(equalTo("1")));
+        assertThat(requestHeaders.values("second"), is(equalTo(Arrays.asList("2.1","2.2"))));
+        assertThat(requestHeaders.get("third"), is(equalTo("3")));
+        Assert.assertEquals("/splitChanges?since=1234567", request.getPath());
+        assertThat(request.getMethod(), is(equalTo("GET")));
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testException() throws URISyntaxException, IOException {
+        URI rootTarget = new URI("https://api.split.io/splitChanges?since=1234567");
+        RequestDecorator requestDecorator = null;
+
+        OkHttpClientImpl okHttpClientImpl = mock(OkHttpClientImpl.class);
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        PowerMockito.doReturn(client).when(okHttpClientImpl).initializeClient(null, "bilal", false,
+                0, 0);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).setHttpClient(null, "bilal", false,
+                0, 0);
+        okHttpClientImpl.setHttpClient(null, "bilal", false,
+                0, 0);
+
+        FetchOptions fetchOptions = new FetchOptions.Builder().cacheControlHeaders(true).build();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).get(rootTarget, fetchOptions, null);
+        okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
+        requestBuilder.url(rootTarget.toString());
+        PowerMockito.doReturn(requestBuilder).when(okHttpClientImpl).getRequestBuilder();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).buildBasicHeaders();
+        Whitebox.setInternalState(okHttpClientImpl, "_metadata", metadata());
+        Whitebox.setInternalState(okHttpClientImpl, "_apikey", "qwerty");
+        Whitebox.setInternalState(okHttpClientImpl, "_decorator", requestDecorator);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).mergeHeaders(buildBasicHeaders(), null);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getResponseHeaders(any());
+        FetchOptions options = new FetchOptions.Builder().cacheControlHeaders(true).build();
+
+        SplitHttpResponse splitHttpResponse = okHttpClientImpl.get(rootTarget,
+                    new FetchOptions.Builder().cacheControlHeaders(true).build(), null);
+    }
+
+
+
+    @Test
+    public void testPost() throws IOException,  InterruptedException {
+        MockWebServer server = new MockWebServer();
+
+        server.enqueue(new MockResponse().addHeader("via", "HTTP/1.1 s_proxy_rio1"));
+        server.start();
+        HttpUrl baseUrl = server.url("/impressions");
+        URI rootTarget = baseUrl.uri();
+        RequestDecorator requestDecorator = new RequestDecorator(null);
+
+        OkHttpClientImpl okHttpClientImpl = mock(OkHttpClientImpl.class);
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        PowerMockito.doReturn(client).when(okHttpClientImpl).initializeClient(null, "bilal", false,
+                0, 0);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).setHttpClient(null, "bilal", false,
+                0, 0);
+        okHttpClientImpl.setHttpClient(null, "bilal", false,
+                0, 0);
+        Map<String, List<String>> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode",
+                Collections.singletonList("OPTIMIZED"));
+
+        okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
+        requestBuilder.url(rootTarget.toString());
+        PowerMockito.doReturn(requestBuilder).when(okHttpClientImpl).getRequestBuilder();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).buildBasicHeaders();
+        Whitebox.setInternalState(okHttpClientImpl, "_metadata", metadata());
+        Whitebox.setInternalState(okHttpClientImpl, "_apikey", "qwerty");
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).mergeHeaders(buildBasicHeaders(), additionalHeaders);
+        Whitebox.setInternalState(okHttpClientImpl, "_decorator", requestDecorator);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getResponseHeaders(any());
+        // Send impressions
+        List<TestImpressions> toSend = Arrays.asList(new TestImpressions("t1", Arrays.asList(
+                KeyImpression.fromImpression(new Impression("k1", null, "t1", "on", 123L, "r1", 456L, null)),
+                KeyImpression.fromImpression(new Impression("k2", null, "t1", "on", 123L, "r1", 456L, null)),
+                KeyImpression.fromImpression(new Impression("k3", null, "t1", "on", 123L, "r1", 456L, null)))),
+                new TestImpressions("t2", Arrays.asList(
+                        KeyImpression.fromImpression(new Impression("k1", null, "t2", "on", 123L, "r1", 456L, null)),
+                        KeyImpression.fromImpression(new Impression("k2", null, "t2", "on", 123L, "r1", 456L, null)),
+                        KeyImpression.fromImpression(new Impression("k3", null, "t2", "on", 123L, "r1", 456L, null)))));
+        String data = Json.toJson(toSend);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).post(rootTarget, data,
+                additionalHeaders);
+
+        SplitHttpResponse splitHttpResponse = okHttpClientImpl.post(rootTarget, data,
+                additionalHeaders);
+
+        RecordedRequest request = server.takeRequest();
+        server.shutdown();
+        Headers requestHeaders = request.getHeaders();
+
+        Assert.assertEquals("POST /impressions HTTP/1.1", request.getRequestLine());
+        Assert.assertEquals(data, request.getBody().readUtf8());
+        assertThat(requestHeaders.get("Authorization"), is(equalTo("Bearer qwerty"))) ;
+        assertThat(requestHeaders.get("SplitSDKClientKey"), is(equalTo("erty")));
+        assertThat(requestHeaders.get("SplitSDKVersion"), is(equalTo("java-1.2.3")));
+        assertThat(requestHeaders.get("SplitSDKMachineIP"), is(equalTo("1.2.3.4")));
+        assertThat(requestHeaders.get("SplitSDKMachineName"), is(equalTo("someIP")));
+        assertThat(requestHeaders.get("SplitSDKImpressionsMode"), is(equalTo("OPTIMIZED")));
+
+        Header[] headers = splitHttpResponse.responseHeaders();
+        assertThat(headers[1].getName(), is(equalTo("via")));
+        assertThat(headers[1].getValues().get(0), is(equalTo("HTTP/1.1 s_proxy_rio1")));
+        assertThat(splitHttpResponse.statusCode(), is(equalTo(200)));
+    }
+
+    @Test
+    public void testPostErrors() throws IOException, InterruptedException {
+        MockWebServer server = new MockWebServer();
+        server.enqueue(new MockResponse().setBody("").setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR));
+        server.start();
+        HttpUrl baseUrl = server.url("/v1/");
+        URI rootTarget = baseUrl.uri();
+        RequestDecorator requestDecorator = new RequestDecorator(null);
+
+        OkHttpClientImpl okHttpClientImpl = mock(OkHttpClientImpl.class);
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        PowerMockito.doReturn(client).when(okHttpClientImpl).initializeClient(null, "bilal", false,
+                0, 0);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).setHttpClient(null, "bilal", false,
+                0, 0);
+        okHttpClientImpl.setHttpClient(null, "bilal", false,
+                0, 0);
+        Map<String, List<String>> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode",
+                Collections.singletonList("OPTIMIZED"));
+
+        okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
+        requestBuilder.url(rootTarget.toString());
+        PowerMockito.doReturn(requestBuilder).when(okHttpClientImpl).getRequestBuilder();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).buildBasicHeaders();
+        Whitebox.setInternalState(okHttpClientImpl, "_metadata", metadata());
+        Whitebox.setInternalState(okHttpClientImpl, "_apikey", "qwerty");
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).mergeHeaders(buildBasicHeaders(), additionalHeaders);
+        Whitebox.setInternalState(okHttpClientImpl, "_decorator", requestDecorator);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getResponseHeaders(any());
+
+        String data = Json.toJson("<>");
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).post(rootTarget, data,
+                additionalHeaders);
+
+        SplitHttpResponse splitHttpResponse = okHttpClientImpl.post(rootTarget, data,
+                additionalHeaders);
+
+        RecordedRequest request = server.takeRequest();
+        server.shutdown();
+        assertThat(splitHttpResponse.statusCode(), is(equalTo(HttpURLConnection.HTTP_INTERNAL_ERROR)));
+        okHttpClientImpl.close();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testPosttException() throws URISyntaxException, IOException {
+        URI rootTarget = new URI("https://kubernetesturl.com/split/api/testImpressions/bulk");
+
+        OkHttpClientImpl okHttpClientImpl = mock(OkHttpClientImpl.class);
+        OkHttpClient client = new OkHttpClient.Builder().build();
+        PowerMockito.doReturn(client).when(okHttpClientImpl).initializeClient(null, "bilal", false,
+                0, 0);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).setHttpClient(null, "bilal", false,
+                0, 0);
+        okHttpClientImpl.setHttpClient(null, "bilal", false,
+                0, 0);
+        Map<String, List<String>> additionalHeaders = Collections.singletonMap("SplitSDKImpressionsMode",
+                Collections.singletonList("OPTIMIZED"));
+
+        okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder();
+        RequestDecorator requestDecorator = new RequestDecorator(null);
+        requestBuilder.url(rootTarget.toString());
+        PowerMockito.doReturn(requestBuilder).when(okHttpClientImpl).getRequestBuilder();
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).buildBasicHeaders();
+        Whitebox.setInternalState(okHttpClientImpl, "_metadata", metadata());
+        Whitebox.setInternalState(okHttpClientImpl, "_apikey", "qwerty");
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).mergeHeaders(buildBasicHeaders(), additionalHeaders);
+        Whitebox.setInternalState(okHttpClientImpl, "_decorator", requestDecorator);
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).getResponseHeaders(any());
+
+        String data = Json.toJson("<>");
+        PowerMockito.doCallRealMethod().when(okHttpClientImpl).post(rootTarget, data,
+                additionalHeaders);
+
+        SplitHttpResponse splitHttpResponse = okHttpClientImpl.post(rootTarget, data,
+                additionalHeaders);
+    }
+
+    private SDKMetadata metadata() {
+        return new SDKMetadata("java-1.2.3", "1.2.3.4", "someIP");
+    }
+
+    private Map<String, List<String>> buildBasicHeaders() {
+        Map<String, List<String>> h = new HashMap<>();
+        h.put("Authorization", Collections.singletonList("Bearer qwerty"));
+        h.put("SplitSDKVersion", Collections.singletonList(metadata().getSdkVersion()));
+        h.put("SplitSDKMachineIP", Collections.singletonList(metadata().getMachineIp()));
+        h.put("SplitSDKMachineName", Collections.singletonList(metadata().getMachineName()));
+        h.put("SplitSDKClientKey", Collections.singletonList("qwerty".length() > 4
+                ? "qwerty".substring("qwerty".length() - 4)
+                : "qwerty"));
+        return h;
+    }
+
+}
diff --git a/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/OkHttpModuleTests.java b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/OkHttpModuleTests.java
new file mode 100644
index 000000000..d8c5b5242
--- /dev/null
+++ b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/OkHttpModuleTests.java
@@ -0,0 +1,111 @@
+package io.split.httpmodules.okhttp;
+
+import io.split.client.RequestDecorator;
+import io.split.client.utils.SDKMetadata;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.stubbing.Answer;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.mockito.Mockito.verify;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(OkHttpModule.class)
+public class OkHttpModuleTests {
+    @Test
+    public void checkProxySettings() {
+        OkHttpModule module = OkHttpModule.builder()
+                        .proxyAuthScheme(ProxyAuthScheme.KERBEROS)
+                        .proxyAuthKerberosPrincipalName("bilal@bilal")
+                        .proxyHost("some-proxy")
+                        .proxyPort(3128)
+                        .build();
+        Assert.assertEquals(ProxyAuthScheme.KERBEROS, module.proxyAuthScheme());
+        Assert.assertEquals("bilal@bilal", module.proxyKerberosPrincipalName());
+        Assert.assertEquals("HTTP @ some-proxy:3128", module.proxy().toString());
+    }
+
+    @Test
+    public void checkDebugLog() {
+        OkHttpModule module = OkHttpModule.builder()
+                .debugEnabled()
+                .build();
+        Assert.assertEquals(true, module.debugEnabled());
+
+        module = OkHttpModule.builder()
+                .build();
+        Assert.assertEquals(false, module.debugEnabled());
+    }
+
+    @Test
+    public void checkTimeouts() {
+        OkHttpModule module = OkHttpModule.builder()
+                .build();
+        Assert.assertEquals(15000, (int) module.connectionTimeout());
+        Assert.assertEquals(15000, (int) module.readTimeout());
+
+        module = OkHttpModule.builder()
+                .connectionTimeout(13000)
+                .readTimeout(14000)
+                .build();
+        Assert.assertEquals(13000, (int) module.connectionTimeout());
+        Assert.assertEquals(14000, (int) module.readTimeout());
+
+        module = OkHttpModule.builder()
+                .connectionTimeout(-1)
+                .readTimeout(-10)
+                .build();
+        Assert.assertEquals(15000, (int) module.connectionTimeout());
+        Assert.assertEquals(15000, (int) module.readTimeout());
+    }
+
+    @Test
+    public void testCreateClient() throws Exception {
+        OkHttpClientImpl mockclient = mock(OkHttpClientImpl.class);
+        AtomicBoolean argsCaptured = new AtomicBoolean(false);
+
+        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("some-proxy", 3128));
+        String apiToken = "qwerty";
+        SDKMetadata sdkMetadata = new SDKMetadata("1.1.1", "ip", "name");
+        RequestDecorator requestDecorator = new RequestDecorator(null);
+
+        whenNew(OkHttpClientImpl.class).withAnyArguments()
+                .then((Answer<OkHttpClientImpl>) invocationOnMock -> {
+                        assertThat("qwerty", is(equalTo((String) invocationOnMock.getArguments()[0])));
+                        assertThat(sdkMetadata, is(equalTo((SDKMetadata) invocationOnMock.getArguments()[1])));
+                        assertThat(proxy, is(equalTo((Proxy) invocationOnMock.getArguments()[2])));
+                        assertThat("bilal@bilal", is(equalTo((String) invocationOnMock.getArguments()[3])));
+                        assertThat(false, is(equalTo((Boolean) invocationOnMock.getArguments()[4])));
+                        assertThat(11000, is(equalTo((Integer) invocationOnMock.getArguments()[5])));
+                        assertThat(12000, is(equalTo((Integer) invocationOnMock.getArguments()[6])));
+                        assertThat(requestDecorator, is(equalTo((RequestDecorator) invocationOnMock.getArguments()[7])));
+                        argsCaptured.set(true);
+                        return mockclient;
+                    }
+                );
+
+        OkHttpModule module = OkHttpModule.builder()
+                .proxyAuthScheme(ProxyAuthScheme.KERBEROS)
+                .proxyAuthKerberosPrincipalName("bilal@bilal")
+                .proxyHost("some-proxy")
+                .proxyPort(3128)
+                .connectionTimeout(12000)
+                .readTimeout(11000)
+                .build();
+
+        module.createClient(apiToken, sdkMetadata, requestDecorator);
+        assertThat(true, is(equalTo(argsCaptured.get())));
+    }
+}
diff --git a/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/SplitConfigTests.java b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/SplitConfigTests.java
new file mode 100644
index 000000000..20feddb38
--- /dev/null
+++ b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/SplitConfigTests.java
@@ -0,0 +1,45 @@
+package io.split.httpmodules.okhttp;
+
+import io.split.client.SplitClientConfig;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SplitConfigTests {
+
+    @Test
+    public void checkExpectedAuthScheme() {
+        SplitClientConfig cfg = SplitClientConfig.builder()
+                .alternativeHTTPModule(OkHttpModule.builder()
+                        .proxyAuthScheme(ProxyAuthScheme.KERBEROS)
+                        .proxyAuthKerberosPrincipalName("bilal@bilal")
+                        .proxyHost("some-proxy")
+                        .proxyPort(3128)
+                        .debugEnabled()
+                        .build()
+                )
+                .streamingEnabled(false)
+                .build();
+        OkHttpModule module =  (OkHttpModule) cfg.alternativeHTTPModule();
+        Assert.assertEquals(ProxyAuthScheme.KERBEROS, module.proxyAuthScheme());
+        Assert.assertEquals("bilal@bilal", module.proxyKerberosPrincipalName());
+        Assert.assertEquals("HTTP @ some-proxy:3128", module.proxy().toString());
+
+        cfg = SplitClientConfig.builder()
+                .build();
+        Assert.assertEquals(null, cfg.alternativeHTTPModule());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void checkStreamingEnabled() {
+        SplitClientConfig cfg = SplitClientConfig.builder()
+                .alternativeHTTPModule(OkHttpModule.builder()
+                        .proxyAuthScheme(ProxyAuthScheme.KERBEROS)
+                        .proxyAuthKerberosPrincipalName("bilal@bilal")
+                        .proxyHost("some-proxy")
+                        .proxyPort(3128)
+                        .debugEnabled()
+                        .build())
+                .streamingEnabled(true)
+                .build();
+    }
+}
diff --git a/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/SplitFactoryTests.java b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/SplitFactoryTests.java
new file mode 100644
index 000000000..23cf3cb53
--- /dev/null
+++ b/okhttp-modules/src/test/java/io/split/httpmodules/okhttp/SplitFactoryTests.java
@@ -0,0 +1,68 @@
+package io.split.httpmodules.okhttp;
+
+import io.split.client.*;
+import io.split.client.utils.SDKMetadata;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.stubbing.Answer;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(OkHttpModule.class)
+public class SplitFactoryTests {
+    @Test
+    public void testFactoryCreatingClient() throws Exception {
+        OkHttpClientImpl mockclient = mock(OkHttpClientImpl.class);
+        AtomicBoolean argsCaptured = new AtomicBoolean(false);
+
+        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("some-proxy", 3128));
+        String apiToken = "qwerty";
+
+        whenNew(OkHttpClientImpl.class).withAnyArguments()
+                .then((Answer<OkHttpClientImpl>) invocationOnMock -> {
+                            assertThat("qwerty", is(equalTo((String) invocationOnMock.getArguments()[0])));
+                            assertThat((SDKMetadata) invocationOnMock.getArguments()[1], instanceOf(SDKMetadata.class));
+                            assertThat(proxy, is(equalTo((Proxy) invocationOnMock.getArguments()[2])));
+                            assertThat("bilal@bilal", is(equalTo((String) invocationOnMock.getArguments()[3])));
+                            assertThat(false, is(equalTo((Boolean) invocationOnMock.getArguments()[4])));
+                            assertThat(11000, is(equalTo((Integer) invocationOnMock.getArguments()[5])));
+                            assertThat(12000, is(equalTo((Integer) invocationOnMock.getArguments()[6])));
+                            assertThat((RequestDecorator) invocationOnMock.getArguments()[7], instanceOf(RequestDecorator.class));
+                            argsCaptured.set(true);
+                            return mockclient;
+                        }
+                );
+
+        OkHttpModule module = OkHttpModule.builder()
+                .proxyAuthScheme(ProxyAuthScheme.KERBEROS)
+                .proxyAuthKerberosPrincipalName("bilal@bilal")
+                .proxyHost("some-proxy")
+                .proxyPort(3128)
+                .connectionTimeout(12000)
+                .readTimeout(11000)
+                .build();
+
+        SplitClientConfig cfg = SplitClientConfig.builder()
+                .alternativeHTTPModule(module)
+                .streamingEnabled(false)
+                .build();
+
+        SplitFactoryImpl factory = (SplitFactoryImpl) SplitFactoryBuilder.build(apiToken, cfg);
+
+//        module.createClient(apiToken, sdkMetadata, requestDecorator);
+        assertThat(true, is(equalTo(argsCaptured.get())));
+    }
+}
diff --git a/okhttp-modules/src/test/resources/krb5.conf b/okhttp-modules/src/test/resources/krb5.conf
new file mode 100644
index 000000000..78d63ba8f
--- /dev/null
+++ b/okhttp-modules/src/test/resources/krb5.conf
@@ -0,0 +1,37 @@
+#
+# 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.
+#
+
+[libdefaults]
+    kdc_realm = ATHENA.MIT.EDU
+    default_realm = ATHENA.MIT.EDU
+    kdc_tcp_port = 88
+    kdc_udp_port = 88
+    dns_lookup_realm = false
+    dns_lookup_kdc = false
+    udp_preference_limit = 1
+
+[logging]
+  default = FILE:/var/logs/krb5kdc.log
+
+[realms]
+    ATHENA.MIT.EDU = {
+#        kdc = 10.12.4.76:88
+#        kdc = tcp/10.12.4.76:88
+#        kdc = tcp/192.168.1.19:88
+        kdc = 192.168.1.19:88
+    }
\ No newline at end of file
diff --git a/okhttp-modules/src/test/resources/org/powermock/extensions/configuration.properties b/okhttp-modules/src/test/resources/org/powermock/extensions/configuration.properties
new file mode 100644
index 000000000..a8ebaeba3
--- /dev/null
+++ b/okhttp-modules/src/test/resources/org/powermock/extensions/configuration.properties
@@ -0,0 +1 @@
+powermock.global-ignore=jdk.internal.reflect.*,javax.net.ssl.*
\ No newline at end of file
diff --git a/okhttp-modules/src/test/resources/split-change-special-characters.json b/okhttp-modules/src/test/resources/split-change-special-characters.json
new file mode 100644
index 000000000..9fd55904e
--- /dev/null
+++ b/okhttp-modules/src/test/resources/split-change-special-characters.json
@@ -0,0 +1,56 @@
+{
+  "splits": [
+    {
+      "trafficTypeName": "user",
+      "name": "DEMO_MURMUR2",
+      "trafficAllocation": 100,
+      "trafficAllocationSeed": 1314112417,
+      "seed": -2059033614,
+      "status": "ACTIVE",
+      "killed": false,
+      "defaultTreatment": "of",
+      "changeNumber": 1491244291288,
+      "sets": [ "set1", "set2" ],
+      "algo": 2,
+      "configurations": {
+        "on": "{\"test\": \"blue\",\"grüne Straße\": 13}",
+        "off": "{\"test\": \"blue\",\"size\": 15}"
+      },
+      "conditions": [
+        {
+          "conditionType": "ROLLOUT",
+          "matcherGroup": {
+            "combiner": "AND",
+            "matchers": [
+              {
+                "keySelector": {
+                  "trafficType": "user",
+                  "attribute": null
+                },
+                "matcherType": "ALL_KEYS",
+                "negate": false,
+                "userDefinedSegmentMatcherData": null,
+                "whitelistMatcherData": null,
+                "unaryNumericMatcherData": null,
+                "betweenMatcherData": null
+              }
+            ]
+          },
+          "partitions": [
+            {
+              "treatment": "on",
+              "size": 0
+            },
+            {
+              "treatment": "of",
+              "size": 100
+            }
+          ],
+          "label": "in segment all"
+        }
+      ]
+    }
+  ],
+  "since": 1491244291288,
+  "till": 1491244291288
+}
diff --git a/pluggable-storage/pom.xml b/pluggable-storage/pom.xml
index f643564f9..2e502e35c 100644
--- a/pluggable-storage/pom.xml
+++ b/pluggable-storage/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>java-client-parent</artifactId>
         <groupId>io.split.client</groupId>
-        <version>4.12.1</version>
+        <version>4.13.0</version>
     </parent>
 
     <version>2.1.0</version>
@@ -29,7 +29,7 @@
                             <serverId>ossrh</serverId>
                             <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                             <autoReleaseAfterClose>true</autoReleaseAfterClose>
-                            <skipNexusStagingDeployMojo>false</skipNexusStagingDeployMojo>
+                            <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
                         </configuration>
                     </plugin>
                 </plugins>
diff --git a/pom.xml b/pom.xml
index 3dc7d33f9..a7c51ff30 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>io.split.client</groupId>
     <artifactId>java-client-parent</artifactId>
-    <version>4.12.1</version>
+    <version>4.13.0</version>
     <dependencyManagement>
         <dependencies>
             <dependency>
@@ -81,10 +81,11 @@
         <maven.compiler.target>1.8</maven.compiler.target>
     </properties>
     <modules>
-        <module>testing</module>
-        <module>client</module>
         <module>pluggable-storage</module>
         <module>redis-wrapper</module>
+        <module>testing</module>
+        <module>okhttp-modules</module>
+        <module>client</module>
     </modules>
     <build>
         <plugins>
diff --git a/redis-wrapper/pom.xml b/redis-wrapper/pom.xml
index a8ce195f5..6a25062ed 100644
--- a/redis-wrapper/pom.xml
+++ b/redis-wrapper/pom.xml
@@ -6,7 +6,7 @@
     <parent>
         <artifactId>java-client-parent</artifactId>
         <groupId>io.split.client</groupId>
-        <version>4.12.1</version>
+        <version>4.13.0</version>
     </parent>
     <artifactId>redis-wrapper</artifactId>
     <version>3.1.0</version>
@@ -51,7 +51,7 @@
                             <serverId>ossrh</serverId>
                             <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                             <autoReleaseAfterClose>true</autoReleaseAfterClose>
-                            <skipNexusStagingDeployMojo>false</skipNexusStagingDeployMojo>
+                            <skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo>
                         </configuration>
                     </plugin>
                 </plugins>
diff --git a/testing/pom.xml b/testing/pom.xml
index 2240c94db..adbffc998 100644
--- a/testing/pom.xml
+++ b/testing/pom.xml
@@ -5,7 +5,7 @@
     <parent>
         <groupId>io.split.client</groupId>
         <artifactId>java-client-parent</artifactId>
-        <version>4.12.1</version>
+        <version>4.13.0</version>
     </parent>
     <artifactId>java-client-testing</artifactId>
     <packaging>jar</packaging>