diff --git a/common/.gitignore b/common/.gitignore
new file mode 100644
index 0000000000000..bfb7f2e650b1d
--- /dev/null
+++ b/common/.gitignore
@@ -0,0 +1,31 @@
+build
+*.class
+target
+gen
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+tmp
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.MF
+.gitrevision
+.gradle
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# ide
+.idea
+*.iml
+.DS_Store
+**/temp/
+
+#eclipse
+.project
+.classpath
+.settings
+
diff --git a/common/.travis.yml b/common/.travis.yml
new file mode 100644
index 0000000000000..10c5ba5405405
--- /dev/null
+++ b/common/.travis.yml
@@ -0,0 +1,17 @@
+language: android
+android:
+  components:
+  - build-tools-23.0.1
+  - android-23
+  - platform-tools
+  - extra-android-support
+  - extra-google-m2repository
+  - extra-android-m2repository
+sudo: false
+env:
+- TERM=dumb # Makes Gradle use plain console output
+script:
+- mvn clean install -B -Dstyle.color=always
+- mvn checkstyle:check -B -Dstyle.color=always
+# (ignore android build for now)
+# - cd ./azure-android-client-authentication && ./gradlew check
diff --git a/common/ChangeLog.txt b/common/ChangeLog.txt
new file mode 100644
index 0000000000000..bafac90a6cb73
--- /dev/null
+++ b/common/ChangeLog.txt
@@ -0,0 +1,15 @@
+2.0.0-beta4 (2018-08-06)
+- Added HttpRequest request() property to RestResponse
+- Added isProxyHTTPS() property to HttpClientConfiguration
+
+2.0.0-beta3 (2018-06-26)
+- Added FlowableUtil.ensureLength() operator to better handle cases where the request body had an unexpected size
+
+2.0.0-beta2 (2018-04-23)
+- Major refinements to HTTP content streaming, in large part thanks to contributions by [David Moten](https://github.com/davidmoten).
+- Removed Joda Time in favor of Java 8 DateTime classes
+- NettyClient.Factory now accepts a Netty Bootstrap object allowing for more user configuration of channel attributes, such as the receive buffer size and low/high write watermarks. Currently, specifying an EventLoopGroup or `Class<? extends Channel>` is not supported.
+- Various other minor improvements
+
+2.0.0-beta1 (2018-03-08)
+- First beta featuring Netty and RxJava 2.
diff --git a/common/LICENSE b/common/LICENSE
new file mode 100644
index 0000000000000..4918d653ba68b
--- /dev/null
+++ b/common/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Microsoft Azure
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/common/README.md b/common/README.md
new file mode 100644
index 0000000000000..ce52d05ef7290
--- /dev/null
+++ b/common/README.md
@@ -0,0 +1,124 @@
+[![Build Status](https://travis-ci.org/Azure/autorest-clientruntime-for-java.svg?branch=v2)](https://travis-ci.org/Azure/autorest-clientruntime-for-java)
+
+# AutoRest Client Runtimes for Java
+The runtime libraries for [AutoRest](https://github.com/azure/autorest) generated Java clients. 
+
+## Usage
+
+### Prerequisites
+
+- JDK 1.8
+
+### Download
+
+```xml
+<dependencies>
+    <!-- For generic, non-Azure Resource Management users --> 
+    <dependency>
+      <groupId>com.microsoft.rest.v3</groupId>
+      <artifactId>client-runtime</artifactId>
+      <version>2.0.0-beta4</version>
+    </dependency>
+    
+    <!-- For Azure Resource Management users -->
+    <dependency>
+      <groupId>com.microsoft.azure.v3</groupId>
+      <artifactId>azure-client-runtime</artifactId>
+      <version>2.0.0-beta4</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>com.microsoft.azure.v3</groupId>
+      <artifactId>azure-client-authentication</artifactId>
+      <version>2.0.0-beta4</version>
+    </dependency>
+    
+    <!-- Below are optional high-performance native dependencies  -->
+
+    <!-- Available on Windows/Mac/Linux x86_64 -->
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-tcnative-boringssl-static</artifactId>
+      <version>2.0.8.Final</version>
+      <classifier>${os.detected.classifier}</classifier>
+    </dependency>
+
+    <!-- Only available on Linux -->
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-transport-native-epoll</artifactId>
+      <version>4.1.23.Final</version>
+      <classifier>${os.detected.classifier}</classifier>
+    </dependency>
+
+    <!-- Only available on macOS/BSD -->
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-transport-native-kqueue</artifactId>
+      <version>4.1.23.Final</version>
+      <classifier>${os.detected.classifier}</classifier>
+    </dependency>
+</dependencies>
+
+<!-- Allows automatic detection of OS for native modules -->
+<build>
+  <extensions>
+    <extension>
+      <groupId>kr.motd.maven</groupId>
+      <artifactId>os-maven-plugin</artifactId>
+      <version>1.6.0</version>
+    </extension>
+  </extensions>
+</build>
+```
+
+### Usage
+
+Non-Azure generated clients will have a constructor that takes no arguments for simple scenarios, while Azure generated clients will require a `ServiceClientCredentials` argument at a minimum.
+
+If you want to have more control over configuration, consider using HttpPipeline. This enables performing transformations on all HTTP messages sent by a client, similar to interceptors or filters in other HTTP clients.
+
+You can build an HttpPipeline out of a sequence of RequestPolicyFactories. These policies will get applied in-order to outgoing requests, and then in reverse order for incoming responses. HttpPipelineBuilder includes convenience methods for adding several built-in RequestPolicyFactories, including policies for credentials, logging, response decoding (deserialization), cookies support, and several others.
+
+```java
+// For Java generator
+HttpPipeline pipeline = new HttpPipelineBuilder()
+    .withHostPolicy("http://localhost")
+    .withDecodingPolicy()
+    .build();
+AutoRestJavaClient client = new AutoRestJavaClientImpl(pipeline);
+
+// For Azure.Java generator
+HttpPipeline azurePipeline = new HttpPipelineBuilder()
+    .withCredentialsPolicy(AzureCliCredentials.create())
+    .withHttpLoggingPolicy(HttpLogDetailLevel.HEADERS)
+    .withDecodingPolicy()
+    .build();
+FooServiceClient azureClient = new FooServiceClientImpl(azurePipeline);
+```
+
+## Components
+
+### client-runtime
+This is the generic runtime. Add this package as a dependency if you are using `Java` generator in AutoRest. This package depends on [Netty](https://github.com/netty/netty), [Jackson](http://wiki.fasterxml.com/JacksonHome), and [RxJava](https://github.com/ReactiveX/RxJava) for making and processing REST requests.
+
+### azure-client-runtime
+This is the runtime with Azure Resource Management customizations. Add this package as a dependency if you are using `--azure-arm` or `--azure-arm --fluent` generator flags in AutoRest.
+
+This combination provides a set of Azure specific behaviors, including long running operations, special handling of HEAD operations, and paginated `list()` calls.
+
+### azure-client-authentication (beta)
+This package provides access to Active Directory authentication on JDK using OrgId or application ID / secret combinations. There are currently 3 types of authentication provided:
+
+- Service principal authentication: `ApplicationTokenCredentials`
+- Username / password login without multi-factor auth: `UserTokenCredentials`
+- Use the credentials logged in [Azure CLI](https://github.com/azure/azure-cli): `AzureCliCredentials`
+
+### azure-android-client-authentication (beta)
+This package provides access to Active Directory authentication on Android. You can login with Microsoft accounts, OrgId, with or without multi-factor auth.
+
+## Build
+To build this repository, you will need maven 2.0+ and gradle 1.6+.
+
+## Contributing
+This repository is for runtime & authentication specifically. For issues in the generated code, please report in [AutoRest](https://github.com/Azure/autorest). For bugs in the Azure SDK, please report in [Azure SDK for Java](https://github.com/Azure/azure-sdk-for-java). If you are unsure, please file here and state that clearly in the issue. Pull requests are welcomed with clear Javadocs.
diff --git a/common/azure-android-client-authentication/build.gradle b/common/azure-android-client-authentication/build.gradle
new file mode 100644
index 0000000000000..b93e53c502be0
--- /dev/null
+++ b/common/azure-android-client-authentication/build.gradle
@@ -0,0 +1,117 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.3.0'
+    }
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'maven'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "23.0.1"
+
+    defaultConfig {
+        minSdkVersion 15
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0.0-beta6-SNAPSHOT"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+}
+
+configurations {
+    deployerJars
+}
+
+repositories {
+    mavenCentral()
+    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    compile 'com.android.support:appcompat-v7:23.0.1'
+    compile 'com.microsoft.aad:adal:1.1.11'
+    compile 'com.microsoft.azure:azure-client-runtime:1.0.0-beta2'
+    testCompile 'junit:junit:4.12'
+    testCompile 'junit:junit-dep:4.11'
+    deployerJars "org.apache.maven.wagon:wagon-ftp:2.10"
+}
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            configuration = configurations.deployerJars
+            snapshotRepository(url: "ftp://waws-prod-bay-005.ftp.azurewebsites.windows.net/site/wwwroot/") {
+                authentication(userName: username, password: password)
+            }
+            repository(url: "file://$buildDir/repository")
+            pom.setArtifactId "azure-android-client-authentication"
+            pom.project {
+                name 'Microsoft Azure AutoRest Authentication Library for Java'
+                description 'This is the authentication library for AutoRest generated Azure Java clients.'
+                url 'https://github.com/Azure/autorest'
+
+                scm {
+                    url 'scm:git:https://github.com/Azure/AutoRest'
+                    connection 'scm:git:git://github.com/Azure/AutoRest.git'
+                }
+
+                licenses {
+                    license {
+                        name 'The MIT License (MIT)'
+                        url 'http://opensource.org/licenses/MIT'
+                        distribution 'repo'
+                    }
+                }
+
+                developers {
+                    developer {
+                        id 'microsoft'
+                        name 'Microsoft'
+                    }
+                }
+            }
+        }
+    }
+}
+
+task sourcesJar(type: Jar) {
+    from android.sourceSets.main.java.srcDirs
+    classifier = 'sources'
+}
+
+task javadoc(type: Javadoc) {
+    source = android.sourceSets.main.java.srcDirs
+    classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
+    options.encoding = 'UTF-8'
+}
+
+task javadocJar(type: Jar, dependsOn: [javadoc]) {
+    classifier = 'javadoc'
+    from javadoc.destinationDir
+}
+
+artifacts {
+    archives sourcesJar
+    archives javadocJar
+}
diff --git a/common/azure-android-client-authentication/gradle.properties b/common/azure-android-client-authentication/gradle.properties
new file mode 100644
index 0000000000000..7311d1b56e3bb
--- /dev/null
+++ b/common/azure-android-client-authentication/gradle.properties
@@ -0,0 +1,2 @@
+username = fake
+password = fake
\ No newline at end of file
diff --git a/common/azure-android-client-authentication/gradle/wrapper/gradle-wrapper.jar b/common/azure-android-client-authentication/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000..05ef575b0cd01
Binary files /dev/null and b/common/azure-android-client-authentication/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/common/azure-android-client-authentication/gradle/wrapper/gradle-wrapper.properties b/common/azure-android-client-authentication/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000..3ae0abbd2b66d
--- /dev/null
+++ b/common/azure-android-client-authentication/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Nov 11 13:21:00 PST 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-bin.zip
diff --git a/common/azure-android-client-authentication/gradlew b/common/azure-android-client-authentication/gradlew
new file mode 100755
index 0000000000000..9d82f78915133
--- /dev/null
+++ b/common/azure-android-client-authentication/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/common/azure-android-client-authentication/gradlew.bat b/common/azure-android-client-authentication/gradlew.bat
new file mode 100644
index 0000000000000..f4c57b05d5739
--- /dev/null
+++ b/common/azure-android-client-authentication/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM pipelineOptions here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM pipelineOptions to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/common/azure-android-client-authentication/proguard-rules.pro b/common/azure-android-client-authentication/proguard-rules.pro
new file mode 100644
index 0000000000000..51508f487ebc5
--- /dev/null
+++ b/common/azure-android-client-authentication/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in E:\Users\jianghlu\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/common/azure-android-client-authentication/src/main/AndroidManifest.xml b/common/azure-android-client-authentication/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000000..281c2ce4932ba
--- /dev/null
+++ b/common/azure-android-client-authentication/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.microsoft.rest.credentials">
+
+    <application android:allowBackup="true" android:label="@string/app_name">
+
+    </application>
+
+</manifest>
diff --git a/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/AzureEnvironment.java b/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/AzureEnvironment.java
new file mode 100644
index 0000000000000..ddb02625b0399
--- /dev/null
+++ b/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/AzureEnvironment.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.microsoft.azure.credentials;
+
+/**
+ * An instance of this class describes an environment in Azure.
+ */
+public final class AzureEnvironment {
+    /**
+     * ActiveDirectory Endpoint for the Azure Environment.
+     */
+    private String authenticationEndpoint;
+    /**
+     * Token audience for an endpoint.
+     */
+    private String tokenAudience;
+    /**
+     * Determines whether the authentication endpoint should
+     * be validated with Azure AD. Default value is true.
+     */
+    private boolean validateAuthority;
+
+    /**
+     * Initializes an instance of AzureEnvironment class.
+     *
+     * @param authenticationEndpoint ActiveDirectory Endpoint for the Azure Environment.
+     * @param tokenAudience token audience for an endpoint.
+     * @param validateAuthority whether the authentication endpoint should
+     *                          be validated with Azure AD.
+     */
+    public AzureEnvironment(String authenticationEndpoint, String tokenAudience, boolean validateAuthority) {
+        this.authenticationEndpoint = authenticationEndpoint;
+        this.tokenAudience = tokenAudience;
+        this.validateAuthority = validateAuthority;
+    }
+
+    /**
+     * Provides the settings for authentication with Azure.
+     */
+    public static final AzureEnvironment AZURE = new AzureEnvironment(
+            "https://login.windows.net/",
+            "https://management.core.windows.net/",
+            true);
+
+    /**
+     * Provides the settings for authentication with Azure China.
+     */
+    public static final AzureEnvironment AZURE_CHINA = new AzureEnvironment(
+            "https://login.chinacloudapi.cn/",
+            "https://management.core.chinacloudapi.cn/",
+            true);
+
+    /**
+     * Gets the ActiveDirectory Endpoint for the Azure Environment.
+     *
+     * @return the ActiveDirectory Endpoint for the Azure Environment.
+     */
+    public String getAuthenticationEndpoint() {
+        return authenticationEndpoint;
+    }
+
+    /**
+     * Gets the token audience for an endpoint.
+     *
+     * @return the token audience for an endpoint.
+     */
+    public String getTokenAudience() {
+        return tokenAudience;
+    }
+
+    /**
+     * Gets whether the authentication endpoint should
+     * be validated with Azure AD.
+     *
+     * @return true if the authentication endpoint should be validated with
+     * Azure AD, false otherwise.
+     */
+    public boolean isValidateAuthority() {
+        return validateAuthority;
+    }
+}
diff --git a/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java b/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java
new file mode 100644
index 0000000000000..95d0c14f12f54
--- /dev/null
+++ b/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/UserTokenCredentials.java
@@ -0,0 +1,189 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.microsoft.azure.credentials;
+
+import android.app.Activity;
+
+import com.microsoft.aad.adal.AuthenticationCallback;
+import com.microsoft.aad.adal.AuthenticationContext;
+import com.microsoft.aad.adal.AuthenticationResult;
+import com.microsoft.aad.adal.DefaultTokenCacheStore;
+import com.microsoft.aad.adal.PromptBehavior;
+import com.microsoft.rest.credentials.TokenCredentials;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.CountDownLatch;
+
+import javax.crypto.NoSuchPaddingException;
+
+/**
+ * Token based credentials for use with a REST Service Client.
+ */
+public class UserTokenCredentials extends TokenCredentials {
+    /** The Active Directory application client id. */
+    private String clientId;
+    /** The domain or tenant id containing this application. */
+    private String domain;
+    /** The Uri where the user will be redirected after authenticating with AD. */
+    private String clientRedirectUri;
+    /** The Azure environment to authenticate with. */
+    private AzureEnvironment environment;
+    /** The caller activity. */
+    private Activity activity;
+    /** The count down latch to synchronize token acquisition. */
+    private CountDownLatch signal = new CountDownLatch(1);
+    /** The static token cache. */
+    private static DefaultTokenCacheStore tokenCacheStore;
+    /** The behavior of when to prompt a login. */
+    private PromptBehavior promptBehavior;
+
+    /**
+     * Initializes a new instance of the UserTokenCredentials.
+     *
+     * @param activity The caller activity.
+     * @param clientId the active directory application client id.
+     * @param domain the domain or tenant id containing this application.
+     * @param clientRedirectUri the Uri where the user will be redirected after authenticating with AD.
+     */
+    public UserTokenCredentials(
+            Activity activity,
+            String clientId,
+            String domain,
+            String clientRedirectUri) {
+        this(activity, clientId, domain, clientRedirectUri, PromptBehavior.Auto, AzureEnvironment.AZURE);
+    }
+
+    /**
+     * Initializes a new instance of the UserTokenCredentials.
+     *
+     * @param activity The caller activity.
+     * @param clientId the active directory application client id.
+     * @param domain the domain or tenant id containing this application.
+     * @param clientRedirectUri the Uri where the user will be redirected after authenticating with AD.
+     * @param promptBehavior the behavior of when to prompt a login.
+     * @param environment the Azure environment to authenticate with.
+     *                    If null is provided, AzureEnvironment.AZURE will be used.
+     */
+    public UserTokenCredentials(
+            Activity activity,
+            String clientId,
+            String domain,
+            String clientRedirectUri,
+            PromptBehavior promptBehavior,
+            AzureEnvironment environment) {
+        super(null, null); // defer token acquisition
+        this.clientId = clientId;
+        this.domain = domain;
+        this.clientRedirectUri = clientRedirectUri;
+        this.activity = activity;
+        this.promptBehavior = promptBehavior;
+        this.environment = environment;
+        if (tokenCacheStore == null) {
+            try {
+                tokenCacheStore = new DefaultTokenCacheStore(activity);
+            } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+                tokenCacheStore = null;
+            }
+        }
+    }
+
+    /**
+     * Clear the items stored in token cache.
+     */
+    public static void clearTokenCache() {
+        tokenCacheStore.removeAll();
+    }
+
+    /**
+     * Gets the active directory application client id.
+     *
+     * @return the active directory application client id.
+     */
+    public String getClientId() {
+        return clientId;
+    }
+
+    /**
+     * Gets the tenant or domain containing the application.
+     *
+     * @return the tenant or domain containing the application.
+     */
+    public String getDomain() {
+        return domain;
+    }
+
+    /**
+     * Sets the tenant of domain containing the application.
+     *
+     * @param domain the tenant or domain containing the application.
+     */
+    public void setDomain(String domain) {
+        this.domain = domain;
+    }
+
+    /**
+     * Gets the Uri where the user will be redirected after authenticating with AD.
+     *
+     * @return the redirecting Uri.
+     */
+    public String getClientRedirectUri() {
+        return clientRedirectUri;
+    }
+
+    /**
+     * Gets the Azure environment to authenticate with.
+     *
+     * @return the Azure environment to authenticate with.
+     */
+    public AzureEnvironment getEnvironment() {
+        return environment;
+    }
+
+    @Override
+    public String getToken() throws IOException {
+        refreshToken();
+        return token;
+    }
+
+    @Override
+    public void refreshToken() throws IOException {
+        acquireAccessToken();
+    }
+
+    private void acquireAccessToken() throws IOException {
+        final String authorityUrl = this.getEnvironment().getAuthenticationEndpoint() + this.getDomain();
+        AuthenticationContext context = new AuthenticationContext(activity, authorityUrl, true, tokenCacheStore);
+        final UserTokenCredentials self = this;
+        context.acquireToken(
+                this.getEnvironment().getTokenAudience(),
+                this.getClientId(),
+                this.getClientRedirectUri(),
+                null,
+                promptBehavior,
+                null,
+                new AuthenticationCallback<AuthenticationResult>() {
+                    @Override
+                    public void onSuccess(AuthenticationResult authenticationResult) {
+                        if (authenticationResult != null && authenticationResult.getAccessToken() != null) {
+                            self.setToken(authenticationResult.getAccessToken());
+                            signal.countDown();
+                        } else {
+                            onError(new IOException("Failed to acquire access token"));
+                        }
+                    }
+
+                    @Override
+                    public void onError(Exception e) {
+                        signal.countDown();
+                    }
+                });
+        try {
+            signal.await();
+        } catch (InterruptedException e) { /* Ignore */ }
+    }
+}
diff --git a/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/package-info.java b/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/package-info.java
new file mode 100644
index 0000000000000..2f23671d5be16
--- /dev/null
+++ b/common/azure-android-client-authentication/src/main/java/com/microsoft/azure/credentials/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * The package provides 2 credential classes that work with AutoRest
+ * generated Azure clients for authentication purposes through Azure.
+ */
+package com.microsoft.azure.credentials;
\ No newline at end of file
diff --git a/common/azure-android-client-authentication/src/main/res/values/strings.xml b/common/azure-android-client-authentication/src/main/res/values/strings.xml
new file mode 100644
index 0000000000000..e2639a86a6c96
--- /dev/null
+++ b/common/azure-android-client-authentication/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">azure-android-client-authentication</string>
+</resources>
diff --git a/common/azure-common-auth/pom.xml b/common/azure-common-auth/pom.xml
new file mode 100644
index 0000000000000..df55011eea293
--- /dev/null
+++ b/common/azure-common-auth/pom.xml
@@ -0,0 +1,104 @@
+<!--
+ Copyright (c) Microsoft Corporation. All rights reserved.
+ Licensed under the MIT License. See License.txt in the project root for
+ license information.
+-->
+<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">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.azure</groupId>
+    <artifactId>azure-common-parent</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <groupId>com.azure</groupId>
+  <artifactId>azure-common-auth</artifactId>
+  <packaging>jar</packaging>
+  <version>1.0.0-SNAPSHOT</version>
+
+  <name>Azure Authentication Java Common Library</name>
+  <description>This package contains the authentication connectors to Active Directory for Azure Java Clients.</description>
+  <url>https://github.com/Azure/autorest-clientruntime-for-java</url>
+
+  <licenses>
+    <license>
+      <name>The MIT License (MIT)</name>
+      <url>http://opensource.org/licenses/MIT</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <scm>
+    <url>scm:git:https://github.com/Azure/autorest-clientruntime-for-java</url>
+    <connection>scm:git:git@github.com:Azure/autorest-clientruntime-for-java.git</connection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <legal><![CDATA[[INFO] Any downloads listed may be third party software.  Microsoft grants you no rights for third party software.]]></legal>
+  </properties>
+
+  <developers>
+    <developer>
+      <id>microsoft</id>
+      <name>Microsoft</name>
+    </developer>
+  </developers>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.azure</groupId>
+      <artifactId>azure-common</artifactId>
+      <version>1.0.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>com.microsoft.azure</groupId>
+      <artifactId>adal4j</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.1</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.8</version>
+        <configuration>
+          <excludePackageNames>*.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization;*.blob.core.storage</excludePackageNames>
+          <bottom><![CDATA[<code>/**
+<br />* Copyright (c) Microsoft Corporation. All rights reserved.
+<br />* Licensed under the MIT License. See License.txt in the project root for
+<br />* license information.
+<br />*/</code>]]></bottom>
+        </configuration>
+      </plugin>
+
+    </plugins>
+  </build>
+</project>
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/ApplicationTokenCredentials.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/ApplicationTokenCredentials.java
new file mode 100644
index 0000000000000..1b6b1f9034d3b
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/ApplicationTokenCredentials.java
@@ -0,0 +1,181 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.microsoft.aad.adal4j.AsymmetricKeyCredential;
+import com.microsoft.aad.adal4j.AuthenticationContext;
+import com.microsoft.aad.adal4j.AuthenticationException;
+import com.microsoft.aad.adal4j.AuthenticationResult;
+import com.microsoft.aad.adal4j.ClientCredential;
+import com.azure.common.AzureEnvironment;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Mono;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Token based credentials for use with a REST Service Client.
+ */
+public class ApplicationTokenCredentials extends AzureTokenCredentials {
+    /** A mapping from resource endpoint to its cached access token. */
+    private Map<String, AuthenticationResult> tokens;
+    /** The active directory application client id. */
+    private String clientId;
+    /** The authentication secret for the application. */
+    private String clientSecret;
+    /** The PKCS12 certificate byte array. */
+    private byte[] clientCertificate;
+    /** The certificate password. */
+    private String clientCertificatePassword;
+
+    /**
+     * Initializes a new instance of the ApplicationTokenCredentials.
+     *
+     * @param clientId the active directory application client id.
+     * @param domain the domain or tenant id containing this application.
+     * @param secret the authentication secret for the application.
+     * @param environment the Azure environment to authenticate with.
+     *                    If null is provided, AzureEnvironment.AZURE will be used.
+     */
+    public ApplicationTokenCredentials(String clientId, String domain, String secret, AzureEnvironment environment) {
+        super(environment, domain); // defer token acquisition
+        this.clientId = clientId;
+        this.clientSecret = secret;
+        this.tokens = new HashMap<>();
+    }
+
+    /**
+     * Initializes a new instance of the ApplicationTokenCredentials.
+     *
+     * @param clientId the active directory application client id.
+     * @param domain the domain or tenant id containing this application.
+     * @param certificate the PKCS12 certificate file content
+     * @param password the password to the certificate file
+     * @param environment the Azure environment to authenticate with.
+     *                    If null is provided, AzureEnvironment.AZURE will be used.
+     */
+    public ApplicationTokenCredentials(String clientId, String domain, byte[] certificate, String password, AzureEnvironment environment) {
+        super(environment, domain);
+        this.clientId = clientId;
+        this.clientCertificate = certificate;
+        this.clientCertificatePassword = password;
+        this.tokens = new HashMap<>();
+    }
+
+    /**
+     * Initializes the credentials based on the provided credentials file.
+     *
+     * @param credentialsFile A  file with credentials, using the standard Java properties format.
+     * and the following keys:
+     *     subscription=&lt;subscription-id&gt;
+     *     tenant=&lt;tenant-id&gt;
+     *     client=&lt;client-id&gt;
+     *     key=&lt;client-key&gt;
+     *     managementURI=&lt;management-URI&gt;
+     *     baseURL=&lt;base-URL&gt;
+     *     authURL=&lt;authentication-URL&gt;
+     * or a JSON format and the following keys
+     * {
+     *     "clientId": "&lt;client-id&gt;",
+     *     "clientSecret": "&lt;client-key&gt;",
+     *     "subscriptionId": "&lt;subscription-id&gt;",
+     *     "tenantId": "&lt;tenant-id&gt;",
+     * }
+     * and any custom endpoints listed in {@link AzureEnvironment}.
+     *
+     * @return The credentials based on the file.
+     * @throws IOException exception thrown from file access errors.
+     */
+    public static ApplicationTokenCredentials fromFile(File credentialsFile) throws IOException {
+        return AuthFile.parse(credentialsFile).generateCredentials();
+    }
+
+    /**
+     * Gets the active directory application client id.
+     *
+     * @return the active directory application client id.
+     */
+    public String clientId() {
+        return clientId;
+    }
+
+    String clientSecret() {
+        return clientSecret;
+    }
+
+    byte[] clientCertificate() {
+        return clientCertificate;
+    }
+
+    String clientCertificatePassword() {
+        return clientCertificatePassword;
+    }
+
+    @Override
+    public synchronized Mono<String> getToken(String resource) {
+        AuthenticationResult authenticationResult = tokens.get(resource);
+        if (authenticationResult != null && authenticationResult.getExpiresOnDate().after(new Date())) {
+            return Mono.just(authenticationResult.getAccessToken());
+        } else {
+            return acquireAccessToken(resource)
+                    .map(ar -> {
+                        tokens.put(resource, ar);
+                        return ar.getAccessToken();
+                    });
+        }
+    }
+
+    private Mono<AuthenticationResult> acquireAccessToken(String resource) {
+        String authorityUrl = this.environment().activeDirectoryEndpoint() + this.domain();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        AuthenticationContext context;
+        try {
+            context = new AuthenticationContext(authorityUrl, false, executor);
+        } catch (MalformedURLException mue) {
+            executor.shutdown();
+            throw Exceptions.propagate(mue);
+        }
+        if (proxy() != null) {
+            context.setProxy(proxy());
+        }
+        Mono<AuthenticationResult> authMono;
+        if (clientSecret != null) {
+            authMono = Mono.create(callback -> {
+                context.acquireToken(
+                        resource,
+                        new ClientCredential(this.clientId(), clientSecret),
+                        Util.authenticationDelegate(callback));
+            });
+        } else if (clientCertificate != null && clientCertificatePassword != null) {
+            authMono = Mono.create(callback -> {
+                AsymmetricKeyCredential keyCredential = Util.createAsymmetricKeyCredential(clientId, clientCertificate, clientCertificatePassword);
+                context.acquireToken(
+                        resource,
+                        keyCredential,
+                        Util.authenticationDelegate(callback));
+            });
+        } else if (clientCertificate != null) {
+            AsymmetricKeyCredential keyCredential = AsymmetricKeyCredential.create(clientId(), Util.privateKeyFromPem(new String(clientCertificate)), Util.publicKeyFromPem(new String(clientCertificate)));
+            authMono = Mono.create(callback -> {
+                context.acquireToken(
+                        resource,
+                        keyCredential,
+                        Util.authenticationDelegate(callback));
+            });
+        } else {
+            authMono = Mono.error(new AuthenticationException("Please provide either a non-null secret or a non-null certificate."));
+        }
+        return authMono.doFinally(s -> executor.shutdown());
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AuthFile.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AuthFile.java
new file mode 100644
index 0000000000000..2ca1c92d0cfca
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AuthFile.java
@@ -0,0 +1,169 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.azure.common.AzureEnvironment;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.gson.reflect.TypeToken;
+import com.azure.common.annotations.Beta;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * This class describes the information from a .azureauth file.
+ */
+@Beta(since = "v1.1.0")
+final class AuthFile {
+
+    private String clientId;
+    private String tenantId;
+    private String clientSecret;
+    private String clientCertificate;
+    private String clientCertificatePassword;
+    private String subscriptionId;
+
+    @JsonIgnore
+    private AzureEnvironment environment;
+    @JsonIgnore
+    private static final JacksonAdapter ADAPTER = new JacksonAdapter();
+    @JsonIgnore
+    private String authFilePath;
+
+    private AuthFile() {
+        environment = new AzureEnvironment(new HashMap<String, String>());
+        environment.endpoints().putAll(AzureEnvironment.AZURE.endpoints());
+    }
+
+    /**
+     * Parses an auth file and read into an AuthFile object.
+     * @param file the auth file to read
+     * @return the AuthFile object created
+     * @throws IOException thrown when the auth file or the certificate file cannot be read or parsed
+     */
+    static AuthFile parse(File file) throws IOException {
+        String content = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+
+        AuthFile authFile;
+        if (isJsonBased(content)) {
+            authFile = ADAPTER.deserialize(content, AuthFile.class, SerializerEncoding.JSON);
+            Map<String, String> endpoints = ADAPTER.deserialize(content, new TypeToken<Map<String, String>>() { }.getType(), SerializerEncoding.JSON);
+            authFile.environment.endpoints().putAll(endpoints);
+        } else {
+            // Set defaults
+            Properties authSettings = new Properties();
+            authSettings.put(CredentialSettings.AUTH_URL.toString(), AzureEnvironment.AZURE.activeDirectoryEndpoint());
+            authSettings.put(CredentialSettings.BASE_URL.toString(), AzureEnvironment.AZURE.resourceManagerEndpoint());
+            authSettings.put(CredentialSettings.MANAGEMENT_URI.toString(), AzureEnvironment.AZURE.managementEndpoint());
+            authSettings.put(CredentialSettings.GRAPH_URL.toString(), AzureEnvironment.AZURE.graphEndpoint());
+            authSettings.put(CredentialSettings.VAULT_SUFFIX.toString(), AzureEnvironment.AZURE.keyVaultDnsSuffix());
+
+            // Load the credentials from the file
+            StringReader credentialsReader = new StringReader(content);
+            authSettings.load(credentialsReader);
+            credentialsReader.close();
+
+            authFile = new AuthFile();
+            authFile.clientId = authSettings.getProperty(CredentialSettings.CLIENT_ID.toString());
+            authFile.tenantId = authSettings.getProperty(CredentialSettings.TENANT_ID.toString());
+            authFile.clientSecret = authSettings.getProperty(CredentialSettings.CLIENT_KEY.toString());
+            authFile.clientCertificate = authSettings.getProperty(CredentialSettings.CLIENT_CERT.toString());
+            authFile.clientCertificatePassword = authSettings.getProperty(CredentialSettings.CLIENT_CERT_PASS.toString());
+            authFile.subscriptionId = authSettings.getProperty(CredentialSettings.SUBSCRIPTION_ID.toString());
+            authFile.environment.endpoints().put(AzureEnvironment.Endpoint.MANAGEMENT.identifier(), authSettings.getProperty(CredentialSettings.MANAGEMENT_URI.toString()));
+            authFile.environment.endpoints().put(AzureEnvironment.Endpoint.ACTIVE_DIRECTORY.identifier(), authSettings.getProperty(CredentialSettings.AUTH_URL.toString()));
+            authFile.environment.endpoints().put(AzureEnvironment.Endpoint.RESOURCE_MANAGER.identifier(), authSettings.getProperty(CredentialSettings.BASE_URL.toString()));
+            authFile.environment.endpoints().put(AzureEnvironment.Endpoint.GRAPH.identifier(), authSettings.getProperty(CredentialSettings.GRAPH_URL.toString()));
+            authFile.environment.endpoints().put(AzureEnvironment.Endpoint.KEYVAULT.identifier(), authSettings.getProperty(CredentialSettings.VAULT_SUFFIX.toString()));
+        }
+        authFile.authFilePath = file.getParent();
+
+        return authFile;
+    }
+
+    private static boolean isJsonBased(String content) {
+        return content.startsWith("{");
+    }
+
+    /**
+     * @return an ApplicationTokenCredentials object from the information in this class
+     */
+    ApplicationTokenCredentials generateCredentials() throws IOException {
+        if (clientSecret != null) {
+            return (ApplicationTokenCredentials) new ApplicationTokenCredentials(
+                    clientId,
+                    tenantId,
+                    clientSecret,
+                    environment).withDefaultSubscriptionId(subscriptionId);
+        } else if (clientCertificate != null) {
+            byte[] certData;
+            if (new File(clientCertificate).exists()) {
+                certData = Files.readAllBytes(Paths.get(clientCertificate));
+            } else {
+                certData = Files.readAllBytes(Paths.get(authFilePath, clientCertificate));
+            }
+
+            return (ApplicationTokenCredentials) new ApplicationTokenCredentials(
+                    clientId,
+                    tenantId,
+                    certData,
+                    clientCertificatePassword,
+                    environment).withDefaultSubscriptionId(subscriptionId);
+        } else {
+            throw new IllegalArgumentException("Please specify either a client key or a client certificate.");
+        }
+    }
+
+    /**
+     * Contains the keys of the settings in a Properties file to read credentials from.
+     */
+    private enum CredentialSettings {
+        /** The subscription GUID. */
+        SUBSCRIPTION_ID("subscription"),
+        /** The tenant GUID or domain. */
+        TENANT_ID("tenant"),
+        /** The client id for the client application. */
+        CLIENT_ID("client"),
+        /** The client secret for the service principal. */
+        CLIENT_KEY("key"),
+        /** The client certificate for the service principal. */
+        CLIENT_CERT("certificate"),
+        /** The password for the client certificate for the service principal. */
+        CLIENT_CERT_PASS("certificatePassword"),
+        /** The management endpoint. */
+        MANAGEMENT_URI("managementURI"),
+        /** The base URL to the current Azure environment. */
+        BASE_URL("baseURL"),
+        /** The URL to Active Directory authentication. */
+        AUTH_URL("authURL"),
+        /** The URL to Active Directory Graph. */
+        GRAPH_URL("graphURL"),
+        /** The suffix of Key Vaults. */
+        VAULT_SUFFIX("vaultSuffix");
+
+        /** The name of the key in the properties file. */
+        private final String name;
+
+        CredentialSettings(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public String toString() {
+            return this.name;
+        }
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliCredentials.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliCredentials.java
new file mode 100644
index 0000000000000..029d7b04e3337
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliCredentials.java
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.azure.common.AzureEnvironment;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.azure.common.annotations.Beta;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Token based credentials for use with a REST Service Client.
+ */
+@Beta
+public final class AzureCliCredentials extends AzureTokenCredentials {
+    private static final ObjectMapper MAPPER = new JacksonAdapter().serializer().setDateFormat(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSSSSS"));
+    /** A mapping from resource endpoint to its cached access token. */
+    private Map<String, AzureCliSubscription> subscriptions;
+    private File azureProfile;
+    private File accessTokens;
+
+    private AzureCliCredentials() {
+        super(null, null);
+        subscriptions = new ConcurrentHashMap<>();
+    }
+
+    private synchronized void loadAccessTokens() throws IOException {
+        try {
+            AzureCliSubscription.Wrapper wrapper = MAPPER.readValue(azureProfile, AzureCliSubscription.Wrapper.class);
+            List<AzureCliToken> tokens = MAPPER.readValue(accessTokens, new TypeReference<List<AzureCliToken>>() { });
+            while (wrapper == null || tokens == null || tokens.isEmpty() || wrapper.subscriptions == null || wrapper.subscriptions.isEmpty()) {
+                System.err.println("Please login in Azure CLI and press any key to continue after you've successfully logged in.");
+                System.in.read();
+                wrapper = MAPPER.readValue(azureProfile, AzureCliSubscription.Wrapper.class);
+                tokens = MAPPER.readValue(accessTokens, new TypeReference<List<AzureCliToken>>() { });
+            }
+            for (AzureCliSubscription subscription : wrapper.subscriptions) {
+                for (AzureCliToken token : tokens) {
+                    // Find match of user and tenant
+                    if (subscription.isServicePrincipal() == token.isServicePrincipal()
+                            && subscription.userName().equalsIgnoreCase(token.user())
+                            && subscription.tenant().equalsIgnoreCase(token.tenant())) {
+                        subscriptions.put(subscription.id(), subscription.withToken(token));
+                        if (subscription.isDefault()) {
+                            withDefaultSubscriptionId(subscription.id());
+                        }
+                    }
+                }
+            }
+        } catch (IOException e) {
+            System.err.println(String.format("Cannot read files %s and %s. Are you logged in Azure CLI?", azureProfile.getAbsolutePath(), accessTokens.getAbsolutePath()));
+            throw e;
+        }
+    }
+
+    /**
+     * Creates an instance of AzureCliCredentials with the default Azure CLI configuration.
+     *
+     * @return an instance of AzureCliCredentials
+     * @throws IOException if the Azure CLI token files are not accessible
+     */
+    public static AzureCliCredentials create() throws IOException {
+        return create(
+            Paths.get(System.getProperty("user.home"), ".azure", "azureProfile.json").toFile(),
+            Paths.get(System.getProperty("user.home"), ".azure", "accessTokens.json").toFile());
+    }
+
+    /**
+     * Creates an instance of AzureCliCredentials with custom locations of the token files.
+     *
+     * @param azureProfile the azureProfile.json file created by Azure CLI
+     * @param accessTokens the accessTokens.json file created by Azure CLI
+     * @return an instance of AzureCliCredentials
+     * @throws IOException if the Azure CLI token files are not accessible
+     */
+    public static AzureCliCredentials create(File azureProfile, File accessTokens) throws IOException {
+        AzureCliCredentials credentials = new AzureCliCredentials();
+        credentials.azureProfile = azureProfile;
+        credentials.accessTokens = accessTokens;
+        credentials.loadAccessTokens();
+        return credentials;
+    }
+
+    /**
+     * @return the active directory application client id
+     */
+    public String clientId() {
+        return subscriptions.get(defaultSubscriptionId()).clientId();
+    }
+
+    /**
+     * @return the tenant or domain the containing the application
+     */
+    @Override
+    public String domain() {
+        return subscriptions.get(defaultSubscriptionId()).tenant();
+    }
+
+    /**
+     * @return the Azure environment to authenticate with
+     */
+    public AzureEnvironment environment() {
+        return subscriptions.get(defaultSubscriptionId()).environment();
+    }
+
+    @Override
+    public synchronized Mono<String> getToken(String resource) {
+        return subscriptions.get(defaultSubscriptionId()).credentialInstance().getToken(resource)
+                .onErrorResume(t -> {
+                    System.err.println("Please login in Azure CLI and press any key to continue after you've successfully logged in.");
+                    try {
+                        System.in.read();
+                        loadAccessTokens();
+                    } catch (IOException ioe) {
+                        throw Exceptions.propagate(ioe);
+                    }
+                    return subscriptions.get(defaultSubscriptionId()).credentialInstance().getToken(resource).subscribeOn(Schedulers.immediate());
+                });
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliSubscription.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliSubscription.java
new file mode 100644
index 0000000000000..97039477e0db7
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliSubscription.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.azure.common.annotations.Beta;
+import com.azure.common.AzureEnvironment;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * An instance of this class represents a subscription record in azureProfiles.json.
+ */
+@Beta
+final class AzureCliSubscription {
+    private String environmentName;
+    private String id;
+    private String name;
+    private String tenantId;
+    private String state;
+    private UserInfo user;
+    private String clientId;
+    private boolean isDefault;
+
+    private AzureTokenCredentials credentialInstance;
+
+    private Map<String, AzureCliToken> userTokens = new ConcurrentHashMap<>();
+    private AzureCliToken servicePrincipalToken;
+
+    String id() {
+        return id;
+    }
+
+    boolean isDefault() {
+        return isDefault;
+    }
+
+    String clientId() {
+        return clientId;
+    }
+
+    AzureCliSubscription withToken(AzureCliToken token) {
+        if (isServicePrincipal()) {
+            this.servicePrincipalToken = token;
+        } else {
+            if (token.resource() != null) {
+                this.userTokens.put(token.resource(), token);
+            }
+            if (this.clientId == null) {
+                this.clientId = token.clientId();
+            }
+        }
+        return this;
+    }
+
+    AzureEnvironment environment() {
+        if (environmentName == null) {
+            return null;
+        } else if (environmentName.equalsIgnoreCase("AzureCloud")) {
+            return AzureEnvironment.AZURE;
+        } else if (environmentName.equalsIgnoreCase("AzureChinaCloud")) {
+            return AzureEnvironment.AZURE_CHINA;
+        } else if (environmentName.equalsIgnoreCase("AzureGermanCloud")) {
+            return AzureEnvironment.AZURE_GERMANY;
+        } else if (environmentName.equalsIgnoreCase("AzureUSGovernment")) {
+            return AzureEnvironment.AZURE_US_GOVERNMENT;
+        } else {
+            return null;
+        }
+    }
+
+    String tenant() {
+        return tenantId;
+    }
+
+    boolean isServicePrincipal() {
+        return user.type.equalsIgnoreCase("ServicePrincipal");
+    }
+
+    String userName() {
+        return user.name;
+    }
+
+    synchronized AzureTokenCredentials credentialInstance() {
+        if (credentialInstance != null) {
+            return credentialInstance;
+        }
+        if (isServicePrincipal()) {
+            credentialInstance = new ApplicationTokenCredentials(
+                clientId(),
+                tenant(),
+                servicePrincipalToken.accessToken(),
+                environment()
+                );
+        } else {
+            credentialInstance = new UserTokenCredentials(clientId(), tenant(), null, null, environment()) {
+                @Override
+                public synchronized Mono<String> getToken(String resource) {
+                    AzureCliToken token = userTokens.get(resource);
+                    // Management endpoint also works for resource manager
+                    if (token == null && (resource.equalsIgnoreCase(environment().resourceManagerEndpoint()))) {
+                        token = userTokens.get(environment().managementEndpoint());
+                    }
+                    // Exact match and token hasn't expired
+                    if (token != null && !token.expired()) {
+                        return Mono.just(token.accessToken());
+                    }
+                    // If found then refresh
+                    boolean shouldRefresh = token != null;
+                    // If not found for the resource, but is MRRT then also refresh
+                    if (token == null && userTokens.values().size() > 0) {
+                        token = new ArrayList<>(userTokens.values()).get(0);
+                        shouldRefresh = token.isMRRT();
+                    }
+                    if (shouldRefresh) {
+                        AzureCliToken finalToken = token;
+                        return acquireAccessTokenFromRefreshToken(resource, token.refreshToken(), token.isMRRT())
+                                .map(authenticationResult -> {
+                                    try {
+                                        AzureCliToken newToken = finalToken.clone().withResource(resource).withAuthenticationResult(authenticationResult);
+                                        userTokens.put(resource, newToken);
+                                        return newToken.accessToken();
+                                    } catch (CloneNotSupportedException cnse) {
+                                        throw Exceptions.propagate(cnse);
+                                    }
+                                });
+                    } else {
+                        return Mono.error(new RuntimeException("No refresh token available for user " + userName()));
+                    }
+                }
+            };
+        }
+        return credentialInstance;
+    }
+
+    private static class UserInfo {
+        private String type;
+        private String name;
+    }
+
+    static class Wrapper {
+        List<AzureCliSubscription> subscriptions;
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliToken.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliToken.java
new file mode 100644
index 0000000000000..19f739631a585
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureCliToken.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.microsoft.aad.adal4j.AuthenticationResult;
+import com.azure.common.annotations.Beta;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * An instance of this class represents an entry in accessTokens.json.
+ */
+@Beta
+final class AzureCliToken implements Cloneable {
+    @JsonProperty("_authority")
+    private String authority;
+    @JsonProperty("_clientId")
+    private String clientId;
+    private String tokenType;
+    private long expiresIn;
+    private String expiresOn;
+    private LocalDateTime expiresOnDate;
+    private String oid;
+    private String userId;
+    private String servicePrincipalId;
+    private String servicePrincipalTenant;
+    private boolean isMRRT;
+    private String resource;
+    private String accessToken;
+    private String refreshToken;
+    private String identityProvider;
+
+    boolean isServicePrincipal() {
+        return servicePrincipalId != null;
+    }
+
+    String tenant() {
+        if (isServicePrincipal()) {
+            return servicePrincipalTenant;
+        } else {
+            String[] parts = authority.split("/");
+            return parts[parts.length - 1];
+        }
+    }
+
+    String clientId() {
+        if (isServicePrincipal()) {
+            return servicePrincipalId;
+        } else {
+            return clientId;
+        }
+    }
+
+    String authority() {
+        return authority;
+    }
+
+    boolean expired() {
+        return expiresOn != null && expiresOn().isBefore(LocalDateTime.now());
+    }
+
+    String accessToken() {
+        return accessToken;
+    }
+
+    LocalDateTime expiresOn() {
+        if (expiresOnDate == null) {
+            try {
+                expiresOnDate = LocalDateTime.parse(expiresOn, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"));
+            } catch (IllegalArgumentException e) {
+                expiresOnDate = LocalDateTime.parse(expiresOn);
+            }
+        }
+        return expiresOnDate;
+    }
+
+    AzureCliToken withAuthenticationResult(AuthenticationResult result) {
+        this.accessToken = result.getAccessToken();
+        this.refreshToken = result.getRefreshToken();
+        this.expiresIn = result.getExpiresAfter();
+        this.expiresOnDate = LocalDateTime.ofInstant(result.getExpiresOnDate().toInstant(), ZoneId.systemDefault());
+        return this;
+    }
+
+    AzureCliToken withAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+        return this;
+    }
+
+    String refreshToken() {
+        return refreshToken;
+    }
+
+    AzureCliToken withRefreshToken(String refreshToken) {
+        this.refreshToken = refreshToken;
+        return this;
+    }
+
+    String user() {
+        if (isServicePrincipal()) {
+            return servicePrincipalId;
+        } else {
+            return userId;
+        }
+    }
+
+    boolean isMRRT() {
+        return isMRRT;
+    }
+
+    String resource() {
+        return resource;
+    }
+
+    AzureCliToken withResource(String resource) {
+        this.resource = resource;
+        return this;
+    }
+
+    public AzureCliToken clone() throws CloneNotSupportedException {
+        AzureCliToken token = (AzureCliToken) super.clone();
+        token.expiresOnDate = LocalDateTime.from(expiresOn());
+        return token;
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureTokenCredentials.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureTokenCredentials.java
new file mode 100644
index 0000000000000..07b5dc79263a1
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/AzureTokenCredentials.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.azure.common.AzureEnvironment;
+import com.azure.common.AzureEnvironment.Endpoint;
+import com.azure.common.credentials.AsyncServiceClientCredentials;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+import java.net.Proxy;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * AzureTokenCredentials represents a credentials object with access to Azure
+ * Resource management.
+ */
+public abstract class AzureTokenCredentials implements AsyncServiceClientCredentials {
+    private static final String SCHEME = "Beareer";
+    private final AzureEnvironment environment;
+    private final String domain;
+    private String defaultSubscription;
+
+    private Proxy proxy;
+
+    /**
+     * Initializes a new instance of the AzureTokenCredentials.
+     *
+     * @param environment the Azure environment to use
+     * @param domain the tenant or domain the credential is authorized to
+     */
+    public AzureTokenCredentials(AzureEnvironment environment, String domain) {
+        this.environment = (environment == null) ? AzureEnvironment.AZURE : environment;
+        this.domain = domain;
+    }
+
+    /**
+     * Gets the token from the given endpoint.
+     *
+     * @param uri the url
+     * @return the token
+     */
+    private Mono<String> getTokenFromUri(String uri) {
+        URL url = null;
+        try {
+            url = new URL(uri);
+        } catch (MalformedURLException e) {
+            return Mono.error(e);
+        }
+        String host = String.format("%s://%s%s/", url.getProtocol(), url.getHost(), url.getPort() > 0 ? ":" + url.getPort() : "");
+        String resource = environment().managementEndpoint();
+        for (Map.Entry<String, String> endpoint : environment().endpoints().entrySet()) {
+            if (host.contains(endpoint.getValue())) {
+                if (endpoint.getKey().equals(Endpoint.KEYVAULT.identifier())) {
+                    resource = String.format("https://%s/", endpoint.getValue().replaceAll("^\\.*", ""));
+                    break;
+                } else if (endpoint.getKey().equals(Endpoint.GRAPH.identifier())) {
+                    resource = environment().graphEndpoint();
+                    break;
+                } else if (endpoint.getKey().equals(Endpoint.LOG_ANALYTICS.identifier())) {
+                    resource = environment().logAnalyticsEndpoint();
+                    break;
+                } else if (endpoint.getKey().equals(Endpoint.APPLICATION_INSIGHTS.identifier())) {
+                    resource = environment().applicationInsightsEndpoint();
+                    break;
+                } else if (endpoint.getKey().equals(Endpoint.DATA_LAKE_STORE.identifier())
+                        || endpoint.getKey().equals(Endpoint.DATA_LAKE_ANALYTICS.identifier())) {
+                    resource = environment().dataLakeEndpointResourceId();
+                    break;
+                }
+            }
+        }
+        return getToken(resource);
+    }
+
+    /**
+     * Override this method to provide the mechanism to get a token.
+     *
+     * @param resource the resource the access token is for
+     * @return the token to access the resource
+     */
+    public abstract Mono<String> getToken(String resource);
+
+    @Override
+    public Mono<String> authorizationHeaderValueAsync(String uri) {
+        return getTokenFromUri(uri).map(token -> "Bearer " + token);
+    }
+
+    /**
+     * Override this method to provide the domain or tenant ID the token is valid in.
+     *
+     * @return the domain or tenant ID string
+     */
+    public String domain() {
+        return domain;
+    }
+
+    /**
+     * @return the environment details the credential has access to.
+     */
+    public AzureEnvironment environment() {
+        return environment;
+    }
+
+    /**
+     * @return The default subscription ID, if any
+     */
+    public String defaultSubscriptionId() {
+        return defaultSubscription;
+    }
+
+    /**
+     * Set default subscription ID.
+     *
+     * @param subscriptionId the default subscription ID.
+     * @return the credentials object itself.
+     */
+    public AzureTokenCredentials withDefaultSubscriptionId(String subscriptionId) {
+        this.defaultSubscription = subscriptionId;
+        return this;
+    }
+
+    /**
+     * @return the proxy being used for accessing Active Directory.
+     */
+    public Proxy proxy() {
+        return proxy;
+    }
+
+    /**
+     * Set the proxy used for accessing Active Directory.
+     * @param proxy the proxy to use
+     * @return the credential itself
+     */
+    public AzureTokenCredentials withProxy(Proxy proxy) {
+        this.proxy = proxy;
+        return this;
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/DelegatedTokenCredentials.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/DelegatedTokenCredentials.java
new file mode 100644
index 0000000000000..76bd112a3dd45
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/DelegatedTokenCredentials.java
@@ -0,0 +1,270 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.microsoft.aad.adal4j.AsymmetricKeyCredential;
+import com.microsoft.aad.adal4j.AuthenticationContext;
+import com.microsoft.aad.adal4j.AuthenticationException;
+import com.microsoft.aad.adal4j.AuthenticationResult;
+import com.microsoft.aad.adal4j.ClientCredential;
+import com.azure.common.annotations.Beta;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Mono;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Token based credentials to authenticate an application on behalf of a user.
+ */
+@Beta(since = "1.2.0")
+public class DelegatedTokenCredentials extends AzureTokenCredentials {
+    /** A mapping from resource endpoint to its cached access token. */
+    private Map<String, AuthenticationResult> tokens;
+    private String redirectUrl;
+    private String authorizationCode;
+    private ApplicationTokenCredentials applicationCredentials;
+
+    /**
+     * Initializes a new instance of the DelegatedTokenCredentials.
+     *
+     * @param applicationCredentials the credentials representing a service principal
+     * @param redirectUrl the URL to redirect to after authentication in Active Directory
+     */
+    public DelegatedTokenCredentials(ApplicationTokenCredentials applicationCredentials, String redirectUrl) {
+        super(applicationCredentials.environment(), applicationCredentials.domain()); // defer token acquisition
+        this.applicationCredentials = applicationCredentials;
+        this.tokens = new ConcurrentHashMap<>();
+        this.redirectUrl = redirectUrl;
+    }
+
+    /**
+     * Initializes a new instance of the DelegatedTokenCredentials, with a pre-acquired oauth2 authorization code.
+     *
+     * @param applicationCredentials the credentials representing a service principal
+     * @param redirectUrl the URL to redirect to after authentication in Active Directory
+     * @param authorizationCode the oauth2 authorization code
+     */
+    public DelegatedTokenCredentials(ApplicationTokenCredentials applicationCredentials, String redirectUrl, String authorizationCode) {
+        this(applicationCredentials, redirectUrl);
+        this.authorizationCode = authorizationCode;
+    }
+
+    /**
+     * Creates a new instance of the DelegatedTokenCredentials from an auth file.
+     *
+     * @param authFile The credentials based on the file
+     * @param redirectUrl the URL to redirect to after authentication in Active Directory
+     * @return a new delegated token credentials
+     * @throws IOException exception thrown from file access errors.
+     */
+    public static DelegatedTokenCredentials fromFile(File authFile, String redirectUrl) throws IOException {
+        return new DelegatedTokenCredentials(ApplicationTokenCredentials.fromFile(authFile), redirectUrl);
+    }
+
+    /**
+     * Creates a new instance of the DelegatedTokenCredentials from an auth file,
+     * with a pre-acquired oauth2 authorization code.
+     *
+     * @param authFile The credentials based on the file
+     * @param redirectUrl the URL to redirect to after authentication in Active Directory
+     * @param authorizationCode the oauth2 authorization code
+     * @return a new delegated token credentials
+     * @throws IOException exception thrown from file access errors.
+     */
+    public static DelegatedTokenCredentials fromFile(File authFile, String redirectUrl, String authorizationCode) throws IOException {
+        return new DelegatedTokenCredentials(ApplicationTokenCredentials.fromFile(authFile), redirectUrl, authorizationCode);
+    }
+
+    /**
+     * @return the active directory application client id
+     */
+    public String clientId() {
+        return applicationCredentials.clientId();
+    }
+
+    /**
+     * @return the URL to authenticate through OAuth2
+     */
+    public String generateAuthenticationUrl() {
+        return String.format("%s/%s/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&response_mode=query&state=%s",
+                environment().activeDirectoryEndpoint(), domain(), clientId(), this.redirectUrl, UUID.randomUUID());
+    }
+
+    /**
+     * Generate the URL to authenticate through OAuth2.
+     *
+     * @param responseMode the method that should be used to send the resulting token back to your app
+     * @param state a value included in the request that is also returned in the token response
+     * @return the URL to authenticate through OAuth2
+     */
+    public String generateAuthenticationUrl(ResponseMode responseMode, String state) {
+        return String.format("%s/%s/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&response_mode=%s&state=%s",
+                environment().activeDirectoryEndpoint(), domain(), clientId(), this.redirectUrl, responseMode.value, state);
+    }
+
+    /**
+     * Set the authorization code acquired returned to the redirect URL.
+     * @param authorizationCode the oauth2 authorization code
+     */
+    public void setAuthorizationCode(String authorizationCode) {
+        this.authorizationCode = authorizationCode;
+    }
+
+    @Override
+    public synchronized Mono<String> getToken(String resource) {
+        // Find exact match for the resource
+        AuthenticationResult[] authenticationResult = new AuthenticationResult[1];
+        authenticationResult[0] = tokens.get(resource);
+        // Return if found and not expired
+        if (authenticationResult[0] != null && authenticationResult[0].getExpiresOnDate().after(new Date())) {
+            return Mono.just(authenticationResult[0].getAccessToken());
+        }
+        // If found then refresh
+        boolean shouldRefresh = authenticationResult[0] != null;
+        // If not found for the resource, but is MRRT then also refresh
+        if (authenticationResult[0] == null && !tokens.isEmpty()) {
+            authenticationResult[0] = new ArrayList<>(tokens.values()).get(0);
+            shouldRefresh = authenticationResult[0].isMultipleResourceRefreshToken();
+        }
+
+        if (shouldRefresh) {
+            return Mono.defer(() -> acquireAccessTokenFromRefreshToken(resource, authenticationResult[0].getRefreshToken(), authenticationResult[0].isMultipleResourceRefreshToken())
+                    .onErrorResume(t -> acquireNewAccessToken(resource))
+                    .doOnNext(ar -> tokens.put(resource, ar))
+                    .then(Mono.just(tokens.get(resource).getAccessToken())));
+        } else {
+            return Mono.just(tokens.get(resource).getAccessToken());
+        }
+    }
+
+    private Mono<AuthenticationResult> acquireNewAccessToken(String resource) {
+        if (authorizationCode == null) {
+            return Mono.error(new IllegalArgumentException("You must acquire an authorization code by redirecting to the authentication URL"));
+        }
+        String authorityUrl = this.environment().activeDirectoryEndpoint() + this.domain();
+        AuthenticationContext context;
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            context = new AuthenticationContext(authorityUrl, false, executor);
+        } catch (MalformedURLException mue) {
+            executor.shutdown();
+            throw Exceptions.propagate(mue);
+        }
+        if (proxy() != null) {
+            context.setProxy(proxy());
+        }
+        Mono<AuthenticationResult> authMono;
+        if (applicationCredentials.clientSecret() != null) {
+            URI uri;
+            try {
+                uri = new URI(redirectUrl);
+            } catch (URISyntaxException use) {
+                return Mono.error(use);
+            }
+            authMono = Mono.create(callback -> {
+                context.acquireTokenByAuthorizationCode(
+                        authorizationCode,
+                        uri,
+                        new ClientCredential(applicationCredentials.clientId(), applicationCredentials.clientSecret()),
+                        resource,
+                        Util.authenticationDelegate(callback));
+            });
+        } else if (applicationCredentials.clientCertificate() != null && applicationCredentials.clientCertificatePassword() != null) {
+            URI uri;
+            try {
+                uri = new URI(redirectUrl);
+            } catch (URISyntaxException use) {
+                return Mono.error(use);
+            }
+            authMono = Mono.create(callback -> {
+                AsymmetricKeyCredential keyCredential  = Util.createAsymmetricKeyCredential(applicationCredentials.clientId(), applicationCredentials.clientCertificate(), applicationCredentials.clientCertificatePassword());
+                context.acquireTokenByAuthorizationCode(
+                        authorizationCode,
+                        uri,
+                        keyCredential,
+                        Util.authenticationDelegate(callback));
+            });
+        } else if (applicationCredentials.clientCertificate() != null) {
+            URI uri;
+            try {
+                uri = new URI(redirectUrl);
+            } catch (URISyntaxException use) {
+                return Mono.error(use);
+            }
+            AsymmetricKeyCredential keyCredential = AsymmetricKeyCredential.create(clientId(),
+                    Util.privateKeyFromPem(new String(applicationCredentials.clientCertificate())),
+                    Util.publicKeyFromPem(new String(applicationCredentials.clientCertificate())));
+            authMono = Mono.create(callback -> {
+                context.acquireTokenByAuthorizationCode(
+                        authorizationCode,
+                        uri,
+                        keyCredential,
+                        resource,
+                        Util.authenticationDelegate(callback));
+            });
+        } else {
+            authMono = Mono.error(new AuthenticationException("Please provide either a non-null secret or a non-null certificate."));
+        }
+        return authMono.doFinally(s -> executor.shutdown());
+    }
+
+    // Refresh tokens are currently not used since we don't know if the refresh token has expired
+    private Mono<AuthenticationResult> acquireAccessTokenFromRefreshToken(String resource, String refreshToken, boolean isMultipleResourceRefreshToken) {
+        String authorityUrl = this.environment().activeDirectoryEndpoint() + this.domain();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        Mono<AuthenticationResult> authMono = Mono.defer(() -> {
+            AuthenticationContext context;
+            try {
+                context = new AuthenticationContext(authorityUrl, false, executor);
+            } catch (MalformedURLException mue) {
+                throw Exceptions.propagate(mue);
+            }
+            if (proxy() != null) {
+                context.setProxy(proxy());
+            }
+            return Mono.create(callback -> context.acquireTokenByRefreshToken(
+                    refreshToken,
+                    clientId(),
+                    resource,
+                    Util.authenticationDelegate(callback)));
+        });
+        return authMono.doFinally(s -> executor.shutdown());
+    }
+
+    /**
+     * Specifies the method that should be used to send the resulting token back to your app.
+     */
+    public enum ResponseMode {
+
+        /**
+         * the token is sent as a query parameter.
+         */
+        QUERY("query"),
+
+        /**
+         * the token is sent as part of a form data.
+         */
+        FORM_DATA("form_data");
+
+        private String value;
+
+        ResponseMode(String value) {
+            this.value = value;
+        }
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIConfigurationForAppService.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIConfigurationForAppService.java
new file mode 100644
index 0000000000000..3d9f5c2f21d26
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIConfigurationForAppService.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.auth.credentials;
+
+import com.azure.common.AzureEnvironment;
+
+/**
+ * Defines the configuration to be used for retrieving access token from
+ * within an app-service with system assigned MSI enabled.
+ */
+public class MSIConfigurationForAppService {
+    private final AzureEnvironment environment;
+    private String resource;
+    private String msiEndpoint;
+    private String msiSecret;
+
+    /**
+     * Creates MSIConfigurationForAppService.
+     *
+     * @param environment azure environment
+     */
+    public MSIConfigurationForAppService(AzureEnvironment environment) {
+        this.environment = environment;
+    }
+
+    /**
+     * Creates MSIConfigurationForAppService.
+     */
+    public MSIConfigurationForAppService() {
+        this(AzureEnvironment.AZURE);
+    }
+
+    /**
+     * @return the azure environment.
+     */
+    public AzureEnvironment azureEnvironment() {
+        return this.environment;
+    }
+    /**
+     * @return the audience identifying who will consume the token.
+     */
+    public String resource() {
+        if (this.resource == null) {
+            this.resource = this.environment.managementEndpoint();
+        }
+        return this.resource;
+    }
+    /**
+     * @return the endpoint from which token needs to be retrieved.
+     */
+    public String msiEndpoint() {
+        if (this.msiEndpoint == null) {
+            this.msiEndpoint = System.getenv("MSI_ENDPOINT");
+        }
+        return this.msiEndpoint;
+    }
+    /**
+     * @return the secret to use to retrieve the token.
+     */
+    public String msiSecret() {
+        if (this.msiSecret == null) {
+            this.msiSecret = System.getenv("MSI_SECRET");
+        }
+        return this.msiSecret;
+    }
+    /**
+     * Specifies the token audience.
+     *
+     * @param resource the audience of the token.
+     *
+     * @return MSIConfigurationForAppService
+     */
+    public MSIConfigurationForAppService withResource(String resource) {
+        this.resource = resource;
+        return this;
+    }
+    /**
+     * Specifies the endpoint from which token needs to retrieved.
+     *
+     * @param msiEndpoint the token endpoint.
+     *
+     * @return MSIConfigurationForAppService
+     */
+    public MSIConfigurationForAppService withMsiEndpoint(String msiEndpoint) {
+        this.msiSecret = msiEndpoint;
+        return this;
+    }
+    /**
+     * Specifies secret to use to retrieve the token.
+     *
+     * @param msiSecret the secret.
+     *
+     * @return MSIConfigurationForAppService
+     */
+    public MSIConfigurationForAppService withMsiSecret(String msiSecret) {
+        this.msiSecret = msiSecret;
+        return this;
+    }
+
+    @Override
+    public MSIConfigurationForAppService clone() {
+        MSIConfigurationForAppService copy = new MSIConfigurationForAppService(this.azureEnvironment());
+        if (this.resource() != null) {
+            copy.withResource(this.resource());
+        }
+        if (this.msiEndpoint() != null) {
+            copy.withMsiEndpoint(this.msiEndpoint());
+        }
+        if (this.msiSecret() != null) {
+            copy.withMsiSecret(this.msiSecret());
+        }
+        return copy;
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIConfigurationForVirtualMachine.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIConfigurationForVirtualMachine.java
new file mode 100644
index 0000000000000..90c0b0118574c
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIConfigurationForVirtualMachine.java
@@ -0,0 +1,216 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.auth.credentials;
+
+import com.azure.common.AzureEnvironment;
+
+/**
+ * Defines the configuration to be used for retrieving access token from
+ * within a VM with user assigned or system assigned MSI enabled.
+ */
+public class MSIConfigurationForVirtualMachine {
+    private final AzureEnvironment environment;
+    private String resource;
+    private MSITokenSource tokenSource;
+    private String objectId;
+    private String clientId;
+    private String identityId;
+    private Integer msiPort = null;
+    private int maxRetry = -1;
+
+    /**
+     * Creates MSIConfigurationForVirtualMachine.
+     *
+     * @param environment azure environment
+     */
+    public MSIConfigurationForVirtualMachine(AzureEnvironment environment) {
+        this.environment = environment;
+    }
+
+    /**
+     * Creates MSIConfigurationForVirtualMachine.
+     */
+    public MSIConfigurationForVirtualMachine() {
+        this(AzureEnvironment.AZURE);
+    }
+
+    /**
+     * @return the azure environment.
+     */
+    public AzureEnvironment azureEnvironment() {
+        return this.environment;
+    }
+
+    /**
+     * @return the token retrieval source (either MSI extension running in VM or IMDS service).
+     */
+    public MSITokenSource tokenSource() {
+        if (this.tokenSource == null) {
+            this.tokenSource = MSITokenSource.IMDS_ENDPOINT;
+        }
+        return this.tokenSource;
+    }
+    /**
+     * @return the audience identifying who will consume the token.
+     */
+    public String resource() {
+        if (this.resource == null) {
+            this.resource = this.environment.managementEndpoint();
+        }
+        return this.resource;
+    }
+    /**
+     * @return the principal id of user assigned or system assigned identity.
+     */
+    public String objectId() {
+        return this.objectId;
+    }
+    /**
+     * @return the client id of user assigned or system assigned identity.
+     */
+    public String clientId() {
+        return this.clientId;
+    }
+    /**
+     * @return the ARM resource id of the user assigned identity resource.
+     */
+    public String identityId() {
+        return this.identityId;
+    }
+    /**
+     * @return the port of token retrieval service running in the extension.
+     */
+    public int msiPort() {
+        if (this.msiPort == null) {
+            this.msiPort = 50342;
+        }
+        return this.msiPort;
+    }
+
+    /**
+     * @return the maximum retries allowed.
+     */
+    public int maxRetry() {
+        return this.maxRetry;
+    }
+
+    /**
+     * Specifies the token retrieval source.
+     *
+     * @param tokenSource the source of token
+     *
+     * @return MSIConfigurationForVirtualMachine
+     */
+    public MSIConfigurationForVirtualMachine withTokenSource(MSITokenSource tokenSource) {
+        this.tokenSource = tokenSource;
+        return this;
+    }
+
+    /**
+     * Specifies the token audience.
+     *
+     * @param resource the audience of the token.
+     *
+     * @return MSIConfigurationForVirtualMachine
+     */
+    public MSIConfigurationForVirtualMachine withResource(String resource) {
+        this.resource = resource;
+        return this;
+    }
+
+    /**
+     * specifies the principal id of user assigned or system assigned identity.
+     *
+     * @param objectId the object (principal) id
+     * @return MSIConfigurationForVirtualMachine
+     */
+    public MSIConfigurationForVirtualMachine withObjectId(String objectId) {
+        this.objectId = objectId;
+        return this;
+    }
+
+    /**
+     * Specifies the client id of user assigned or system assigned identity.
+     *
+     * @param clientId the client id
+     * @return MSIConfigurationForVirtualMachine
+     */
+    public MSIConfigurationForVirtualMachine withClientId(String clientId) {
+        this.clientId = clientId;
+        return this;
+    }
+
+    /**
+     * Specifies the ARM resource id of the user assigned identity resource.
+     *
+     * @param identityId the identity ARM id
+     * @return MSIConfigurationForVirtualMachine
+     */
+    public MSIConfigurationForVirtualMachine withIdentityId(String identityId) {
+        this.identityId = identityId;
+        return this;
+    }
+
+    /**
+     * Specifies the port of token retrieval msi extension service.
+     *
+     * @param msiPort the port
+     * @return MSIConfigurationForVirtualMachine
+     */
+    public MSIConfigurationForVirtualMachine withMsiPort(int msiPort) {
+        this.msiPort = msiPort;
+        return this;
+    }
+
+    /**
+     * Specifies the the maximum retries allowed.
+     *
+     * @param maxRetry max retry count
+     * @return MSIConfigurationForVirtualMachine
+     */
+    public MSIConfigurationForVirtualMachine withMaxRetry(int maxRetry) {
+        this.maxRetry = maxRetry;
+        return this;
+    }
+
+    @Override
+    public MSIConfigurationForVirtualMachine clone() {
+        MSIConfigurationForVirtualMachine copy = new MSIConfigurationForVirtualMachine(this.azureEnvironment());
+        if (this.clientId() != null) {
+            copy.withClientId(this.clientId());
+        }
+        if (this.identityId() != null) {
+            copy.withIdentityId(this.identityId());
+        }
+        if (this.objectId() != null) {
+            copy.withObjectId(this.objectId());
+        }
+        if (this.resource() != null) {
+            copy.withResource(this.resource());
+        }
+        if (this.tokenSource() != null) {
+            copy.withTokenSource(this.tokenSource());
+        }
+        copy.withMaxRetry(this.maxRetry());
+        copy.withMsiPort(this.msiPort());
+        return copy;
+    }
+
+
+    /**
+     * The source of MSI token.
+     */
+    public enum MSITokenSource {
+        /**
+         * Indicate that token should be retrieved from MSI extension installed in the VM.
+         */
+        MSI_EXTENSION,
+        /**
+         * Indicate that token should be retrieved from IMDS service.
+         */
+        IMDS_ENDPOINT
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSICredentials.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSICredentials.java
new file mode 100644
index 0000000000000..c620a387e6911
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSICredentials.java
@@ -0,0 +1,315 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.azure.common.annotations.Beta;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import reactor.core.publisher.Mono;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Managed Service Identity token based credentials for use with a REST Service Client.
+ */
+@Beta
+public final class MSICredentials extends AzureTokenCredentials {
+    //
+    private final List<Integer> retrySlots = new ArrayList<>();
+    private final Lock lock = new ReentrantLock();
+    private final ConcurrentHashMap<String, MSIToken> cache = new ConcurrentHashMap<>();
+    //
+    private final JacksonAdapter adapter = new JacksonAdapter();
+    private final MSIConfigurationForVirtualMachine configForVM;
+    private final MSIConfigurationForAppService configForAppService;
+    private final HostType hostType;
+    private final int maxRetry;
+    private static final int MAX_RETRY_DEFAULT_LIMIT = 20;
+    /**
+     * Creates MSICredentials for application running on MSI enabled virtual machine.
+     *
+     * @return MSICredentials
+     */
+    public static MSICredentials forVirtualMachine() {
+        return new MSICredentials(new MSIConfigurationForVirtualMachine());
+    }
+
+    /**
+     * Creates MSICredentials for application running on MSI enabled virtual machine.
+     *
+     * @param config the configuration to be used for token request.
+     * @return MSICredentials
+     */
+    public static MSICredentials forVirtualMachine(MSIConfigurationForVirtualMachine config) {
+        return new MSICredentials(config.clone());
+    }
+
+    /**
+     * Creates MSICredentials for application running on MSI enabled app service.
+     *
+     * @return MSICredentials
+     */
+    public static MSICredentials forAppService() {
+        return new MSICredentials(new MSIConfigurationForAppService());
+    }
+
+    /**
+     * Creates MSICredentials for application running on MSI enabled app service.
+     *
+     * @param config the configuration to be used for token request.
+     * @return MSICredentials
+     */
+    public static MSICredentials forAppService(MSIConfigurationForAppService config) {
+        return new MSICredentials(config.clone());
+    }
+
+    private MSICredentials(MSIConfigurationForVirtualMachine config) {
+        super(config.azureEnvironment(), null /** retrieving MSI token does not require tenant **/);
+        this.configForVM = config;
+        this.configForAppService = null;
+        this.hostType = HostType.VIRTUAL_MACHINE;
+        this.maxRetry = config.maxRetry() < 0 ? MAX_RETRY_DEFAULT_LIMIT : config.maxRetry();
+        // Simplified variant of https://en.wikipedia.org/wiki/Exponential_backoff
+        for (int x = 0; x < this.maxRetry; x++) {
+            this.retrySlots.add(500 * ((2 << 1) - 1) / 1000);
+        }
+    }
+
+    private MSICredentials(MSIConfigurationForAppService config) {
+        super(config.azureEnvironment(), null /** retrieving MSI token does not require tenant **/);
+        this.configForAppService = config;
+        this.configForVM = null;
+        this.hostType = HostType.APP_SERVICE;
+        this.maxRetry = -1;
+    }
+
+    @Override
+    public Mono<String> getToken(String tokenAudience) {
+        switch (hostType) {
+            case VIRTUAL_MACHINE:
+                if (this.configForVM.tokenSource() == MSIConfigurationForVirtualMachine.MSITokenSource.MSI_EXTENSION) {
+                    return Mono.fromCallable(() -> this.getTokenForVirtualMachineFromMSIExtension(tokenAudience == null ? this.configForVM.resource() : tokenAudience));
+                } else {
+                    return Mono.fromCallable(() -> this.getTokenForVirtualMachineFromIMDSEndpoint(tokenAudience == null ? this.configForVM.resource() : tokenAudience));
+                }
+            case APP_SERVICE:
+                return Mono.fromCallable(() -> getTokenForAppService(tokenAudience));
+            default:
+                throw new IllegalArgumentException("unknown host type:" + hostType);
+        }
+    }
+
+    private String getTokenForAppService(String tokenAudience) throws IOException {
+        String urlString = String.format("%s?resource=%s&api-version=2017-09-01", this.configForAppService.msiEndpoint(), tokenAudience == null ? this.configForAppService.resource() : tokenAudience);
+        URL url = new URL(urlString);
+        HttpURLConnection connection = null;
+
+        try {
+            connection = (HttpURLConnection) url.openConnection();
+
+            connection.setRequestMethod("GET");
+            connection.setRequestProperty("Secret", this.configForAppService.msiSecret());
+            connection.setRequestProperty("Metadata", "true");
+
+            connection.connect();
+
+            InputStream stream = connection.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"), 100);
+            String result = reader.readLine();
+
+            MSIToken msiToken = adapter.deserialize(result, MSIToken.class, SerializerEncoding.JSON);
+            return msiToken.accessToken();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    private String getTokenForVirtualMachineFromMSIExtension(String tokenAudience) throws IOException {
+        URL url = new URL(String.format("http://localhost:%d/oauth2/token", this.configForVM.msiPort()));
+        String postData = String.format("resource=%s", tokenAudience);
+        if (this.configForVM.objectId() != null) {
+            postData += String.format("&object_id=%s", this.configForVM.objectId());
+        } else if (this.configForVM.clientId() != null) {
+            postData += String.format("&client_id=%s", this.configForVM.clientId());
+        } else if (this.configForVM.identityId() != null) {
+            postData += String.format("&msi_res_id=%s", this.configForVM.identityId());
+        }
+        HttpURLConnection connection = null;
+
+        try {
+            connection = (HttpURLConnection) url.openConnection();
+
+            connection.setRequestMethod("POST");
+            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
+            connection.setRequestProperty("Metadata", "true");
+            connection.setRequestProperty("Content-Length", Integer.toString(postData.length()));
+            connection.setDoOutput(true);
+
+            connection.connect();
+
+            OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream());
+            wr.write(postData);
+            wr.flush();
+
+            InputStream stream = connection.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"), 100);
+            String result = reader.readLine();
+
+            MSIToken msiToken = adapter.deserialize(result, MSIToken.class, SerializerEncoding.JSON);
+            return msiToken.accessToken();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw e;
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    private String getTokenForVirtualMachineFromIMDSEndpoint(String tokenAudience) {
+        MSIToken token = cache.get(tokenAudience);
+        if (token != null && !token.isExpired()) {
+            return token.accessToken();
+        }
+        lock.lock();
+        try {
+            token = cache.get(tokenAudience);
+            if (token != null && !token.isExpired()) {
+                return token.accessToken();
+            }
+            try {
+                token = retrieveTokenFromIDMSWithRetry(tokenAudience);
+                if (token != null) {
+                    cache.put(tokenAudience, token);
+                }
+            } catch (IOException exception) {
+                throw new RuntimeException(exception);
+            }
+            return token.accessToken();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private MSIToken retrieveTokenFromIDMSWithRetry(String tokenAudience) throws IOException {
+        StringBuilder payload = new StringBuilder();
+        final int imdsUpgradeTimeInMs = 70 * 1000;
+
+        //
+        try {
+            payload.append("api-version");
+            payload.append("=");
+            payload.append(URLEncoder.encode("2018-02-01", "UTF-8"));
+            payload.append("&");
+            payload.append("resource");
+            payload.append("=");
+            payload.append(URLEncoder.encode(tokenAudience, "UTF-8"));
+            if (this.configForVM.objectId() != null) {
+                payload.append("&");
+                payload.append("object_id");
+                payload.append("=");
+                payload.append(URLEncoder.encode(this.configForVM.objectId(), "UTF-8"));
+            } else if (this.configForVM.clientId() != null) {
+                payload.append("&");
+                payload.append("client_id");
+                payload.append("=");
+                payload.append(URLEncoder.encode(this.configForVM.clientId(), "UTF-8"));
+            } else if (this.configForVM.identityId() != null) {
+                payload.append("&");
+                payload.append("msi_res_id");
+                payload.append("=");
+                payload.append(URLEncoder.encode(this.configForVM.identityId(), "UTF-8"));
+            }
+        } catch (IOException exception) {
+            throw new RuntimeException(exception);
+        }
+
+        int retry = 1;
+        while (retry <= maxRetry) {
+            URL url = new URL(String.format("http://169.254.169.254/metadata/identity/oauth2/token?%s", payload.toString()));
+            //
+            HttpURLConnection connection = null;
+            //
+            try {
+                connection = (HttpURLConnection) url.openConnection();
+                connection.setRequestMethod("GET");
+                connection.setRequestProperty("Metadata", "true");
+                connection.connect();
+                InputStream stream = connection.getInputStream();
+                BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"), 100);
+                String result = reader.readLine();
+                return adapter.deserialize(result, MSIToken.class, SerializerEncoding.JSON);
+            } catch (Exception exception) {
+                int responseCode = connection.getResponseCode();
+                if (responseCode == 410 || responseCode == 429 || responseCode == 404 || (responseCode >= 500 && responseCode <= 599)) {
+                    int retryTimeoutInMs = retrySlots.get(new Random().nextInt(retry));
+                    // Error code 410 indicates IMDS upgrade is in progress, which can take up to 70s
+                    //
+                    retryTimeoutInMs = (responseCode == 410 && retryTimeoutInMs < imdsUpgradeTimeInMs) ? imdsUpgradeTimeInMs : retryTimeoutInMs;
+                    retry++;
+                    if (retry > maxRetry) {
+                        break;
+                    } else {
+                        sleep(retryTimeoutInMs);
+                    }
+                } else {
+                    throw new RuntimeException("Couldn't acquire access token from IMDS, verify your objectId, clientId or msiResourceId", exception);
+                }
+            } finally {
+                if (connection != null) {
+                    connection.disconnect();
+                }
+            }
+        }
+        //
+        if (retry > maxRetry) {
+            throw new RuntimeException(String.format("MSI: Failed to acquire tokens after retrying %s times", maxRetry));
+        }
+        return null;
+    }
+
+    private static void sleep(int millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * The host in which application is running.
+     */
+    private enum HostType {
+        /**
+         * indicate that host is an Azure virtual machine.
+         */
+        VIRTUAL_MACHINE,
+        /**
+         * indicate that host is an Azure app-service instance.
+         */
+        APP_SERVICE
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIToken.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIToken.java
new file mode 100644
index 0000000000000..f3b5d69684b12
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/MSIToken.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+
+/**
+ * Type representing response from the local MSI token provider.
+ */
+class MSIToken {
+
+    private static OffsetDateTime epoch = OffsetDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC);
+
+    @JsonProperty(value = "token_type")
+    private String tokenType;
+
+    @JsonProperty(value = "access_token")
+    private String accessToken;
+
+    @JsonProperty(value = "expires_on")
+    private String expiresOn;
+
+    String accessToken() {
+        return accessToken;
+    }
+
+    String tokenType() {
+        return tokenType;
+    }
+
+    boolean isExpired() {
+        OffsetDateTime now = OffsetDateTime.now();
+        OffsetDateTime expireOn = epoch.plusSeconds(Integer.parseInt(this.expiresOn));
+        return now.plusMinutes(5).isAfter(expireOn);
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/UserTokenCredentials.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/UserTokenCredentials.java
new file mode 100644
index 0000000000000..e4511a9d080df
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/UserTokenCredentials.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.azure.common.AzureEnvironment;
+import com.microsoft.aad.adal4j.AuthenticationContext;
+import com.microsoft.aad.adal4j.AuthenticationResult;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Token based credentials for use with a REST Service Client.
+ */
+public class UserTokenCredentials extends AzureTokenCredentials {
+    /** A mapping from resource endpoint to its cached access token. */
+    private Map<String, AuthenticationResult> tokens;
+    /** The Active Directory application client id. */
+    private String clientId;
+    /** The user name for the Organization Id account. */
+    private String username;
+    /** The password for the Organization Id account. */
+    private String password;
+
+    /**
+     * Initializes a new instance of the UserTokenCredentials.
+     *
+     * @param clientId the active directory application client id.
+     * @param domain the domain or tenant id containing this application.
+     * @param username the user name for the Organization Id account.
+     * @param password the password for the Organization Id account.
+     * @param environment the Azure environment to authenticate with.
+     *                    If null is provided, AzureEnvironment.AZURE will be used.
+     */
+    public UserTokenCredentials(String clientId, String domain, String username, String password, AzureEnvironment environment) {
+        super(environment, domain); // defer token acquisition
+        this.clientId = clientId;
+        this.username = username;
+        this.password = password;
+        this.tokens = new ConcurrentHashMap<>();
+    }
+
+    /**
+     * Gets the active directory application client id.
+     *
+     * @return the active directory application client id.
+     */
+    public String clientId() {
+        return clientId;
+    }
+
+    /**
+     * Gets the user name for the Organization Id account.
+     *
+     * @return the user name.
+     */
+    public String username() {
+        return username;
+    }
+
+    @Override
+    public synchronized Mono<String> getToken(String resource) {
+        // Find exact match for the resource
+        AuthenticationResult[] authenticationResult = new AuthenticationResult[1];
+        authenticationResult[0] = tokens.get(resource);
+        // Return if found and not expired
+        if (authenticationResult[0] != null && authenticationResult[0].getExpiresOnDate().after(new Date())) {
+            return Mono.just(authenticationResult[0].getAccessToken());
+        }
+        // If found then refresh
+        boolean shouldRefresh = authenticationResult[0] != null;
+        // If not found for the resource, but is MRRT then also refresh
+        if (authenticationResult[0] == null && !tokens.isEmpty()) {
+            authenticationResult[0] = new ArrayList<>(tokens.values()).get(0);
+            shouldRefresh = authenticationResult[0].isMultipleResourceRefreshToken();
+        }
+
+        if (shouldRefresh) {
+            return Mono.defer(() -> acquireAccessTokenFromRefreshToken(resource, authenticationResult[0].getRefreshToken(), authenticationResult[0].isMultipleResourceRefreshToken())
+                    .onErrorResume(t -> acquireNewAccessToken(resource))
+                    .doOnNext(ar -> tokens.put(resource, ar))
+                    .then(Mono.just(tokens.get(resource).getAccessToken())));
+        } else {
+            return Mono.just(tokens.get(resource).getAccessToken());
+        }
+    }
+
+    Mono<AuthenticationResult> acquireNewAccessToken(String resource) {
+        String authorityUrl = this.environment().activeDirectoryEndpoint() + this.domain();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        Mono<AuthenticationResult> authMono = Mono.defer(() -> {
+            AuthenticationContext context;
+            try {
+                context = new AuthenticationContext(authorityUrl, false, executor);
+            } catch (MalformedURLException mue) {
+                return Mono.error(mue);
+            }
+            if (proxy() != null) {
+                context.setProxy(proxy());
+            }
+            return Mono.create(callback -> {
+                context.acquireToken(
+                        resource,
+                        this.clientId(),
+                        this.username(),
+                        this.password,
+                        Util.authenticationDelegate(callback));
+            });
+        });
+        return authMono.doFinally(s -> executor.shutdown());
+    }
+
+    // Refresh tokens are currently not used since we don't know if the refresh token has expired
+    Mono<AuthenticationResult> acquireAccessTokenFromRefreshToken(String resource, String refreshToken, boolean isMultipleResourceRefreshToken) {
+        String authorityUrl = this.environment().activeDirectoryEndpoint() + this.domain();
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        Mono<AuthenticationResult> authMono = Mono.defer(() -> {
+            AuthenticationContext context;
+            try {
+                context = new AuthenticationContext(authorityUrl, false, executor);
+            } catch (MalformedURLException mue) {
+                return Mono.error(mue);
+            }
+            if (proxy() != null) {
+                context.setProxy(proxy());
+            }
+            return Mono.create(callback -> {
+                context.acquireTokenByRefreshToken(
+                        refreshToken,
+                        clientId(),
+                        resource,
+                        Util.authenticationDelegate(callback));
+            });
+        });
+        return authMono.doFinally(s -> executor.shutdown());
+    }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/Util.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/Util.java
new file mode 100644
index 0000000000000..7f9546575639a
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/Util.java
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.microsoft.aad.adal4j.AsymmetricKeyCredential;
+import com.microsoft.aad.adal4j.AuthenticationCallback;
+import com.microsoft.aad.adal4j.AuthenticationResult;
+import com.azure.common.implementation.util.Base64Util;
+import reactor.core.Exceptions;
+import reactor.core.publisher.MonoSink;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+final class Util {
+    static AuthenticationCallback authenticationDelegate(final MonoSink<AuthenticationResult> callback) {
+        return new AuthenticationCallback() {
+            @Override
+            public void onSuccess(Object o) {
+                callback.success((AuthenticationResult) o);
+            }
+
+            @Override
+            public void onFailure(Throwable throwable) {
+                callback.error(throwable);
+            }
+        };
+    }
+
+    static AsymmetricKeyCredential createAsymmetricKeyCredential(String clientId, byte[] clientCertificate, String clientCertificatePassword) {
+        try {
+            return AsymmetricKeyCredential.create(clientId, new ByteArrayInputStream(clientCertificate), clientCertificatePassword);
+        } catch (KeyStoreException kse) {
+            throw  Exceptions.propagate(kse);
+        } catch (NoSuchProviderException nspe) {
+            throw  Exceptions.propagate(nspe);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw  Exceptions.propagate(nsae);
+        } catch (CertificateException ce) {
+            throw  Exceptions.propagate(ce);
+        } catch (IOException ioe) {
+            throw  Exceptions.propagate(ioe);
+        } catch (UnrecoverableKeyException uke) {
+            throw  Exceptions.propagate(uke);
+        }
+    }
+
+
+    static PrivateKey privateKeyFromPem(String pem) {
+        Pattern pattern = Pattern.compile("(?s)-----BEGIN PRIVATE KEY-----.*-----END PRIVATE KEY-----");
+        Matcher matcher = pattern.matcher(pem);
+        matcher.find();
+        String base64 = matcher.group()
+                .replace("-----BEGIN PRIVATE KEY-----", "")
+                .replace("-----END PRIVATE KEY-----", "")
+                .replace("\n", "")
+                .replace("\r", "");
+        byte[] key = Base64Util.decode(base64.getBytes(StandardCharsets.UTF_8));
+        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key);
+        try {
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            return kf.generatePrivate(spec);
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    static X509Certificate publicKeyFromPem(String pem) {
+        Pattern pattern = Pattern.compile("(?s)-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----");
+        Matcher matcher = pattern.matcher(pem);
+        matcher.find();
+        try {
+            CertificateFactory factory = CertificateFactory.getInstance("X.509");
+            InputStream stream = new ByteArrayInputStream(matcher.group().getBytes());
+            return (X509Certificate) factory.generateCertificate(stream);
+        } catch (CertificateException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private Util() { }
+}
diff --git a/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/package-info.java b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/package-info.java
new file mode 100644
index 0000000000000..d600561d46f95
--- /dev/null
+++ b/common/azure-common-auth/src/main/java/com/azure/common/auth/credentials/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The package provides credential classes for Azure authentication purposes.
+ */
+package com.azure.common.auth.credentials;
\ No newline at end of file
diff --git a/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/AuthFileTests.java b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/AuthFileTests.java
new file mode 100644
index 0000000000000..ae4bfba1a3a97
--- /dev/null
+++ b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/AuthFileTests.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+
+public class AuthFileTests {
+    @Test
+    public void canReadJavaPropertiesAuthFile() throws Exception {
+        File file = new File(AuthFileTests.class.getResource("/properties.azureauth").toURI());
+        AuthFile authFile = AuthFile.parse(file);
+        ApplicationTokenCredentials credentials = authFile.generateCredentials();
+
+        Assert.assertNotNull(credentials);
+        Assert.assertEquals("test-client", credentials.clientId());
+        Assert.assertEquals("test-tenant", credentials.domain());
+        Assert.assertEquals("test-subscription", credentials.defaultSubscriptionId());
+        Assert.assertEquals("https://testbase.com/", credentials.environment().resourceManagerEndpoint());
+        Assert.assertEquals("https://testmanagement.com/", credentials.environment().managementEndpoint());
+        Assert.assertEquals("https://testgraph.net/", credentials.environment().graphEndpoint());
+        Assert.assertEquals("https://testauth.net/", credentials.environment().activeDirectoryEndpoint());
+        Assert.assertEquals("https://management.core.windows.net:8443/", credentials.environment().sqlManagementEndpoint());
+    }
+
+    @Test
+    public void canReadJsonAuthFile() throws Exception {
+        File file = new File(AuthFileTests.class.getResource("/json.azureauth").toURI());
+        AuthFile authFile = AuthFile.parse(file);
+        ApplicationTokenCredentials credentials = authFile.generateCredentials();
+
+        Assert.assertNotNull(credentials);
+        Assert.assertEquals("sample-clientid", credentials.clientId());
+        Assert.assertEquals("sample-tenant", credentials.domain());
+        Assert.assertEquals("sample-subscription", credentials.defaultSubscriptionId());
+        Assert.assertEquals("https://samplearm.com/", credentials.environment().resourceManagerEndpoint());
+        Assert.assertEquals("https://samplemanagement.net/", credentials.environment().managementEndpoint());
+        Assert.assertEquals("https://samplegraph.net/", credentials.environment().graphEndpoint());
+        Assert.assertEquals("https://samplead.com/", credentials.environment().activeDirectoryEndpoint());
+        Assert.assertEquals("https://samplesql.net:8443/", credentials.environment().sqlManagementEndpoint());
+        Assert.assertEquals("http://go.microsoft.com/fwlink/?LinkId=254432", credentials.environment().publishingProfile());
+    }
+}
diff --git a/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/UserTokenCredentialsTests.java b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/UserTokenCredentialsTests.java
new file mode 100644
index 0000000000000..20d0d3640004d
--- /dev/null
+++ b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/UserTokenCredentialsTests.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials;
+
+import com.azure.common.AzureEnvironment;
+import com.microsoft.aad.adal4j.AuthenticationResult;
+import org.junit.Assert;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import java.util.Date;
+
+public class UserTokenCredentialsTests {
+    private static MockUserTokenCredentials credentials = new MockUserTokenCredentials(
+            "clientId",
+            "domain",
+            "username",
+            "password",
+            AzureEnvironment.AZURE
+    );
+
+    @Test
+    public void testAcquireToken() throws Exception {
+        credentials.acquireAccessToken();
+        Assert.assertEquals("token1", credentials.getToken((String)null).block());
+        Thread.sleep(1500);
+        Assert.assertEquals("token2", credentials.getToken((String)null).block());
+    }
+
+    public static class MockUserTokenCredentials extends UserTokenCredentials {
+        private AuthenticationResult authenticationResult;
+
+        public MockUserTokenCredentials(String clientId, String domain, String username, String password, AzureEnvironment environment) {
+            super(clientId, domain, username, password, environment);
+        }
+
+        @Override
+        public Mono<String> getToken(String resource) {
+            if (authenticationResult != null
+                && authenticationResult.getExpiresOnDate().before(new Date())) {
+                acquireAccessTokenFromRefreshToken();
+            } else {
+                acquireAccessToken();
+            }
+            return Mono.just(authenticationResult.getAccessToken());
+        }
+
+        private void acquireAccessToken() {
+            this.authenticationResult = new AuthenticationResult(
+                    null,
+                    "token1",
+                    "refresh",
+                    1,
+                    null,
+                    null,
+                    false);
+        }
+
+        private void acquireAccessTokenFromRefreshToken() {
+            this.authenticationResult = new AuthenticationResult(
+                    null,
+                    "token2",
+                    "refresh",
+                    1,
+                    null,
+                    null,
+                    false);
+        }
+    }
+}
diff --git a/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/http/MockHttpClient.java b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/http/MockHttpClient.java
new file mode 100644
index 0000000000000..40704be07d324
--- /dev/null
+++ b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/http/MockHttpClient.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials.http;
+
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.ProxyOptions;
+import reactor.core.publisher.Mono;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * This HttpClient attempts to mimic the behavior of http://httpbin.org without ever making a network call.
+ */
+public class MockHttpClient implements HttpClient {
+    private static final HttpResponse mockResponse = new MockHttpResponse(200);
+    private final List<HttpRequest> requests;
+
+    public MockHttpClient() {
+        requests = new ArrayList<>();
+    }
+
+    public List<HttpRequest> requests() {
+        return requests;
+    }
+
+    @Override
+    public Mono<HttpResponse> send(HttpRequest request) {
+        requests.add(request);
+
+        return Mono.just(mockResponse);
+    }
+
+    @Override
+    public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+        throw new IllegalStateException("MockHttpClient.proxy");
+    }
+
+    @Override
+    public HttpClient wiretap(boolean enableWiretap) {
+        throw new IllegalStateException("MockHttpClient.wiretap");
+    }
+
+    @Override
+    public HttpClient port(int port) {
+        throw new IllegalStateException("MockHttpClient.port");
+    }
+}
diff --git a/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/http/MockHttpResponse.java b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/http/MockHttpResponse.java
new file mode 100644
index 0000000000000..36b9587af4b91
--- /dev/null
+++ b/common/azure-common-auth/src/test/java/com/azure/common/auth/credentials/http/MockHttpResponse.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.auth.credentials.http;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+public class MockHttpResponse extends HttpResponse {
+    private final static SerializerAdapter serializer = new JacksonAdapter();
+
+    private final int statusCode;
+
+    private final HttpHeaders headers;
+
+    private byte[] byteArray;
+    private String string;
+
+    public MockHttpResponse(int statusCode) {
+        this.statusCode = statusCode;
+
+        headers = new HttpHeaders();
+    }
+
+    public MockHttpResponse(int statusCode, byte[] byteArray) {
+        this(statusCode);
+
+        this.byteArray = byteArray;
+    }
+
+    public MockHttpResponse(int statusCode, String string) {
+        this(statusCode);
+
+        this.string = string;
+    }
+
+    public MockHttpResponse(int statusCode, Object serializable) {
+        this(statusCode);
+
+        try {
+            this.string = serializer.serialize(serializable, SerializerEncoding.JSON);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public int statusCode() {
+        return statusCode;
+    }
+
+    @Override
+    public String headerValue(String name) {
+        return headers.value(name);
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        return new HttpHeaders(headers);
+    }
+
+    @Override
+    public Mono<byte[]> bodyAsByteArray() {
+        return Mono.just(byteArray);
+    }
+
+    @Override
+    public Flux<ByteBuf> body() {
+        return Flux.just(Unpooled.wrappedBuffer(byteArray));
+    }
+
+    @Override
+    public Mono<String> bodyAsString() {
+        return Mono.just(string);
+    }
+
+    @Override
+    public Mono<String> bodyAsString(Charset charset) {
+        return Mono.just(string);
+    }
+}
diff --git a/common/azure-common-auth/src/test/resources/json.azureauth b/common/azure-common-auth/src/test/resources/json.azureauth
new file mode 100644
index 0000000000000..63a25e228f8a3
--- /dev/null
+++ b/common/azure-common-auth/src/test/resources/json.azureauth
@@ -0,0 +1,12 @@
+{
+  "clientId": "sample-clientid",
+  "clientSecret": "sample-clientsecret",
+  "subscriptionId": "sample-subscription",
+  "tenantId": "sample-tenant",
+  "activeDirectoryEndpointUrl": "https://samplead.com/",
+  "resourceManagerEndpointUrl": "https://samplearm.com/",
+  "activeDirectoryGraphResourceId": "https://samplegraph.net/",
+  "sqlManagementEndpointUrl": "https://samplesql.net:8443/",
+  "galleryEndpointUrl": "https://samplegallery.com/",
+  "managementEndpointUrl": "https://samplemanagement.net/"
+}
diff --git a/common/azure-common-auth/src/test/resources/properties.azureauth b/common/azure-common-auth/src/test/resources/properties.azureauth
new file mode 100644
index 0000000000000..8fe4f029b5ddf
--- /dev/null
+++ b/common/azure-common-auth/src/test/resources/properties.azureauth
@@ -0,0 +1,8 @@
+subscription=test-subscription
+tenant=test-tenant
+client=test-client
+key=test-key!12
+managementURI=https\://testmanagement.com/
+baseURL=https\://testbase.com/
+authURL=https\://testauth.net/
+graphURL=https\://testgraph.net/
\ No newline at end of file
diff --git a/common/azure-common-mgmt/pom.xml b/common/azure-common-mgmt/pom.xml
new file mode 100644
index 0000000000000..2c94821407962
--- /dev/null
+++ b/common/azure-common-mgmt/pom.xml
@@ -0,0 +1,99 @@
+<!--
+ Copyright (c) Microsoft Corporation. All rights reserved.
+ Licensed under the MIT License. See License.txt in the project root for
+ license information.
+-->
+<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">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.azure</groupId>
+    <artifactId>azure-common-parent</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <groupId>com.azure</groupId>
+  <artifactId>azure-common-mgmt</artifactId>
+  <version>1.0.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>Azure Management Java Common Library</name>
+  <description>This package contains common types for Azure management (ARM) Java clients.</description>
+  <url>https://github.com/Azure/autorest-clientruntime-for-java</url>
+
+  <licenses>
+    <license>
+      <name>The MIT License (MIT)</name>
+      <url>http://opensource.org/licenses/MIT</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <scm>
+    <url>scm:git:https://github.com/Azure/autorest-clientruntime-for-java</url>
+    <connection>scm:git:git@github.com:Azure/autorest-clientruntime-for-java.git</connection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <legal><![CDATA[[INFO] Any downloads listed may be third party software.  Microsoft grants you no rights for third party software.]]></legal>
+  </properties>
+
+  <developers>
+    <developer>
+      <id>microsoft</id>
+      <name>Microsoft</name>
+    </developer>
+  </developers>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.azure</groupId>
+      <artifactId>azure-common</artifactId>
+      <version>1.0.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.1</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.8</version>
+        <configuration>
+          <excludePackageNames>*.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization;*.blob.core.storage</excludePackageNames>
+          <bottom><![CDATA[<code>/**
+<br />* Copyright (c) Microsoft Corporation. All rights reserved.
+<br />* Licensed under the MIT License. See License.txt in the project root for
+<br />* license information.
+<br />*/</code>]]></bottom>
+        </configuration>
+      </plugin>
+
+    </plugins>
+  </build>
+</project>
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AsyncOperationResource.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AsyncOperationResource.java
new file mode 100644
index 0000000000000..f3cf8d0ac9706
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AsyncOperationResource.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * A deserialized POJO representation of an asynchronous operation.
+ */
+public class AsyncOperationResource {
+    @JsonProperty(value = "status")
+    private String status;
+
+    /**
+     * The status of the asynchronous operation.
+     * @return The status of the asynchronous operation.
+     */
+    public String status() {
+        return status;
+    }
+
+    /**
+     * Set the status of the asynchronous operation.
+     * @param status The status of the asynchronous operation.
+     */
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    @JsonProperty(value = "id")
+    private String id;
+
+    /**
+     * @return The resource's id.
+     */
+    public String id() {
+        return id;
+    }
+
+    /**
+     * Set the id of this resource.
+     * @param id The id of this resource.
+     */
+    public void setId(String id) {
+        this.id = id;
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureAsyncOperationPollStrategy.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureAsyncOperationPollStrategy.java
new file mode 100644
index 0000000000000..c5bda8e9fbe43
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureAsyncOperationPollStrategy.java
@@ -0,0 +1,207 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.implementation.SwaggerMethodParser;
+import com.azure.common.implementation.RestProxy;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * A PollStrategy type that uses the Azure-AsyncOperation header value to check the status of a long
+ * running operation.
+ */
+public final class AzureAsyncOperationPollStrategy extends PollStrategy {
+    private AzureAsyncOperationPollStrategyData data;
+
+    /**
+     * The name of the header that indicates that a long running operation will use the
+     * Azure-AsyncOperation strategy.
+     */
+    public static final String HEADER_NAME = "Azure-AsyncOperation";
+
+    /**
+     * Create a new AzureAsyncOperationPollStrategy object that will poll the provided operation
+     * resource URL.
+     * @param data The AzureAsyncOperationPollStrategyData data object.
+     */
+    private AzureAsyncOperationPollStrategy(AzureAsyncOperationPollStrategyData data) {
+        super(data);
+        this.data = data;
+    }
+
+    /**
+     * The AzureAsyncOperationPollStrategy data.
+     */
+    private static class AzureAsyncOperationPollStrategyData extends PollStrategyData {
+        private boolean pollingCompleted;
+        private boolean pollingSucceeded;
+        private boolean gotResourceResponse;
+        private final HttpMethod initialHttpMethod;
+
+        final URL operationResourceUrl;
+        final URL originalResourceUrl;
+        final URL locationUrl;
+
+        /**
+         * Create a new AzureAsyncOperationPollStrategyData object that will poll the provided operation
+         * resource URL.
+         * @param operationResourceUrl The URL of the operation resource this pollStrategy will poll.
+         * @param originalResourceUrl  The URL of the resource that the long running operation is
+         *                             operating on.
+         * @param locationUrl          The location uri received from service along with operationResourceUrl.
+         * @param initialHttpMethod    The http method used to initiate the long running operation
+         * @param delayInMilliseconds  The delay (in milliseconds) that the pollStrategy will use when
+         *                             polling.
+         */
+        AzureAsyncOperationPollStrategyData(RestProxy restProxy, SwaggerMethodParser methodParser, URL operationResourceUrl, URL originalResourceUrl, URL locationUrl, HttpMethod initialHttpMethod, long delayInMilliseconds) {
+            super(restProxy, methodParser, delayInMilliseconds);
+            this.operationResourceUrl = operationResourceUrl;
+            this.originalResourceUrl = originalResourceUrl;
+            this.locationUrl = locationUrl;
+            this.initialHttpMethod = initialHttpMethod;
+        }
+
+        PollStrategy initializeStrategy(RestProxy restProxy,
+                                        SwaggerMethodParser methodParser) {
+            this.restProxy = restProxy;
+            this.methodParser = methodParser;
+            return new AzureAsyncOperationPollStrategy(this);
+        }
+    }
+
+    @Override
+    public HttpRequest createPollRequest() {
+        URL pollUrl;
+        if (!data.pollingCompleted) {
+            pollUrl = data.operationResourceUrl;
+        } else if (data.pollingSucceeded) {
+            if (data.initialHttpMethod == HttpMethod.POST || data.initialHttpMethod == HttpMethod.DELETE) {
+                if (data.locationUrl != null) {
+                    pollUrl = data.locationUrl;
+                } else {
+                    pollUrl = data.operationResourceUrl;
+                }
+            } else {
+                // For PUT|PATCH do a final get on the original resource uri.
+                //
+                pollUrl = data.originalResourceUrl;
+            }
+        } else {
+            throw new IllegalStateException("Polling is completed and did not succeed. Cannot create a polling request.");
+        }
+
+        return new HttpRequest(HttpMethod.GET, pollUrl);
+    }
+
+    @Override
+    public Mono<HttpResponse> updateFromAsync(HttpResponse httpPollResponse) {
+        return ensureExpectedStatus(httpPollResponse)
+                .flatMap(response -> {
+                    updateDelayInMillisecondsFrom(response);
+                    Mono<HttpResponse> result;
+                    if (!data.pollingCompleted) {
+                        final HttpResponse bufferedHttpPollResponse = response.buffer();
+                        result = bufferedHttpPollResponse.bodyAsString()
+                                .map(bodyString -> {
+                                    AsyncOperationResource operationResource = null;
+                                    try {
+                                        operationResource = deserialize(bodyString, AsyncOperationResource.class);
+                                    } catch (IOException ignored) { }
+                                    //
+                                    if (operationResource == null || operationResource.status() == null) {
+                                        throw new CloudException("The polling response does not contain a valid body", bufferedHttpPollResponse, null);
+                                    } else {
+                                        final String status = operationResource.status();
+                                        setStatus(status);
+
+                                        data.pollingCompleted = OperationState.isCompleted(status);
+                                        if (data.pollingCompleted) {
+                                            data.pollingSucceeded = OperationState.SUCCEEDED.equalsIgnoreCase(status);
+                                            clearDelayInMilliseconds();
+
+                                            if (!data.pollingSucceeded) {
+                                                throw new CloudException("Async operation failed with provisioning state: " + status, bufferedHttpPollResponse);
+                                            }
+
+                                            if (operationResource.id() != null) {
+                                                data.gotResourceResponse = true;
+                                            }
+                                        }
+                                        return bufferedHttpPollResponse;
+                                    }
+                                });
+                    } else {
+                        if (data.pollingSucceeded) {
+                            data.gotResourceResponse = true;
+                        }
+                        result = Mono.just(response);
+                    }
+                    return result;
+                });
+    }
+
+    @Override
+    public boolean isDone() {
+        return data.pollingCompleted && (!data.pollingSucceeded || !expectsResourceResponse() || data.gotResourceResponse);
+    }
+
+    /**
+     * Try to create a new AzureAsyncOperationPollStrategy object that will poll the provided
+     * operation resource URL. If the provided HttpResponse doesn't have an Azure-AsyncOperation
+     * header or if the header is empty, then null will be returned.
+     * @param restProxy The proxy object that is attempting to create a PollStrategy.
+     * @param methodParser The method parser that describes the service interface method that
+     *                     initiated the long running operation.
+     * @param originalHttpRequest The original HTTP request that initiated the long running
+     *                            operation.
+     * @param httpResponse The HTTP response that the required header values for this pollStrategy
+     *                     will be read from.
+     * @param delayInMilliseconds The delay (in milliseconds) that the resulting pollStrategy will
+     *                            use when polling.
+     */
+    static PollStrategy tryToCreate(RestProxy restProxy, SwaggerMethodParser methodParser, HttpRequest originalHttpRequest, HttpResponse httpResponse, long delayInMilliseconds) {
+        String urlHeader = getHeader(httpResponse);
+        URL azureAsyncOperationUrl = null;
+        if (urlHeader != null) {
+            try {
+                azureAsyncOperationUrl = new URL(urlHeader);
+            } catch (MalformedURLException ignored) {
+            }
+        }
+
+        urlHeader = httpResponse.headerValue("Location");
+        URL locationUrl = null;
+        if (urlHeader != null) {
+            try {
+                locationUrl = new URL(urlHeader);
+            } catch (MalformedURLException ignored) {
+            }
+        }
+
+        return azureAsyncOperationUrl != null
+                ? new AzureAsyncOperationPollStrategy(
+                        new AzureAsyncOperationPollStrategyData(restProxy, methodParser, azureAsyncOperationUrl, originalHttpRequest.url(), locationUrl, originalHttpRequest.httpMethod(), delayInMilliseconds))
+                : null;
+    }
+
+    static String getHeader(HttpResponse httpResponse) {
+        return httpResponse.headerValue(HEADER_NAME);
+    }
+
+    @Override
+    public Serializable strategyData() {
+        return this.data;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureProxy.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureProxy.java
new file mode 100644
index 0000000000000..2d0f36949ae73
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureProxy.java
@@ -0,0 +1,407 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.AzureEnvironment;
+import com.azure.common.credentials.AsyncServiceClientCredentials;
+import com.azure.common.mgmt.annotations.AzureHost;
+import com.azure.common.mgmt.policy.AsyncCredentialsPolicy;
+import com.azure.common.mgmt.serializer.AzureJacksonAdapter;
+import com.azure.common.implementation.exception.InvalidReturnTypeException;
+import com.azure.common.implementation.OperationDescription;
+import com.azure.common.implementation.RestProxy;
+import com.azure.common.implementation.SwaggerInterfaceParser;
+import com.azure.common.implementation.SwaggerMethodParser;
+import com.azure.common.credentials.ServiceClientCredentials;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.policy.HttpPipelinePolicy;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.policy.CookiePolicy;
+import com.azure.common.http.policy.CredentialsPolicy;
+import com.azure.common.http.policy.RetryPolicy;
+import com.azure.common.http.policy.UserAgentPolicy;
+import com.azure.common.implementation.serializer.HttpResponseDecoder;
+import com.azure.common.implementation.serializer.HttpResponseDecoder.HttpDecodedResponse;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.util.TypeUtil;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.net.NetworkInterface;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * This class can be used to create an Azure specific proxy implementation for a provided Swagger
+ * generated interface.
+ */
+public final class AzureProxy extends RestProxy {
+    private static long defaultPollingDelayInMilliseconds = 30 * 1000;
+
+    /**
+     * Create a new instance of RestProxy.
+     * @param httpPipeline The HttpPipeline that will be used by this AzureProxy to send HttpRequests.
+     * @param serializer The serializer that will be used to convert response bodies to POJOs.
+     * @param interfaceParser The parser that contains information about the swagger interface that
+     *                        this RestProxy "implements".
+     */
+    private AzureProxy(HttpPipeline httpPipeline, SerializerAdapter serializer, SwaggerInterfaceParser interfaceParser) {
+        super(httpPipeline, serializer, interfaceParser);
+    }
+
+    /**
+     * @return The millisecond delay that will occur by default between long running operation polls.
+     */
+    public static long defaultDelayInMilliseconds() {
+        return AzureProxy.defaultPollingDelayInMilliseconds;
+    }
+
+    /**
+     * Set the millisecond delay that will occur by default between long running operation polls.
+     * @param defaultPollingDelayInMilliseconds The number of milliseconds to delay before sending the next
+     *                                   long running operation status poll.
+     */
+    public static void setDefaultPollingDelayInMilliseconds(long defaultPollingDelayInMilliseconds) {
+        AzureProxy.defaultPollingDelayInMilliseconds = defaultPollingDelayInMilliseconds;
+    }
+
+    /**
+     * Get the default serializer.
+     * @return the default serializer.
+     */
+    public static SerializerAdapter createDefaultSerializer() {
+        return new AzureJacksonAdapter();
+    }
+
+    private static String operatingSystem;
+    private static String operatingSystem() {
+        if (operatingSystem == null) {
+            operatingSystem = System.getProperty("os.name") + "/" + System.getProperty("os.version");
+        }
+        return operatingSystem;
+    }
+
+    private static String macAddressHash;
+    private static String macAddressHash() {
+        if (macAddressHash == null) {
+            byte[] macBytes = null;
+            try {
+                Enumeration<NetworkInterface> networks = NetworkInterface.getNetworkInterfaces();
+                while (networks.hasMoreElements()) {
+                    NetworkInterface network = networks.nextElement();
+                    macBytes = network.getHardwareAddress();
+
+                    if (macBytes != null) {
+                        break;
+                    }
+                }
+
+                MessageDigest digest = MessageDigest.getInstance("SHA-256");
+                byte[] hash = digest.digest(macBytes);
+                StringBuffer builder = new StringBuffer();
+                for (int i = 0; i < hash.length; i++) {
+                    builder.append(String.format("%02x", hash[i]));
+                }
+                macAddressHash = builder.toString();
+            } catch (Throwable t) {
+                // It's okay ignore mac address hash telemetry
+            }
+
+            if (macBytes == null) {
+                macAddressHash = "Unknown";
+            }
+
+        }
+        return macAddressHash;
+    }
+
+    private static String javaVersion;
+    private static String javaVersion() {
+        if (javaVersion == null) {
+            final String versionProperty = System.getProperty("java.version");
+            javaVersion = versionProperty != null ? versionProperty : "Unknown";
+        }
+        return javaVersion;
+    }
+
+    private static String getDefaultUserAgentString(Class<?> swaggerInterface) {
+        final String packageImplementationVersion = swaggerInterface == null ? "" : "/" + swaggerInterface.getPackage().getImplementationVersion();
+        final String operatingSystem = operatingSystem();
+        final String macAddressHash = macAddressHash();
+        final String javaVersion = javaVersion();
+        return String.format("Azure-SDK-For-Java%s OS:%s MacAddressHash:%s Java:%s",
+                packageImplementationVersion,
+                operatingSystem,
+                macAddressHash,
+                javaVersion);
+    }
+
+    /**
+     * Create the default HttpPipeline.
+     * @param swaggerInterface The interface that the pipeline will use to generate a user-agent
+     *                         string.
+     * @return the default HttpPipeline.
+     */
+    public static HttpPipeline createDefaultPipeline(Class<?> swaggerInterface) {
+        return createDefaultPipeline(swaggerInterface, (HttpPipelinePolicy) null);
+    }
+
+    /**
+     * Create the default HttpPipeline.
+     * @param swaggerInterface The interface that the pipeline will use to generate a user-agent
+     *                         string.
+     * @param credentials The credentials to use to apply authentication to the pipeline.
+     * @return the default HttpPipeline.
+     */
+    public static HttpPipeline createDefaultPipeline(Class<?> swaggerInterface, ServiceClientCredentials credentials) {
+        return createDefaultPipeline(swaggerInterface, new CredentialsPolicy(credentials));
+    }
+
+    /**
+     * Create the default HttpPipeline.
+     * @param swaggerInterface The interface that the pipeline will use to generate a user-agent
+     *                         string.
+     * @param credentials The credentials to use to apply authentication to the pipeline.
+     * @return the default HttpPipeline.
+     */
+    public static HttpPipeline createDefaultPipeline(Class<?> swaggerInterface, AsyncServiceClientCredentials credentials) {
+        return createDefaultPipeline(swaggerInterface, new AsyncCredentialsPolicy(credentials));
+    }
+
+    /**
+     * Create the default HttpPipeline.
+     * @param swaggerInterface The interface that the pipeline will use to generate a user-agent
+     *                         string.
+     * @param credentialsPolicy The credentials policy factory to use to apply authentication to the
+     *                          pipeline.
+     * @return the default HttpPipeline.
+     */
+    public static HttpPipeline createDefaultPipeline(Class<?> swaggerInterface, HttpPipelinePolicy credentialsPolicy) {
+        // Order in which policies applied will be the order in which they appear in the array
+        //
+        List<HttpPipelinePolicy> policies = new ArrayList<HttpPipelinePolicy>();
+        policies.add(new UserAgentPolicy(getDefaultUserAgentString(swaggerInterface)));
+        policies.add(new RetryPolicy());
+        policies.add(new CookiePolicy());
+        if (credentialsPolicy != null) {
+            policies.add(credentialsPolicy);
+        }
+        return new HttpPipeline(policies.toArray(new HttpPipelinePolicy[policies.size()]));
+    }
+
+    /**
+     * Create a proxy implementation of the provided Swagger interface.
+     * @param swaggerInterface The Swagger interface to provide a proxy implementation for.
+     * @param azureServiceClient The AzureServiceClient that contains the details to use to create
+     *                          the AzureProxy implementation of the swagger interface.
+     * @param <A> The type of the Swagger interface.
+     * @return A proxy implementation of the provided Swagger interface.
+     */
+    @SuppressWarnings("unchecked")
+    public static <A> A create(Class<A> swaggerInterface, AzureServiceClient azureServiceClient) {
+        return AzureProxy.create(swaggerInterface, azureServiceClient.azureEnvironment(), azureServiceClient.httpPipeline(), azureServiceClient.serializerAdapter());
+    }
+
+    /**
+     * Create a proxy implementation of the provided Swagger interface.
+     * @param swaggerInterface The Swagger interface to provide a proxy implementation for.
+     * @param httpPipeline The HTTP httpPipeline will be used to make REST calls.
+     * @param serializer The serializer that will be used to convert POJOs to and from request and
+     *                   response bodies.
+     * @param <A> The type of the Swagger interface.
+     * @return A proxy implementation of the provided Swagger interface.
+     */
+    @SuppressWarnings("unchecked")
+    public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline, SerializerAdapter serializer) {
+        return AzureProxy.create(swaggerInterface, null, httpPipeline, serializer);
+    }
+
+    /**
+     * Create a proxy implementation of the provided Swagger interface.
+     * @param swaggerInterface The Swagger interface to provide a proxy implementation for.
+     * @param azureEnvironment The azure environment that the proxy implementation will target.
+     * @param httpPipeline The HTTP httpPipeline will be used to make REST calls.
+     * @param serializer The serializer that will be used to convert POJOs to and from request and
+     *                   response bodies.
+     * @param <A> The type of the Swagger interface.
+     * @return A proxy implementation of the provided Swagger interface.
+     */
+    @SuppressWarnings("unchecked")
+    public static <A> A create(Class<A> swaggerInterface, AzureEnvironment azureEnvironment, HttpPipeline httpPipeline, SerializerAdapter serializer) {
+        String baseUrl = null;
+
+        if (azureEnvironment != null) {
+            final AzureHost azureHost = swaggerInterface.getAnnotation(AzureHost.class);
+            if (azureHost != null) {
+                baseUrl = azureEnvironment.url(azureHost.endpoint());
+            }
+        }
+
+        final SwaggerInterfaceParser interfaceParser = new SwaggerInterfaceParser(swaggerInterface, serializer, baseUrl);
+        final AzureProxy azureProxy = new AzureProxy(httpPipeline, serializer, interfaceParser);
+        return (A) Proxy.newProxyInstance(swaggerInterface.getClassLoader(), new Class[]{swaggerInterface}, azureProxy);
+    }
+
+    @Override
+    protected Object handleHttpResponse(final HttpRequest httpRequest, Mono<HttpDecodedResponse> asyncHttpResponse, final SwaggerMethodParser methodParser, Type returnType) {
+        if (TypeUtil.isTypeOrSubTypeOf(returnType, Flux.class)) {
+            final Type operationStatusType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
+            if (!TypeUtil.isTypeOrSubTypeOf(operationStatusType, OperationStatus.class)) {
+                throw new InvalidReturnTypeException("AzureProxy only supports swagger interface methods that return Flux (such as " + methodParser.fullyQualifiedMethodName() + "()) if the Flux's inner type that is OperationStatus (not " + returnType.toString() + ").");
+            } else {
+                // Get ResultTypeT in OperationStatus<ResultTypeT>
+                final Type operationStatusResultType = ((ParameterizedType) operationStatusType).getActualTypeArguments()[0];
+                //
+                return asyncHttpResponse.flatMapMany(httpResponse -> {
+                    return createPollStrategy(httpRequest, Mono.just(httpResponse), methodParser)
+                            .flatMapMany(pollStrategy -> {
+                                Mono<OperationStatus<Object>> first = handleBodyReturnType(httpResponse, methodParser, operationStatusResultType)
+                                        .map(operationResult -> new OperationStatus<Object>(operationResult, pollStrategy.status()))
+                                        .switchIfEmpty(Mono.defer((Supplier<Mono<OperationStatus<Object>>>) () -> Mono.just(new OperationStatus<Object>((Object) null, pollStrategy.status()))));
+                                Flux<OperationStatus<Object>> rest = pollStrategy.pollUntilDoneWithStatusUpdates(httpRequest, methodParser, operationStatusResultType);
+                                return first.concatWith(rest);
+                            });
+                });
+            }
+        } else {
+            final Mono<HttpResponse> lastAsyncHttpResponse = createPollStrategy(httpRequest, asyncHttpResponse, methodParser)
+                    .flatMap((Function<PollStrategy, Mono<HttpResponse>>) pollStrategy -> pollStrategy.pollUntilDone());
+            return handleRestReturnType(new HttpResponseDecoder(this.serializer()).decode(lastAsyncHttpResponse, methodParser), methodParser, returnType);
+        }
+    }
+
+    @Override
+    protected Object handleResumeOperation(final HttpRequest httpRequest,
+                                           OperationDescription operationDescription,
+                                           final SwaggerMethodParser methodParser,
+                                           Type returnType) {
+        final Type operationStatusType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
+        if (!TypeUtil.isTypeOrSubTypeOf(operationStatusType, OperationStatus.class)) {
+            throw new InvalidReturnTypeException("AzureProxy only supports swagger interface methods that return Flux (such as " + methodParser.fullyQualifiedMethodName() + "()) if the Flux's inner type that is OperationStatus (not " + returnType.toString() + ").");
+        }
+
+        PollStrategy.PollStrategyData pollStrategyData =
+                (PollStrategy.PollStrategyData) operationDescription.pollStrategyData();
+        PollStrategy pollStrategy = pollStrategyData.initializeStrategy(this, methodParser);
+        return pollStrategy.pollUntilDoneWithStatusUpdates(httpRequest, methodParser, operationStatusType);
+    }
+
+    private Mono<PollStrategy> createPollStrategy(final HttpRequest originalHttpRequest, final Mono<HttpDecodedResponse> asyncOriginalHttpDecodedResponse, final SwaggerMethodParser methodParser) {
+        return asyncOriginalHttpDecodedResponse
+                .flatMap((Function<HttpDecodedResponse, Mono<PollStrategy>>) originalHttpDecodedResponse -> {
+                    final int httpStatusCode = originalHttpDecodedResponse.sourceResponse().statusCode();
+                    final HttpResponse originalHttpResponse = originalHttpDecodedResponse.sourceResponse();
+                    final int[] longRunningOperationStatusCodes = new int[] {200, 201, 202};
+                    return ensureExpectedStatus(originalHttpDecodedResponse, methodParser, longRunningOperationStatusCodes)
+                            .flatMap(response -> {
+                                Mono<PollStrategy> result = null;
+
+                                final Long parsedDelayInMilliseconds = PollStrategy.delayInMillisecondsFrom(originalHttpResponse);
+                                final long delayInMilliseconds = parsedDelayInMilliseconds != null ? parsedDelayInMilliseconds : AzureProxy.defaultDelayInMilliseconds();
+
+                                final HttpMethod originalHttpRequestMethod = originalHttpRequest.httpMethod();
+
+                                PollStrategy pollStrategy = null;
+                                if (httpStatusCode == 200) {
+                                    pollStrategy = AzureAsyncOperationPollStrategy.tryToCreate(AzureProxy.this, methodParser, originalHttpRequest, originalHttpResponse, delayInMilliseconds);
+                                    if (pollStrategy != null) {
+                                        result = Mono.just(pollStrategy);
+                                    }
+                                    else {
+                                        result = createProvisioningStateOrCompletedPollStrategy(originalHttpRequest, originalHttpResponse, methodParser, delayInMilliseconds);
+                                    }
+                                }
+                                else if (originalHttpRequestMethod == HttpMethod.PUT || originalHttpRequestMethod == HttpMethod.PATCH) {
+                                    if (httpStatusCode == 201) {
+                                        pollStrategy = AzureAsyncOperationPollStrategy.tryToCreate(AzureProxy.this, methodParser, originalHttpRequest, originalHttpResponse, delayInMilliseconds);
+                                        if (pollStrategy == null) {
+                                            result = createProvisioningStateOrCompletedPollStrategy(originalHttpRequest, originalHttpResponse, methodParser, delayInMilliseconds);
+                                        }
+                                    } else if (httpStatusCode == 202) {
+                                        pollStrategy = AzureAsyncOperationPollStrategy.tryToCreate(AzureProxy.this, methodParser, originalHttpRequest, originalHttpResponse, delayInMilliseconds);
+                                        if (pollStrategy == null) {
+                                            pollStrategy = LocationPollStrategy.tryToCreate(AzureProxy.this, methodParser, originalHttpRequest, originalHttpDecodedResponse.sourceResponse(), delayInMilliseconds);
+                                        }
+                                    }
+                                }
+                                else {
+                                    if (httpStatusCode == 202) {
+                                        pollStrategy = AzureAsyncOperationPollStrategy.tryToCreate(AzureProxy.this, methodParser, originalHttpRequest, originalHttpResponse, delayInMilliseconds);
+                                        if (pollStrategy == null) {
+                                            pollStrategy = LocationPollStrategy.tryToCreate(AzureProxy.this, methodParser, originalHttpRequest, originalHttpResponse, delayInMilliseconds);
+                                            if (pollStrategy == null) {
+                                                throw new CloudException("Response does not contain an Azure-AsyncOperation or Location header.", originalHttpResponse);
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (pollStrategy == null && result == null) {
+                                    pollStrategy = new CompletedPollStrategy(
+                                            new CompletedPollStrategy.CompletedPollStrategyData(AzureProxy.this, methodParser, originalHttpResponse));
+                                }
+
+                                if (pollStrategy != null) {
+                                    result = Mono.just(pollStrategy);
+                                }
+
+                                return result;
+                            });
+                });
+    }
+
+    private Mono<PollStrategy> createProvisioningStateOrCompletedPollStrategy(final HttpRequest httpRequest, HttpResponse httpResponse, final SwaggerMethodParser methodParser, final long delayInMilliseconds) {
+        Mono<PollStrategy> pollStrategyMono;
+
+        final HttpMethod httpRequestMethod = httpRequest.httpMethod();
+        if (httpRequestMethod == HttpMethod.DELETE
+                || httpRequestMethod == HttpMethod.GET
+                || httpRequestMethod == HttpMethod.HEAD
+                || !methodParser.expectsResponseBody()) {
+            pollStrategyMono = Mono.<PollStrategy>just(new CompletedPollStrategy(
+                    new CompletedPollStrategy.CompletedPollStrategyData(AzureProxy.this, methodParser, httpResponse)));
+        } else {
+            final HttpResponse bufferedOriginalHttpResponse = httpResponse.buffer();
+            pollStrategyMono = bufferedOriginalHttpResponse.bodyAsString()
+                    .map(originalHttpResponseBody -> {
+                        if (originalHttpResponseBody == null || originalHttpResponseBody.isEmpty()) {
+                            throw new CloudException("The HTTP response does not contain a body.", bufferedOriginalHttpResponse);
+                        }
+                        PollStrategy pollStrategy;
+                        try {
+                            final SerializerAdapter serializer = serializer();
+                            final ResourceWithProvisioningState resource = serializer.deserialize(originalHttpResponseBody, ResourceWithProvisioningState.class, SerializerEncoding.JSON);
+                            if (resource != null && resource.properties() != null && !OperationState.isCompleted(resource.properties().provisioningState())) {
+                                pollStrategy = new ProvisioningStatePollStrategy(
+                                        new ProvisioningStatePollStrategy.ProvisioningStatePollStrategyData(
+                                                AzureProxy.this, methodParser, httpRequest, resource.properties().provisioningState(), delayInMilliseconds));
+                            } else {
+                                pollStrategy = new CompletedPollStrategy(
+                                        new CompletedPollStrategy.CompletedPollStrategyData(
+                                                AzureProxy.this, methodParser, bufferedOriginalHttpResponse));
+                            }
+                        } catch (IOException e) {
+                            throw Exceptions.propagate(e);
+                        }
+                        return pollStrategy;
+                    });
+        }
+        return pollStrategyMono;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureServiceClient.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureServiceClient.java
new file mode 100644
index 0000000000000..c011cde7a7022
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/AzureServiceClient.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.AzureEnvironment;
+import com.azure.common.ServiceClient;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+
+/**
+ * The base class for generated Azure service clients.
+ */
+public abstract class AzureServiceClient extends ServiceClient {
+    /**
+     * The environment that this AzureServiceClient targets.
+     */
+    private final AzureEnvironment azureEnvironment;
+
+    /**
+     * Initializes a new instance of the AzureServiceClient class.
+     *
+     * @param httpPipeline The HTTP pipeline to send requests through
+     * @param azureEnvironment The environment that this AzureServiceClient targets.
+     */
+    protected AzureServiceClient(HttpPipeline httpPipeline, AzureEnvironment azureEnvironment) {
+        super(httpPipeline);
+
+        this.azureEnvironment = azureEnvironment;
+    }
+
+    @Override
+    protected SerializerAdapter createSerializerAdapter() {
+        return AzureProxy.createDefaultSerializer();
+    }
+
+    /**
+     * Get the environment that this AzureServiceClient targets.
+     * @return the environment that this AzureServiceClient targets.
+     */
+    public AzureEnvironment azureEnvironment() {
+        return azureEnvironment;
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CloudError.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CloudError.java
new file mode 100644
index 0000000000000..6a73b4793ac4f
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CloudError.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An instance of this class provides additional information about an http error response.
+ */
+public final class CloudError {
+    /**
+     * The error code parsed from the body of the http error response.
+     */
+    private String code;
+
+    /**
+     * The error message parsed from the body of the http error response.
+     */
+    private String message;
+
+    /**
+     * The target of the error.
+     */
+    private String target;
+
+    /**
+     * Details for the error.
+     */
+    private List<CloudError> details;
+
+    /**
+     * Initializes a new instance of CloudError.
+     */
+    public CloudError() {
+        this.details = new ArrayList<CloudError>();
+    }
+
+    /**
+     * @return the error code parsed from the body of the http error response
+     */
+    public String code() {
+        return code;
+    }
+
+    /**
+     * Sets the error code parsed from the body of the http error response.
+     *
+     * @param code the error code
+     * @return the CloudError object itself
+     */
+    public CloudError withCode(String code) {
+        this.code = code;
+        return this;
+    }
+
+    /**
+     * @return the error message
+     */
+    public String message() {
+        return message;
+    }
+
+    /**
+     * Sets the error message parsed from the body of the http error response.
+     *
+     * @param message the error message
+     * @return the CloudError object itself
+     */
+    public CloudError withMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    /**
+     * @return the target of the error
+     */
+    public String target() {
+        return target;
+    }
+
+    /**
+     * Sets the target of the error.
+     *
+     * @param target the target of the error
+     * @return the CloudError object itself
+     */
+    public CloudError withTarget(String target) {
+        this.target = target;
+        return this;
+    }
+
+    /**
+     * @return the details for the error
+     */
+    public List<CloudError> details() {
+        return details;
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CloudException.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CloudException.java
new file mode 100644
index 0000000000000..0101257302157
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CloudException.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.http.rest.RestException;
+import com.azure.common.http.HttpResponse;
+
+/**
+ * Exception thrown for an invalid response with custom error information.
+ */
+public final class CloudException extends RestException {
+    /**
+     * Initializes a new instance of the CloudException class.
+     *
+     * @param message the exception message or the response content if a message is not available
+     * @param response the HTTP response
+     */
+    public CloudException(String message, HttpResponse response) {
+        super(message, response);
+    }
+
+    /**
+     * Initializes a new instance of the CloudException class.
+     *
+     * @param message the exception message or the response content if a message is not available
+     * @param response the HTTP response
+     * @param body the deserialized response body
+     */
+    public CloudException(String message, HttpResponse response, CloudError body) {
+        super(message, response, body);
+    }
+
+    @Override
+    public CloudError body() {
+        return (CloudError) super.body();
+    }
+
+    @Override
+    public String toString() {
+        String message = super.toString();
+        if (body() != null && body().message() != null) {
+            message = message + ": " + body().message();
+        }
+        return message;
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CompletedPollStrategy.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CompletedPollStrategy.java
new file mode 100644
index 0000000000000..cc461635ae8ed
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/CompletedPollStrategy.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.implementation.RestProxy;
+import com.azure.common.implementation.SwaggerMethodParser;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.Serializable;
+import java.lang.reflect.Type;
+
+/**
+ * The "polling strategy" that is used when a request completes immediately and does not require any
+ * further polling.
+ */
+public class CompletedPollStrategy extends PollStrategy {
+    private final HttpResponse firstHttpResponse;
+    private CompletedPollStrategyData data;
+
+    /**
+     * Create a new CompletedPollStrategy.
+     * @param data The poll strategy data.
+     */
+    public CompletedPollStrategy(CompletedPollStrategyData data) {
+        super(data);
+        this.firstHttpResponse = data.firstHttpResponse.buffer();
+        setStatus(OperationState.SUCCEEDED);
+        this.data = data;
+    }
+
+    /**
+     * The CompletedPollStrategy data.
+     */
+    public static class CompletedPollStrategyData extends PollStrategyData {
+        HttpResponse firstHttpResponse;
+
+        /**
+         * Create a new CompletedPollStrategyData.
+         * @param restProxy The RestProxy that created this PollStrategy.
+         * @param methodParser The method parser that describes the service interface method that
+         *                     initiated the long running operation.
+         * @param firstHttpResponse The HTTP response to the original HTTP request.
+         */
+        public CompletedPollStrategyData(RestProxy restProxy, SwaggerMethodParser methodParser, HttpResponse firstHttpResponse) {
+            super(restProxy, methodParser, 0);
+            this.firstHttpResponse = firstHttpResponse;
+        }
+
+        PollStrategy initializeStrategy(RestProxy restProxy,
+                                        SwaggerMethodParser methodParser) {
+            this.restProxy = restProxy;
+            this.methodParser = methodParser;
+            return new CompletedPollStrategy(this);
+        }
+    }
+
+    public
+    @Override
+    HttpRequest createPollRequest() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    Mono<HttpResponse> updateFromAsync(HttpResponse httpPollResponse) {
+        return Mono.error(new UnsupportedOperationException());
+    }
+
+    @Override
+    boolean isDone() {
+        return true;
+    }
+
+    Flux<OperationStatus<Object>> pollUntilDoneWithStatusUpdates(final HttpRequest originalHttpRequest, final SwaggerMethodParser methodParser, final Type operationStatusResultType) {
+        return createOperationStatusMono(originalHttpRequest, firstHttpResponse, methodParser, operationStatusResultType)
+                .flatMapMany(cos -> Flux.just(cos));
+    }
+
+    Mono<HttpResponse> pollUntilDone() {
+        return Mono.<HttpResponse>just(firstHttpResponse);
+    }
+
+    @Override
+    public Serializable strategyData() {
+        return this.data;
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/LocationPollStrategy.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/LocationPollStrategy.java
new file mode 100644
index 0000000000000..9e0d0370c6ac9
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/LocationPollStrategy.java
@@ -0,0 +1,160 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.implementation.RestProxy;
+import com.azure.common.implementation.SwaggerMethodParser;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Mono;
+
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * A PollStrategy type that uses the Location header value to check the status of a long running
+ * operation.
+ */
+public final class LocationPollStrategy extends PollStrategy {
+    LocationPollStrategyData data;
+
+    /**
+     * The name of the header that indicates that a long running operation will use the Location
+     * strategy.
+     */
+    public static final String HEADER_NAME = "Location";
+
+    private LocationPollStrategy(LocationPollStrategyData data) {
+        super(data);
+        this.data = data;
+    }
+
+    /**
+     * The LocationPollStrategy data.
+     */
+    public static class LocationPollStrategyData extends PollStrategyData {
+        URL locationUrl;
+        boolean done;
+
+        /**
+         * Create a new LocationPollStrategyData.
+         */
+        public LocationPollStrategyData() {
+            super(null, null, 0);
+            this.locationUrl = null;
+        }
+
+        /**
+         * Create a new LocationPollStrategyData.
+         * @param restProxy The RestProxy that created this PollStrategy.
+         * @param methodParser The method parser that describes the service interface method that
+         *                     initiated the long running operation.
+         * @param locationUrl The location url.
+         * @param delayInMilliseconds The delay value.
+         */
+        public LocationPollStrategyData(RestProxy restProxy,
+                                        SwaggerMethodParser methodParser,
+                                        URL locationUrl,
+                                        long delayInMilliseconds) {
+            super(restProxy, methodParser, delayInMilliseconds);
+            this.locationUrl = locationUrl;
+        }
+
+        PollStrategy initializeStrategy(RestProxy restProxy,
+                                        SwaggerMethodParser methodParser) {
+            this.restProxy = restProxy;
+            this.methodParser = methodParser;
+            return new LocationPollStrategy(this);
+        }
+    }
+
+    @Override
+    public HttpRequest createPollRequest() {
+        return new HttpRequest(HttpMethod.GET, data.locationUrl);
+    }
+
+    @Override
+    public Mono<HttpResponse> updateFromAsync(HttpResponse httpPollResponse) {
+        return ensureExpectedStatus(httpPollResponse, new int[] {202})
+                .map(response -> {
+                    final int httpStatusCode = response.statusCode();
+                    updateDelayInMillisecondsFrom(response);
+                    if (httpStatusCode == 202) {
+                        String newLocationUrl = getHeader(response);
+                        if (newLocationUrl != null) {
+                            try {
+                                data.locationUrl = new URL(newLocationUrl);
+                            } catch (MalformedURLException mfue) {
+                                throw Exceptions.propagate(mfue);
+                            }
+                        }
+                    }
+                    else {
+                        data.done = true;
+                    }
+                    return response;
+                });
+    }
+
+    @Override
+    public boolean isDone() {
+        return data.done;
+    }
+
+    /**
+     * Try to create a new LocationOperationPollStrategy object that will poll the provided location
+     * URL. If the provided HttpResponse doesn't have a Location header or the header is empty,
+     * then null will be returned.
+     * @param originalHttpRequest The original HTTP request.
+     * @param methodParser The method parser that describes the service interface method that
+     *                     initiated the long running operation.
+     * @param httpResponse The HTTP response that the required header values for this pollStrategy
+     *                     will be read from.
+     * @param delayInMilliseconds The delay (in milliseconds) that the resulting pollStrategy will
+     *                            use when polling.
+     */
+    static PollStrategy tryToCreate(RestProxy restProxy, SwaggerMethodParser methodParser, HttpRequest originalHttpRequest, HttpResponse httpResponse, long delayInMilliseconds) {
+        final String locationUrl = getHeader(httpResponse);
+
+        URL pollUrl = null;
+        if (locationUrl != null && !locationUrl.isEmpty()) {
+            if (locationUrl.startsWith("/")) {
+                try {
+                    final URL originalRequestUrl = originalHttpRequest.url();
+                    pollUrl = new URL(originalRequestUrl, locationUrl);
+                } catch (MalformedURLException ignored) {
+                }
+            }
+            else {
+                final String locationUrlLower = locationUrl.toLowerCase();
+                if (locationUrlLower.startsWith("http://") || locationUrlLower.startsWith("https://")) {
+                    try {
+                        pollUrl = new URL(locationUrl);
+                    } catch (MalformedURLException ignored) {
+                    }
+                }
+            }
+        }
+
+        return pollUrl == null
+                ? null
+                : new LocationPollStrategy(
+                        new LocationPollStrategyData(restProxy, methodParser, pollUrl, delayInMilliseconds));
+    }
+
+    static String getHeader(HttpResponse httpResponse) {
+        return httpResponse.headerValue(HEADER_NAME);
+    }
+
+    @Override
+    public Serializable strategyData() {
+        return this.data;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/OperationState.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/OperationState.java
new file mode 100644
index 0000000000000..bfa81efabba17
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/OperationState.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+/**
+ * The different states that a long running operation can be in.
+ */
+public final class OperationState {
+    /**
+     * The provisioning state of the operation resource if the operation is still in progress.
+     */
+    public static final String IN_PROGRESS = "InProgress";
+
+    /**
+     * The provisioning state of the operation resource if the operation is successful.
+     */
+    public static final String SUCCEEDED = "Succeeded";
+
+    /**
+     * The provisioning state of the operation resource if the operation is unsuccessful.
+     */
+    public static final String FAILED = "Failed";
+
+    /**
+     * The provisioning state of the operation resource if the operation is canceled.
+     */
+    public static final String CANCELED = "Canceled";
+
+    /**
+     * Get whether or not the provided operation state represents a completed state.
+     * @param operationState The operation state to check.
+     * @return Whether or not the provided operation state represents a completed state.
+     */
+    public static boolean isCompleted(String operationState) {
+        return operationState == null
+                || operationState.length() == 0
+                || SUCCEEDED.equalsIgnoreCase(operationState)
+                || isFailedOrCanceled(operationState);
+    }
+
+    /**
+     * Get whether or not the provided operation state represents a failed or canceled state.
+     * @param operationState The operation state to check.
+     * @return Whether or not the provided operation state represents a failed or canceled state.
+     */
+    public static boolean isFailedOrCanceled(String operationState) {
+        return FAILED.equalsIgnoreCase(operationState)
+                || CANCELED.equalsIgnoreCase(operationState);
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/OperationStatus.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/OperationStatus.java
new file mode 100644
index 0000000000000..4e8dd5f3d5b60
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/OperationStatus.java
@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.implementation.OperationDescription;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.http.HttpRequest;
+
+/**
+ * The current state of polling for the result of a long running operation.
+ * @param <T> The type of value that will be returned from the long running operation.
+ */
+public class OperationStatus<T> {
+    private final PollStrategy pollStrategy;
+    private final HttpRequest originalHttpRequest;
+    private final T result;
+    private final RestException error;
+    private final String status;
+
+    /**
+     * Create a new OperationStatus with the provided PollStrategy.
+     * @param pollStrategy The polling strategy that the OperationStatus will use to check the
+     *                     progress of a long running operation.
+     */
+    OperationStatus(PollStrategy pollStrategy, HttpRequest originalHttpRequest) {
+        this.originalHttpRequest = originalHttpRequest;
+        this.pollStrategy = pollStrategy;
+        this.result = null;
+        this.error = null;
+        this.status = pollStrategy.status();
+    }
+
+    /**
+     * Create a new OperationStatus with the provided result.
+     * @param result The final result of a long running operation.
+     */
+    OperationStatus(T result, String provisioningState) {
+        this.pollStrategy = null;
+        this.originalHttpRequest = null;
+        this.result = result;
+        this.error = null;
+        this.status = provisioningState;
+    }
+
+    OperationStatus(RestException error, String provisioningState) {
+        this.pollStrategy = null;
+        this.originalHttpRequest = null;
+        this.result = null;
+        this.error = error;
+        this.status = provisioningState;
+    }
+
+    /**
+     * @return Whether or not the long running operation is done.
+     */
+    public boolean isDone() {
+        return pollStrategy == null;
+    }
+
+    /**
+     * @return the current status of the long running operation.
+     */
+    public String status() {
+        return status;
+    }
+
+    /**
+     * If the long running operation is done, get the result of the operation. If the operation is
+     * not done or if the operation failed, then return null.
+     * @return The result of the operation, or null if the operation isn't done yet or if it failed.
+     */
+    public T result() {
+        return result;
+    }
+
+    /**
+     * If the long running operation failed, get the error that occurred. If the operation is not
+     * done or did not fail, then return null.
+     * @return The error of the operation, or null if the operation isn't done or didn't fail.
+     */
+    public RestException error() {
+        return error;
+    }
+
+    /**
+     * Builds an object that can be used to resume the polling of the operation.
+     * @return The OperationDescription.
+     */
+    public OperationDescription buildDescription() {
+        if (this.isDone()) {
+            return null;
+        }
+
+        return new OperationDescription(
+                this.pollStrategy.methodParser().fullyQualifiedMethodName(),
+                this.pollStrategy.strategyData(),
+                this.originalHttpRequest);
+    }
+
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/Page.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/Page.java
new file mode 100644
index 0000000000000..84921bee220cc
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/Page.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import java.util.List;
+
+/**
+ * Defines a page interface in Azure responses.
+ *
+ * @param <E> the element type.
+ */
+public interface Page<E> {
+    /**
+     * Gets the link to the next page.
+     *
+     * @return the link.
+     */
+    String nextPageLink();
+
+    /**
+     * Gets the list of items.
+     *
+     * @return the list of items.
+     */
+    List<E> items();
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/PagedList.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/PagedList.java
new file mode 100644
index 0000000000000..8a2f237ec0c9d
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/PagedList.java
@@ -0,0 +1,413 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.http.rest.RestException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Defines a list response from a paging operation. The pages are
+ * lazy initialized when an instance of this class is iterated.
+ *
+ * @param <E> the element type.
+ */
+public abstract class PagedList<E> implements List<E> {
+    /** The actual items in the list. */
+    private List<E> items;
+    /** Stores the latest page fetched. */
+    private Page<E> currentPage;
+    /** Cached page right after the current one. */
+    private Page<E> cachedPage;
+
+    /**
+     * Creates an instance of Pagedlist.
+     */
+    public PagedList() {
+        items = new ArrayList<>();
+    }
+
+    /**
+     * Creates an instance of PagedList from a {@link Page} response.
+     *
+     * @param page the {@link Page} object.
+     */
+    public PagedList(Page<E> page) {
+        this();
+        if (page == null) {
+            return;
+        }
+        List<E> retrievedItems = page.items();
+        if (retrievedItems != null) {
+            items.addAll(retrievedItems);
+        }
+        currentPage = page;
+        cachePage(page.nextPageLink());
+    }
+
+    private void cachePage(String nextPageLink) {
+        try {
+            while (nextPageLink != null && nextPageLink != "") {
+                cachedPage = nextPage(nextPageLink);
+                if (cachedPage == null) {
+                    break;
+                }
+                nextPageLink = cachedPage.nextPageLink();
+                if (hasNextPage()) {
+                    // a legit, non-empty page has been fetched, otherwise keep fetching
+                    break;
+                }
+            }
+        } catch (IOException ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    /**
+     * Override this method to load the next page of items from a next page link.
+     *
+     * @param nextPageLink the link to get the next page of items.
+     * @return the {@link Page} object storing a page of items and a link to the next page.
+     * @throws RestException thrown if an error is raised from Azure.
+     * @throws IOException thrown if there's any failure in deserialization.
+     */
+    public abstract Page<E> nextPage(String nextPageLink) throws RestException, IOException;
+
+    /**
+     * If there are more pages available.
+     *
+     * @return true if there are more pages to load. False otherwise.
+     */
+    public boolean hasNextPage() {
+        return this.cachedPage != null && this.cachedPage.items() != null && !this.cachedPage.items().isEmpty();
+    }
+
+    /**
+     * Loads a page from next page link.
+     * The exceptions are wrapped into Java Runtime exceptions.
+     */
+    public void loadNextPage() {
+        this.currentPage = cachedPage;
+        cachedPage = null;
+        this.items.addAll(currentPage.items());
+        cachePage(currentPage.nextPageLink());
+    }
+
+    /**
+     * Keep loading the next page from the next page link until all items are loaded.
+     */
+    public void loadAll() {
+        while (hasNextPage()) {
+            loadNextPage();
+        }
+    }
+
+    /**
+     * Gets the latest page fetched.
+     *
+     * @return the latest page.
+     */
+    public Page<E> currentPage() {
+        return currentPage;
+    }
+
+    /**
+     * Sets the current page.
+     *
+     * @param currentPage the current page.
+     */
+    protected void setCurrentPage(Page<E> currentPage) {
+        this.currentPage = currentPage;
+        List<E> retrievedItems = currentPage.items();
+        if (retrievedItems != null) {
+            items.addAll(retrievedItems);
+        }
+        cachePage(currentPage.nextPageLink());
+    }
+
+    /**
+     * The implementation of {@link ListIterator} for PagedList.
+     */
+    private class ListItr implements ListIterator<E> {
+        /**
+         * index of next element to return.
+         */
+        private int nextIndex;
+        /**
+         * index of last element returned; -1 if no such action happened.
+         */
+        private int lastRetIndex = -1;
+
+        /**
+         * Creates an instance of the ListIterator.
+         *
+         * @param index the position in the list to start.
+         */
+        ListItr(int index) {
+            this.nextIndex = index;
+        }
+
+        @Override
+        public boolean hasNext() {
+            return this.nextIndex != items.size() || hasNextPage();
+        }
+
+        @Override
+        public E next() {
+            if (this.nextIndex >= items.size()) {
+                if (!hasNextPage()) {
+                    throw new NoSuchElementException();
+                } else {
+                    loadNextPage();
+                }
+                // Recurse until we load a page with non-zero items.
+                return next();
+            } else {
+                try {
+                    E nextItem = items.get(this.nextIndex);
+                    this.lastRetIndex = this.nextIndex;
+                    this.nextIndex = this.nextIndex + 1;
+                    return nextItem;
+                } catch (IndexOutOfBoundsException ex) {
+                    // The nextIndex got invalid means a different instance of iterator
+                    // removed item from this index.
+                    throw new ConcurrentModificationException();
+                }
+            }
+        }
+
+        @Override
+        public void remove() {
+            if (this.lastRetIndex < 0) {
+                throw new IllegalStateException();
+            } else {
+                try {
+                    items.remove(this.lastRetIndex);
+                    this.nextIndex = this.lastRetIndex;
+                    this.lastRetIndex = -1;
+                } catch (IndexOutOfBoundsException ex) {
+                    throw new ConcurrentModificationException();
+                }
+            }
+        }
+
+        @Override
+        public boolean hasPrevious() {
+            return this.nextIndex != 0;
+        }
+
+        @Override
+        public E previous() {
+            int i = this.nextIndex - 1;
+            if (i < 0) {
+                throw new NoSuchElementException();
+            } else if (i >= items.size()) {
+                    throw new ConcurrentModificationException();
+            } else {
+                try {
+                    this.nextIndex = i;
+                    this.lastRetIndex = i;
+                    return items.get(this.lastRetIndex);
+                } catch (IndexOutOfBoundsException ex) {
+                    throw new ConcurrentModificationException();
+                }
+            }
+        }
+
+        @Override
+        public int nextIndex() {
+            return this.nextIndex;
+        }
+
+        @Override
+        public int previousIndex() {
+            return this.nextIndex - 1;
+        }
+
+        @Override
+        public void set(E e) {
+            if (this.lastRetIndex < 0) {
+                throw new IllegalStateException();
+            } else {
+                try {
+                    items.set(this.lastRetIndex, e);
+                } catch (IndexOutOfBoundsException ex) {
+                    throw new ConcurrentModificationException();
+                }
+            }
+        }
+
+        @Override
+        public void add(E e) {
+            try {
+                items.add(this.nextIndex, e);
+                this.nextIndex = this.nextIndex + 1;
+                this.lastRetIndex = -1;
+            } catch (IndexOutOfBoundsException ex) {
+                throw new ConcurrentModificationException();
+            }
+        }
+    }
+
+    @Override
+    public int size() {
+        loadAll();
+        return items.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return items.isEmpty() && !hasNextPage();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return indexOf(o) >= 0;
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return new ListItr(0);
+    }
+
+    @Override
+    public Object[] toArray() {
+        loadAll();
+        return items.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+        loadAll();
+        return items.toArray(a);
+    }
+
+    @Override
+    public boolean add(E e) {
+        return items.add(e);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        return items.remove(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        for (Object e : c) {
+            if (!contains(e)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends E> c) {
+        return items.addAll(c);
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends E> c) {
+        return items.addAll(index, c);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        return items.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        return items.retainAll(c);
+    }
+
+    @Override
+    public void clear() {
+        items.clear();
+    }
+
+    @Override
+    public E get(int index) {
+        while (index >= items.size() && hasNextPage()) {
+            loadNextPage();
+        }
+        return items.get(index);
+    }
+
+    @Override
+    public E set(int index, E element) {
+        return items.set(index, element);
+    }
+
+    @Override
+    public void add(int index, E element) {
+        items.add(index, element);
+    }
+
+    @Override
+    public E remove(int index) {
+        return items.remove(index);
+    }
+
+    @Override
+    public int indexOf(Object o) {
+        int index = 0;
+        if (o == null) {
+            for (E item : this) {
+                if (item == null) {
+                    return index;
+                }
+                ++index;
+            }
+        } else {
+            for (E item : this) {
+                if (item == o) {
+                    return index;
+                }
+                ++index;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public int lastIndexOf(Object o) {
+        loadAll();
+        return items.lastIndexOf(o);
+    }
+
+    @Override
+    public ListIterator<E> listIterator() {
+        return new ListItr(0);
+    }
+
+    @Override
+    public ListIterator<E> listIterator(int index) {
+        while (index >= items.size() && hasNextPage()) {
+            loadNextPage();
+        }
+        return new ListItr(index);
+    }
+
+    @Override
+    public List<E> subList(int fromIndex, int toIndex) {
+        while ((fromIndex >= items.size()
+                || toIndex >= items.size())
+                && hasNextPage()) {
+            loadNextPage();
+        }
+        return items.subList(fromIndex, toIndex);
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/PollStrategy.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/PollStrategy.java
new file mode 100644
index 0000000000000..c1404d134ae69
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/PollStrategy.java
@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.http.rest.RestException;
+import com.azure.common.implementation.RestProxy;
+import com.azure.common.implementation.SwaggerMethodParser;
+import com.azure.common.http.ContextData;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.implementation.serializer.HttpResponseDecoder;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Type;
+import java.time.Duration;
+
+/**
+ * An abstract class for the different strategies that an OperationStatus can use when checking the
+ * status of a long running operation.
+ */
+abstract class PollStrategy {
+    private final RestProxy restProxy;
+    private final SwaggerMethodParser methodParser;
+
+    private long delayInMilliseconds;
+    private String status;
+
+    PollStrategy(PollStrategyData data) {
+        this.restProxy = data.restProxy;
+        this.methodParser = data.methodParser;
+        this.delayInMilliseconds = data.delayInMilliseconds;
+    }
+
+    abstract static class PollStrategyData implements Serializable {
+        transient RestProxy restProxy;
+        transient SwaggerMethodParser methodParser;
+        long delayInMilliseconds;
+
+        PollStrategyData(RestProxy restProxy,
+                                SwaggerMethodParser methodParser,
+                                long delayInMilliseconds) {
+            this.restProxy = restProxy;
+            this.methodParser = methodParser;
+            this.delayInMilliseconds = delayInMilliseconds;
+        }
+
+
+        abstract PollStrategy initializeStrategy(RestProxy restProxy,
+                                        SwaggerMethodParser methodParser);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T> T deserialize(String value, Type returnType) throws IOException {
+        return (T) restProxy.serializer().deserialize(value, returnType, SerializerEncoding.JSON);
+    }
+
+    protected Mono<HttpResponse> ensureExpectedStatus(HttpResponse httpResponse) {
+        return ensureExpectedStatus(httpResponse, null);
+    }
+
+    protected Mono<HttpResponse> ensureExpectedStatus(HttpResponse httpResponse, int[] additionalAllowedStatusCodes) {
+        Mono<HttpResponseDecoder.HttpDecodedResponse> asyncDecodedResponse = new HttpResponseDecoder(restProxy.serializer()).decode(Mono.just(httpResponse), this.methodParser);
+        return asyncDecodedResponse.flatMap(decodedResponse -> {
+            return restProxy.ensureExpectedStatus(decodedResponse, methodParser, additionalAllowedStatusCodes);
+        }).map(decodedResponse -> httpResponse);
+    }
+
+    protected String fullyQualifiedMethodName() {
+        return methodParser.fullyQualifiedMethodName();
+    }
+
+    protected boolean expectsResourceResponse() {
+        return methodParser.expectsResponseBody();
+    }
+
+    /**
+     * Set the delay in milliseconds to 0.
+     */
+    final void clearDelayInMilliseconds() {
+        this.delayInMilliseconds = 0;
+    }
+
+    /**
+     * Update the delay in milliseconds from the provided HTTP poll response.
+     * @param httpPollResponse The HTTP poll response to update the delay in milliseconds from.
+     */
+    final void updateDelayInMillisecondsFrom(HttpResponse httpPollResponse) {
+        final Long parsedDelayInMilliseconds = delayInMillisecondsFrom(httpPollResponse);
+        if (parsedDelayInMilliseconds != null) {
+            delayInMilliseconds = parsedDelayInMilliseconds;
+        }
+    }
+
+    static Long delayInMillisecondsFrom(HttpResponse httpResponse) {
+        Long result = null;
+
+        final String retryAfterSecondsString = httpResponse.headerValue("Retry-After");
+        if (retryAfterSecondsString != null && !retryAfterSecondsString.isEmpty()) {
+            result = Long.valueOf(retryAfterSecondsString) * 1000;
+        }
+
+        return result;
+    }
+
+    /**
+     * If this OperationStatus has a retryAfterSeconds value, return an Mono that is delayed by the
+     * number of seconds that are in the retryAfterSeconds value. If this OperationStatus doesn't have
+     * a retryAfterSeconds value, then return an Single with no delay.
+     * @return A Mono with delay if this OperationStatus has a retryAfterSeconds value.
+     */
+    Mono<Void> delayAsync() {
+        Mono<Void> result = Mono.empty();
+        if (delayInMilliseconds > 0) {
+            result = result.delaySubscription(Duration.ofMillis(delayInMilliseconds));
+        }
+        return result;
+    }
+
+    /**
+     * @return the current status of the long running operation.
+     */
+    String status() {
+        return status;
+    }
+
+    /**
+     * Set the current status of the long running operation.
+     * @param status The current status of the long running operation.
+     */
+    void setStatus(String status) {
+        this.status = status;
+    }
+
+    /**
+     * Create a new HTTP poll request.
+     * @return A new HTTP poll request.
+     */
+    abstract HttpRequest createPollRequest();
+
+    /**
+     * Update the status of this PollStrategy from the provided HTTP poll response.
+     * @param httpPollResponse The response of the most recent poll request.
+     * @return A Completable that can be used to chain off of this operation.
+     */
+    abstract Mono<HttpResponse> updateFromAsync(HttpResponse httpPollResponse);
+
+    /**
+     * Get whether or not this PollStrategy's long running operation is done.
+     * @return Whether or not this PollStrategy's long running operation is done.
+     */
+    abstract boolean isDone();
+
+    Mono<HttpResponse> sendPollRequestWithDelay() {
+        return Mono.defer(() -> delayAsync().then(Mono.defer(() -> {
+            final HttpRequest pollRequest = createPollRequest();
+            return restProxy.send(pollRequest, new ContextData("caller-method", fullyQualifiedMethodName()));
+        })).flatMap(response -> updateFromAsync(response)));
+    }
+
+    Mono<OperationStatus<Object>> createOperationStatusMono(HttpRequest httpRequest, HttpResponse httpResponse, SwaggerMethodParser methodParser, Type operationStatusResultType) {
+        OperationStatus<Object> operationStatus;
+        if (!isDone()) {
+            operationStatus = new OperationStatus<>(this, httpRequest);
+        } else {
+            try {
+                final Object resultObject = restProxy.handleRestReturnType(new HttpResponseDecoder(restProxy.serializer()).decode(Mono.just(httpResponse), this.methodParser), methodParser, operationStatusResultType);
+                operationStatus = new OperationStatus<>(resultObject, status());
+            } catch (RestException e) {
+                operationStatus = new OperationStatus<>(e, OperationState.FAILED);
+            }
+        }
+        return Mono.just(operationStatus);
+    }
+
+    Flux<OperationStatus<Object>> pollUntilDoneWithStatusUpdates(final HttpRequest originalHttpRequest, final SwaggerMethodParser methodParser, final Type operationStatusResultType) {
+            return sendPollRequestWithDelay()
+                    .flatMap(httpResponse -> createOperationStatusMono(originalHttpRequest, httpResponse, methodParser, operationStatusResultType))
+                    .repeat()
+                    .takeUntil(operationStatus -> isDone());
+    }
+
+    Mono<HttpResponse> pollUntilDone() {
+        return sendPollRequestWithDelay()
+                .repeat()
+                .takeUntil(ignored -> isDone())
+                .last();
+    }
+
+    /**
+     * @return The data for the strategy.
+     */
+    public abstract Serializable strategyData();
+
+    SwaggerMethodParser methodParser() {
+        return this.methodParser;
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/ProvisioningStatePollStrategy.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/ProvisioningStatePollStrategy.java
new file mode 100644
index 0000000000000..767bc24483302
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/ProvisioningStatePollStrategy.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.implementation.RestProxy;
+import com.azure.common.implementation.SwaggerMethodParser;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * A PollStrategy that will continue to poll a resource's URL until the resource's provisioning
+ * state property is in a completed state.
+ */
+public final class ProvisioningStatePollStrategy extends PollStrategy {
+    private ProvisioningStatePollStrategyData data;
+    ProvisioningStatePollStrategy(ProvisioningStatePollStrategyData data) {
+        super(data);
+        setStatus(data.provisioningState);
+        this.data = data;
+    }
+
+    /**
+     * The ProvisioningStatePollStrategy data.
+     */
+    public static class ProvisioningStatePollStrategyData extends PollStrategy.PollStrategyData {
+        HttpRequest originalRequest;
+        String provisioningState;
+
+        /**
+         * Create a new ProvisioningStatePollStrategyData.
+         * @param restProxy The RestProxy that created this PollStrategy.
+         * @param methodParser The method parser that describes the service interface method that
+         *                     initiated the long running operation.
+         * @param originalRequest The HTTP response to the original HTTP request.
+         * @param provisioningState The provisioning state.
+         * @param delayInMilliseconds The delay value.
+         */
+        public ProvisioningStatePollStrategyData(RestProxy restProxy,
+                                                 SwaggerMethodParser methodParser,
+                                                 HttpRequest originalRequest,
+                                                 String provisioningState,
+                                                 long delayInMilliseconds) {
+            super(restProxy, methodParser, delayInMilliseconds);
+            this.originalRequest = originalRequest;
+            this.provisioningState = provisioningState;
+        }
+
+        PollStrategy initializeStrategy(RestProxy restProxy,
+                                                 SwaggerMethodParser methodParser) {
+            this.restProxy = restProxy;
+            this.methodParser = methodParser;
+            return new ProvisioningStatePollStrategy(this);
+        }
+
+    }
+
+    @Override
+    HttpRequest createPollRequest() {
+        return new HttpRequest(HttpMethod.GET, data.originalRequest.url());
+    }
+
+    @Override
+    Mono<HttpResponse> updateFromAsync(HttpResponse pollResponse) {
+        return ensureExpectedStatus(pollResponse)
+                .flatMap(response -> {
+                        final HttpResponse bufferedHttpPollResponse = response.buffer();
+                        return bufferedHttpPollResponse.bodyAsString()
+                                .map(responseBody -> {
+                                        ResourceWithProvisioningState resource = null;
+                                        try {
+                                            resource = deserialize(responseBody, ResourceWithProvisioningState.class);
+                                        } catch (IOException ignored) {
+                                        }
+
+                                        if (resource == null || resource.properties() == null || resource.properties().provisioningState() == null) {
+                                            throw new CloudException("The polling response does not contain a valid body", bufferedHttpPollResponse, null);
+                                        }
+                                        else if (OperationState.isFailedOrCanceled(resource.properties().provisioningState())) {
+                                            throw new CloudException("Async operation failed with provisioning state: " + resource.properties().provisioningState(), bufferedHttpPollResponse);
+                                        }
+                                        else {
+                                            setStatus(resource.properties().provisioningState());
+                                        }
+                                        return bufferedHttpPollResponse;
+                                });
+                });
+    }
+
+    @Override
+    boolean isDone() {
+        return OperationState.isCompleted(status());
+    }
+
+    @Override
+    public Serializable strategyData() {
+        return this.data;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/Resource.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/Resource.java
new file mode 100644
index 0000000000000..bf91cf9f6777b
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/Resource.java
@@ -0,0 +1,112 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.util.Map;
+
+/**
+ * The Resource model.
+ */
+public class Resource {
+    /**
+     * Resource Id.
+     */
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    private String id;
+
+    /**
+     * Resource name.
+     */
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    private String name;
+
+    /**
+     * Resource type.
+     */
+    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
+    private String type;
+
+    /**
+     * Resource location.
+     */
+    @JsonProperty(required = true)
+    private String location;
+
+    /**
+     * Resource tags.
+     */
+    private Map<String, String> tags;
+
+    /**
+     * Get the id value.
+     *
+     * @return the id value
+     */
+    public String id() {
+        return this.id;
+    }
+
+    /**
+     * Get the name value.
+     *
+     * @return the name value
+     */
+    public String name() {
+        return this.name;
+    }
+
+    /**
+     * Get the type value.
+     *
+     * @return the type value
+     */
+    public String type() {
+        return this.type;
+    }
+
+    /**
+     * Get the location value.
+     *
+     * @return the location value
+     */
+    public String location() {
+        return this.location;
+    }
+
+    /**
+     * Set the location value.
+     *
+     * @param location the location value to set
+     * @return the resource itself
+     */
+    public Resource withLocation(String location) {
+        this.location = location;
+        return this;
+    }
+
+    /**
+     * Get the tags value.
+     *
+     * @return the tags value
+     */
+    public Map<String, String> getTags() {
+        return this.tags;
+    }
+
+    /**
+     * Set the tags value.
+     *
+     * @param tags the tags value to set
+     * @return the resource itself
+     */
+    public Resource withTags(Map<String, String> tags) {
+        this.tags = tags;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/ResourceWithProvisioningState.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/ResourceWithProvisioningState.java
new file mode 100644
index 0000000000000..a63a81c8b4f5f
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/ResourceWithProvisioningState.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * The ResourceWithProvisioningState class is a POJO representation of any Azure resource that has a
+ * provisioningState property.
+ */
+public class ResourceWithProvisioningState {
+    @JsonProperty(value = "properties")
+    private Properties properties;
+
+    /**
+     * @return The inner properties object.
+     */
+    public Properties properties() {
+        return properties;
+    }
+
+    /**
+     * Set the properties of this ResourceWithProvisioningState.
+     * @param properties The properties of this ResourceWithProvisioningState.
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * Inner properties class.
+     */
+    public static class Properties {
+        @JsonProperty(value = "provisioningState")
+        private String provisioningState;
+
+        /**
+         * @return The provisioning state of the resource.
+         */
+        String provisioningState() {
+            return provisioningState;
+        }
+
+        /**
+         * Set the provisioning state of this ResourceWithProvisioningState.
+         * @param provisioningState The provisioning state of this ResourceWithProvisioningState.
+         */
+        public void setProvisioningState(String provisioningState) {
+            this.provisioningState = provisioningState;
+        }
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/SubResource.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/SubResource.java
new file mode 100644
index 0000000000000..7e986295a46a7
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/SubResource.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+/**
+ * The SubResource model.
+ */
+public class SubResource {
+    /**
+     * Resource Id.
+     */
+    private String id;
+
+    /**
+     * Get the id value.
+     *
+     * @return the id value
+     */
+    public String id() {
+        return this.id;
+    }
+
+    /**
+     * Set the id value.
+     *
+     * @param id the id value to set
+     * @return the sub resource itself
+     */
+    public SubResource withId(String id) {
+        this.id = id;
+        return this;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/annotations/AzureHost.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/annotations/AzureHost.java
new file mode 100644
index 0000000000000..1f65619d407a8
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/annotations/AzureHost.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt.annotations;
+
+import com.azure.common.AzureEnvironment;
+import com.azure.common.AzureEnvironment.Endpoint;
+import com.azure.common.annotations.Host;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+/**
+ * An extension to {@link Host}, allowing endpoints
+ * of {@link AzureEnvironment} to be specified instead of string
+ * host names. This allows self adaptive base URLs based on the environment the
+ * client is running in.
+ *
+ * Example 1: Azure Resource Manager
+ *
+ *   {@literal @}AzureHost(AzureEnvironment.Endpoint.RESOURCE_MANAGER)
+ *   interface VirtualMachinesService {
+ *     {@literal @}GET("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *     VirtualMachine getByResourceGroup(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId);
+ *   }
+ *
+ * Example 2: Azure Key Vault
+ *
+ *   {@literal @}AzureHost(AzureEnvironment.Endpoint.KEY_VAULT)
+ *   interface KeyVaultService {
+ *     {@literal @}GET("secrets/{secretName}")
+ *     Secret getSecret(@HostParam String vaultName, @PathParam("secretName") String secretName);
+ *   }
+ */
+@Target(value = {TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AzureHost {
+    /**
+     * The endpoint that all REST APIs within the Swagger interface will send their requests to.
+     * @return The endpoint that all REST APIs within the Swagger interface will send their requests
+     *      to.
+     */
+    String value() default "";
+
+    /**
+     * The endpoint that all REST APIs within the Swagger interface will send their requests to.
+     * @return The endpoint that all REST APIs within the Swagger interface will send their requests
+     *      to.
+     */
+    AzureEnvironment.Endpoint endpoint() default Endpoint.RESOURCE_MANAGER;
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/annotations/package-info.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/annotations/package-info.java
new file mode 100644
index 0000000000000..7b1b924ccf9da
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/annotations/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Annotations used on Swagger generated interfaces that are specific to Azure ARM REST APIs.
+ */
+package com.azure.common.mgmt.annotations;
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/package-info.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/package-info.java
new file mode 100644
index 0000000000000..694a07d00a3f2
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing the types for Azure ARM client side http communication with a REST endpoint.
+ */
+package com.azure.common.mgmt;
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/policy/AsyncCredentialsPolicy.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/policy/AsyncCredentialsPolicy.java
new file mode 100644
index 0000000000000..4c032cf91cf2b
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/policy/AsyncCredentialsPolicy.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt.policy;
+
+import com.azure.common.credentials.AsyncServiceClientCredentials;
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.http.policy.HttpPipelinePolicy;
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Mono;
+
+/**
+ * Creates a policy which adds credentials from AsyncServiceClientCredentials to a request.
+ */
+public class AsyncCredentialsPolicy implements HttpPipelinePolicy {
+    private final AsyncServiceClientCredentials credentials;
+
+    /**
+     * Creates CredentialsPolicy.
+     *
+     * @param credentials The credentials to use for authentication.
+     */
+    public AsyncCredentialsPolicy(AsyncServiceClientCredentials credentials) {
+        this.credentials = credentials;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        return credentials.authorizationHeaderValueAsync(context.httpRequest().url().toString())
+                .flatMap(token -> {
+                    context.httpRequest().headers().set("Authorization", token);
+                    return next.process();
+                });
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/policy/package-info.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/policy/package-info.java
new file mode 100644
index 0000000000000..30edee352565d
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/policy/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing HttpPipelinePolicy interface and it's implementations used by Azure ARM Clients.
+ */
+package com.azure.common.mgmt.policy;
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/AzureJacksonAdapter.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/AzureJacksonAdapter.java
new file mode 100644
index 0000000000000..1c568b4a96f4b
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/AzureJacksonAdapter.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt.serializer;
+
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+
+/**
+ * A serialization helper class overriding {@link JacksonAdapter} with extra
+ * functionality useful for Azure operations.
+ */
+public final class AzureJacksonAdapter extends JacksonAdapter implements SerializerAdapter {
+    /**
+     * Creates an instance of the Azure flavored Jackson adapter.
+     */
+    public AzureJacksonAdapter() {
+        super();
+        serializer().registerModule(CloudErrorDeserializer.getModule(simpleMapper()));
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/CloudErrorDeserializer.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/CloudErrorDeserializer.java
new file mode 100644
index 0000000000000..50800af952b07
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/CloudErrorDeserializer.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt.serializer;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.azure.common.mgmt.CloudError;
+
+import java.io.IOException;
+
+/**
+ * Custom serializer for serializing {@link CloudError} objects.
+ */
+final class CloudErrorDeserializer extends JsonDeserializer<CloudError> {
+    /** Object mapper for default deserializations. */
+    private ObjectMapper mapper;
+
+    /**
+     * Creates an instance of CloudErrorDeserializer.
+     *
+     * @param mapper the object mapper for default deserializations.
+     */
+    private CloudErrorDeserializer(ObjectMapper mapper) {
+        this.mapper = mapper;
+    }
+
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @param mapper the object mapper for default deserializations.
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    static SimpleModule getModule(ObjectMapper mapper) {
+        SimpleModule module = new SimpleModule();
+        module.addDeserializer(CloudError.class, new CloudErrorDeserializer(mapper));
+        return module;
+    }
+
+    @Override
+    public CloudError deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+        JsonNode errorNode = p.readValueAsTree();
+        if (errorNode == null) {
+            return null;
+        }
+        if (errorNode.get("error") != null) {
+            errorNode = errorNode.get("error");
+        }
+        String nodeContent = errorNode.toString();
+        nodeContent = nodeContent.replaceFirst("(?i)\"code\"", "\"code\"")
+                .replaceFirst("(?i)\"message\"", "\"message\"")
+                .replaceFirst("(?i)\"target\"", "\"target\"")
+                .replaceFirst("(?i)\"details\"", "\"details\"");
+        JsonParser parser = new JsonFactory().createParser(nodeContent);
+        parser.setCodec(mapper);
+        return parser.readValueAs(CloudError.class);
+    }
+}
diff --git a/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/package-info.java b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/package-info.java
new file mode 100644
index 0000000000000..4826f193421ea
--- /dev/null
+++ b/common/azure-common-mgmt/src/main/java/com/azure/common/mgmt/serializer/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * The package contains classes that handle serialization and deserialization for the REST call payloads in Azure ARM.
+ */
+package com.azure.common.mgmt.serializer;
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyTests.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyTests.java
new file mode 100644
index 0000000000000..fe9f05770955c
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyTests.java
@@ -0,0 +1,864 @@
+package com.azure.common.mgmt;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.azure.common.mgmt.http.MockAzureHttpClient;
+import com.azure.common.mgmt.http.MockAzureHttpResponse;
+import com.azure.common.implementation.OperationDescription;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import com.azure.common.implementation.exception.InvalidReturnTypeException;
+import com.azure.common.annotations.DELETE;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.Host;
+import com.azure.common.annotations.PUT;
+import com.azure.common.annotations.PathParam;
+import com.azure.common.annotations.ResumeOperation;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import static org.junit.Assert.*;
+
+public class AzureProxyTests {
+    private long delayInMillisecondsBackup;
+
+    @Before
+    public void beforeTest() {
+        delayInMillisecondsBackup = AzureProxy.defaultDelayInMilliseconds();
+        AzureProxy.setDefaultPollingDelayInMilliseconds(0);
+    }
+
+    @After
+    public void afterTest() {
+        AzureProxy.setDefaultPollingDelayInMilliseconds(delayInMillisecondsBackup);
+    }
+
+    @Host("https://mock.azure.com")
+    private interface MockResourceService {
+        @GET("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}")
+        @ExpectedResponses({200})
+        MockResource get(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @GET("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}")
+        @ExpectedResponses({200})
+        Mono<MockResource> getAsync(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}")
+        @ExpectedResponses({200})
+        MockResource create(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location")
+        @ExpectedResponses({200})
+        MockResource createWithLocation(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        MockResource createWithLocationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsRemaining);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Azure-AsyncOperation")
+        @ExpectedResponses({200})
+        MockResource createWithAzureAsyncOperation(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Azure-AsyncOperation&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        MockResource createWithAzureAsyncOperationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsRemaining);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=ProvisioningState")
+        @ExpectedResponses({200})
+        MockResource createWithProvisioningState(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=ProvisioningState&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        MockResource createWithProvisioningStateAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsRemaining);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}")
+        @ExpectedResponses({200})
+        Mono<MockResource> createAsync(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location")
+        @ExpectedResponses({200})
+        Mono<MockResource> createAsyncWithLocation(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Mono<MockResource> createAsyncWithLocationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Azure-AsyncOperation")
+        @ExpectedResponses({200})
+        Mono<MockResource> createAsyncWithAzureAsyncOperation(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Azure-AsyncOperation&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Mono<MockResource> createAsyncWithAzureAsyncOperationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=ProvisioningState")
+        @ExpectedResponses({200})
+        Mono<MockResource> createAsyncWithProvisioningState(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=ProvisioningState&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Mono<MockResource> createAsyncWithProvisioningStateAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Flux<MockResource> beginCreateAsyncWithBadReturnType(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining=1&InitialResponseStatusCode=294")
+        @ExpectedResponses({200})
+        Flux<OperationStatus<MockResource>> beginCreateAsyncWithLocationAndPollsAndUnexpectedStatusCode(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Flux<OperationStatus<MockResource>> beginCreateAsyncWithLocationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @ExpectedResponses({200})
+        @ResumeOperation
+        Flux<OperationStatus<MockResource>> resumeCreateAsyncWithLocationAndPolls(OperationDescription operationDescription);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Azure-AsyncOperation&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Flux<OperationStatus<MockResource>> beginCreateAsyncWithAzureAsyncOperationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=ProvisioningState&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Flux<OperationStatus<MockResource>> beginCreateAsyncWithProvisioningStateAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}")
+        @ExpectedResponses({200})
+        void delete(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location")
+        @ExpectedResponses({200})
+        void deleteWithLocation(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        void deleteWithLocationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}")
+        @ExpectedResponses({200})
+        Mono<Void> deleteAsync(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location")
+        @ExpectedResponses({200})
+        Mono<Void> deleteAsyncWithLocation(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName);
+
+        @DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Mono<Void> deleteAsyncWithLocationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/mockprovider/mockresources/{mockResourceName}?PollType=Location&PollsRemaining={pollsRemaining}")
+        @ExpectedResponses({200})
+        Flux<OperationStatus<Void>> beginDeleteAsyncWithLocationAndPolls(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String resourceGroupName, @PathParam("mockResourceName") String mockResourceName, @PathParam("pollsRemaining") int pollsUntilResource);
+
+        @DELETE("errors/403")
+        @ExpectedResponses({200})
+        Mono<Void> deleteAsyncWithForbiddenResponse();
+    }
+
+    @Test
+    public void get() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .get("1", "mine", "a");
+        assertNotNull(resource);
+        assertEquals("a", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void getAsync() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .getAsync("1", "mine", "b")
+                .block();
+        assertNotNull(resource);
+        assertEquals("b", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void create() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .create("1", "mine", "c");
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createWithLocation() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createWithLocation("1", "mine", "c");
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(1, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createWithLocationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createWithLocationAndPolls("1", "mine", "c", 2);
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(2, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createWithAzureAsyncOperation() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createWithAzureAsyncOperation("1", "mine", "c");
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(1, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createWithAzureAsyncOperationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createWithAzureAsyncOperationAndPolls("1", "mine", "c", 2);
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(2, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createWithProvisioningState() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createWithProvisioningState("1", "mine", "c");
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createWithProvisioningStateAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createWithProvisioningStateAndPolls("1", "mine", "c", 3);
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(3, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsync() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createAsync("1", "mine", "c")
+                .block();
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsyncWithLocation() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createAsyncWithLocation("1", "mine", "c")
+                .block();
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(1, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsyncWithLocationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createAsyncWithLocationAndPolls("1", "mine", "c", 3)
+                .block();
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(3, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsyncWithAzureAsyncOperation() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createAsyncWithAzureAsyncOperation("1", "mine", "c")
+                .block();
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(1, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsyncWithAzureAsyncOperationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createAsyncWithAzureAsyncOperationAndPolls("1", "mine", "c", 3)
+                .block();
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(3, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsyncWithAzureAsyncOperationAndPollsWithDelay() throws InterruptedException {
+        final long delayInMilliseconds = 100;
+        AzureProxy.setDefaultPollingDelayInMilliseconds(delayInMilliseconds);
+
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+        final int pollsUntilResource = 3;
+        createMockService(MockResourceService.class, httpClient)
+                .createAsyncWithAzureAsyncOperationAndPolls("1", "mine", "c", pollsUntilResource)
+                .subscribe();
+
+        Thread.sleep((long)(delayInMilliseconds * 0.75));
+
+        for (int i = 0; i < pollsUntilResource; ++i) {
+            assertEquals(0, httpClient.getRequests());
+            assertEquals(1, httpClient.createRequests());
+            assertEquals(0, httpClient.deleteRequests());
+            assertEquals(i, httpClient.pollRequests());
+
+            Thread.sleep(delayInMilliseconds);
+        }
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(pollsUntilResource, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsyncWithProvisioningState() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createAsyncWithProvisioningState("1", "mine", "c")
+                .block();
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void createAsyncWithProvisioningStateAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResource resource = createMockService(MockResourceService.class, httpClient)
+                .createAsyncWithProvisioningStateAndPolls("1", "mine", "c", 5)
+                .block();
+        assertNotNull(resource);
+        assertEquals("c", resource.name);
+
+        assertEquals(5, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginCreateAsyncWithBadReturnType() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final MockResourceService service = createMockService(MockResourceService.class, httpClient);
+        try {
+            service.beginCreateAsyncWithBadReturnType("1", "mine", "c", 2);
+            fail("Expected exception.");
+        }
+        catch (InvalidReturnTypeException e) {
+            assertContains(e.getMessage(), "AzureProxyTests$MockResourceService.beginCreateAsyncWithBadReturnType()");
+            assertContains(e.getMessage(), "reactor.core.publisher.Flux<com.azure.common.mgmt.MockResource>");
+        }
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests()); // Request won't reach HttpClient and fail in AzureProxy
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginCreateAsyncWithLocationAndPollsAndUnexpectedStatusCode() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginCreateAsyncWithLocationAndPollsAndUnexpectedStatusCode("1", "mine", "c")
+                .subscribe(
+                        new Consumer<OperationStatus<MockResource>>() {
+                           @Override
+                           public void accept(OperationStatus<MockResource> mockResourceOperationStatus) {
+                                fail();
+                           }
+                       },
+                        new Consumer<Throwable>() {
+                            @Override
+                            public void accept(Throwable throwable) {
+                                assertEquals(RestException.class, throwable.getClass());
+                                assertEquals("Status code 294, (empty body)", throwable.getMessage());
+                            }
+                        });
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginCreateAsyncWithLocationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final AtomicInteger inProgressCount = new AtomicInteger();
+        final Value<MockResource> resource = new Value<>();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginCreateAsyncWithLocationAndPolls("1", "mine", "c", 3)
+                .subscribe(new Consumer<OperationStatus<MockResource>>() {
+                    @Override
+                    public void accept(OperationStatus<MockResource> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            resource.set(operationStatus.result());
+                        }
+                    }
+                });
+
+        assertEquals(2, inProgressCount.get());
+        assertNotNull(resource.get());
+        assertEquals("c", resource.get().name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(3, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginAndResumeCreateAsyncWithLocationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final AtomicInteger inProgressCount = new AtomicInteger();
+        final Value<MockResource> resource = new Value<>();
+        final StringBuffer data = new StringBuffer();
+        final ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
+        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+        mapper.enableDefaultTyping();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginCreateAsyncWithLocationAndPolls("1", "mine", "c", 10)
+                .take(2)
+                .subscribe(new Consumer<OperationStatus<MockResource>>() {
+                    @Override
+                    public void accept(OperationStatus<MockResource> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            OperationDescription operationDescription = operationStatus.buildDescription();
+                            try {
+                                data.append(mapper.writeValueAsString(operationDescription));
+                            } catch (JsonProcessingException e) {
+                                fail("Error serializing OperationDescription object");
+                                e.printStackTrace();
+                            }
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            resource.set(operationStatus.result());
+                        }
+                    }
+                });
+
+        OperationDescription operationDescription = null;
+        PollStrategy.PollStrategyData pollData = null;
+        try {
+            operationDescription = mapper.readValue(data.toString(), OperationDescription.class);
+            pollData = (PollStrategy.PollStrategyData)operationDescription.pollStrategyData();
+        } catch (IOException e) {
+            fail("Error deserializing OperationDescription object");
+            e.printStackTrace();
+        }
+
+        assertNotNull(operationDescription);
+        assertNotNull(pollData);
+
+        createMockService(MockResourceService.class, httpClient)
+                .resumeCreateAsyncWithLocationAndPolls(operationDescription)
+                .subscribe(new Consumer<OperationStatus<MockResource>>() {
+                    @Override
+                    public void accept(OperationStatus<MockResource> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            OperationDescription operationDescription = operationStatus.buildDescription();
+                            try {
+                                data.append(mapper.writeValueAsString(operationDescription));
+                            } catch (JsonProcessingException e) {
+                                fail("Error serializing OperationDescription object");
+                                e.printStackTrace();
+                            }
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            resource.set(operationStatus.result());
+                        }
+                    }
+                });
+
+    }
+
+    @Test
+    public void beginCreateAsyncWithAzureAsyncOperationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final AtomicInteger inProgressCount = new AtomicInteger();
+        final Value<MockResource> resource = new Value<>();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginCreateAsyncWithAzureAsyncOperationAndPolls("1", "mine", "c", 3)
+                .subscribe(new Consumer<OperationStatus<MockResource>>() {
+                    @Override
+                    public void accept(OperationStatus<MockResource> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            resource.set(operationStatus.result());
+                        }
+                    }
+                });
+
+        assertEquals(3, inProgressCount.get());
+        assertNotNull(resource.get());
+        assertEquals("c", resource.get().name);
+
+        assertEquals(1, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(3, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginCreateAsyncWithProvisioningStateAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final AtomicInteger inProgressCount = new AtomicInteger();
+        final Value<MockResource> resource = new Value<>();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginCreateAsyncWithProvisioningStateAndPolls("1", "mine", "c", 4)
+                .subscribe(new Consumer<OperationStatus<MockResource>>() {
+                    @Override
+                    public void accept(OperationStatus<MockResource> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            resource.set(operationStatus.result());
+                        }
+                    }
+                });
+
+        assertEquals(3, inProgressCount.get());
+        assertNotNull(resource.get());
+        assertEquals("c", resource.get().name);
+
+        assertEquals(4, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginCreateAsyncWithLocationAndPollsWhenPollsUntilResourceIs0() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final AtomicInteger inProgressCount = new AtomicInteger();
+        final Value<MockResource> resource = new Value<>();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginCreateAsyncWithLocationAndPolls("1", "mine", "c", 0)
+                .subscribe(new Consumer<OperationStatus<MockResource>>() {
+                    @Override
+                    public void accept(OperationStatus<MockResource> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            resource.set(operationStatus.result());
+                        }
+                    }
+                });
+
+        assertEquals(0, inProgressCount.get());
+        assertNotNull(resource.get());
+        assertEquals("c", resource.get().name);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(1, httpClient.createRequests());
+        assertEquals(0, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void delete() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        createMockService(MockResourceService.class, httpClient)
+                .delete("1", "mine", "c");
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void deleteWithLocation() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        createMockService(MockResourceService.class, httpClient)
+                .deleteWithLocation("1", "mine", "c");
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(1, httpClient.pollRequests());
+    }
+
+    @Test
+    public void deleteWithLocationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        createMockService(MockResourceService.class, httpClient)
+                .deleteWithLocationAndPolls("1", "mine", "c", 4);
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(4, httpClient.pollRequests());
+    }
+
+    @Test
+    public void deleteAsync() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        createMockService(MockResourceService.class, httpClient)
+                .deleteAsync("1", "mine", "c")
+                .block();
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void deleteAsyncWithLocation() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        createMockService(MockResourceService.class, httpClient)
+                .deleteAsyncWithLocation("1", "mine", "c")
+                .block();
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(1, httpClient.pollRequests());
+    }
+
+    @Test
+    public void deleteAsyncWithLocationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        createMockService(MockResourceService.class, httpClient)
+                .deleteAsyncWithLocationAndPolls("1", "mine", "c", 10)
+                .block();
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(10, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginDeleteAsyncWithLocationAndPolls() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final AtomicInteger inProgressCount = new AtomicInteger();
+        final Value<Boolean> completed = new Value<>();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginDeleteAsyncWithLocationAndPolls("1", "mine", "c", 3)
+                .subscribe(new Consumer<OperationStatus<Void>>() {
+                    @Override
+                    public void accept(OperationStatus<Void> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            completed.set(true);
+                        }
+                    }
+                });
+
+        assertEquals(2, inProgressCount.get());
+        assertNotNull(completed.get());
+        assertTrue(completed.get());
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(3, httpClient.pollRequests());
+    }
+
+    @Test
+    public void beginDeleteAsyncWithLocationAndPollsWhenPollsUntilResourceIs0() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient();
+
+        final AtomicInteger inProgressCount = new AtomicInteger();
+        final Value<Boolean> completed = new Value<>();
+
+        createMockService(MockResourceService.class, httpClient)
+                .beginDeleteAsyncWithLocationAndPolls("1", "mine", "c", 0)
+                .subscribe(new Consumer<OperationStatus<Void>>() {
+                    @Override
+                    public void accept(OperationStatus<Void> operationStatus) {
+                        if (!operationStatus.isDone()) {
+                            inProgressCount.incrementAndGet();
+                        }
+                        else {
+                            completed.set(true);
+                        }
+                    }
+                });
+
+        assertEquals(0, inProgressCount.get());
+        assertNotNull(completed.get());
+        assertTrue(completed.get());
+
+        assertEquals(0, httpClient.getRequests());
+        assertEquals(0, httpClient.createRequests());
+        assertEquals(1, httpClient.deleteRequests());
+        assertEquals(0, httpClient.pollRequests());
+    }
+
+    @Test
+    public void deleteAsyncWithForbiddenResponse() {
+        final MockAzureHttpClient httpClient = new MockAzureHttpClient() {
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                return Mono.<HttpResponse>just(new MockAzureHttpResponse(request, 403, MockAzureHttpClient.responseHeaders()));
+            }
+        };
+
+        final MockResourceService service = createMockService(MockResourceService.class, httpClient);
+        try {
+            service.deleteAsyncWithForbiddenResponse().block();
+            fail("Expected RestException to be thrown.");
+        }
+        catch (RestException e) {
+            assertEquals(403, e.response().statusCode());
+            assertEquals("Status code 403, (empty body)", e.getMessage());
+        }
+    }
+
+    private static <T> T createMockService(Class<T> serviceClass, MockAzureHttpClient httpClient) {
+        HttpPipeline pipeline = new HttpPipeline(httpClient);
+
+        return AzureProxy.create(serviceClass, null, pipeline, serializer);
+    }
+
+    private static void assertContains(String value, String expectedSubstring) {
+        assertTrue("Expected \"" + value + "\" to contain \"" + expectedSubstring + "\".", value.contains(expectedSubstring));
+    }
+
+    private static final SerializerAdapter serializer = new JacksonAdapter();
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyTests.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyTests.java
new file mode 100644
index 0000000000000..e5ef41e04cccc
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyTests.java
@@ -0,0 +1,768 @@
+package com.azure.common.mgmt;
+
+import com.azure.common.implementation.exception.InvalidReturnTypeException;
+import com.azure.common.implementation.http.ContentType;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.http.rest.RestResponseBase;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import com.azure.common.annotations.BodyParam;
+import com.azure.common.annotations.DELETE;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.HEAD;
+import com.azure.common.annotations.HeaderParam;
+import com.azure.common.annotations.Headers;
+import com.azure.common.annotations.Host;
+import com.azure.common.annotations.HostParam;
+import com.azure.common.annotations.PATCH;
+import com.azure.common.annotations.POST;
+import com.azure.common.annotations.PUT;
+import com.azure.common.annotations.PathParam;
+import com.azure.common.annotations.QueryParam;
+import com.azure.common.annotations.UnexpectedResponseExceptionType;
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpHeaders;
+import org.junit.Assert;
+import org.junit.Test;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.LinkedHashMap;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public abstract class AzureProxyToRestProxyTests {
+    /**
+     * Get the HTTP client that will be used for each test. This will be called once per test.
+     * @return The HTTP client to use for each test.
+     */
+    protected abstract HttpClient createHttpClient();
+
+    @Host("http://httpbin.org")
+    private interface Service1 {
+        @GET("bytes/100")
+        @ExpectedResponses({200})
+        byte[] getByteArray();
+
+        @GET("bytes/100")
+        @ExpectedResponses({200})
+        Mono<byte[]> getByteArrayAsync();
+
+        @GET("bytes/100")
+        Mono<byte[]> getByteArrayAsyncWithNoExpectedResponses();
+    }
+
+    @Test
+    public void SyncRequestWithByteArrayReturnType() {
+        final byte[] result = createService(Service1.class)
+                .getByteArray();
+        assertNotNull(result);
+        assertEquals(result.length, 100);
+    }
+
+    @Test
+    public void AsyncRequestWithByteArrayReturnType() {
+        final byte[] result = createService(Service1.class)
+                .getByteArrayAsync()
+                .block();
+        assertNotNull(result);
+        assertEquals(result.length, 100);
+    }
+
+    @Test
+    public void getByteArrayAsyncWithNoExpectedResponses() {
+        final byte[] result = createService(Service1.class)
+                .getByteArrayAsyncWithNoExpectedResponses()
+                .block();
+        assertNotNull(result);
+        assertEquals(result.length, 100);
+    }
+
+    @Host("http://{hostName}.org")
+    private interface Service2 {
+        @GET("bytes/{numberOfBytes}")
+        @ExpectedResponses({200})
+        byte[] getByteArray(@HostParam("hostName") String host, @PathParam("numberOfBytes") int numberOfBytes);
+
+        @GET("bytes/{numberOfBytes}")
+        @ExpectedResponses({200})
+        Mono<byte[]> getByteArrayAsync(@HostParam("hostName") String host, @PathParam("numberOfBytes") int numberOfBytes);
+    }
+
+    @Test
+    public void SyncRequestWithByteArrayReturnTypeAndParameterizedHostAndPath() {
+        final byte[] result = createService(Service2.class)
+                .getByteArray("httpbin", 50);
+        assertNotNull(result);
+        assertEquals(result.length, 50);
+    }
+
+    @Test
+    public void AsyncRequestWithByteArrayReturnTypeAndParameterizedHostAndPath() {
+        final byte[] result = createService(Service2.class)
+                .getByteArrayAsync("httpbin", 50)
+                .block();
+        assertNotNull(result);
+        assertEquals(result.length, 50);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service3 {
+        @GET("bytes/2")
+        @ExpectedResponses({200})
+        void getNothing();
+
+        @GET("bytes/2")
+        @ExpectedResponses({200})
+        Mono<Void> getNothingAsync();
+    }
+
+    @Test
+    public void SyncGetRequestWithNoReturn() {
+        createService(Service3.class).getNothing();
+    }
+
+    @Test
+    public void AsyncGetRequestWithNoReturn() {
+        createService(Service3.class)
+                .getNothingAsync()
+                .block();
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service5 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnything();
+
+        @GET("anything/with+plus")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithPlus();
+
+        @GET("anything/{path}")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithPathParam(@PathParam("path") String pathParam);
+
+        @GET("anything/{path}")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithEncodedPathParam(@PathParam(value="path", encoded=true) String pathParam);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAnythingAsync();
+    }
+
+    @Test
+    public void SyncGetRequestWithAnything() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnything();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPlus() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPlus();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with+plus", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPathParam() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPathParam("withpathparam");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/withpathparam", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPathParamWithSpace() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPathParam("with path param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with path param", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPathParamWithPlus() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPathParam("with+path+param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with+path+param", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithEncodedPathParam() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithEncodedPathParam("withpathparam");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/withpathparam", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithEncodedPathParamWithPercent20() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithEncodedPathParam("with%20path%20param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with path param", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithEncodedPathParamWithPlus() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithEncodedPathParam("with+path+param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with+path+param", json.url);
+    }
+
+    @Test
+    public void AsyncGetRequestWithAnything() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingAsync()
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service6 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnything(@QueryParam("a") String a, @QueryParam("b") int b);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithEncoded(@QueryParam(value="a", encoded=true) String a, @QueryParam("b") int b);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAnythingAsync(@QueryParam("a") String a, @QueryParam("b") int b);
+    }
+
+    @Test
+    public void SyncGetRequestWithQueryParametersAndAnything() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnything("A", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=A&b=15", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithQueryParametersAndAnythingWithPercent20() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnything("A%20Z", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=A%2520Z&b=15", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithQueryParametersAndAnythingWithEncodedWithPercent20() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnythingWithEncoded("x%20y", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=x y&b=15", json.url);
+    }
+
+    @Test
+    public void AsyncGetRequestWithQueryParametersAndAnything() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnythingAsync("A", 15)
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=A&b=15", json.url);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service7 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnything(@HeaderParam("a") String a, @HeaderParam("b") int b);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAnythingAsync(@HeaderParam("a") String a, @HeaderParam("b") int b);
+    }
+
+    @Test
+    public void SyncGetRequestWithHeaderParametersAndAnythingReturn() {
+        final HttpBinJSON json = createService(Service7.class)
+                .getAnything("A", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("A", headers.value("A"));
+        assertArrayEquals(new String[]{"A"}, headers.values("A"));
+        assertEquals("15", headers.value("B"));
+        assertArrayEquals(new String[]{"15"}, headers.values("B"));
+    }
+
+    @Test
+    public void AsyncGetRequestWithHeaderParametersAndAnything() {
+        final HttpBinJSON json = createService(Service7.class)
+                .getAnythingAsync("A", 15)
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("A", headers.value("A"));
+        assertArrayEquals(new String[]{"A"}, headers.values("A"));
+        assertEquals("15", headers.value("B"));
+        assertArrayEquals(new String[]{"15"}, headers.values("B"));
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service8 {
+        @POST("post")
+        @ExpectedResponses({200})
+        HttpBinJSON post(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String postBody);
+
+        @POST("post")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> postAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String postBody);
+    }
+
+    @Test
+    public void SyncPostRequestWithStringBody() {
+        final HttpBinJSON json = createService(Service8.class)
+                .post("I'm a post body!");
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("I'm a post body!", (String)json.data);
+    }
+
+    @Test
+    public void AsyncPostRequestWithStringBody() {
+        final HttpBinJSON json = createService(Service8.class)
+                .postAsync("I'm a post body!")
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("I'm a post body!", (String)json.data);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service9 {
+        @PUT("put")
+        @ExpectedResponses({200})
+        HttpBinJSON put(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) int putBody);
+
+        @PUT("put")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> putAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) int putBody);
+
+        @PUT("put")
+        @ExpectedResponses({201})
+        HttpBinJSON putWithUnexpectedResponse(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String putBody);
+
+        @PUT("put")
+        @ExpectedResponses({201})
+        @UnexpectedResponseExceptionType(MyAzureException.class)
+        HttpBinJSON putWithUnexpectedResponseAndExceptionType(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String putBody);
+    }
+
+    @Test
+    public void SyncPutRequestWithIntBody() {
+        final HttpBinJSON json = createService(Service9.class)
+                .put(42);
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("42", (String)json.data);
+    }
+
+    @Test
+    public void AsyncPutRequestWithIntBody() {
+        final HttpBinJSON json = createService(Service9.class)
+                .putAsync(42)
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("42", (String)json.data);
+    }
+
+    @Test
+    public void SyncPutRequestWithUnexpectedResponse() {
+        try {
+            createService(Service9.class)
+                    .putWithUnexpectedResponse("I'm the body!");
+            fail("Expected RestException would be thrown.");
+        } catch (RestException e) {
+            assertNotNull(e.body());
+            assertTrue(e.body() instanceof LinkedHashMap);
+
+            final LinkedHashMap<String,String> expectedBody = (LinkedHashMap<String, String>)e.body();
+            assertEquals("I'm the body!", expectedBody.get("data"));
+        }
+    }
+
+    @Test
+    public void SyncPutRequestWithUnexpectedResponseAndExceptionType() {
+        try {
+            createService(Service9.class)
+                    .putWithUnexpectedResponseAndExceptionType("I'm the body!");
+            fail("Expected RestException would be thrown.");
+        } catch (MyAzureException e) {
+            assertNotNull(e.body());
+            assertEquals("I'm the body!", e.body().data);
+        } catch (Throwable e) {
+            fail("Throwable of wrong type thrown.");
+        }
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service10 {
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        RestResponseBase<Void, Void> restResponseHead();
+
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        void voidHead();
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        Mono<RestResponseBase<Void, Void>> restResponseHeadAsync();
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        Mono<Void> completableHeadAsync();
+    }
+
+    @Test
+    public void SyncRestResponseHeadRequest() {
+        RestResponseBase<?, ?> res = createService(Service10.class)
+                .restResponseHead();
+        assertNull(res.body());
+    }
+
+    @Test
+    public void SyncVoidHeadRequest() {
+        createService(Service10.class)
+                .voidHead();
+    }
+
+    @Test
+    public void AsyncRestResponseHeadRequest() {
+        RestResponseBase<?, ?> res = createService(Service10.class)
+                .restResponseHeadAsync()
+                .block();
+
+        assertNull(res.body());
+    }
+
+    @Test
+    public void AsyncCompletableHeadRequest() {
+        createService(Service10.class)
+                .completableHeadAsync()
+                .block();
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service11 {
+        @DELETE("delete")
+        @ExpectedResponses({200})
+        HttpBinJSON delete(@BodyParam(ContentType.APPLICATION_JSON) boolean bodyBoolean);
+
+        @DELETE("delete")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> deleteAsync(@BodyParam(ContentType.APPLICATION_JSON) boolean bodyBoolean);
+    }
+
+    @Test
+    public void SyncDeleteRequest() {
+        final HttpBinJSON json = createService(Service11.class)
+                .delete(false);
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("false", (String)json.data);
+    }
+
+    @Test
+    public void AsyncDeleteRequest() {
+        final HttpBinJSON json = createService(Service11.class)
+                .deleteAsync(false)
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("false", (String)json.data);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service12 {
+        @PATCH("patch")
+        @ExpectedResponses({200})
+        HttpBinJSON patch(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String bodyString);
+
+        @PATCH("patch")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> patchAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String bodyString);
+    }
+
+    @Test
+    public void SyncPatchRequest() {
+        final HttpBinJSON json = createService(Service12.class)
+                .patch("body-contents");
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("body-contents", (String)json.data);
+    }
+
+    @Test
+    public void AsyncPatchRequest() {
+        final HttpBinJSON json = createService(Service12.class)
+                .patchAsync("body-contents")
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("body-contents", (String)json.data);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service13 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue", "MyOtherHeader:My,Header,Value" })
+        HttpBinJSON get();
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue", "MyOtherHeader:My,Header,Value" })
+        Mono<HttpBinJSON> getAsync();
+    }
+
+    @Test
+    public void SyncHeadersRequest() {
+        final HttpBinJSON json = createService(Service13.class)
+                .get();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("MyHeaderValue", headers.value("MyHeader"));
+        assertArrayEquals(new String[]{"MyHeaderValue"}, headers.values("MyHeader"));
+        assertEquals("My,Header,Value", headers.value("MyOtherHeader"));
+        assertArrayEquals(new String[]{"My", "Header", "Value"}, headers.values("MyOtherHeader"));
+    }
+
+    @Test
+    public void AsyncHeadersRequest() {
+        final HttpBinJSON json = createService(Service13.class)
+                .getAsync()
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("MyHeaderValue", headers.value("MyHeader"));
+        assertArrayEquals(new String[]{"MyHeaderValue"}, headers.values("MyHeader"));
+    }
+
+    @Host("https://httpbin.org")
+    private interface Service14 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue" })
+        HttpBinJSON get();
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue" })
+        Mono<HttpBinJSON> getAsync();
+    }
+
+    @Test
+    public void AsyncHttpsHeadersRequest() {
+        final HttpBinJSON json = createService(Service14.class)
+                .getAsync()
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("MyHeaderValue", headers.value("MyHeader"));
+    }
+
+    @Host("https://httpbin.org")
+    private interface Service15 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        Flux<HttpBinJSON> get();
+    }
+
+    @Test
+    public void service15Get() {
+        final Service15 service = createService(Service15.class);
+        try {
+            service.get();
+            fail("Expected exception.");
+        }
+        catch (InvalidReturnTypeException e) {
+            assertContains(e.getMessage(), "reactor.core.publisher.Flux<com.azure.common.mgmt.HttpBinJSON>");
+            assertContains(e.getMessage(), "AzureProxyToRestProxyTests$Service15.get()");
+        }
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service16 {
+        @PUT("put")
+        @ExpectedResponses({200})
+        HttpBinJSON put(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) byte[] putBody);
+
+        @PUT("put")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> putAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) byte[] putBody);
+    }
+
+    @Test
+    public void service16Put() {
+        final Service16 service = createService(Service16.class);
+        final HttpBinJSON result = service.put(new byte[] { 0, 1, 2, 3, 4, 5 });
+        assertNotNull(result);
+        assertMatchWithHttpOrHttps("httpbin.org/put", result.url);
+        assertTrue(result.data instanceof String);
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5 }, ((String)result.data).getBytes());
+    }
+
+    @Test
+    public void service16PutAsync() {
+        final Service16 service = createService(Service16.class);
+        final HttpBinJSON result = service.putAsync(new byte[] { 0, 1, 2, 3, 4, 5 })
+                .block();
+        assertNotNull(result);
+        assertMatchWithHttpOrHttps("httpbin.org/put", result.url);
+        assertTrue(result.data instanceof String);
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5 }, ((String)result.data).getBytes());
+    }
+
+    @Host("http://{hostPart1}{hostPart2}.org")
+    private interface Service17 {
+        @GET("get")
+        @ExpectedResponses({200})
+        HttpBinJSON get(@HostParam("hostPart1") String hostPart1, @HostParam("hostPart2") String hostPart2);
+
+        @GET("get")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAsync(@HostParam("hostPart1") String hostPart1, @HostParam("hostPart2") String hostPart2);
+    }
+
+    @Test
+    public void SyncRequestWithMultipleHostParams() {
+        final Service17 service17 = createService(Service17.class);
+        final HttpBinJSON result = service17.get("http", "bin");
+        assertNotNull(result);
+        assertMatchWithHttpOrHttps("httpbin.org/get", result.url);
+    }
+
+    @Test
+    public void AsyncRequestWithMultipleHostParams() {
+        final Service17 service17 = createService(Service17.class);
+        final HttpBinJSON result = service17.getAsync("http", "bin").block();
+        assertNotNull(result);
+        assertMatchWithHttpOrHttps("httpbin.org/get", result.url);
+    }
+
+    @Host("https://httpbin.org")
+    private interface Service18 {
+        @GET("status/200")
+        void getStatus200();
+
+        @GET("status/200")
+        @ExpectedResponses({200})
+        void getStatus200WithExpectedResponse200();
+
+        @GET("status/300")
+        void getStatus300();
+
+        @GET("status/300")
+        @ExpectedResponses({300})
+        void getStatus300WithExpectedResponse300();
+
+        @GET("status/400")
+        void getStatus400();
+
+        @GET("status/400")
+        @ExpectedResponses({400})
+        void getStatus400WithExpectedResponse400();
+
+        @GET("status/500")
+        void getStatus500();
+
+        @GET("status/500")
+        @ExpectedResponses({500})
+        void getStatus500WithExpectedResponse500();
+    }
+
+    @Test
+    public void service18GetStatus200() {
+        createService(Service18.class)
+                .getStatus200();
+    }
+
+    @Test
+    public void service18GetStatus200WithExpectedResponse200() {
+        createService(Service18.class)
+                .getStatus200WithExpectedResponse200();
+    }
+
+    @Test
+    public void service18GetStatus300() {
+        createService(Service18.class)
+                .getStatus300();
+    }
+
+    @Test
+    public void service18GetStatus300WithExpectedResponse300() {
+        createService(Service18.class)
+                .getStatus300WithExpectedResponse300();
+    }
+
+    @Test(expected = RestException.class)
+    public void service18GetStatus400() {
+        createService(Service18.class)
+                .getStatus400();
+    }
+
+    @Test
+    public void service18GetStatus400WithExpectedResponse400() {
+        createService(Service18.class)
+                .getStatus400WithExpectedResponse400();
+    }
+
+    @Test(expected = RestException.class)
+    public void service18GetStatus500() {
+        createService(Service18.class)
+                .getStatus500();
+    }
+
+    @Test
+    public void service18GetStatus500WithExpectedResponse500() {
+        createService(Service18.class)
+                .getStatus500WithExpectedResponse500();
+    }
+
+    private <T> T createService(Class<T> serviceClass) {
+        HttpPipeline pipeline = new HttpPipeline(createHttpClient());
+        //
+        return AzureProxy.create(serviceClass, null, pipeline, serializer);
+    }
+
+    private static void assertContains(String value, String expectedSubstring) {
+        assertTrue("Expected \"" + value + "\" to contain \"" + expectedSubstring + "\".", value.contains(expectedSubstring));
+    }
+
+    private static void assertMatchWithHttpOrHttps(String url1, String url2) {
+        final String s1 = "http://" + url1;
+        if (s1.equalsIgnoreCase(url2)) {
+            return;
+        }
+        final String s2 = "https://" + url1;
+        if (s2.equalsIgnoreCase(url2)) {
+            return;
+        }
+        Assert.assertTrue("'" + url2 + "' does not match with '" + s1 + "' or '" + s2 + "'." , false);
+    }
+
+    private static final SerializerAdapter serializer = new JacksonAdapter();
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyWithMockTests.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyWithMockTests.java
new file mode 100644
index 0000000000000..541c228f45665
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyWithMockTests.java
@@ -0,0 +1,11 @@
+package com.azure.common.mgmt;
+
+import com.azure.common.mgmt.http.MockAzureHttpClient;
+import com.azure.common.http.HttpClient;
+
+public class AzureProxyToRestProxyWithMockTests extends AzureProxyToRestProxyTests {
+    @Override
+    protected HttpClient createHttpClient() {
+        return new MockAzureHttpClient();
+    }
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyWithNettyTests.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyWithNettyTests.java
new file mode 100644
index 0000000000000..77bbc35399102
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureProxyToRestProxyWithNettyTests.java
@@ -0,0 +1,11 @@
+package com.azure.common.mgmt;
+
+import com.azure.common.http.HttpClient;
+
+public class AzureProxyToRestProxyWithNettyTests extends AzureProxyToRestProxyTests {
+
+    @Override
+    protected HttpClient createHttpClient() {
+        return HttpClient.createDefault();
+    }
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureTests.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureTests.java
new file mode 100644
index 0000000000000..5e11515972dcb
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/AzureTests.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import com.azure.common.mgmt.annotations.AzureHost;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.HostParam;
+import com.azure.common.annotations.PathParam;
+
+public class AzureTests {
+
+    @AzureHost("{vaultBaseUrl}")
+    public interface HttpBinService {
+        @GET("secrets/{secretName}")
+        String getSecret(@HostParam("vaultBaseUrl") String vaultBaseUrl, @PathParam("secretName") String secretName);
+    }
+
+// @AzureHost not yet supported.
+//    @Test
+//    public void getBytes() throws Exception {
+//        RestClient client = RestClient.newDefaultBuilder()
+//                .withBaseUrl("http://localhost")
+//                .withResponseBuilderFactory(new ServiceResponseBuilder.Factory())
+//                .build();
+//        HttpBinService service = RestProxy.create(HttpBinService.class, client);
+//
+//        Assert.assertEquals("http://vault1.vault.azure.net/secrets/{secretName}", service.getSecret("http://vault1.vault.azure.net", "secret1"));
+//    }
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/HttpBinJSON.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/HttpBinJSON.java
new file mode 100644
index 0000000000000..03a6374750427
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/HttpBinJSON.java
@@ -0,0 +1,12 @@
+package com.azure.common.mgmt;
+
+import java.util.Map;
+
+/**
+ * Maps to the JSON return values from http://httpbin.org.
+ */
+public class HttpBinJSON {
+    public String url;
+    public Map<String,String> headers;
+    public Object data;
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/MockResource.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/MockResource.java
new file mode 100644
index 0000000000000..b1b7aac382e21
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/MockResource.java
@@ -0,0 +1,11 @@
+package com.azure.common.mgmt;
+
+public class MockResource {
+    public String name;
+
+    public Properties properties;
+
+    public static class Properties {
+        public String provisioningState;
+    }
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/MyAzureException.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/MyAzureException.java
new file mode 100644
index 0000000000000..3c09f711f5a97
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/MyAzureException.java
@@ -0,0 +1,15 @@
+package com.azure.common.mgmt;
+
+import com.azure.common.http.rest.RestException;
+import com.azure.common.http.HttpResponse;
+
+public class MyAzureException extends RestException {
+    public MyAzureException(String message, HttpResponse response, HttpBinJSON body) {
+        super(message, response, body);
+    }
+
+    @Override
+    public HttpBinJSON body() {
+        return (HttpBinJSON) super.body();
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/PagedListTests.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/PagedListTests.java
new file mode 100644
index 0000000000000..f9252d27b70b6
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/PagedListTests.java
@@ -0,0 +1,343 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import reactor.core.publisher.Flux;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.function.Supplier;
+
+public class PagedListTests {
+    private PagedList<Integer> list;
+
+    @Before
+    public void setupList() {
+        list = new PagedList<Integer>(new TestPage(0, 21)) {
+            @Override
+            public Page<Integer> nextPage(String nextPageLink) {
+                int pageNum = Integer.parseInt(nextPageLink);
+                return new TestPage(pageNum, 21);
+            }
+        };
+    }
+
+    @Test
+    public void sizeTest() {
+        Assert.assertEquals(20, list.size());
+    }
+
+    @Test
+    public void getTest() {
+        Assert.assertEquals(15, (int) list.get(15));
+    }
+
+    @Test
+    public void iterateTest() {
+        int j = 0;
+        for (int i : list) {
+            Assert.assertEquals(i, j++);
+        }
+    }
+
+    @Test
+    public void removeTest() {
+        Integer i = list.get(10);
+        list.remove(10);
+        Assert.assertEquals(19, list.size());
+        Assert.assertEquals(19, (int) list.get(18));
+    }
+
+    @Test
+    public void addTest() {
+        Integer i = list.get(10);
+        list.add(100);
+        Assert.assertEquals(21, list.size());
+        Assert.assertEquals(100, (int) list.get(11));
+        Assert.assertEquals(19, (int) list.get(20));
+    }
+
+    @Test
+    public void containsTest() {
+        Assert.assertTrue(list.contains(0));
+        Assert.assertTrue(list.contains(3));
+        Assert.assertTrue(list.contains(19));
+        Assert.assertFalse(list.contains(20));
+    }
+
+    @Test
+    public void containsAllTest() {
+        List<Integer> subList = new ArrayList<>();
+        subList.addAll(Arrays.asList(0, 3, 19));
+        Assert.assertTrue(list.containsAll(subList));
+        subList.add(20);
+        Assert.assertFalse(list.containsAll(subList));
+    }
+
+    @Test
+    public void subListTest() {
+        List<Integer> subList = list.subList(5, 15);
+        Assert.assertEquals(10, subList.size());
+        Assert.assertTrue(list.containsAll(subList));
+        Assert.assertEquals(7, (int) subList.get(2));
+    }
+
+    @Test
+    public void testIndexOf() {
+        Assert.assertEquals(15, list.indexOf(15));
+    }
+
+    @Test
+    public void testLastIndexOf() {
+        Assert.assertEquals(15, list.lastIndexOf(15));
+    }
+
+
+    @Test
+    public void testIteratorWithListSizeInvocation() {
+        ListIterator<Integer> itr = list.listIterator();
+        list.size();
+        int j = 0;
+        while (itr.hasNext()) {
+            Assert.assertEquals(j++, (long) itr.next());
+        }
+    }
+
+    @Test
+    public void testIteratorPartsWithSizeInvocation() {
+        ListIterator<Integer> itr = list.listIterator();
+        int j = 0;
+        while (j < 5) {
+            Assert.assertTrue(itr.hasNext());
+            Assert.assertEquals(j++, (long) itr.next());
+        }
+        list.size();
+        while (j < 10) {
+            Assert.assertTrue(itr.hasNext());
+            Assert.assertEquals(j++, (long) itr.next());
+        }
+    }
+
+    @Test
+    public void testIteratorWithLoadNextPageInvocation() {
+        ListIterator<Integer> itr = list.listIterator();
+        int j = 0;
+        while (j < 5) {
+            Assert.assertTrue(itr.hasNext());
+            Assert.assertEquals(j++, (long) itr.next());
+        }
+        list.loadNextPage();
+        while (j < 10) {
+            Assert.assertTrue(itr.hasNext());
+            Assert.assertEquals(j++, (long) itr.next());
+        }
+        list.loadNextPage();
+        while (itr.hasNext()) {
+            Assert.assertEquals(j++, (long) itr.next());
+        }
+        Assert.assertEquals(20, j);
+    }
+
+    @Test
+    public void testIteratorOperations() {
+        ListIterator<Integer> itr1 = list.listIterator();
+        IllegalStateException expectedException = null;
+        try {
+            itr1.remove();
+        } catch (IllegalStateException ex) {
+            expectedException = ex;
+        }
+        Assert.assertNotNull(expectedException);
+
+        ListIterator<Integer> itr2 = list.listIterator();
+        Assert.assertTrue(itr2.hasNext());
+        Assert.assertEquals(0, (long) itr2.next());
+        itr2.remove();
+        Assert.assertTrue(itr2.hasNext());
+        Assert.assertEquals(1, (long) itr2.next());
+
+        itr2.set(100);
+        Assert.assertTrue(itr2.hasPrevious());
+        Assert.assertEquals(100, (long) itr2.previous());
+        Assert.assertTrue(itr2.hasNext());
+        Assert.assertEquals(100, (long) itr2.next());
+    }
+
+    @Test
+    public void testAddViaIteratorWhileIterating() {
+        ListIterator<Integer> itr1 = list.listIterator();
+        while (itr1.hasNext()) {
+            Integer val = itr1.next();
+            if (val < 10) {
+                itr1.add(99);
+            }
+        }
+        Assert.assertEquals(30, list.size());
+    }
+
+    @Test
+    public void testRemoveViaIteratorWhileIterating() {
+        ListIterator<Integer> itr1 = list.listIterator();
+        while (itr1.hasNext()) {
+            itr1.next();
+            itr1.remove();
+        }
+        Assert.assertEquals(0, list.size());
+    }
+
+    @Test
+    public void canHandleIntermediateEmptyPage() {
+        List<Integer> pagedList = new PagedList<Integer>(new Page<Integer>() {
+            @Override
+            public String nextPageLink() {
+                return "A";
+            }
+
+            @Override
+            public List<Integer> items() {
+                List<Integer> list = new ArrayList<>();
+                list.add(1);
+                list.add(2);
+                return list;
+            }
+        }) {
+            @Override
+            public Page<Integer> nextPage(String nextPageLink) {
+                if (nextPageLink == "A") {
+                    return new Page<Integer>() {
+                        @Override
+                        public String nextPageLink() {
+                            return "B";
+                        }
+
+                        @Override
+                        public List<Integer> items() {
+                            return new ArrayList<>(); // EMPTY PAGE
+                        }
+                    };
+                } else if (nextPageLink == "B") {
+                    return new Page<Integer>() {
+                        @Override
+                        public String nextPageLink() {
+                            return "C";
+                        }
+
+                        @Override
+                        public List<Integer> items() {
+                            List<Integer> list = new ArrayList<>();
+                            list.add(3);
+                            list.add(4);
+                            return list;
+                        }
+                    };
+                } else if (nextPageLink == "C") {
+                    return new Page<Integer>() {
+                        @Override
+                        public String nextPageLink() {
+                            return null;
+                        }
+
+                        @Override
+                        public List<Integer> items() {
+                            List<Integer> list = new ArrayList<>();
+                            list.add(5);
+                            list.add(6);
+                            return list;
+                        }
+                    };
+                }
+                throw new RuntimeException("nextPage should not be called after a page with next link as null");
+            }
+        };
+        ListIterator<Integer> itr = pagedList.listIterator();
+        int c = 1;
+        while (itr.hasNext()) {
+            Assert.assertEquals(c, (int) itr.next());
+            c++;
+        }
+        Assert.assertEquals(7, c);
+    }
+
+    @Test
+    public void canCreateFluxFromPagedList() {
+        // Test lazy flux can be created by ensuring loadNextPage invoked lazily
+        //
+        class FluxFromPagedList {
+            int loadNextPageCallCount;
+
+            Flux<Integer> toFlux() {
+                return firstFlux().concatWith(nextFlux());
+            }
+
+            Flux<Integer> firstFlux() {
+                return Flux.defer((Supplier<Flux<Integer>>) () -> Flux.fromIterable(list.currentPage().items()));
+            }
+
+            Flux<Integer> nextFlux() {
+                return Flux.defer((Supplier<Flux<Integer>>) () -> {
+                    if (list.hasNextPage()) {
+                        list.loadNextPage();
+                        loadNextPageCallCount++;
+                        return Flux.fromIterable(list.currentPage().items()).concatWith(Flux.defer(new Supplier<Flux<Integer>>() {
+                            @Override
+                            public Flux<Integer> get() {
+                                return nextFlux();
+                            }
+                        }));
+                    } else {
+                        return Flux.empty();
+                    }
+                });
+            }
+        }
+
+        FluxFromPagedList obpl = new FluxFromPagedList();
+
+        final Integer[] cnt = new Integer[] { 0 };
+        obpl.toFlux().subscribe(integer -> {
+            Assert.assertEquals(cnt[0], integer);
+            cnt[0]++;
+        });
+        Assert.assertEquals(20, (long) cnt[0]);
+        Assert.assertEquals(19, obpl.loadNextPageCallCount);
+    }
+
+
+    public static class TestPage implements Page<Integer> {
+        private int page;
+        private int max;
+
+        public TestPage(int page, int max) {
+            this.page = page;
+            this.max = max;
+        }
+
+        @Override
+        public String nextPageLink() {
+            if (page + 1 == max) {
+                return null;
+            }
+            return Integer.toString(page + 1);
+        }
+
+        @Override
+        public List<Integer> items() {
+            if (page + 1 != max) {
+                List<Integer> items = new ArrayList<>();
+                items.add(page);
+                return items;
+            } else {
+                return new ArrayList<>();
+            }
+        }
+    }
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/Value.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/Value.java
new file mode 100644
index 0000000000000..679e510a38b63
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/Value.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt;
+
+/**
+ * A container for a generic type. Serves a similar purpose as pointers in C/C++. It's a workaround
+ * for the fact that Java doesn't allow mutation of local variables in closure.
+ * @param <T>
+ */
+class Value<T> {
+    private T value;
+
+    /**
+     * Create a new Value with inner value.
+     */
+    Value() {
+    }
+
+    /**
+     * Create a new Value with the provided inner value.
+     * @param value
+     */
+    Value(T value) {
+        set(value);
+    }
+
+    /**
+     * Get the inner value of this Value.
+     * @return The inner value of this Value.
+     */
+    public T get() {
+        return value;
+    }
+
+    /**
+     * Set the inner value of this Value.
+     * @param value The new inner value of this Value.
+     */
+    public void set(T value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(value);
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/ValueTests.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/ValueTests.java
new file mode 100644
index 0000000000000..758272207c324
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/ValueTests.java
@@ -0,0 +1,21 @@
+package com.azure.common.mgmt;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class ValueTests {
+    @Test
+    public void constructorWithNoArguments() {
+        final Value<Integer> v = new Value<>();
+        assertNull(v.get());
+        assertEquals("null", v.toString());
+    }
+
+    @Test
+    public void constructorWithArgument() {
+        final Value<Integer> v = new Value<>(20);
+        assertEquals(20, v.get().intValue());
+        assertEquals("20", v.toString());
+    }
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/http/MockAzureHttpClient.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/http/MockAzureHttpClient.java
new file mode 100644
index 0000000000000..a444c34e0b88b
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/http/MockAzureHttpClient.java
@@ -0,0 +1,337 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt.http;
+
+import com.azure.common.mgmt.AsyncOperationResource;
+import com.azure.common.mgmt.AzureAsyncOperationPollStrategy;
+import com.azure.common.mgmt.MockResource;
+import com.azure.common.mgmt.OperationState;
+import com.azure.common.mgmt.HttpBinJSON;
+import com.azure.common.mgmt.LocationPollStrategy;
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpHeader;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.ProxyOptions;
+import com.azure.common.implementation.util.FluxUtil;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * This HttpClient attempts to mimic the behavior of http://httpbin.org without ever making a network call.
+ */
+public class MockAzureHttpClient implements HttpClient {
+    private int pollsRemaining;
+
+    private int getRequests;
+    private int createRequests;
+    private int deleteRequests;
+    private int pollRequests;
+
+    public int getRequests() {
+        return getRequests;
+    }
+
+    public int createRequests() {
+        return createRequests;
+    }
+
+    public int deleteRequests() {
+        return deleteRequests;
+    }
+
+    public int pollRequests() {
+        return pollRequests;
+    }
+
+    @Override
+    public Mono<HttpResponse> send(HttpRequest request) {
+        MockAzureHttpResponse response = null;
+
+        try {
+            final URL requestUrl = request.url();
+            final String requestHost = requestUrl.getHost();
+            final String requestPath = requestUrl.getPath();
+            final String requestPathLower = requestPath.toLowerCase();
+            if (requestHost.equalsIgnoreCase("httpbin.org")) {
+                if (requestPathLower.equals("/anything") || requestPathLower.startsWith("/anything/")) {
+                    if ("HEAD".equals(request.httpMethod())) {
+                        response = new MockAzureHttpResponse(request, 200, responseHeaders(), "");
+                    } else {
+                        final HttpBinJSON json = new HttpBinJSON();
+                        json.url = request.url().toString()
+                                // This is just to mimic the behavior we've seen with httpbin.org.
+                                .replace("%20", " ");
+                        json.headers = toMap(request.headers());
+                        response = new MockAzureHttpResponse(request, 200, responseHeaders(), json);
+                    }
+                }
+                else if (requestPathLower.startsWith("/bytes/")) {
+                    final String byteCountString = requestPath.substring("/bytes/".length());
+                    final int byteCount = Integer.parseInt(byteCountString);
+                    response = new MockAzureHttpResponse(request, 200, responseHeaders(), new byte[byteCount]);
+                }
+                else if (requestPathLower.equals("/delete")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = bodyToString(request);
+                    response = new MockAzureHttpResponse(request, 200, responseHeaders(), json);
+                }
+                else if (requestPathLower.equals("/get")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.headers = toMap(request.headers());
+                    response = new MockAzureHttpResponse(request, 200, responseHeaders(), json);
+                }
+                else if (requestPathLower.equals("/patch")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = bodyToString(request);
+                    response = new MockAzureHttpResponse(request, 200, responseHeaders(), json);
+                }
+                else if (requestPathLower.equals("/post")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = bodyToString(request);
+                    response = new MockAzureHttpResponse(request, 200, responseHeaders(), json);
+                }
+                else if (requestPathLower.equals("/put")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = bodyToString(request);
+                    response = new MockAzureHttpResponse(request, 200, responseHeaders(), json);
+                }
+                else if (requestPathLower.startsWith("/status/")) {
+                    final String statusCodeString = requestPathLower.substring("/status/".length());
+                    final int statusCode = Integer.valueOf(statusCodeString);
+                    response = new MockAzureHttpResponse(request, statusCode, responseHeaders());
+                }
+            }
+            else if (requestHost.equalsIgnoreCase("mock.azure.com")) {
+                if (request.httpMethod() == HttpMethod.GET) {
+                    if (requestPathLower.contains("/mockprovider/mockresources/")) {
+                        ++getRequests;
+                        --pollsRemaining;
+
+                        final MockResource resource = new MockResource();
+                        resource.name = requestPath.substring(requestPath.lastIndexOf('/') + 1);
+                        resource.properties = new MockResource.Properties();
+                        resource.properties.provisioningState = (pollsRemaining <= 0 ? OperationState.SUCCEEDED : OperationState.IN_PROGRESS);
+                        response = new MockAzureHttpResponse(request, 200, responseHeaders(), resource);
+                    }
+                    else if (requestPathLower.contains("/mockprovider/mockoperations/")) {
+                        ++pollRequests;
+
+                        final Map<String,String> requestQueryMap = queryToMap(requestUrl.getQuery());
+
+                        final String pollType = requestQueryMap.get("PollType");
+
+                        if (pollType.equalsIgnoreCase(AzureAsyncOperationPollStrategy.HEADER_NAME)) {
+                            String operationStatus;
+                            if (pollsRemaining <= 1) {
+                                operationStatus = OperationState.SUCCEEDED;
+                            }
+                            else {
+                                --pollsRemaining;
+                                operationStatus = OperationState.IN_PROGRESS;
+                            }
+                            final AsyncOperationResource operationResource = new AsyncOperationResource();
+                            operationResource.setStatus(operationStatus);
+                            response = new MockAzureHttpResponse(request, 200, responseHeaders(), operationResource);
+                        }
+                        else if (pollType.equalsIgnoreCase(LocationPollStrategy.HEADER_NAME)) {
+                            if (pollsRemaining <= 1) {
+                                final MockResource mockResource = new MockResource();
+                                mockResource.name = "c";
+                                mockResource.properties = new MockResource.Properties();
+                                mockResource.properties.provisioningState = OperationState.SUCCEEDED;
+                                response = new MockAzureHttpResponse(request, 200, responseHeaders(), mockResource);
+                            }
+                            else {
+                                --pollsRemaining;
+                                response = new MockAzureHttpResponse(request, 202, responseHeaders())
+                                        .withHeader(LocationPollStrategy.HEADER_NAME, request.url().toString());
+                            }
+                        }
+                    }
+                }
+                else if (request.httpMethod() == HttpMethod.PUT) {
+                    ++createRequests;
+
+                    final Map<String, String> requestQueryMap = queryToMap(requestUrl.getQuery());
+
+                    final String pollType = requestQueryMap.get("PollType");
+                    String pollsRemainingString = requestQueryMap.get("PollsRemaining");
+
+                    if (pollType == null || "0".equals(pollsRemainingString)) {
+                        final MockResource resource = new MockResource();
+                        resource.name = "c";
+                        resource.properties = new MockResource.Properties();
+                        resource.properties.provisioningState = OperationState.SUCCEEDED;
+                        response = new MockAzureHttpResponse(request, 200, responseHeaders(), resource);
+                    }
+                    else if (pollType.equalsIgnoreCase("ProvisioningState")) {
+
+                        if (pollsRemainingString == null) {
+                            pollsRemaining = 1;
+                        }
+                        else {
+                            pollsRemaining = Integer.valueOf(pollsRemainingString);
+                        }
+
+                        final MockResource resource = new MockResource();
+                        resource.name = "c";
+                        resource.properties = new MockResource.Properties();
+                        resource.properties.provisioningState = (pollsRemaining <= 0 ? OperationState.SUCCEEDED : OperationState.IN_PROGRESS);
+                        response = new MockAzureHttpResponse(request, 200, responseHeaders(), resource);
+                    }
+                    else {
+                        if (pollsRemainingString == null) {
+                            pollsRemaining = 1;
+                        }
+                        else {
+                            pollsRemaining = Integer.valueOf(pollsRemainingString);
+                        }
+
+                        final String initialResponseStatusCodeString = requestQueryMap.get("InitialResponseStatusCode");
+                        int initialResponseStatusCode;
+                        if (initialResponseStatusCodeString != null) {
+                            initialResponseStatusCode = Integer.valueOf(initialResponseStatusCodeString);
+                        }
+                        else if (pollType.equalsIgnoreCase(LocationPollStrategy.HEADER_NAME)) {
+                            initialResponseStatusCode = 202;
+                        }
+                        else {
+                            initialResponseStatusCode = 201;
+                        }
+
+                        response = new MockAzureHttpResponse(request, initialResponseStatusCode, responseHeaders());
+
+                        final String pollUrl = "https://mock.azure.com/subscriptions/1/resourceGroups/mine/providers/mockprovider/mockoperations/1";
+                        if (pollType.contains(AzureAsyncOperationPollStrategy.HEADER_NAME)) {
+                            response.withHeader(AzureAsyncOperationPollStrategy.HEADER_NAME, pollUrl + "?PollType=" + AzureAsyncOperationPollStrategy.HEADER_NAME);
+                        }
+                        if (pollType.contains(LocationPollStrategy.HEADER_NAME)) {
+                            response.withHeader(LocationPollStrategy.HEADER_NAME, pollUrl + "?PollType=" + LocationPollStrategy.HEADER_NAME);
+                        }
+                    }
+                }
+                else if (request.httpMethod() == HttpMethod.DELETE) {
+                    ++deleteRequests;
+
+                    final Map<String,String> requestQueryMap = queryToMap(requestUrl.getQuery());
+
+                    final String pollType = requestQueryMap.get("PollType");
+                    String pollsRemainingString = requestQueryMap.get("PollsRemaining");
+
+                    if (pollType == null || "0".equals(pollsRemainingString)) {
+                        response = new MockAzureHttpResponse(request, 200, responseHeaders());
+                    }
+                    else if (pollType.equals(LocationPollStrategy.HEADER_NAME)) {
+                        if (pollsRemainingString == null) {
+                            pollsRemaining = 1;
+                        }
+                        else {
+                            pollsRemaining = Integer.valueOf(pollsRemainingString);
+                        }
+
+                        final String initialResponseStatusCodeString = requestQueryMap.get("InitialResponseStatusCode");
+                        int initialResponseStatusCode;
+                        if (initialResponseStatusCodeString != null) {
+                            initialResponseStatusCode = Integer.valueOf(initialResponseStatusCodeString);
+                        }
+                        else if (pollType.equalsIgnoreCase(LocationPollStrategy.HEADER_NAME)) {
+                            initialResponseStatusCode = 202;
+                        }
+                        else {
+                            initialResponseStatusCode = 201;
+                        }
+
+                        response = new MockAzureHttpResponse(request, initialResponseStatusCode, responseHeaders());
+
+                        final String pollUrl = "https://mock.azure.com/subscriptions/1/resourceGroups/mine/providers/mockprovider/mockoperations/1";
+                        if (pollType.contains(AzureAsyncOperationPollStrategy.HEADER_NAME)) {
+                            response.withHeader(AzureAsyncOperationPollStrategy.HEADER_NAME, pollUrl + "?PollType=" + AzureAsyncOperationPollStrategy.HEADER_NAME);
+                        }
+                        if (pollType.contains(LocationPollStrategy.HEADER_NAME)) {
+                            response.withHeader(LocationPollStrategy.HEADER_NAME, pollUrl + "?PollType=" + LocationPollStrategy.HEADER_NAME);
+                        }
+                    }
+                }
+            }
+        }
+        catch (Exception ignored) {
+        }
+
+        return Mono.<HttpResponse>just(response);
+    }
+
+    @Override
+    public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+        throw new IllegalStateException("MockHttpClient.proxy");
+    }
+
+    @Override
+    public HttpClient wiretap(boolean enableWiretap) {
+        throw new IllegalStateException("MockHttpClient.wiretap");
+    }
+
+    @Override
+    public HttpClient port(int port) {
+        throw new IllegalStateException("MockHttpClient.port");
+    }
+
+    private static Map<String,String> queryToMap(String url) {
+        final Map<String,String> result = new HashMap<>();
+
+        if (url != null) {
+            final int questionMarkIndex = url.indexOf('?');
+            if (questionMarkIndex >= 0) {
+                url = url.substring(questionMarkIndex + 1);
+            }
+
+            for (String querySegments : url.split("&")) {
+                final String[] querySegmentParts = querySegments.split("=");
+                result.put(querySegmentParts[0], querySegmentParts[1]);
+            }
+        }
+
+        return result;
+    }
+
+    private static String bodyToString(HttpRequest request) throws IOException {
+        Mono<String> asyncString = FluxUtil.collectBytesInByteBufStream(request.body(), false)
+                .map(bytes -> new String(bytes, StandardCharsets.UTF_8));
+        return asyncString.block();
+    }
+
+    private static Map<String, String> toMap(HttpHeaders headers) {
+        final Map<String, String> result = new HashMap<>();
+        for (final HttpHeader header : headers) {
+            result.put(header.name(), header.value());
+        }
+        return result;
+    }
+
+    public static HttpHeaders responseHeaders() {
+        return new HttpHeaders()
+                .set("Date", "Fri, 13 Oct 2017 20:33:09 GMT")
+                .set("Via", "1.1 vegur")
+                .set("Connection", "keep-alive")
+                .set("X-Processed-Time", "1.0")
+                .set("Access-Control-Allow-Credentials", "true")
+                .set("Content-Type", "application/json");
+    }
+}
diff --git a/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/http/MockAzureHttpResponse.java b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/http/MockAzureHttpResponse.java
new file mode 100644
index 0000000000000..760c1655f1b72
--- /dev/null
+++ b/common/azure-common-mgmt/src/test/java/com/azure/common/mgmt/http/MockAzureHttpResponse.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.mgmt.http;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public class MockAzureHttpResponse extends HttpResponse {
+    private final static SerializerAdapter serializer = new JacksonAdapter();
+
+    private final int statusCode;
+
+    private final HttpHeaders headers;
+
+    private final byte[] bodyBytes;
+
+    public MockAzureHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, byte[] bodyBytes) {
+        this.headers = headers;
+
+        this.statusCode = statusCode;
+        this.bodyBytes = bodyBytes;
+        this.withRequest(request);
+    }
+
+    public MockAzureHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers) {
+        this(request, statusCode, headers, new byte[0]);
+    }
+
+    public MockAzureHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, String string) {
+        this(request, statusCode, headers, string == null ? new byte[0] : string.getBytes());
+    }
+
+    public MockAzureHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, Object serializable) {
+        this(request, statusCode, headers, serialize(serializable));
+    }
+
+    private static byte[] serialize(Object serializable) {
+        byte[] result = null;
+        try {
+            final String serializedString = serializer.serialize(serializable, SerializerEncoding.JSON);
+            result = serializedString == null ? null : serializedString.getBytes();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    @Override
+    public int statusCode() {
+        return statusCode;
+    }
+
+    @Override
+    public String headerValue(String name) {
+        return headers.value(name);
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        return new HttpHeaders(headers);
+    }
+
+    @Override
+    public Mono<byte[]> bodyAsByteArray() {
+        return Mono.just(bodyBytes);
+    }
+
+    @Override
+    public Flux<ByteBuf> body() {
+        return Flux.just(Unpooled.wrappedBuffer(bodyBytes));
+    }
+
+    @Override
+    public Mono<String> bodyAsString() {
+        return Mono.just(new String(bodyBytes, StandardCharsets.UTF_8));
+    }
+
+    @Override
+    public Mono<String> bodyAsString(Charset charset) {
+        return Mono.just(new String(bodyBytes, charset));
+    }
+
+    public MockAzureHttpResponse withHeader(String headerName, String headerValue) {
+        headers.set(headerName, headerValue);
+        return this;
+    }
+}
diff --git a/common/azure-common/pom.xml b/common/azure-common/pom.xml
new file mode 100644
index 0000000000000..44555a3e9b182
--- /dev/null
+++ b/common/azure-common/pom.xml
@@ -0,0 +1,187 @@
+<!--
+ Copyright (c) Microsoft Corporation. All rights reserved.
+ Licensed under the MIT License. See License.txt in the project root for
+ license information.
+-->
+<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">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.azure</groupId>
+    <artifactId>azure-common-parent</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <groupId>com.azure</groupId>
+  <artifactId>azure-common</artifactId>
+  <packaging>jar</packaging>
+
+  <name>Azure Java Common Library</name>
+  <description>This package contains common types for Azure Java clients.</description>
+  <url>https://github.com/Azure/autorest-clientruntime-for-java</url>
+
+  <licenses>
+    <license>
+      <name>The MIT License (MIT)</name>
+      <url>http://opensource.org/licenses/MIT</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <scm>
+    <url>scm:git:https://github.com/Azure/autorest-clientruntime-for-java</url>
+    <connection>scm:git:git@github.com:Azure/autorest-clientruntime-for-java.git</connection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <legal><![CDATA[[INFO] Any downloads listed may be third party software.  Microsoft grants you no rights for third party software.]]></legal>
+  </properties>
+
+  <developers>
+    <developer>
+      <id>microsoft</id>
+      <name>Microsoft</name>
+    </developer>
+  </developers>
+
+  <dependencies>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-handler</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-handler-proxy</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-buffer</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-codec-http</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.datatype</groupId>
+      <artifactId>jackson-datatype-jsr310</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.fasterxml.jackson.dataformat</groupId>
+      <artifactId>jackson-dataformat-xml</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>io.projectreactor.netty</groupId>
+      <artifactId>reactor-netty</artifactId>
+      <version>0.8.3.RELEASE</version>
+    </dependency>
+    <dependency>
+      <groupId>io.projectreactor</groupId>
+      <artifactId>reactor-test</artifactId>
+      <version>3.2.3.RELEASE</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.reactivex.rxjava2</groupId>
+      <artifactId>rxjava</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.github.tomakehurst</groupId>
+      <artifactId>wiremock-standalone</artifactId>
+      <version>2.15.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-http</artifactId>
+      <version>9.4.8.v20171121</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>9.4.8.v20171121</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.1</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.8</version>
+        <configuration>
+          <excludePackageNames>*.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization;*.blob.core.storage</excludePackageNames>
+          <bottom><![CDATA[<code>/**
+<br />* Copyright (c) Microsoft Corporation. All rights reserved.
+<br />* Licensed under the MIT License. See License.txt in the project root for
+<br />* license information.
+<br />*/</code>]]></bottom>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.eclipse.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <version>9.3.22.v20171030</version>
+        <configuration>
+          <httpConnector>
+            <port>11081</port>
+          </httpConnector>
+          <webApp>
+            <contextPath>/javasdktest/upload</contextPath>
+          </webApp>
+          <webAppSourceDirectory>temp/</webAppSourceDirectory>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.6.0</version>
+        <executions>
+          <execution>
+            <goals><goal>java</goal></goals>
+          </execution>
+        </executions>
+        <configuration>
+          <mainClass>com.azure.common.MockServer</mainClass>
+          <classpathScope>test</classpathScope>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/common/azure-common/src/main/java/com/azure/common/AzureEnvironment.java b/common/azure-common/src/main/java/com/azure/common/AzureEnvironment.java
new file mode 100644
index 0000000000000..79eceab2645bd
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/AzureEnvironment.java
@@ -0,0 +1,329 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An instance of this class describes an environment in Azure.
+ */
+public final class AzureEnvironment {
+    /** the map of all endpoints. */
+    private final Map<String, String> endpoints;
+
+    /**
+     * Initializes an instance of AzureEnvironment class.
+     *
+     * @param endpoints a map storing all the endpoint info
+     */
+    public AzureEnvironment(Map<String, String> endpoints) {
+        this.endpoints = endpoints;
+    }
+
+    /**
+     * Provides the settings for authentication with Azure.
+     */
+    public static final AzureEnvironment AZURE = new AzureEnvironment(new HashMap<String, String>() {{
+        put("portalUrl", "http://go.microsoft.com/fwlink/?LinkId=254433");
+        put("publishingProfileUrl", "http://go.microsoft.com/fwlink/?LinkId=254432");
+        put("managementEndpointUrl", "https://management.core.windows.net/");
+        put("resourceManagerEndpointUrl", "https://management.azure.com/");
+        put("sqlManagementEndpointUrl", "https://management.core.windows.net:8443/");
+        put("sqlServerHostnameSuffix", ".database.windows.net");
+        put("galleryEndpointUrl", "https://gallery.azure.com/");
+        put("activeDirectoryEndpointUrl", "https://login.microsoftonline.com/");
+        put("activeDirectoryResourceId", "https://management.core.windows.net/");
+        put("activeDirectoryGraphResourceId", "https://graph.windows.net/");
+        put("dataLakeEndpointResourceId", "https://datalake.azure.net/");
+        put("activeDirectoryGraphApiVersion", "2013-04-05");
+        put("storageEndpointSuffix", ".core.windows.net");
+        put("keyVaultDnsSuffix", ".vault.azure.net");
+        put("azureDataLakeStoreFileSystemEndpointSuffix", "azuredatalakestore.net");
+        put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "azuredatalakeanalytics.net");
+        put("azureLogAnalyticsResourceId", "https://api.loganalytics.io/");
+        put("azureApplicationInsightsResourceId", "https://api.applicationinsights.io/");
+    }});
+
+    /**
+     * Provides the settings for authentication with Azure China.
+     */
+    public static final AzureEnvironment AZURE_CHINA = new AzureEnvironment(new HashMap<String, String>() {{
+        put("portalUrl", "http://go.microsoft.com/fwlink/?LinkId=301902");
+        put("publishingProfileUrl", "http://go.microsoft.com/fwlink/?LinkID=301774");
+        put("managementEndpointUrl", "https://management.core.chinacloudapi.cn/");
+        put("resourceManagerEndpointUrl", "https://management.chinacloudapi.cn/");
+        put("sqlManagementEndpointUrl", "https://management.core.chinacloudapi.cn:8443/");
+        put("sqlServerHostnameSuffix", ".database.chinacloudapi.cn");
+        put("galleryEndpointUrl", "https://gallery.chinacloudapi.cn/");
+        put("activeDirectoryEndpointUrl", "https://login.chinacloudapi.cn/");
+        put("activeDirectoryResourceId", "https://management.core.chinacloudapi.cn/");
+        put("activeDirectoryGraphResourceId", "https://graph.chinacloudapi.cn/");
+        // TODO: add resource id for the china cloud for datalake once it is defined.
+        put("dataLakeEndpointResourceId", "N/A");
+        put("activeDirectoryGraphApiVersion", "2013-04-05");
+        put("storageEndpointSuffix", ".core.chinacloudapi.cn");
+        put("keyVaultDnsSuffix", ".vault.azure.cn");
+        // TODO: add dns suffixes for the china cloud for datalake store and datalake analytics once they are defined.
+        put("azureDataLakeStoreFileSystemEndpointSuffix", "N/A");
+        put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "N/A");
+        put("azureLogAnalyticsResourceId", "N/A");
+        put("azureApplicationInsightsResourceId", "N/A");
+    }});
+
+    /**
+     * Provides the settings for authentication with Azure US Government.
+     */
+    public static final AzureEnvironment AZURE_US_GOVERNMENT = new AzureEnvironment(new HashMap<String, String>() {{
+        put("portalUrl", "https://manage.windowsazure.us");
+        put("publishingProfileUrl", "https://manage.windowsazure.us/publishsettings/index");
+        put("managementEndpointUrl", "https://management.core.usgovcloudapi.net/");
+        put("resourceManagerEndpointUrl", "https://management.usgovcloudapi.net/");
+        put("sqlManagementEndpointUrl", "https://management.core.usgovcloudapi.net:8443/");
+        put("sqlServerHostnameSuffix", ".database.usgovcloudapi.net");
+        put("galleryEndpointUrl", "https://gallery.usgovcloudapi.net/");
+        put("activeDirectoryEndpointUrl", "https://login.microsoftonline.us/");
+        put("activeDirectoryResourceId", "https://management.core.usgovcloudapi.net/");
+        put("activeDirectoryGraphResourceId", "https://graph.windows.net/");
+        // TODO: add resource id for the US government for datalake once it is defined.
+        put("dataLakeEndpointResourceId", "N/A");
+        put("activeDirectoryGraphApiVersion", "2013-04-05");
+        put("storageEndpointSuffix", ".core.usgovcloudapi.net");
+        put("keyVaultDnsSuffix", ".vault.usgovcloudapi.net");
+        // TODO: add dns suffixes for the US government for datalake store and datalake analytics once they are defined.
+        put("azureDataLakeStoreFileSystemEndpointSuffix", "N/A");
+        put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "N/A");
+        put("azureLogAnalyticsResourceId", "https://api.loganalytics.us/");
+        put("azureApplicationInsightsResourceId", "N/A");
+    }});
+
+    /**
+     * Provides the settings for authentication with Azure Germany.
+     */
+    public static final AzureEnvironment AZURE_GERMANY = new AzureEnvironment(new HashMap<String, String>() {{
+        put("portalUrl", "http://portal.microsoftazure.de/");
+        put("publishingProfileUrl", "https://manage.microsoftazure.de/publishsettings/index");
+        put("managementEndpointUrl", "https://management.core.cloudapi.de/");
+        put("resourceManagerEndpointUrl", "https://management.microsoftazure.de/");
+        put("sqlManagementEndpointUrl", "https://management.core.cloudapi.de:8443/");
+        put("sqlServerHostnameSuffix", ".database.cloudapi.de");
+        put("galleryEndpointUrl", "https://gallery.cloudapi.de/");
+        put("activeDirectoryEndpointUrl", "https://login.microsoftonline.de/");
+        put("activeDirectoryResourceId", "https://management.core.cloudapi.de/");
+        put("activeDirectoryGraphResourceId", "https://graph.cloudapi.de/");
+        // TODO: add resource id for the germany cloud for datalake once it is defined.
+        put("dataLakeEndpointResourceId", "N/A");
+        put("activeDirectoryGraphApiVersion", "2013-04-05");
+        put("storageEndpointSuffix", ".core.cloudapi.de");
+        put("keyVaultDnsSuffix", ".vault.microsoftazure.de");
+        // TODO: add dns suffixes for the germany cloud for datalake store and datalake analytics once they are defined.
+        put("azureDataLakeStoreFileSystemEndpointSuffix", "N/A");
+        put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "N/A");
+        put("azureLogAnalyticsResourceId", "N/A");
+        put("azureApplicationInsightsResourceId", "N/A");
+    }});
+
+    /**
+     * @return the entirety of the endpoints associated with the current environment.
+     */
+    public Map<String, String> endpoints() {
+        return endpoints;
+    }
+
+    /**
+     * @return the array of known environments to Azure SDK.
+     */
+    public static AzureEnvironment[] knownEnvironments() {
+        List<AzureEnvironment> environments = Arrays.asList(AZURE, AZURE_CHINA, AZURE_GERMANY, AZURE_US_GOVERNMENT);
+        return environments.toArray(new AzureEnvironment[environments.size()]);
+    }
+
+    /**
+     * @return the management portal URL.
+     */
+    public String portal() {
+        return endpoints.get("portalUrl");
+    }
+
+    /**
+     * @return the publish settings file URL.
+     */
+    public String publishingProfile() {
+        return endpoints.get("publishingProfileUrl");
+    }
+
+    /**
+     * @return the management service endpoint.
+     */
+    public String managementEndpoint() {
+        return endpoints.get("managementEndpointUrl");
+    }
+
+    /**
+     * @return the resource management endpoint.
+     */
+    public String resourceManagerEndpoint() {
+        return endpoints.get("resourceManagerEndpointUrl");
+    }
+
+    /**
+     * @return the sql server management endpoint for mobile commands.
+     */
+    public String sqlManagementEndpoint() {
+        return endpoints.get("sqlManagementEndpointUrl");
+    }
+
+    /**
+     * @return the dns suffix for sql servers.
+     */
+    public String sqlServerHostnameSuffix() {
+        return endpoints.get("sqlServerHostnameSuffix");
+    }
+
+    /**
+     * @return the Active Directory login endpoint.
+     */
+    public String activeDirectoryEndpoint() {
+        return endpoints.get("activeDirectoryEndpointUrl").replaceAll("/$", "") + "/";
+    }
+
+    /**
+     * @return The resource ID to obtain AD tokens for.
+     */
+    public String activeDirectoryResourceId() {
+        return endpoints.get("activeDirectoryResourceId");
+    }
+
+    /**
+     * @return the template gallery endpoint.
+     */
+    public String galleryEndpoint() {
+        return endpoints.get("galleryEndpointUrl");
+    }
+
+    /**
+     * @return the Active Directory resource ID.
+     */
+    public String graphEndpoint() {
+        return endpoints.get("activeDirectoryGraphResourceId");
+    }
+
+    /**
+     * @return the Data Lake resource ID.
+     */
+    public String dataLakeEndpointResourceId() {
+        return endpoints.get("dataLakeEndpointResourceId");
+    }
+
+    /**
+     * @return the Active Directory api version.
+     */
+    public String activeDirectoryGraphApiVersion() {
+        return endpoints.get("activeDirectoryGraphApiVersion");
+    }
+
+    /**
+     * @return the endpoint suffix for storage accounts.
+     */
+    public String storageEndpointSuffix() {
+        return endpoints.get("storageEndpointSuffix");
+    }
+
+    /**
+     * @return the keyvault service dns suffix.
+     */
+    public String keyVaultDnsSuffix() {
+        return endpoints.get("keyVaultDnsSuffix");
+    }
+
+    /**
+     * @return the data lake store filesystem service dns suffix.
+     */
+    public String azureDataLakeStoreFileSystemEndpointSuffix() {
+        return endpoints.get("azureDataLakeStoreFileSystemEndpointSuffix");
+    }
+
+    /**
+     * @return the data lake analytics job and catalog service dns suffix.
+     */
+    public String azureDataLakeAnalyticsCatalogAndJobEndpointSuffix() {
+        return endpoints.get("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix");
+    }
+
+    /**
+     * @return the log analytics endpoint.
+     */
+    public String logAnalyticsEndpoint() {
+        return endpoints.get("azureLogAnalyticsResourceId");
+    }
+
+    /**
+     * @return the log analytics endpoint.
+     */
+    public String applicationInsightsEndpoint() {
+        return endpoints.get("azureApplicationInsightsResourceId");
+    }
+
+
+    /**
+     * The enum representing available endpoints in an environment.
+     */
+    public enum Endpoint {
+        /** Azure management endpoint. */
+        MANAGEMENT("managementEndpointUrl"),
+        /** Azure Resource Manager endpoint. */
+        RESOURCE_MANAGER("resourceManagerEndpointUrl"),
+        /** Azure SQL endpoint. */
+        SQL("sqlManagementEndpointUrl"),
+        /** Azure Gallery endpoint. */
+        GALLERY("galleryEndpointUrl"),
+        /** Active Directory authentication endpoint. */
+        ACTIVE_DIRECTORY("activeDirectoryEndpointUrl"),
+        /** Azure Active Directory Graph APIs endpoint. */
+        GRAPH("activeDirectoryGraphResourceId"),
+        /** Key Vault DNS suffix. */
+        KEYVAULT("keyVaultDnsSuffix"),
+        /** Azure Data Lake Store DNS suffix. */
+        DATA_LAKE_STORE("azureDataLakeStoreFileSystemEndpointSuffix"),
+        /** Azure Data Lake Analytics DNS suffix. */
+        DATA_LAKE_ANALYTICS("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix"),
+        /** Azure Log Analytics endpoint. */
+        LOG_ANALYTICS("azureLogAnalyticsResourceId"),
+        /** Azure Application Insights. */
+        APPLICATION_INSIGHTS("azureApplicationInsightsResourceId");
+
+        private String field;
+
+        Endpoint(String value) {
+            this.field = value;
+        }
+
+        /**
+         * @return a unique identifier for the endpoint in the environment
+         */
+        public String identifier() {
+            return field;
+        }
+
+        @Override
+        public String toString() {
+            return field;
+        }
+    }
+
+    /**
+     * Get the endpoint URL for the current environment.
+     *
+     * @param endpoint the endpoint
+     * @return the URL
+     */
+    public String url(Endpoint endpoint) {
+        return endpoints.get(endpoint.identifier());
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/ServiceClient.java b/common/azure-common/src/main/java/com/azure/common/ServiceClient.java
new file mode 100644
index 0000000000000..892d52dd3cb5d
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/ServiceClient.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common;
+
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.implementation.RestProxy;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+
+/**
+ * The base class for REST service clients.
+ */
+public abstract class ServiceClient {
+    /**
+     * The HTTP pipeline to send requests through.
+     */
+    private HttpPipeline httpPipeline;
+
+    /**
+     * The lazily-created serializer for this ServiceClient.
+     */
+    private SerializerAdapter serializerAdapter;
+
+    /**
+     * Creates ServiceClient.
+     *
+     * @param httpPipeline The HTTP pipeline to send requests through
+     */
+    protected ServiceClient(HttpPipeline httpPipeline) {
+        this.httpPipeline = httpPipeline;
+    }
+
+    /**
+     * @return the HTTP pipeline to send requests through.
+     */
+    public HttpPipeline httpPipeline() {
+        return this.httpPipeline;
+    }
+
+    /**
+     * @return the serializer for this ServiceClient.
+     */
+    public SerializerAdapter serializerAdapter() {
+        if (this.serializerAdapter == null) {
+            this.serializerAdapter = createSerializerAdapter();
+        }
+        return this.serializerAdapter;
+    }
+
+    protected SerializerAdapter createSerializerAdapter() {
+        return RestProxy.createDefaultSerializer();
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/Beta.java b/common/azure-common/src/main/java/com/azure/common/annotations/Beta.java
new file mode 100644
index 0000000000000..6dc2919b3def2
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/Beta.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate that a functionality is in preview.
+ */
+@Documented
+@Retention(RetentionPolicy.CLASS)
+@Target({ TYPE, METHOD, PARAMETER, CONSTRUCTOR })
+@Inherited
+/**
+ * Indicates functionality that is in preview and as such is subject to change in non-backwards compatible ways in future releases,
+ * including removal, regardless of any compatibility expectations set by the containing library version.
+ *
+ *  Examples:
+ *
+ *  {@literal @}Beta
+ *  {@literal @}Beta(since="v1.0.0")
+ *  {@literal @}Beta(since="v1.2.0", reason="the feature is in preview")
+ *  {@literal @}Beta("introducing Foo which eventually replaces Bar")
+ */
+public @interface Beta {
+    /**
+     * @return the free-form value for the annotation (used if details cannot be provided using since and reason attributes).
+     */
+    String value() default "";
+
+    /**
+     * @return the version number indicating when the annotated target was first introduced to the library as in beta.
+     */
+    String since() default "";
+
+    /**
+     * @return the reason for annotating the target as beta.
+     */
+    String reason() default "";
+
+    /**
+     * @return the warning message.
+     */
+    String warningText() default "This functionality is in preview and as such is subject to change in non-backwards compatible ways in future releases, including removal, regardless of any compatibility expectations set by the containing library version.";
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/BodyParam.java b/common/azure-common/src/main/java/com/azure/common/annotations/BodyParam.java
new file mode 100644
index 0000000000000..861f4aa2aa7b8
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/BodyParam.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to annotate a parameter to send to a REST endpoint as HTTP Request content.
+ *
+ * <p>If the parameter type extends <code>InputStream</code>, this payload is streamed to server through "application/octet-stream".
+ * Otherwise, the body is serialized first and sent as "application/json" or "application/xml", based on the serializer.
+ * </p>
+ *
+ * <p><strong>Example 1: Put JSON</strong></p>
+ *
+ * <pre>
+ * {@literal @}PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *  VirtualMachine createOrUpdate(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId, @BodyParam("application/json") VirtualMachine vm);</pre>
+ *
+ * <p><strong>Example 2: Stream</strong></p>
+ *
+ * <pre>
+ * {@literal @}POST("formdata/stream/uploadfile")
+ *  void uploadFileViaBody(@BodyParam("application/octet-stream") FileInputStream fileContent);</pre>
+ */
+@Retention(RUNTIME)
+@Target(PARAMETER)
+public @interface BodyParam {
+    /**
+     * @return the Content-Type that the body should be treated as
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/DELETE.java b/common/azure-common/src/main/java/com/azure/common/annotations/DELETE.java
new file mode 100644
index 0000000000000..cd794b732e0a4
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/DELETE.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * HTTP DELETE method annotation describing the parameterized relative path to a REST endpoint for resource deletion.
+ *
+ * <p>The required value can be either a relative path or an absolute path. When it's an absolute path, it must start
+ * with a protocol or a parameterized segment (otherwise the parse cannot tell if it's absolute or relative).</p>
+ *
+ * <p><strong>Example 1: Relative path segments</strong></p>
+ *
+ * <pre>
+ * {@literal @}DELETE("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *  void delete(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId);</pre>
+ *
+ * <p><strong>Example 2: Absolute path segment</strong></p>
+ *
+ * <pre>
+ * {@literal @}DELETE({vaultBaseUrl}/secrets/{secretName})
+ *  void delete(@PathParam("vaultBaseUrl" encoded = true) String vaultBaseUrl, @PathParam("secretName") String secretName);</pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DELETE {
+    /**
+     * Get the relative path of the annotated method's DELETE URL.
+     * @return The relative path of the annotated method's DELETE URL.
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/ExpectedResponses.java b/common/azure-common/src/main/java/com/azure/common/annotations/ExpectedResponses.java
new file mode 100644
index 0000000000000..ac7a927e04fbe
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/ExpectedResponses.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to annotate list of HTTP status codes that are expected in response from a REST endpoint.
+ *
+ * <p><strong>Example:</strong></p>
+ *
+ * <pre>
+ * {@literal @}ExpectedResponses({200, 201})
+ * {@literal @}POST("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CustomerInsights/hubs/{hubName}/images/getEntityTypeImageUploadUrl")
+ *  void getUploadUrlForEntityType(@Path("resourceGroupName") String resourceGroupName, @Path("hubName") String hubName, @Path("subscriptionId") String subscriptionId, @Body GetImageUploadUrlInputInner parameters);</pre>
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface ExpectedResponses {
+    /**
+     * The status code that will trigger that an error of type errorType should be returned.
+     * @return The status code that will trigger than an error of type errorType should be returned.
+     */
+    int[] value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/GET.java b/common/azure-common/src/main/java/com/azure/common/annotations/GET.java
new file mode 100644
index 0000000000000..15e3093124ddc
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/GET.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * HTTP GET method annotation describing the parameterized relative path to a REST endpoint for resource retrieval.
+ *
+ * <p>The required value can be either a relative path or an absolute path. When it's an absolute path, it must start
+ * with a protocol or a parameterized segment (otherwise the parse cannot tell if it's absolute or relative).</p>
+ *
+ * <p><strong>Example 1: Relative path segments</strong></p>
+ *
+ * <pre>
+ * {@literal @}GET("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *  VirtualMachine getByResourceGroup(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId);</pre>
+ *
+ * <p><strong>Example 2: Absolute path segment</strong></p>
+ *
+ * <pre>
+ * {@literal @}GET({nextLink})
+ * {@literal List<VirtualMachine>} listNext(@PathParam("nextLink") String nextLink);</pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface GET {
+    /**
+     * Get the relative path of the annotated method's GET URL.
+     * @return The relative path of the annotated method's GET URL.
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/HEAD.java b/common/azure-common/src/main/java/com/azure/common/annotations/HEAD.java
new file mode 100644
index 0000000000000..7d16558ed8112
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/HEAD.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * HTTP HEAD method annotation describing the parameterized relative path to a REST endpoint.
+ *
+ * <p>The required value can be either a relative path or an absolute path. When it's an absolute path, it must start
+ * with a protocol or a parameterized segment (otherwise the parse cannot tell if it's absolute or relative)</p>
+ *
+ * <p><strong>Example 1: Relative path segments</strong></p>
+ *
+ * <pre>
+ * {@literal @}HEAD("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *  boolean checkNameAvailability(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId);</pre>
+ *
+ * <p><strong>Example 2: Absolute path segment</strong></p>
+ *
+ * <pre>
+ * {@literal @}HEAD(https://management.azure.com/{storageAccountId})
+ *  boolean checkNameAvailability(@PathParam("nextLink") String storageAccountId);</pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HEAD {
+    /**
+     * Get the relative path of the annotated method's HEAD URL.
+     * @return The relative path of the annotated method's HEAD URL.
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/HeaderCollection.java b/common/azure-common/src/main/java/com/azure/common/annotations/HeaderCollection.java
new file mode 100644
index 0000000000000..608dbbe9d14b7
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/HeaderCollection.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation on a deserialized header type that indicates that the property should
+ * be treated as a header collection with the provided prefix.
+ */
+@Retention(RUNTIME)
+@Target(FIELD)
+public @interface HeaderCollection {
+    /**
+     * The header collection prefix.
+     *
+     * @return The header collection prefix
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/HeaderParam.java b/common/azure-common/src/main/java/com/azure/common/annotations/HeaderParam.java
new file mode 100644
index 0000000000000..e6ed211cc1f59
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/HeaderParam.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Replaces the header with the value of its target. The value specified here replaces headers specified statically in
+ * the {@link Headers}. If the parameter this annotation is attached to is a Map type, then this will be treated as a
+ * header collection. In that case each of the entries in the argument's map will be individual header values that use
+ * the value of this annotation as a prefix to their key/header name.
+ *
+ * <p><strong>Example 1:</strong></p>
+ *
+ * <pre>
+ * {@code @PUT("{functionId}")}
+ * {@code Mono<RestResponseBase<Headers, Body>>} createOrReplace(@PathParam("functionId", encoded = true) String functionId, @BodyParam FunctionInner function, @HeaderParam("If-Match") String ifMatch);</pre>
+ *
+ * <p>"If-Match: user passed value" will show up as one of the headers.</p>
+ *
+ * <p><strong>Example 2:</strong></p>
+ *
+ * <pre>
+ * {@code @}GET("subscriptions/{subscriptionId}/providers/Microsoft.ServiceBus/namespaces")
+ * {@code Mono<RestResponseBase<Headers, Body>>} list(@Path("subscriptionId") String subscriptionId, @Header("accept-language") String acceptLanguage, @Header("User-Agent") String userAgent);</pre>
+ *
+ * <p>"accept-language" generated by the HTTP client will be overwritten by the user passed value.</p>
+ *
+ * <p><strong>Example 3:</strong></p>
+ *
+ * <pre>
+ * {@code @GET("subscriptions/{subscriptionId}/providers/Microsoft.ServiceBus/namespaces")}
+ * {@code Mono<RestResponseBase<Headers, Body>>} list(@Path("subscriptionId") String subscriptionId, @Header("Authorization") String token);</pre>
+ *
+ * <p>The token parameter will replace the effect of any credentials in the HTTP pipeline.</p>
+ *
+ * <p><strong>Example 4:</strong></p>
+ *
+ * <pre>
+ * {@code @PUT("{containerName}/{blob}")}
+ * {@code @ExpectedResponses({200})}
+ * {@code Mono<RestResponseBase<BlobSetMetadataHeaders, Void>> setMetadata(@HostParam("url") String url, @QueryParam("timeout") Integer timeout, @HeaderParam("x-ms-meta-") Map<String, String> metadata, @HeaderParam("x-ms-lease-id") String leaseId, @HeaderParam("If-Modified-Since") String ifModifiedSince, @HeaderParam("If-Unmodified-Since") String ifUnmodifiedSince, @HeaderParam("If-Match") String ifMatches, @HeaderParam("If-None-Match") String ifNoneMatch, @HeaderParam("x-ms-version") String version, @HeaderParam("x-ms-client-request-id") String requestId, @QueryParam("comp") String comp);}</pre>
+ *
+ * <p>The metadata parameter will be expanded out so that each entry becomes
+ * "x-ms-meta-{@literal <entryKey>}: {@literal <entryValue>}".</p>
+ */
+@Retention(RUNTIME)
+@Target(PARAMETER)
+public @interface HeaderParam {
+    /**
+     * The name of the variable in the endpoint uri template which will be replaced with the value
+     * of the parameter annotated with this annotation.
+     * @return The name of the variable in the endpoint uri template which will be replaced with the
+     *      value of the parameter annotated with this annotation.
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/Headers.java b/common/azure-common/src/main/java/com/azure/common/annotations/Headers.java
new file mode 100644
index 0000000000000..bae4987e05297
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/Headers.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to annotate list of static headers sent to a REST endpoint.
+ *
+ * <p>Headers are comma separated strings, with each in the format of "header name: header value1,header value2".</p>
+ *
+ * <p><strong>Examples:</strong></p>
+ *
+ * <pre>
+ * {@literal @}Headers({ "Content-Type: application/json; charset=utf-8", "accept-language: en-US" })
+ * {@literal @}POST("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CustomerInsights/hubs/{hubName}/images/getEntityTypeImageUploadUrl")
+ *  void getUploadUrlForEntityType(@Path("resourceGroupName") String resourceGroupName, @Path("hubName") String hubName, @Path("subscriptionId") String subscriptionId, @Body GetImageUploadUrlInputInner parameters);</pre>
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface Headers {
+    /**
+     * List of static headers.
+     * @return List of static headers.
+     */
+    String[] value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/Host.java b/common/azure-common/src/main/java/com/azure/common/annotations/Host.java
new file mode 100644
index 0000000000000..f44627b2f8fbf
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/Host.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+/**
+ * Annotation for parameterized host name targeting a REST service.
+ *
+ * <p>This is the 'host' field or 'x-ms-parameterized-host.hostTemplate' field in a Swagger document. parameters are
+ * enclosed in {}s, e.g. {accountName}. An HTTP client must accept the parameterized host as the base URL for the request,
+ * replacing the parameters during runtime with the actual values users provide.</p>
+ *
+ * <p>For parameterized hosts, parameters annotated with {@link HostParam} must be provided. See Java docs in
+ * {@link HostParam} for directions for host parameters.</p>
+ *
+ * <p>The host's value must contain the scheme/protocol and the host. The host's value may contain the
+ * port number.</p>
+ *
+ * <p><strong>Example 1: Static annotation</strong></p>
+ *
+ * <pre>
+ * {@literal @}Host("https://management.azure.com")
+ *  interface VirtualMachinesService {
+ *   {@literal @}GET("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *    VirtualMachine getByResourceGroup(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId);
+ *  }</pre>
+ *
+ * <p><strong>Example 2: Dynamic annotation</strong></p>
+ *
+ * <pre>
+ * {@literal @}Host("https://{vaultName}.vault.azure.net:443")
+ *  interface KeyVaultService {
+ *    {@literal @}GET("secrets/{secretName}")
+ *     Secret get(@HostParam("vaultName") String vaultName, @PathParam("secretName") String secretName);
+ *  }</pre>
+ */
+@Target(value = {TYPE})
+@Retention(RetentionPolicy.RUNTIME)        // Record this annotation in the class file and make it available during runtime.
+public @interface Host {
+    /**
+     * Get the protocol/scheme, host, and optional port number in a single string.
+     * @return The protocol/scheme, host, and optional port number in a single string.
+     */
+    String value() default "";
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/HostParam.java b/common/azure-common/src/main/java/com/azure/common/annotations/HostParam.java
new file mode 100644
index 0000000000000..036ecb650458e
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/HostParam.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to annotate replacement of parameterized segments in a dynamic {@link Host}.
+ *
+ * <p>You provide the value, which should be the same (case sensitive) with the parameterized segments in '{}' in the
+ * host, unless there's only one parameterized segment, then you can leave the value empty. This is extremely
+ * useful when the designer of the API interface doesn't know about the named parameters in the host.</p>
+ *
+ * <p><strong>Example 1: Named parameters</strong></p>
+ *
+ * <pre>
+ * {@literal @}Host("{accountName}.{suffix}")
+ *  interface DatalakeService {
+ *   {@literal @}GET("jobs/{jobIdentity}")
+ *    Job getJob(@HostParam("accountName") String accountName, @HostParam("suffix") String suffix, @PathParam("jobIdentity") jobIdentity);
+ *  }</pre>
+ *
+ * <p><strong>Example 2: Unnamed parameter</strong></p>
+ *
+ * <pre>
+ * {@literal @}Host(KEY_VAULT_ENDPOINT)
+ *  interface KeyVaultService {
+ *   {@literal @}GET("secrets/{secretName}")
+ *    Secret get(@HostParam String vaultName, @PathParam("secretName") String secretName);
+ *  }</pre>
+ */
+@Retention(RUNTIME)
+@Target(PARAMETER)
+public @interface HostParam {
+    /**
+     * The name of the variable in the endpoint uri template which will be replaced with the value
+     * of the parameter annotated with this annotation.
+     * @return The name of the variable in the endpoint uri template which will be replaced with the
+     * value of the parameter annotated with this annotation.
+     */
+    String value();
+    /**
+     * A value true for this argument indicates that value of {@link HostParam#value()} is already
+     * encoded hence engine should not encode it, by default value will be encoded.
+     * @return Whether or not this argument is already encoded.
+     */
+    boolean encoded() default true;
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/PATCH.java b/common/azure-common/src/main/java/com/azure/common/annotations/PATCH.java
new file mode 100644
index 0000000000000..ba76c6bfc381b
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/PATCH.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * HTTP PATCH method annotation describing the parameterized relative path to a REST endpoint for resource update.
+ *
+ * <p>The required value can be either a relative path or an absolute path. When it's an absolute path, it must start
+ * with a protocol or a parameterized segment (Otherwise the parse cannot tell if it's absolute or relative).</p>
+ *
+ * <p><strong>Example 1: Relative path segments</strong></p>
+ *
+ * <pre>
+ * {@literal @}PATCH("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *  VirtualMachine patch(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId, @BodyParam VirtualMachineUpdateParameters updateParameters);</pre>
+ *
+ * <p><strong>Example 2: Absolute path segment</strong></p>
+ *
+ * <pre>
+ * {@literal @}PATCH({vaultBaseUrl}/secrets/{secretName})
+ *  Secret patch(@PathParam("vaultBaseUrl" encoded = true) String vaultBaseUrl, @PathParam("secretName") String secretName, @BodyParam SecretUpdateParameters updateParameters);</pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PATCH {
+    /**
+     * Get the relative path of the annotated method's PATCH URL.
+     * @return The relative path of the annotated method's PATCH URL.
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/POST.java b/common/azure-common/src/main/java/com/azure/common/annotations/POST.java
new file mode 100644
index 0000000000000..cd3743c0c66a7
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/POST.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * HTTP POST method annotation describing the parameterized relative path to a REST endpoint for an action.
+ *
+ * <p>The required value can be either a relative path or an absolute path. When it's an absolute path, it must start
+ * with a protocol or a parameterized segment (Otherwise the parse cannot tell if it's absolute or relative).</p>
+ *
+ * <p><strong>Example 1: Relative path segments</strong></p>
+ *
+ * <pre>
+ * {@literal @}POST("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}/restart")
+ *  void restart(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId);</pre>
+ *
+ * <p><strong>Example 2: Absolute path segment</strong></p>
+ *
+ * <pre>
+ * {@literal @}POST(https://{functionApp}.azurewebsites.net/admin/functions/{name}/keys/{keyName})
+ *  NameValuePair generateFunctionKey(@PathParam("functionApp") String functionApp, @PathParam("name") String function, @PathParam("keyName") String keyName);</pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface POST {
+    /**
+     * Get the relative path of the annotated method's POST URL.
+     * @return The relative path of the annotated method's POST URL.
+     */
+    String value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/PUT.java b/common/azure-common/src/main/java/com/azure/common/annotations/PUT.java
new file mode 100644
index 0000000000000..f2ef16a8d32bb
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/PUT.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * HTTP PUT method annotation describing the parameterized relative path to a REST endpoint for resource creation or update.
+ *
+ * <p>The required value can be either a relative path or an absolute path. When it's an absolute path, it must start
+ * with a protocol or a parameterized segment (Otherwise the parse cannot tell if it's absolute or relative).</p>
+ *
+ * <p><strong>Example 1: Relative path segments</strong></p>
+ *
+ * <pre>
+ * {@literal @}PUT("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}")
+ *  VirtualMachine createOrUpdate(@PathParam("resourceGroupName") String rgName, @PathParam("vmName") String vmName, @PathParam("subscriptionId") String subscriptionId, @BodyParam VirtualMachine vm);</pre>
+ *
+ * <p><strong>Example 2: Absolute path segment</strong></p>
+ *
+ * <pre>
+ * {@literal @}PUT({vaultBaseUrl}/secrets/{secretName})
+ *  Secret createOrUpdate(@PathParam("vaultBaseUrl" encoded = true) String vaultBaseUrl, @PathParam("secretName") String secretName, @BodyParam SecretCreateParameters secret);</pre>
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface PUT {
+    /**
+     * Get the relative path of the annotated method's PUT URL.
+     * @return The relative path of the annotated method's PUT URL.
+     */
+    String value();
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/PathParam.java b/common/azure-common/src/main/java/com/azure/common/annotations/PathParam.java
new file mode 100644
index 0000000000000..7097e71c31099
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/PathParam.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation to annotate replacement for a named path segment in REST endpoint URL.
+ *
+ * <p>A parameter that is annotated with PathParam will be ignored if the "uri template" does not contain a path
+ * segment variable with name {@link PathParam#value()}.</p>
+ *
+ * <p><strong>Example 1:</strong></p>
+ *
+ * <pre>
+ * {@literal @}GET("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/")
+ *  VirtualMachine getByResourceGroup(@PathParam("subscriptionId") String subscriptionId, @PathParam("resourceGroupName") String rgName, @PathParam("foo") String bar);</pre>
+ *
+ * <p>The value of parameters subscriptionId, resourceGroupName will be encoded and encoded value will be used to
+ * replace the corresponding path segment <code>{subscriptionId}</code>, <code>{resourceGroupName}</code>
+ * respectively.</p>
+ *
+ * <p><strong>Example 2: (A use case where PathParam.encoded=true will be used)</strong></p>
+ *
+ * <p>It is possible that, a path segment variable can be used to represent sub path:</p>
+ *
+ * <pre>
+ * {@literal @}GET("http://wq.com/foo/{subpath}/values")
+ *  String getValues(@PathParam("subpath") String param1);</pre>
+ *
+ * <p>In this case, if consumer pass "a/b" as the value for param1 then the resolved url looks like:
+ * "<code>http://wq.com/foo/a%2Fb/values</code>".</p>
+ *
+ * <p>For such cases the encoded attribute can be used:</p>
+ *
+ * <pre>
+ * {@literal @}GET("http://wq.com/foo/{subpath}/values")
+ *  String getValues(@PathParam(value = "subpath", encoded = true) String param1);</pre>
+ *
+ * <p>In this case, if consumer pass "a/b" as the value for param1 then the resolved url looks as expected:
+ * "<code>http://wq.com/foo/a/b/values</code>".</p>
+ */
+@Retention(RUNTIME)
+@Target(PARAMETER)
+public @interface PathParam {
+    /**
+     * The name of the variable in the endpoint uri template which will be replaced with the value
+     * of the parameter annotated with this annotation.
+     * @return The name of the variable in the endpoint uri template which will be replaced with the
+     * value of the parameter annotated with this annotation.
+     */
+    String value();
+    /**
+     * A value true for this argument indicates that value of {@link PathParam#value()} is already encoded
+     * hence engine should not encode it, by default value will be encoded.
+     * @return Whether or not this path parameter is already encoded.
+     */
+    boolean encoded() default false;
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/QueryParam.java b/common/azure-common/src/main/java/com/azure/common/annotations/QueryParam.java
new file mode 100644
index 0000000000000..958e8ba852c5a
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/QueryParam.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation for query parameters to be appended to a REST API Request URI.
+ *
+ * <p><strong>Example 1:</strong></p>
+ *
+ * <pre>
+ * {@literal @}GET("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/resources")
+ * {@literal Single<RestResponseBase<Headers, Body>>} listByResourceGroup(@PathParam("resourceGroupName") String resourceGroupName, @PathParam("subscriptionId") String subscriptionId, @QueryParam("$filter") String filter, @QueryParam("$expand") String expand, @QueryParam("$top") Integer top, @QueryParam("api-version") String apiVersion);</pre>
+ *
+ * <p>The value of parameters filter, expand, top, apiVersion will be encoded and encoded value will be used to replace the corresponding path segment {$filter},
+ * {$expand}, {$top}, {api-version} respectively.</p>
+ *
+ * <p><strong>Example 2:</strong> (A use case where PathParam.encoded=true will be used)</p>
+ *
+ * <p>It is possible that, a path segment variable can be used to represent sub path:</p>
+ *
+ * <pre>
+ * {@literal @}GET("http://wq.com/foo/{subpath}/values")
+ *  String getValues(@PathParam("subpath") String param, @QueryParam("connectionString") String connectionString);</pre>
+ *
+ * <p>In this case, if consumer pass "a=b" as the value for query then the resolved url looks like:
+ * "<code>http://wq.com/foo/paramblah/values?connectionString=a%3Db</code>"</p>
+ *
+ * <p>For such cases the encoded attribute can be used:</p>
+ *
+ * <pre>
+ * {@literal @}GET("http://wq.com/foo/{subpath}/values")
+ *  String getValues(@PathParam("subpath") String param, @QueryParam("query", encoded = true) String query);</pre>
+ *
+ * <p>In this case, if consumer pass "a=b" as the value for param1 then the resolved url looks as expected:
+ * "<code>http://wq.com/foo/paramblah/values?connectionString=a=b</code>"</p>
+ */
+@Retention(RUNTIME)
+@Target(PARAMETER)
+public @interface QueryParam {
+    /**
+     * The name of the variable in the endpoint uri template which will be replaced with the value
+     * of the parameter annotated with this annotation.
+     * @return The name of the variable in the endpoint uri template which will be replaced with the
+     * value of the parameter annotated with this annotation.
+     */
+    String value();
+    /**
+     * A value true for this argument indicates that value of {@link QueryParam#value()} is already encoded
+     * hence engine should not encode it, by default value will be encoded.
+     * @return Whether or not this query parameter is already encoded.
+     */
+    boolean encoded() default false;
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/ResumeOperation.java b/common/azure-common/src/main/java/com/azure/common/annotations/ResumeOperation.java
new file mode 100644
index 0000000000000..1ff0457c041e5
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/ResumeOperation.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for method representing continuation operation.
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ResumeOperation {
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/ReturnValueWireType.java b/common/azure-common/src/main/java/com/azure/common/annotations/ReturnValueWireType.java
new file mode 100644
index 0000000000000..3de32cc8ffe5d
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/ReturnValueWireType.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Annotation for the type that will be used to deserialize the return value of a REST API response.
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface ReturnValueWireType {
+    /**
+     * The type that the service interface method's return value will be converted from.
+     * @return The type that the service interface method's return value will be converted from.
+     */
+    Class<?> value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/SkipParentValidation.java b/common/azure-common/src/main/java/com/azure/common/annotations/SkipParentValidation.java
new file mode 100644
index 0000000000000..ca59542de0ad8
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/SkipParentValidation.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to notify the validator to skip validation for the properties in the parent class.
+ */
+@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SkipParentValidation {
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/UnexpectedResponseExceptionType.java b/common/azure-common/src/main/java/com/azure/common/annotations/UnexpectedResponseExceptionType.java
new file mode 100644
index 0000000000000..9266e7feca85d
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/UnexpectedResponseExceptionType.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.annotations;
+
+import com.azure.common.http.rest.RestException;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * The error type that will be thrown or returned when an unexpected status code is returned from an REST API.
+ *
+ * <p><strong>Example:</strong></p>
+ *
+ * <pre>
+ * {@literal @}UnexpectedResponseExceptionType(MyCustomException.class)
+ * {@literal @}POST("subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.CustomerInsights/hubs/{hubName}/images/getEntityTypeImageUploadUrl")
+ *  void getUploadUrlForEntityType(@Path("resourceGroupName") String resourceGroupName, @Path("hubName") String hubName, @Path("subscriptionId") String subscriptionId, @Body GetImageUploadUrlInputInner parameters);
+ * </pre>
+ */
+@Retention(RUNTIME)
+@Target(METHOD)
+public @interface UnexpectedResponseExceptionType {
+    /**
+     * The type of RestException that should be thrown/returned when the API returns an unrecognized
+     * status code.
+     * @return The type of RestException that should be thrown/returned.
+     */
+    Class<? extends RestException> value();
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/annotations/package-info.java b/common/azure-common/src/main/java/com/azure/common/annotations/package-info.java
new file mode 100644
index 0000000000000..7758987283cd2
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/annotations/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing annotations for client side methods that maps to REST APIs.
+ */
+package com.azure.common.annotations;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/credentials/AsyncServiceClientCredentials.java b/common/azure-common/src/main/java/com/azure/common/credentials/AsyncServiceClientCredentials.java
new file mode 100644
index 0000000000000..e8a3c0de83fef
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/credentials/AsyncServiceClientCredentials.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.credentials;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * Provides credentials to be put in the HTTP Authorization header.
+ */
+public interface AsyncServiceClientCredentials {
+    /**
+     * @param uri The URI to which the request is being made.
+     * @return The value containing currently valid credentials to put in the HTTP header.
+     */
+    Mono<String> authorizationHeaderValueAsync(String uri);
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/credentials/BasicAuthenticationCredentials.java b/common/azure-common/src/main/java/com/azure/common/credentials/BasicAuthenticationCredentials.java
new file mode 100644
index 0000000000000..055911097b5df
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/credentials/BasicAuthenticationCredentials.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.credentials;
+
+import com.azure.common.implementation.util.Base64Util;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Basic Auth credentials for use with a REST Service Client.
+ */
+public class BasicAuthenticationCredentials implements ServiceClientCredentials {
+    /**
+     * Basic auth user name.
+     */
+    private String userName;
+
+    /**
+     * Basic auth password.
+     */
+    private String password;
+
+    /**
+     * Creates a basic authentication credential.
+     *
+     * @param userName basic auth user name
+     * @param password basic auth password
+     */
+    public BasicAuthenticationCredentials(String userName, String password) {
+        this.userName = userName;
+        this.password = password;
+    }
+
+    @Override
+    public String authorizationHeaderValue(String uri) {
+        String credential = userName + ":" + password;
+        String encodedCredential;
+        try {
+            encodedCredential = Base64Util.encodeToString(credential.getBytes("UTF8"));
+        } catch (UnsupportedEncodingException e) {
+            // The encoding is hard-coded, so if it's unsupported, it needs to be fixed right here.
+            throw new RuntimeException(e);
+        }
+
+        return "Basic " + encodedCredential;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/credentials/ServiceClientCredentials.java b/common/azure-common/src/main/java/com/azure/common/credentials/ServiceClientCredentials.java
new file mode 100644
index 0000000000000..d90f8a7a73589
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/credentials/ServiceClientCredentials.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.credentials;
+
+import java.io.IOException;
+
+/**
+ * Provides credentials to be put in the HTTP Authorization header.
+ */
+public interface ServiceClientCredentials {
+    /**
+     * The Authorization header value for the provided url.
+     *
+     * @param uri The URI to which the request is being made.
+     * @return The value containing currently valid credentials to put in the HTTP header.
+     * @throws IOException if unable to get the authorization header value
+     */
+    String authorizationHeaderValue(String uri) throws IOException;
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/credentials/TokenCredentials.java b/common/azure-common/src/main/java/com/azure/common/credentials/TokenCredentials.java
new file mode 100644
index 0000000000000..b2fce4367d9f4
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/credentials/TokenCredentials.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.credentials;
+
+import java.io.IOException;
+
+/**
+ * Token based credentials for use with a REST Service Client.
+ */
+public class TokenCredentials implements ServiceClientCredentials {
+    /**
+     * The authentication scheme.
+     */
+    private String scheme;
+
+    /**
+     * The secure token.
+     */
+    private String token;
+
+    /**
+     * Creates TokenCredentials.
+     *
+     * @param scheme scheme to use. If null, defaults to Bearer
+     * @param token  valid token
+     */
+    public TokenCredentials(String scheme, String token) {
+        if (scheme == null) {
+            scheme = "Bearer";
+        }
+        this.scheme = scheme;
+        this.token = token;
+    }
+
+    @Override
+    public String authorizationHeaderValue(String uri) throws IOException {
+        return scheme + " " + token;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/credentials/package-info.java b/common/azure-common/src/main/java/com/azure/common/credentials/package-info.java
new file mode 100644
index 0000000000000..074f6935a785b
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/credentials/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing basic credential classes for authentication purposes.
+ */
+package com.azure.common.credentials;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/ContextData.java b/common/azure-common/src/main/java/com/azure/common/http/ContextData.java
new file mode 100644
index 0000000000000..11c54b5a2ba25
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/ContextData.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import java.util.Optional;
+
+/**
+ * {@code ContextData} offers a means of passing arbitrary data (key-value pairs) to {@link HttpPipeline}'s
+ * policy objects. Most applications do not need to pass arbitrary data to the pipeline and can pass
+ * {@code ContextData.NONE} or {@code null}. Each context object is immutable.
+ * The {@code addData(Object, Object)} method creates a new {@code ContextData} object that refers
+ * to its parent, forming a linked list.
+ */
+public class ContextData {
+    // All fields must be immutable.
+    //
+    /**
+     * Signifies that no data need be passed to the pipeline.
+     */
+    public static final ContextData NONE = new ContextData(null, null, null);
+
+    private final ContextData parent;
+    private final Object key;
+    private final Object value;
+
+    /**
+     * Constructs a new {@link ContextData} object.
+     *
+     * @param key the key
+     * @param value the value
+     */
+    public ContextData(Object key, Object value) {
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+        this.parent = null;
+        this.key = key;
+        this.value = value;
+    }
+
+    private ContextData(ContextData parent, Object key, Object value) {
+        this.parent = parent;
+        this.key = key;
+        this.value = value;
+    }
+
+    /**
+     * Adds a new immutable {@link ContextData} object with the specified key-value pair to
+     * the existing {@link ContextData} chain.
+     *
+     * @param key the key
+     * @param value the value
+     * @return the new {@link ContextData} object containing the specified pair added to the set of pairs
+     */
+    public ContextData addData(Object key, Object value) {
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+        return new ContextData(this, key, value);
+    }
+
+    /**
+     * Scans the linked-list of {@link ContextData} objects looking for one with the specified key.
+     * Note that the first key found, i.e. the most recently added, will be returned.
+     *
+     * @param key the key to search for
+     * @return the value of the key if it exists
+     */
+    public Optional<Object> getData(Object key) {
+        if (key == null) {
+            throw new IllegalArgumentException("key cannot be null");
+        }
+        for (ContextData c = this; c != null; c = c.parent) {
+            if (key.equals(c.key)) {
+                return Optional.of(c.value);
+            }
+        }
+        return Optional.empty();
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpClient.java b/common/azure-common/src/main/java/com/azure/common/http/HttpClient.java
new file mode 100644
index 0000000000000..b0f407800e840
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpClient.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import reactor.core.publisher.Mono;
+import java.util.function.Supplier;
+
+/**
+ * A generic interface for sending HTTP requests and getting responses.
+ */
+public interface HttpClient {
+    /**
+     * Send the provided request asynchronously.
+     *
+     * @param request The HTTP request to send
+     * @return A {@link Mono} that emits response asynchronously
+     */
+    Mono<HttpResponse> send(HttpRequest request);
+
+    /**
+     * Create default HttpClient instance.
+     *
+     * @return the HttpClient
+     */
+    static HttpClient createDefault() {
+        return new ReactorNettyClient();
+    }
+
+    /**
+     * Apply the provided proxy configuration to the HttpClient.
+     *
+     * @param proxyOptions the proxy configuration supplier
+     * @return a HttpClient with proxy applied
+     */
+    HttpClient proxy(Supplier<ProxyOptions> proxyOptions);
+
+    /**
+     * Apply or remove a wire logger configuration.
+     *
+     * @param enableWiretap wiretap config
+     * @return a HttpClient with wire logging enabled or disabled
+     */
+    HttpClient wiretap(boolean enableWiretap);
+
+    /**
+     * Set the port that client should connect to.
+     *
+     * @param port the port
+     * @return a HttpClient with port applied
+     */
+    HttpClient port(int port);
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpHeader.java b/common/azure-common/src/main/java/com/azure/common/http/HttpHeader.java
new file mode 100644
index 0000000000000..e77b633da7716
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpHeader.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+/**
+ * A single header within a HTTP request or response.
+ *
+ * If multiple header values are added to a HTTP request or response with
+ * the same name (case-insensitive), then the values will be appended
+ * to the end of the same Header with commas separating them.
+ */
+public class HttpHeader {
+    private final String name;
+    private String value;
+
+    /**
+     * Create a HttpHeader instance using the provided name and value.
+     *
+     * @param name the name
+     * @param value the value
+     */
+    public HttpHeader(String name, String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    /**
+     * Get the header name.
+     *
+     * @return the name of this Header
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Get the header value.
+     *
+     * @return the value of this Header
+     */
+    public String value() {
+        return value;
+    }
+
+    /**
+     * Get the comma separated value as an array.
+     *
+     * @return the values of this Header that are separated by a comma
+     */
+    public String[] values() {
+        return value == null ? null : value.split(",");
+    }
+
+    /**
+     * Add a new value to the end of the Header.
+     *
+     * @param value the value to add
+     */
+    public void addValue(String value) {
+        this.value += "," + value;
+    }
+
+    /**
+     * Get the String representation of the header.
+     *
+     * @return the String representation of this HttpHeader
+     */
+    @Override
+    public String toString() {
+        return name + ":" + value;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpHeaders.java b/common/azure-common/src/main/java/com/azure/common/http/HttpHeaders.java
new file mode 100644
index 0000000000000..8bcb0e5b835ae
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpHeaders.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializable;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A collection of headers on an HTTP request or response.
+ */
+public class HttpHeaders implements Iterable<HttpHeader>, JsonSerializable {
+    private final Map<String, HttpHeader> headers = new HashMap<>();
+
+    /**
+     * Create an empty HttpHeaders instance.
+     */
+    public HttpHeaders() {
+    }
+
+    /**
+     * Create a HttpHeaders instance with the provided initial headers.
+     *
+     * @param headers the map of initial headers
+     */
+    public HttpHeaders(Map<String, String> headers) {
+        for (final Map.Entry<String, String> header : headers.entrySet()) {
+            this.set(header.getKey(), header.getValue());
+        }
+    }
+
+    /**
+     * Create a HttpHeaders instance with the provided initial headers.
+     *
+     * @param headers the collection of initial headers
+     */
+    public HttpHeaders(Iterable<HttpHeader> headers) {
+        this();
+
+        for (final HttpHeader header : headers) {
+            this.set(header.name(), header.value());
+        }
+    }
+
+    /**
+     * Gets the number of headers in the collection.
+     *
+     * @return the number of headers in this collection.
+     */
+    public int size() {
+        return headers.size();
+    }
+
+    /**
+     * Set a header.
+     *
+     * if header with same name already exists then the value will be overwritten.
+     * if value is null and header with provided name already exists then it will be removed.
+     *
+     * @param name the name
+     * @param value the value
+     * @return this HttpHeaders
+     */
+    public HttpHeaders set(String name, String value) {
+        final String headerKey = name.toLowerCase();
+        if (value == null) {
+            headers.remove(headerKey);
+        }
+        else {
+            headers.put(headerKey, new HttpHeader(name, value));
+        }
+        return this;
+    }
+
+    /**
+     * Get the header value for the provided header name. Null will be returned if the header
+     * name isn't found.
+     *
+     * @param name the name of the header to look for
+     * @return The String value of the header, or null if the header isn't found
+     */
+    public String value(String name) {
+        final HttpHeader header = getHeader(name);
+        return header == null ? null : header.value();
+    }
+
+    /**
+     * Get the header values for the provided header name. Null will be returned if
+     * the header name isn't found.
+     *
+     * @param name the name of the header to look for
+     * @return the values of the header, or null if the header isn't found
+     */
+    public String[] values(String name) {
+        final HttpHeader header = getHeader(name);
+        return header == null ? null : header.values();
+    }
+
+    private HttpHeader getHeader(String headerName) {
+        final String headerKey = headerName.toLowerCase();
+        return headers.get(headerKey);
+    }
+
+    /**
+     * Get {@link Map} representation of the HttpHeaders collection.
+     *
+     * @return the headers as map
+     */
+    public Map<String, String> toMap() {
+        final Map<String, String> result = new HashMap<>();
+        for (final HttpHeader header : headers.values()) {
+            result.put(header.name(), header.value());
+        }
+        return result;
+    }
+
+    @Override
+    public Iterator<HttpHeader> iterator() {
+        return headers.values().iterator();
+    }
+
+    @Override
+    public void serialize(JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+        jsonGenerator.writeObject(toMap());
+    }
+
+    @Override
+    public void serializeWithType(JsonGenerator jsonGenerator, SerializerProvider serializerProvider, TypeSerializer typeSerializer) throws IOException {
+        serialize(jsonGenerator, serializerProvider);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpMethod.java b/common/azure-common/src/main/java/com/azure/common/http/HttpMethod.java
new file mode 100644
index 0000000000000..d713b70a046c3
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpMethod.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+/**
+ * The HTTP request methods.
+ */
+public enum HttpMethod {
+    /**
+     * The HTTP GET method.
+     */
+    GET,
+
+    /**
+     * The HTTP PUT method.
+     */
+    PUT,
+
+    /**
+     * The HTTP POST method.
+     */
+    POST,
+
+    /**
+     * The HTTP PATCH method.
+     */
+    PATCH,
+
+    /**
+     * The HTTP DELETE method.
+     */
+    DELETE,
+
+    /**
+     * The HTTP HEAD method.
+     */
+    HEAD,
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpPipeline.java b/common/azure-common/src/main/java/com/azure/common/http/HttpPipeline.java
new file mode 100644
index 0000000000000..c02101e713b7a
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpPipeline.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import com.azure.common.http.policy.HttpPipelinePolicy;
+import reactor.core.publisher.Mono;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * The http pipeline.
+ */
+public final class HttpPipeline {
+    private final HttpClient httpClient;
+    private final HttpPipelinePolicy[] pipelinePolicies;
+
+    /**
+     * Creates a HttpPipeline holding array of policies that gets applied to all request initiated through
+     * {@link HttpPipeline#send(HttpPipelineCallContext)} and it's response.
+     *
+     * @param httpClient the http client to write request to wire and receive response from wire.
+     * @param pipelinePolicies pipeline policies in the order they need to applied, a copy of this array will
+     *                                  be made hence changing the original array after the creation of pipeline
+     *                                  will not  mutate the pipeline
+     */
+    public HttpPipeline(HttpClient httpClient, HttpPipelinePolicy... pipelinePolicies) {
+        Objects.requireNonNull(httpClient);
+        Objects.requireNonNull(pipelinePolicies);
+        this.pipelinePolicies = Arrays.copyOf(pipelinePolicies, pipelinePolicies.length);
+        this.httpClient = httpClient;
+    }
+
+    /**
+     * Creates a HttpPipeline holding array of policies that gets applied all request initiated through
+     * {@link HttpPipeline#send(HttpPipelineCallContext)} and it's response.
+     *
+     * The default HttpClient {@link HttpClient#createDefault()} will be used to write request to wire and
+     * receive response from wire.
+     *
+     * @param pipelinePolicies pipeline policies in the order they need to applied, a copy of this array will
+     *                                  be made hence changing the original array after the creation of pipeline
+     *                                  will not  mutate the pipeline
+     */
+    public HttpPipeline(HttpPipelinePolicy... pipelinePolicies) {
+        this(HttpClient.createDefault(), pipelinePolicies);
+    }
+
+    /**
+     * Creates a HttpPipeline holding array of policies that gets applied to all request initiated through
+     * {@link HttpPipeline#send(HttpPipelineCallContext)} and it's response.
+     *
+     * @param httpClient the http client to write request to wire and receive response from wire.
+     * @param pipelinePolicies pipeline policies in the order they need to applied, a copy of this list
+     *                         will be made so changing the original list after the creation of pipeline
+     *                         will not mutate the pipeline
+     */
+    public HttpPipeline(HttpClient httpClient, List<HttpPipelinePolicy> pipelinePolicies) {
+        Objects.requireNonNull(httpClient);
+        Objects.requireNonNull(pipelinePolicies);
+        this.pipelinePolicies = pipelinePolicies.toArray(new HttpPipelinePolicy[0]);
+        this.httpClient = httpClient;
+    }
+
+    /**
+     * Creates a HttpPipeline holding array of policies that gets applied all request initiated through
+     * {@link HttpPipeline#send(HttpPipelineCallContext)} and it's response.
+     *
+     * The default HttpClient {@link HttpClient#createDefault()} will be used to write request to wire and
+     * receive response from wire.
+     *
+     * @param pipelinePolicies pipeline policies in the order they need to applied, a copy of this list
+     *                         will be made so changing the original list after the creation of pipeline
+     *                         will not mutate the pipeline
+     */
+    public HttpPipeline(List<HttpPipelinePolicy> pipelinePolicies) {
+        this(HttpClient.createDefault(), pipelinePolicies);
+    }
+
+    /**
+     * Get the policies in the pipeline.
+     *
+     * @return policies in the pipeline
+     */
+    public HttpPipelinePolicy[] pipelinePolicies() {
+        return Arrays.copyOf(this.pipelinePolicies, this.pipelinePolicies.length);
+    }
+
+    /**
+     * Get the {@link HttpClient} associated with the pipeline.
+     *
+     * @return the {@link HttpClient} associated with the pipeline
+     */
+    public HttpClient httpClient() {
+        return this.httpClient;
+    }
+
+    /**
+     * Creates a new context local to the provided http request.
+     *
+     * @param httpRequest the request for a context needs to be created
+     * @return the request context
+     */
+    public HttpPipelineCallContext newContext(HttpRequest httpRequest) {
+        return new HttpPipelineCallContext(httpRequest);
+    }
+
+    /**
+     * Creates a new context local to the provided http request.
+     *
+     * @param httpRequest the request for a context needs to be created
+     * @param data the data to associate with the context
+     * @return the request context
+     */
+    public HttpPipelineCallContext newContext(HttpRequest httpRequest, ContextData data) {
+        return new HttpPipelineCallContext(httpRequest, data);
+    }
+
+    /**
+     * Wraps the request in a context and send it through pipeline.
+     *
+     * @param request the request
+     * @return a publisher upon subscription flows the context through policies, sends the request and emits response upon completion
+     */
+    public Mono<HttpResponse> send(HttpRequest request) {
+        return this.send(this.newContext(request));
+    }
+
+    /**
+     * Sends the context (containing request) through pipeline.
+     *
+     * @param context the request context
+     * @return a publisher upon subscription flows the context through policies, sends the request and emits response upon completion
+     */
+    public Mono<HttpResponse> send(HttpPipelineCallContext context) {
+        // Return deferred to mono for complete lazy behaviour.
+        //
+        return Mono.defer(() -> {
+            HttpPipelineNextPolicy next = new HttpPipelineNextPolicy(this, context);
+            return next.process();
+        });
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpPipelineCallContext.java b/common/azure-common/src/main/java/com/azure/common/http/HttpPipelineCallContext.java
new file mode 100644
index 0000000000000..8dbcddc92201f
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpPipelineCallContext.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Type representing context local to a single http request and it's response.
+ */
+public final class HttpPipelineCallContext {
+    private HttpRequest httpRequest;
+    private ContextData data;
+
+    //<editor-fold defaultstate="collapsed" desc="Package internal methods">
+    /**
+     * Package private ctr.
+     *
+     * Creates HttpPipelineCallContext.
+     *
+     * @param httpRequest the request for which context needs to be created
+     *
+     * @throws IllegalArgumentException if there are multiple policies with same name
+     */
+    HttpPipelineCallContext(HttpRequest httpRequest) {
+       this(httpRequest, ContextData.NONE);
+    }
+
+    /**
+     * Package private ctr.
+     *
+     * Creates HttpPipelineCallContext.
+     *
+     * @param httpRequest the request for which context needs to be created
+     * @param data the data to associate with this context
+     *
+     * @throws IllegalArgumentException if there are multiple policies with same name
+     */
+    HttpPipelineCallContext(HttpRequest httpRequest, ContextData data) {
+        Objects.requireNonNull(httpRequest);
+        Objects.requireNonNull(data);
+        //
+        this.httpRequest = httpRequest;
+        this.data = data;
+    }
+    //</editor-fold>
+
+    //<editor-fold defaultstate="collapsed" desc="Public methods">
+
+    /**
+     * Stores a key-value data in the context.
+     *
+     * @param key the key
+     * @param value the value
+     */
+    public void setData(String key, Object value) {
+        this.data = this.data.addData(key, value);
+    }
+
+    /**
+     * Gets a value with the given key stored in the context.
+     *
+     * @param key the key
+     * @return the value
+     */
+    public Optional<Object> getData(String key) {
+        return this.data.getData(key);
+    }
+
+    /**
+     * Get the http request.
+     *
+     * @return the request.
+     */
+    public HttpRequest httpRequest() {
+        return this.httpRequest;
+    }
+
+    /**
+     * Sets the http request object in the context.
+     *
+     * @param request request object
+     * @return HttpPipelineCallContext
+     */
+    public HttpPipelineCallContext withHttpRequest(HttpRequest request) {
+        this.httpRequest = request;
+        return this;
+    }
+
+    //</editor-fold>
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpPipelineNextPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/HttpPipelineNextPolicy.java
new file mode 100644
index 0000000000000..c1ea6e80bb77d
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpPipelineNextPolicy.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import com.azure.common.http.policy.HttpPipelinePolicy;
+import reactor.core.publisher.Mono;
+
+/**
+ * A type that invokes next policy in the pipeline.
+ */
+public class HttpPipelineNextPolicy {
+    private final HttpPipeline pipeline;
+    private final HttpPipelineCallContext context;
+    private int currentPolicyIndex;
+
+    /**
+     * Package Private ctr.
+     *
+     * Creates HttpPipelineNextPolicy.
+     *
+     * @param pipeline the pipeline
+     * @param context the request-response context
+     */
+    HttpPipelineNextPolicy(final HttpPipeline pipeline, HttpPipelineCallContext context) {
+        this.pipeline = pipeline;
+        this.context = context;
+        this.currentPolicyIndex = -1;
+    }
+
+    /**
+     * Invokes the next {@link HttpPipelinePolicy}.
+     *
+     * @return a publisher upon subscription invokes next policy and emits response from the policy.
+     */
+    public Mono<HttpResponse> process() {
+        final int size = this.pipeline.pipelinePolicies().length;
+        if (this.currentPolicyIndex > size) {
+            return Mono.error(new IllegalStateException("There is no more policies to execute."));
+        } else {
+            this.currentPolicyIndex++;
+            if (this.currentPolicyIndex == size) {
+                return this.pipeline.httpClient().send(this.context.httpRequest());
+            } else {
+                return this.pipeline.pipelinePolicies()[this.currentPolicyIndex].process(this.context, this);
+            }
+        }
+    }
+
+    @Override
+    public HttpPipelineNextPolicy clone() {
+        HttpPipelineNextPolicy cloned = new HttpPipelineNextPolicy(this.pipeline, this.context);
+        cloned.currentPolicyIndex = this.currentPolicyIndex;
+        return cloned;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpRequest.java b/common/azure-common/src/main/java/com/azure/common/http/HttpRequest.java
new file mode 100644
index 0000000000000..360f9c8962cbd
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpRequest.java
@@ -0,0 +1,185 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import reactor.core.publisher.Flux;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The outgoing Http request.
+ */
+public class HttpRequest {
+    private HttpMethod httpMethod;
+    private URL url;
+    private HttpHeaders headers;
+    private Flux<ByteBuf> body;
+
+    /**
+     * Create a new HttpRequest instance.
+     *
+     * @param httpMethod the HTTP request method
+     * @param url the target address to send the request to
+     */
+    public HttpRequest(HttpMethod httpMethod, URL url) {
+        this.httpMethod = httpMethod;
+        this.url = url;
+        this.headers = new HttpHeaders();
+    }
+
+    /**
+     * Create a new HttpRequest instance.
+     *
+     * @param httpMethod the HTTP request method
+     * @param url the target address to send the request to
+     * @param headers the HTTP headers to use with this request
+     * @param body the request content
+     */
+    public HttpRequest(HttpMethod httpMethod, URL url, HttpHeaders headers, Flux<ByteBuf> body) {
+        this.httpMethod = httpMethod;
+        this.url = url;
+        this.headers = headers;
+        this.body = body;
+    }
+
+    /**
+     * Get the request method.
+     *
+     * @return the request method
+     */
+    public HttpMethod httpMethod() {
+        return httpMethod;
+    }
+
+    /**
+     * Set the request method.
+     *
+     * @param httpMethod the request method
+     * @return this HttpRequest
+     */
+    public HttpRequest withHttpMethod(HttpMethod httpMethod) {
+        this.httpMethod = httpMethod;
+        return this;
+    }
+
+    /**
+     * Get the target address.
+     *
+     * @return the target address
+     */
+    public URL url() {
+        return url;
+    }
+
+    /**
+     * Set the target address to send the request to.
+     *
+     * @param url target address as {@link URL}
+     * @return this HttpRequest
+     */
+    public HttpRequest withUrl(URL url) {
+        this.url = url;
+        return this;
+    }
+
+    /**
+     * Get the request headers.
+     *
+     * @return headers to be sent
+     */
+    public HttpHeaders headers() {
+        return headers;
+    }
+
+    /**
+     * Set the request headers.
+     *
+     * @param headers the set of headers
+     * @return this HttpRequest
+     */
+    public HttpRequest withHeaders(HttpHeaders headers) {
+        this.headers = headers;
+        return this;
+    }
+
+    /**
+     * Set a request header, replacing any existing value.
+     * A null for {@code value} will remove the header if one with matching name exists.
+     *
+     * @param name the header name
+     * @param value the header value
+     * @return this HttpRequest
+     */
+    public HttpRequest withHeader(String name, String value) {
+        headers.set(name, value);
+        return this;
+    }
+
+    /**
+     * Get the request content.
+     *
+     * @return the content to be send
+     */
+    public Flux<ByteBuf> body() {
+        return body;
+    }
+
+    /**
+     * Set the request content.
+     *
+     * @param content the request content
+     * @return this HttpRequest
+     */
+    public HttpRequest withBody(String content) {
+        final byte[] bodyBytes = content.getBytes(StandardCharsets.UTF_8);
+        return withBody(bodyBytes);
+    }
+
+    /**
+     * Set the request content.
+     * The Content-Length header will be set based on the given content's length
+     *
+     * @param content the request content
+     * @return this HttpRequest
+     */
+    public HttpRequest withBody(byte[] content) {
+        headers.set("Content-Length", String.valueOf(content.length));
+        // Unpooled.wrappedBuffer(body) allocates ByteBuf from unpooled heap
+        return withBody(Flux.just(Unpooled.wrappedBuffer(content)));
+    }
+
+    /**
+     * Set request content.
+     *
+     * Caller must set the Content-Length header to indicate the length of the content,
+     * or use Transfer-Encoding: chunked.
+     *
+     * @param content the request content
+     * @return this HttpRequest
+     */
+    public HttpRequest withBody(Flux<ByteBuf> content) {
+        this.body = content;
+        return this;
+    }
+
+    /**
+     * Creates a clone of the request.
+     *
+     * The main purpose of this is so that this HttpRequest can be changed and the resulting
+     * HttpRequest can be a backup. This means that the buffered HttpHeaders and body must
+     * not be able to change from side effects of this HttpRequest.
+     *
+     * @return a new HTTP request instance with cloned instances of all mutable properties.
+     */
+    public HttpRequest buffer() {
+        final HttpHeaders bufferedHeaders = new HttpHeaders(headers);
+        return new HttpRequest(httpMethod, url, bufferedHeaders, body);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/HttpResponse.java b/common/azure-common/src/main/java/com/azure/common/http/HttpResponse.java
new file mode 100644
index 0000000000000..1a17d210942da
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/HttpResponse.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import java.io.Closeable;
+import java.nio.charset.Charset;
+
+import com.azure.common.implementation.http.BufferedHttpResponse;
+import io.netty.buffer.ByteBuf;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.netty.Connection;
+
+/**
+ * The type representing response of {@link HttpRequest}.
+ */
+public abstract class HttpResponse implements Closeable {
+    private HttpRequest request;
+
+    /**
+     * Get the response status code.
+     *
+     * @return the response status code
+     */
+    public abstract int statusCode();
+
+    /**
+     * Lookup a response header with the provided name.
+     *
+     * @param name the name of the header to lookup.
+     * @return the value of the header, or null if the header doesn't exist in the response.
+     */
+    public abstract String headerValue(String name);
+
+    /**
+     * Get all response headers.
+     *
+     * @return the response headers
+     */
+    public abstract HttpHeaders headers();
+
+    /**
+     * Get the publisher emitting response content chunks.
+     *
+     * <p>
+     * Returns a stream of the response's body content. Emissions may occur on the
+     * Netty EventLoop threads which are shared across channels and should not be
+     * blocked. Blocking should be avoided as much as possible/practical in reactive
+     * programming but if you do use methods like {@code blockingSubscribe} or {@code blockingGet}
+     * on the stream then be sure to use {@code subscribeOn} and {@code observeOn}
+     * before the blocking call. For example:
+     *
+     * <pre>
+     * {@code
+     *   response.body()
+     *     .map(bb -> bb.limit())
+     *     .reduce((x,y) -> x + y)
+     *     .subscribeOn(Schedulers.io())
+     *     .observeOn(Schedulers.io())
+     *     .blockingGet();
+     * }
+     * </pre>
+     * <p>
+     * The above code is a simplistic example and would probably run fine without
+     * the `subscribeOn` and `observeOn` but should be considered a template for
+     * more complex situations.
+     *
+     * @return The response's content as a stream of {@link ByteBuf}.
+     */
+    public abstract Flux<ByteBuf> body();
+
+    /**
+     * Get the response content as a byte[].
+     *
+     * @return this response content as a byte[]
+     */
+    public abstract Mono<byte[]> bodyAsByteArray();
+
+    /**
+     * Get the response content as a string.
+     *
+     * @return This response content as a string
+     */
+    public abstract Mono<String> bodyAsString();
+
+    /**
+     * Get the response content as a string.
+     *
+     * @param charset the charset to use as encoding
+     * @return This response content as a string
+     */
+    public abstract Mono<String> bodyAsString(Charset charset);
+
+    /**
+     * Get the request which resulted in this response.
+     *
+     * @return the request which resulted in this response.
+     */
+    public final HttpRequest request() {
+        return request;
+    }
+
+    /**
+     * Sets the request which resulted in this HttpResponse.
+     *
+     * @param request the request
+     * @return this HTTP response
+     */
+    public final HttpResponse withRequest(HttpRequest request) {
+        this.request = request;
+        return this;
+    }
+
+    /**
+     * Get a new Response object wrapping this response with it's content
+     * buffered into memory.
+     *
+     * @return the new Response object
+     */
+    public HttpResponse buffer() {
+        return new BufferedHttpResponse(this);
+    }
+
+    /**
+     * Closes the response content stream, if any.
+     */
+    @Override
+    public void close() {
+    }
+
+    // package private for test purpose
+    Connection internConnection() {
+        return null;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/ProxyOptions.java b/common/azure-common/src/main/java/com/azure/common/http/ProxyOptions.java
new file mode 100644
index 0000000000000..372603173f870
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/ProxyOptions.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import reactor.netty.tcp.ProxyProvider.Proxy;
+
+import java.net.InetSocketAddress;
+
+/**
+ * proxy configuration.
+ */
+public class ProxyOptions {
+    private final InetSocketAddress address;
+    private final Type type;
+
+    /**
+     * Creates ProxyOptions.
+     *
+     * @param type the proxy type
+     * @param address the proxy address (ip and port number)
+     */
+    public ProxyOptions(Type type, InetSocketAddress address) {
+        this.type = type;
+        this.address = address;
+    }
+
+    /**
+     * @return the address of the proxy.
+     */
+    public InetSocketAddress address() {
+        return address;
+    }
+
+    /**
+     * @return the type of the proxy.
+     */
+    public Type type() {
+        return type;
+    }
+
+    /**
+     * The type of the proxy.
+     */
+    public enum Type {
+        /**
+         * HTTP proxy type.
+         */
+        HTTP(Proxy.HTTP),
+        /**
+         * SOCKS4 proxy type.
+         */
+        SOCKS4(Proxy.SOCKS4),
+        /**
+         * SOCKS5 proxy type.
+         */
+        SOCKS5(Proxy.SOCKS5);
+
+        private final Proxy value;
+
+        Type(Proxy reactorProxyType) {
+            this.value = reactorProxyType;
+        }
+
+        Proxy value() {
+            return value;
+        }
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/ReactorNettyClient.java b/common/azure-common/src/main/java/com/azure/common/http/ReactorNettyClient.java
new file mode 100644
index 0000000000000..2257d65f8d06b
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/ReactorNettyClient.java
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http.HttpMethod;
+import org.reactivestreams.Publisher;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.netty.ByteBufFlux;
+import reactor.netty.Connection;
+import reactor.netty.NettyOutbound;
+import reactor.netty.http.client.HttpClientRequest;
+import reactor.netty.http.client.HttpClientResponse;
+
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * HttpClient that is implemented using reactor-netty.
+ */
+class ReactorNettyClient implements HttpClient {
+    private reactor.netty.http.client.HttpClient httpClient;
+
+    /**
+     * Creates default ReactorNettyClient.
+     */
+    ReactorNettyClient() {
+        this(reactor.netty.http.client.HttpClient.create());
+    }
+
+    /**
+     * Creates ReactorNettyClient with provided http client.
+     *
+     * @param httpClient the reactor http client
+     */
+    private ReactorNettyClient(reactor.netty.http.client.HttpClient httpClient) {
+        this.httpClient = httpClient;
+    }
+
+    /**
+     *  Creates ReactorNettyClient with provided http client with configuration applied.
+     *
+     * @param httpClient the reactor http client
+     * @param config the configuration to apply on the http client
+     */
+    private ReactorNettyClient(reactor.netty.http.client.HttpClient httpClient, Function<reactor.netty.http.client.HttpClient, reactor.netty.http.client.HttpClient> config) {
+        this.httpClient = config.apply(httpClient);
+    }
+
+    @Override
+    public Mono<HttpResponse> send(final HttpRequest request) {
+        Objects.requireNonNull(request.httpMethod());
+        Objects.requireNonNull(request.url());
+        Objects.requireNonNull(request.url().getProtocol());
+        //
+        Mono<HttpResponse> response = httpClient
+                .request(HttpMethod.valueOf(request.httpMethod().toString()))
+                .uri(request.url().toString())
+                .send(bodySendDelegate(request))
+                .responseConnection(responseDelegate(request))
+                .single();
+        return response;
+    }
+
+    /**
+     * Delegate to send the request content.
+     *
+     * @param restRequest the Rest request contains the body to be sent
+     * @return a delegate upon invocation sets the request body in reactor-netty outbound object
+     */
+    private static BiFunction<HttpClientRequest, NettyOutbound, Publisher<Void>> bodySendDelegate(final HttpRequest restRequest) {
+        BiFunction<HttpClientRequest, NettyOutbound, Publisher<Void>> sendDelegate = (reactorNettyRequest, reactorNettyOutbound) -> {
+            for (HttpHeader header : restRequest.headers()) {
+                reactorNettyRequest.header(header.name(), header.value());
+            }
+            if (restRequest.body() != null) {
+                Flux<ByteBuf> nettyByteBufFlux = restRequest.body().map(Unpooled::wrappedBuffer);
+                return reactorNettyOutbound.send(nettyByteBufFlux);
+            } else {
+                return reactorNettyOutbound;
+            }
+        };
+        return sendDelegate;
+    }
+
+    /**
+     * Delegate to receive response.
+     *
+     * @param restRequest the Rest request whose response this delegate handles
+     * @return a delegate upon invocation setup Rest response object
+     */
+    private static BiFunction<HttpClientResponse, Connection, Publisher<HttpResponse>> responseDelegate(final HttpRequest restRequest) {
+        BiFunction<HttpClientResponse, Connection, Publisher<HttpResponse>> responseDelegate = (reactorNettyResponse, reactorNettyConnection) -> {
+            HttpResponse httpResponse = new HttpResponse() {
+                @Override
+                public int statusCode() {
+                    return reactorNettyResponse.status().code();
+                }
+
+                @Override
+                public String headerValue(String name) {
+                    return reactorNettyResponse.responseHeaders().get(name);
+                }
+
+                @Override
+                public com.azure.common.http.HttpHeaders headers() {
+                    Map<String, String> map = new HashMap<>();
+                    reactorNettyResponse.responseHeaders().forEach(e -> map.put(e.getKey(), e.getValue()));
+                    return new com.azure.common.http.HttpHeaders(map);
+                }
+
+                @Override
+                public Flux<ByteBuf> body() {
+                    final ByteBufFlux body = bodyIntern();
+                    //
+                    return body.doFinally(s -> {
+                        if (!reactorNettyConnection.isDisposed()) {
+                            reactorNettyConnection.channel().eventLoop().execute(reactorNettyConnection::dispose);
+                        }
+                    });
+                }
+
+                @Override
+                public Mono<byte[]> bodyAsByteArray() {
+                    return bodyIntern().aggregate().asByteArray().doFinally(s -> {
+                        if (!reactorNettyConnection.isDisposed()) {
+                            reactorNettyConnection.channel().eventLoop().execute(reactorNettyConnection::dispose);
+                        }
+                    });
+                }
+
+                @Override
+                public Mono<String> bodyAsString() {
+                    return bodyIntern().aggregate().asString().doFinally(s -> {
+                        if (!reactorNettyConnection.isDisposed()) {
+                            reactorNettyConnection.channel().eventLoop().execute(reactorNettyConnection::dispose);
+                        }
+                    });
+                }
+
+                @Override
+                public Mono<String> bodyAsString(Charset charset) {
+                    return bodyIntern().aggregate().asString(charset).doFinally(s -> {
+                        if (!reactorNettyConnection.isDisposed()) {
+                            reactorNettyConnection.channel().eventLoop().execute(reactorNettyConnection::dispose);
+                        }
+                    });
+                }
+
+                @Override
+                public void close() {
+                    if (!reactorNettyConnection.isDisposed()) {
+                        reactorNettyConnection.channel().eventLoop().execute(reactorNettyConnection::dispose);
+                    }
+                }
+
+                private ByteBufFlux bodyIntern() {
+                    return reactorNettyConnection.inbound().receive();
+                }
+
+                @Override
+                Connection internConnection() {
+                    return reactorNettyConnection;
+                }
+            };
+            return Mono.just(httpResponse.withRequest(restRequest));
+        };
+        return responseDelegate;
+    }
+
+    @Override
+    public final HttpClient proxy(Supplier<ProxyOptions> proxyOptionsSupplier) {
+        return new ReactorNettyClient(this.httpClient, client -> client.tcpConfiguration(c -> {
+            ProxyOptions options = proxyOptionsSupplier.get();
+            return c.proxy(ts -> ts.type(options.type().value()).address(options.address()));
+        }));
+    }
+
+    @Override
+    public final HttpClient wiretap(boolean enableWiretap) {
+        return new ReactorNettyClient(this.httpClient, client -> client.wiretap(enableWiretap));
+    }
+
+    @Override
+    public final HttpClient port(int port) {
+        return new ReactorNettyClient(this.httpClient, client -> client.port(port));
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/package-info.java b/common/azure-common/src/main/java/com/azure/common/http/package-info.java
new file mode 100644
index 0000000000000..1ccbcd9d19cf8
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing the HTTP abstractions between the AnnotationParser, RestProxy and HTTP client.
+ */
+package com.azure.common.http;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/AddDatePolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/AddDatePolicy.java
new file mode 100644
index 0000000000000..f055fb5a9629a
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/AddDatePolicy.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import reactor.core.publisher.Mono;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * The Pipeline policy that adds Date header in RFC 1123 format when sending an HTTP request.
+ */
+public class AddDatePolicy implements HttpPipelinePolicy {
+    private final DateTimeFormatter format = DateTimeFormatter
+            .ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'")
+            .withZone(ZoneId.of("UTC"))
+            .withLocale(Locale.US);
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        return Mono.defer(() -> {
+            context.httpRequest().headers().set("Date", format.format(OffsetDateTime.now()));
+            return next.process();
+        });
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/AddHeadersPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/AddHeadersPolicy.java
new file mode 100644
index 0000000000000..4e159764fc714
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/AddHeadersPolicy.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpHeader;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import reactor.core.publisher.Mono;
+
+/**
+ * The Pipeline policy that adds a particular set of headers to HTTP requests.
+ */
+public class AddHeadersPolicy implements HttpPipelinePolicy {
+    private final HttpHeaders headers;
+
+    /**
+     * Creates a AddHeadersPolicy.
+     *
+     * @param headers The headers to add to outgoing requests.
+     */
+    public AddHeadersPolicy(HttpHeaders headers) {
+        this.headers = headers;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        for (HttpHeader header : headers) {
+            context.httpRequest().withHeader(header.name(), header.value());
+        }
+        return next.process();
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/CookiePolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/CookiePolicy.java
new file mode 100644
index 0000000000000..672ff727644e5
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/CookiePolicy.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpHeader;
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.http.HttpResponse;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The Pipeline policy that which stores cookies based on the response Set-Cookie header and adds cookies to requests.
+ */
+public class CookiePolicy implements HttpPipelinePolicy {
+    private final CookieHandler cookies = new CookieManager();
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        try {
+            final URI uri = context.httpRequest().url().toURI();
+
+            Map<String, List<String>> cookieHeaders = new HashMap<>();
+            for (HttpHeader header : context.httpRequest().headers()) {
+                cookieHeaders.put(header.name(), Arrays.asList(context.httpRequest().headers().values(header.name())));
+            }
+
+            Map<String, List<String>> requestCookies = cookies.get(uri, cookieHeaders);
+            for (Map.Entry<String, List<String>> entry : requestCookies.entrySet()) {
+                context.httpRequest().headers().set(entry.getKey(), String.join(",", entry.getValue()));
+            }
+
+            return next.process().map(httpResponse -> {
+                Map<String, List<String>> responseHeaders = new HashMap<>();
+                for (HttpHeader header : httpResponse.headers()) {
+                    responseHeaders.put(header.name(), Collections.singletonList(header.value()));
+                }
+
+                try {
+                    cookies.put(uri, responseHeaders);
+                } catch (IOException e) {
+                    throw Exceptions.propagate(e);
+                }
+                return httpResponse;
+            });
+        } catch (URISyntaxException | IOException e) {
+            return Mono.error(e);
+        }
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/CredentialsPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/CredentialsPolicy.java
new file mode 100644
index 0000000000000..27f5472fa6244
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/CredentialsPolicy.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.credentials.ServiceClientCredentials;
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+
+/**
+ * The Pipeline policy that adds credentials from ServiceClientCredentials to a request.
+ */
+public class CredentialsPolicy implements HttpPipelinePolicy {
+    private final ServiceClientCredentials credentials;
+
+    /**
+     * Creates CredentialsPolicy.
+     *
+     * @param credentials the credentials
+     */
+    public CredentialsPolicy(ServiceClientCredentials credentials) {
+        this.credentials = credentials;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        try {
+            String token = credentials.authorizationHeaderValue(context.httpRequest().url().toString());
+            context.httpRequest().headers().set("Authorization", token);
+            return next.process();
+        } catch (IOException e) {
+            return Mono.error(e);
+        }
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/HostPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/HostPolicy.java
new file mode 100644
index 0000000000000..61e2f92c3a070
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/HostPolicy.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.implementation.http.UrlBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+
+/**
+ * The Pipeline policy that adds the given host to each HttpRequest.
+ */
+public class HostPolicy implements HttpPipelinePolicy {
+    private final String host;
+    private static final Logger LOGGER = LoggerFactory.getLogger(HostPolicy.class);
+
+    /**
+     * Create HostPolicy.
+     *
+     * @param host The host to set on every HttpRequest.
+     */
+    public HostPolicy(String host) {
+        this.host = host;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        LOGGER.info("Setting host to {0}", host);
+
+        Mono<HttpResponse> result;
+        final UrlBuilder urlBuilder = UrlBuilder.parse(context.httpRequest().url());
+        try {
+            context.httpRequest().withUrl(urlBuilder.withHost(host).toURL());
+            result = next.process();
+        } catch (MalformedURLException e) {
+            result = Mono.error(e);
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/HttpLogDetailLevel.java b/common/azure-common/src/main/java/com/azure/common/http/policy/HttpLogDetailLevel.java
new file mode 100644
index 0000000000000..3e8893b65bd23
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/HttpLogDetailLevel.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+/**
+ * The level of detail to log on HTTP messages.
+ */
+public enum HttpLogDetailLevel {
+    /**
+     * Logging is turned off.
+     */
+    NONE,
+
+    /**
+     * Logs only URLs, HTTP methods, and time to finish the request.
+     */
+    BASIC,
+
+    /**
+     * Logs everything in BASIC, plus all the request and response headers.
+     */
+    HEADERS,
+
+    /**
+     * Logs everything in BASIC, plus all the request and response body.
+     * Note that only payloads in plain text or plain text encoded in GZIP
+     * will be logged.
+     */
+    BODY,
+
+    /**
+     * Logs everything in HEADERS and BODY.
+     */
+    BODY_AND_HEADERS;
+
+    /**
+     * @return a value indicating whether a request's URL should be logged.
+     */
+    public boolean shouldLogURL() {
+        return this != NONE;
+    }
+
+    /**
+     * @return a value indicating whether HTTP message headers should be logged.
+     */
+    public boolean shouldLogHeaders() {
+        return this == HEADERS || this == BODY_AND_HEADERS;
+    }
+
+    /**
+     * @return a value indicating whether HTTP message bodies should be logged.
+     */
+    public boolean shouldLogBody() {
+        return this == BODY || this == BODY_AND_HEADERS;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/HttpLoggingPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/HttpLoggingPolicy.java
new file mode 100644
index 0000000000000..581186e0f29d3
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/HttpLoggingPolicy.java
@@ -0,0 +1,201 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.azure.common.http.HttpHeader;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.implementation.util.FluxUtil;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.publisher.Mono;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+/**
+ * The Pipeline policy that handles logging of HTTP requests and responses.
+ */
+public class HttpLoggingPolicy implements HttpPipelinePolicy {
+    private static final ObjectMapper PRETTY_PRINTER = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
+    private final HttpLogDetailLevel detailLevel;
+    private final boolean prettyPrintJSON;
+    private static final int MAX_BODY_LOG_SIZE = 1024 * 16;
+
+    /**
+     * Creates an HttpLoggingPolicy with the given log level.
+     *
+     * @param detailLevel The HTTP logging detail level.
+     */
+    public HttpLoggingPolicy(HttpLogDetailLevel detailLevel) {
+        this(detailLevel, false);
+    }
+
+    /**
+     * Creates an HttpLoggingPolicy with the given log level and pretty printing setting.
+     *
+     * @param detailLevel The HTTP logging detail level.
+     * @param prettyPrintJSON If true, pretty prints JSON message bodies when logging.
+     *                        If the detailLevel does not include body logging, this flag does nothing.
+     */
+    public HttpLoggingPolicy(HttpLogDetailLevel detailLevel, boolean prettyPrintJSON) {
+        this.detailLevel = detailLevel;
+        this.prettyPrintJSON = prettyPrintJSON;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        //
+        Optional<Object> data = context.getData("caller-method");
+        String callerMethod;
+        if (!data.isPresent() || data.get() == null) {
+            callerMethod = "";
+        } else {
+            callerMethod = (String) data.get();
+        }
+        //
+        final Logger logger = LoggerFactory.getLogger(callerMethod);
+        final long startNs = System.nanoTime();
+        //
+        Mono<Void> logRequest = logRequest(logger, context.httpRequest());
+        Function<HttpResponse, Mono<HttpResponse>> logResponseDelegate = logResponseDelegate(logger, context.httpRequest().url(), startNs);
+        //
+        return logRequest.then(next.process()).flatMap(logResponseDelegate)
+                .doOnError(throwable -> log(logger, "<-- HTTP FAILED: " + throwable));
+    }
+
+    private Mono<Void> logRequest(final Logger logger, final HttpRequest request) {
+        if (detailLevel.shouldLogURL()) {
+            log(logger, String.format("--> %s %s", request.httpMethod(), request.url()));
+        }
+
+        if (detailLevel.shouldLogHeaders()) {
+            for (HttpHeader header : request.headers()) {
+                log(logger, header.toString());
+            }
+        }
+        //
+        Mono<Void> reqBodyLoggingMono = Mono.empty();
+        //
+        if (detailLevel.shouldLogBody()) {
+            if (request.body() == null) {
+                log(logger, "(empty body)");
+                log(logger, "--> END " + request.httpMethod());
+            } else {
+                boolean isHumanReadableContentType = !"application/octet-stream".equalsIgnoreCase(request.headers().value("Content-Type"));
+                final long contentLength = getContentLength(request.headers());
+
+                if (contentLength < MAX_BODY_LOG_SIZE && isHumanReadableContentType) {
+                    try {
+                        Mono<byte[]> collectedBytes = FluxUtil.collectBytesInByteBufStream(request.body(), true);
+                        reqBodyLoggingMono = collectedBytes.flatMap(bytes -> {
+                            String bodyString = new String(bytes, StandardCharsets.UTF_8);
+                            bodyString = prettyPrintIfNeeded(logger, request.headers().value("Content-Type"), bodyString);
+                            log(logger, String.format("%s-byte body:\n%s", contentLength, bodyString));
+                            log(logger, "--> END " + request.httpMethod());
+                            return Mono.empty();
+                        });
+                    } catch (Exception e) {
+                        reqBodyLoggingMono = Mono.error(e);
+                    }
+                } else {
+                    log(logger, contentLength + "-byte body: (content not logged)");
+                    log(logger, "--> END " + request.httpMethod());
+                }
+            }
+        }
+        return reqBodyLoggingMono;
+    }
+
+    private Function<HttpResponse, Mono<HttpResponse>> logResponseDelegate(final Logger logger, final URL url, final long startNs) {
+        return (HttpResponse response) -> {
+            long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
+            //
+            String contentLengthString = response.headerValue("Content-Length");
+            String bodySize;
+            if (contentLengthString == null || contentLengthString.isEmpty()) {
+                bodySize = "unknown-length";
+            } else {
+                bodySize = contentLengthString + "-byte";
+            }
+
+            HttpResponseStatus responseStatus = HttpResponseStatus.valueOf(response.statusCode());
+            if (detailLevel.shouldLogURL()) {
+                log(logger, String.format("<-- %s %s %s (%s ms, %s body)", response.statusCode(), responseStatus.reasonPhrase(), url, tookMs, bodySize));
+            }
+
+            if (detailLevel.shouldLogHeaders()) {
+                for (HttpHeader header : response.headers()) {
+                    log(logger, header.toString());
+                }
+            }
+
+            if (detailLevel.shouldLogBody()) {
+                long contentLength = getContentLength(response.headers());
+                final String contentTypeHeader = response.headerValue("Content-Type");
+                if ((contentTypeHeader == null || !"application/octet-stream".equalsIgnoreCase(contentTypeHeader))
+                        && contentLength != 0 && contentLength < MAX_BODY_LOG_SIZE) {
+                    final HttpResponse bufferedResponse = response.buffer();
+                    return bufferedResponse.bodyAsString().map(bodyStr -> {
+                        bodyStr = prettyPrintIfNeeded(logger, contentTypeHeader, bodyStr);
+                        log(logger, "Response body:\n" + bodyStr);
+                        log(logger, "<-- END HTTP");
+                        return bufferedResponse;
+                    });
+                } else {
+                    log(logger, "(body content not logged)");
+                    log(logger, "<-- END HTTP");
+                }
+            } else {
+                log(logger, "<-- END HTTP");
+            }
+            return Mono.just(response);
+        };
+    }
+
+    private String prettyPrintIfNeeded(Logger logger, String contentType, String body) {
+        String result = body;
+        if (prettyPrintJSON && contentType != null && (contentType.startsWith("application/json") || contentType.startsWith("text/json"))) {
+            try {
+                final Object deserialized = PRETTY_PRINTER.readTree(body);
+                result = PRETTY_PRINTER.writeValueAsString(deserialized);
+            } catch (Exception e) {
+                log(logger, "Failed to pretty print JSON: " + e.getMessage());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Process the log using an SLF4j logger and an HTTP message.
+     *
+     * @param logger the SLF4j logger with the context of the request
+     * @param s      the message for logging
+     */
+    private void log(Logger logger, String s) {
+        logger.info(s);
+    }
+
+    private long getContentLength(HttpHeaders headers) {
+        long contentLength = 0;
+        try {
+            contentLength = Long.parseLong(headers.value("content-length"));
+        } catch (NumberFormatException | NullPointerException ignored) {
+        }
+
+        return contentLength;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/HttpPipelinePolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/HttpPipelinePolicy.java
new file mode 100644
index 0000000000000..fa44ac8ff1d97
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/HttpPipelinePolicy.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import reactor.core.publisher.Mono;
+
+/**
+ * Pipeline policy.
+ */
+@FunctionalInterface
+public interface HttpPipelinePolicy {
+    /**
+     * Process provided request context and invokes the next policy.
+     *
+     * @param context request context
+     * @param next the next policy to invoke
+     * @return publisher that initiate the request upon subscription and emits response on completion.
+     */
+    Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next);
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/PortPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/PortPolicy.java
new file mode 100644
index 0000000000000..574fb45923af3
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/PortPolicy.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.implementation.http.UrlBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+
+/**
+ * The Pipeline policy that adds a given port to each HttpRequest.
+ */
+public class PortPolicy implements HttpPipelinePolicy {
+    private final int port;
+    private final boolean overwrite;
+    private static final Logger LOGGER = LoggerFactory.getLogger(PortPolicy.class);
+
+    /**
+     * Create a new PortPolicy object.
+     *
+     * @param port The port to set.
+     * @param overwrite Whether or not to overwrite a HttpRequest's port if it already has one.
+     */
+    public PortPolicy(int port, boolean overwrite) {
+        this.port = port;
+        this.overwrite = overwrite;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        final UrlBuilder urlBuilder = UrlBuilder.parse(context.httpRequest().url());
+        if (overwrite || urlBuilder.port() == null) {
+            LOGGER.info("Changing port to {0}", port);
+
+            try {
+                context.httpRequest().withUrl(urlBuilder.withPort(port).toURL());
+            } catch (MalformedURLException e) {
+                return Mono.error(e);
+            }
+        }
+        return next.process();
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/ProtocolPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/ProtocolPolicy.java
new file mode 100644
index 0000000000000..dc3038e45a8d5
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/ProtocolPolicy.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.implementation.http.UrlBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+
+/**
+ * The Pipeline policy that adds a given protocol to each HttpRequest.
+ */
+public class ProtocolPolicy implements HttpPipelinePolicy {
+    private final String protocol;
+    private final boolean overwrite;
+    private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolPolicy.class);
+
+    /**
+     * Create a new ProtocolPolicy.
+     *
+     * @param protocol The protocol to set.
+     * @param overwrite Whether or not to overwrite a HttpRequest's protocol if it already has one.
+     */
+    public ProtocolPolicy(String protocol, boolean overwrite) {
+        this.protocol = protocol;
+        this.overwrite = overwrite;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        final UrlBuilder urlBuilder = UrlBuilder.parse(context.httpRequest().url());
+        if (overwrite || urlBuilder.scheme() == null) {
+            LOGGER.info("Setting protocol to {0}", protocol);
+
+            try {
+                context.httpRequest().withUrl(urlBuilder.withScheme(protocol).toURL());
+            } catch (MalformedURLException e) {
+                return Mono.error(e);
+            }
+        }
+        return next.process();
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/ProxyAuthenticationPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/ProxyAuthenticationPolicy.java
new file mode 100644
index 0000000000000..e67bd6226b87c
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/ProxyAuthenticationPolicy.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.implementation.util.Base64Util;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * The Pipeline policy that adds basic proxy authentication to outgoing HTTP requests.
+ */
+public class ProxyAuthenticationPolicy implements HttpPipelinePolicy {
+    private final String username;
+    private final String password;
+
+    /**
+     * Creates a ProxyAuthenticationPolicy.
+     *
+     * @param username the username for authentication.
+     * @param password the password for authentication.
+     */
+    public ProxyAuthenticationPolicy(String username, String password) {
+        this.username = username;
+        this.password = password;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        String auth = username + ":" + password;
+        String encodedAuth = Base64Util.encodeToString(auth.getBytes(StandardCharsets.UTF_8));
+        context.httpRequest().withHeader("Proxy-Authentication", "Basic " + encodedAuth);
+        return next.process();
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/RequestIdPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/RequestIdPolicy.java
new file mode 100644
index 0000000000000..2f2917e543fe1
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/RequestIdPolicy.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Mono;
+
+import java.util.UUID;
+
+/**
+ * The Pipeline policy that puts a UUID in the request header. Azure uses the request id as
+ * the unique identifier for the request.
+ */
+public class RequestIdPolicy implements HttpPipelinePolicy {
+    private static final String REQUEST_ID_HEADER = "x-ms-client-request-id";
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        String requestId = context.httpRequest().headers().value(REQUEST_ID_HEADER);
+        if (requestId == null) {
+            context.httpRequest().headers().set(REQUEST_ID_HEADER, UUID.randomUUID().toString());
+        }
+        return next.process();
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/RetryPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/RetryPolicy.java
new file mode 100644
index 0000000000000..c41205447ca8a
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/RetryPolicy.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Mono;
+
+import java.net.HttpURLConnection;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * A pipeline policy that retries when a recoverable HTTP error occurs.
+ */
+public class RetryPolicy implements HttpPipelinePolicy {
+    private static final int DEFAULT_MAX_RETRIES = 3;
+    private static final int DEFAULT_DELAY = 0;
+    private static final ChronoUnit DEFAULT_TIME_UNIT = ChronoUnit.MILLIS;
+    private final int maxRetries;
+    private final Duration delayDuration;
+
+    /**
+     * Creates a RetryPolicy with the default number of retry attempts and delay between retries.
+     */
+    public RetryPolicy() {
+        this.maxRetries = DEFAULT_MAX_RETRIES;
+        this.delayDuration = Duration.of(DEFAULT_DELAY, DEFAULT_TIME_UNIT);
+    }
+
+    /**
+     * Creates a RetryPolicy.
+     *
+     * @param maxRetries the maximum number of retries to attempt.
+     * @param delayDuration the delay between retries
+     */
+    public RetryPolicy(int maxRetries, Duration delayDuration) {
+        this.maxRetries = maxRetries;
+        this.delayDuration = delayDuration;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        return attemptAsync(context, next, context.httpRequest(), 0);
+    }
+
+    private Mono<HttpResponse> attemptAsync(final HttpPipelineCallContext context, final HttpPipelineNextPolicy next, final HttpRequest originalHttpRequest, final int tryCount) {
+        context.withHttpRequest(originalHttpRequest.buffer());
+        return next.clone().process()
+                .flatMap(httpResponse -> {
+                    if (shouldRetry(httpResponse, tryCount)) {
+                        return attemptAsync(context, next, originalHttpRequest, tryCount + 1).delaySubscription(this.delayDuration);
+                    } else {
+                        return Mono.just(httpResponse);
+                    }
+                })
+                .onErrorResume(err -> {
+                    if (tryCount < maxRetries) {
+                        return attemptAsync(context, next, originalHttpRequest, tryCount + 1).delaySubscription(this.delayDuration);
+                    } else {
+                        return Mono.error(err);
+                    }
+                });
+    }
+
+    private boolean shouldRetry(HttpResponse response, int tryCount) {
+        int code = response.statusCode();
+        return tryCount < maxRetries
+                && (code == HttpURLConnection.HTTP_CLIENT_TIMEOUT
+                || (code >= HttpURLConnection.HTTP_INTERNAL_ERROR
+                && code != HttpURLConnection.HTTP_NOT_IMPLEMENTED
+                && code != HttpURLConnection.HTTP_VERSION));
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/TimeoutPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/TimeoutPolicy.java
new file mode 100644
index 0000000000000..9baaeda7dd60c
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/TimeoutPolicy.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+
+/**
+ * The Pipeline policy that limits the time allowed between sending a request
+ * and receiving the response.
+ *
+ */
+public class TimeoutPolicy implements HttpPipelinePolicy {
+    private final Duration timoutDuration;
+
+    /**
+     * Creates a TimeoutPolicy.
+     *
+     * @param timoutDuration the timeout duration
+     */
+    public TimeoutPolicy(Duration timoutDuration) {
+        this.timoutDuration = timoutDuration;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        return next.process().timeout(this.timoutDuration);
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/UserAgentPolicy.java b/common/azure-common/src/main/java/com/azure/common/http/policy/UserAgentPolicy.java
new file mode 100644
index 0000000000000..ee2244cdfa456
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/UserAgentPolicy.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Mono;
+
+/**
+ * Pipeline policy that adds 'User-Agent' header to a request.
+ */
+public class UserAgentPolicy implements HttpPipelinePolicy {
+    private static final String DEFAULT_USER_AGENT_HEADER = "AutoRest-Java";
+    private final String userAgent;
+
+    /**
+     * Creates UserAgentPolicy.
+     *
+     * @param userAgent The user agent string to add to request headers.
+     */
+    public UserAgentPolicy(String userAgent) {
+        if (userAgent != null) {
+            this.userAgent = userAgent;
+        } else {
+            this.userAgent = DEFAULT_USER_AGENT_HEADER;
+        }
+    }
+
+    /**
+     * Creates a {@link UserAgentPolicy} with a default user agent string.
+     */
+    public UserAgentPolicy() {
+        this.userAgent = DEFAULT_USER_AGENT_HEADER;
+    }
+
+    @Override
+    public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+        String header = context.httpRequest().headers().value("User-Agent");
+        if (header == null || DEFAULT_USER_AGENT_HEADER.equals(header)) {
+            header = userAgent;
+        } else {
+            header = userAgent + " " + header;
+        }
+        context.httpRequest().headers().set("User-Agent", header);
+        return next.process();
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/http/policy/package-info.java b/common/azure-common/src/main/java/com/azure/common/http/policy/package-info.java
new file mode 100644
index 0000000000000..db48bb0f54953
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/policy/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * Package containing HttpPipelinePolicy interface and it's implementations.
+ */
+package com.azure.common.http.policy;
+
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/RestException.java b/common/azure-common/src/main/java/com/azure/common/http/rest/RestException.java
new file mode 100644
index 0000000000000..986c88c53ca25
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/RestException.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.rest;
+
+import com.azure.common.http.HttpResponse;
+
+/**
+ * An exception thrown for an invalid response with custom error information.
+ */
+public class RestException extends RuntimeException {
+    /**
+     * Information about the associated HTTP response.
+     */
+    private HttpResponse response;
+
+    /**
+     * The HTTP response body.
+     */
+    private Object body;
+
+    /**
+     * Initializes a new instance of the RestException class.
+     *
+     * @param message the exception message or the response content if a message is not available
+     * @param response the HTTP response
+     */
+    public RestException(String message, HttpResponse response) {
+        super(message);
+        this.response = response;
+    }
+
+    /**
+     * Initializes a new instance of the RestException class.
+     *
+     * @param message the exception message or the response content if a message is not available
+     * @param response the HTTP response
+     * @param body the deserialized response body
+     */
+    public RestException(String message, HttpResponse response, Object body) {
+        super(message);
+        this.response = response;
+        this.body = body;
+    }
+
+    /**
+     * Initializes a new instance of the RestException class.
+     *
+     * @param message the exception message or the response content if a message is not available
+     * @param response the HTTP response
+     * @param cause the Throwable which caused the creation of this RestException
+     */
+    public RestException(String message, HttpResponse response, Throwable cause) {
+        super(message, cause);
+        this.response = response;
+    }
+
+    /**
+     * @return information about the associated HTTP response
+     */
+    public HttpResponse response() {
+        return response;
+    }
+
+    /**
+     * @return the HTTP response body
+     */
+    public Object body() {
+        return body;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/RestPagedResponse.java b/common/azure-common/src/main/java/com/azure/common/http/rest/RestPagedResponse.java
new file mode 100644
index 0000000000000..0f3586ab09ba3
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/RestPagedResponse.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.http.rest;
+
+import java.io.Closeable;
+import java.util.List;
+
+/**
+ * Response of a REST API that returns page.
+ *
+ * @param <T> the type items in the page
+ */
+public interface RestPagedResponse<T> extends RestResponse<List<T>>, Closeable {
+    /**
+     * Gets the items in the page.
+     *
+     * @return The items in the page.
+     */
+    List<T> items();
+
+    /**
+     * Get the link to retrieve RestPagedResponse containing next page.
+     *
+     * @return the next page link.
+     */
+    String nextLink();
+
+    /**
+     * Returns the items in the page.
+     *
+     * @return The items in the page.
+     */
+    default List<T> body() {
+        return items();
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/RestResponse.java b/common/azure-common/src/main/java/com/azure/common/http/rest/RestResponse.java
new file mode 100644
index 0000000000000..618735aa937db
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/RestResponse.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.http.rest;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpRequest;
+
+/**
+ * REST response with a strongly-typed content specified.
+ *
+ * @param <T> The deserialized type of the response content.
+ * @see RestResponseBase
+ */
+public interface RestResponse<T> {
+
+    /**
+     * Get the HTTP response status code.
+     *
+     * @return the status code of the HTTP response.
+     */
+    int statusCode();
+
+    /**
+     * Get the headers from the HTTP response.
+     *
+     * @return an HttpHeaders instance containing the HTTP response headers.
+     */
+    HttpHeaders headers();
+
+    /**
+     * Get the HTTP request which resulted in this response.
+     *
+     * @return the HTTP request.
+     */
+    HttpRequest request();
+
+    /**
+     * @return the deserialized body of the HTTP response.
+     */
+    T body();
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/RestResponseBase.java b/common/azure-common/src/main/java/com/azure/common/http/rest/RestResponseBase.java
new file mode 100644
index 0000000000000..4dad85c448796
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/RestResponseBase.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.http.rest;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpRequest;
+
+/**
+ * The response of a REST request.
+ *
+ * @param <H> The deserialized type of the response headers.
+ * @param <T> The deserialized type of the response body.
+ */
+public class RestResponseBase<H, T> implements RestResponse<T> {
+    private final HttpRequest request;
+    private final int statusCode;
+    private final H deserializedHeaders;
+    private final HttpHeaders headers;
+    private final T body;
+
+    /**
+     * Create RestResponseBase.
+     *
+     * @param request the request which resulted in this response
+     * @param statusCode the status code of the HTTP response
+     * @param headers the headers of the HTTP response
+     * @param deserializedHeaders the deserialized headers of the HTTP response
+     * @param body the deserialized body
+     */
+    public RestResponseBase(HttpRequest request, int statusCode, HttpHeaders headers, T body, H deserializedHeaders) {
+        this.request = request;
+        this.statusCode = statusCode;
+        this.headers = headers;
+        this.deserializedHeaders = deserializedHeaders;
+        this.body = body;
+    }
+
+    /**
+     * @return the request which resulted in this RestResponseBase.
+     */
+    @Override
+    public HttpRequest request() {
+        return request;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int statusCode() {
+        return statusCode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public HttpHeaders headers() {
+        return headers;
+    }
+
+    /**
+     * Get the headers from the HTTP response, transformed into the header type H.
+     *
+     * @return an instance of header type H, containing the HTTP response headers.
+     */
+    public H deserializedHeaders() {
+        return deserializedHeaders;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public T body() {
+        return body;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/RestStreamResponse.java b/common/azure-common/src/main/java/com/azure/common/http/rest/RestStreamResponse.java
new file mode 100644
index 0000000000000..3a98167eb2631
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/RestStreamResponse.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.http.rest;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpRequest;
+import io.netty.buffer.ByteBuf;
+import reactor.core.publisher.Flux;
+
+import java.io.Closeable;
+
+/**
+ * REST response with a streaming content.
+ */
+public final class RestStreamResponse extends SimpleRestResponse<Flux<ByteBuf>> implements Closeable {
+    /**
+     * Creates RestStreamResponse.
+     *
+     * @param request the request which resulted in this response
+     * @param statusCode the status code of the HTTP response
+     * @param headers the headers of the HTTP response
+     * @param body the streaming body
+     */
+    public RestStreamResponse(HttpRequest request, int statusCode, HttpHeaders headers, Flux<ByteBuf> body) {
+        super(request, statusCode, headers, body);
+    }
+
+    /**
+     * @return the stream content
+     */
+    @Override
+    public Flux<ByteBuf> body() {
+        return super.body();
+    }
+
+    /**
+     * Disposes the connection associated with this RestStreamResponse.
+     */
+    @Override
+    public void close() {
+        body().subscribe().dispose();
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/RestVoidResponse.java b/common/azure-common/src/main/java/com/azure/common/http/rest/RestVoidResponse.java
new file mode 100644
index 0000000000000..22e7a5864a0aa
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/RestVoidResponse.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.http.rest;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpRequest;
+
+/**
+ * REST response containing only a status code and raw headers.
+ */
+public final class RestVoidResponse extends SimpleRestResponse<Void> {
+    /**
+     * Creates RestVoidResponse.
+     *
+     * @param request the request which resulted in this response
+     * @param statusCode the status code of the HTTP response
+     * @param headers the headers of the HTTP response
+     */
+    public RestVoidResponse(HttpRequest request, int statusCode, HttpHeaders headers) {
+        super(request, statusCode, headers, null);
+    }
+
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/SimpleRestResponse.java b/common/azure-common/src/main/java/com/azure/common/http/rest/SimpleRestResponse.java
new file mode 100644
index 0000000000000..bc7b15d2382e9
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/SimpleRestResponse.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+package com.azure.common.http.rest;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpRequest;
+
+/**
+ * REST response with a strongly-typed content specified.
+ *
+ * @param <T> The deserialized type of the response content.
+ */
+public class SimpleRestResponse<T> implements RestResponse<T> {
+    private final HttpRequest request;
+    private final int statusCode;
+    private final HttpHeaders headers;
+    private final T body;
+
+    /**
+     * Creates RestResponse.
+     *
+     * @param request the request which resulted in this response
+     * @param statusCode the status code of the HTTP response
+     * @param headers the headers of the HTTP response
+     * @param body the deserialized body
+     */
+    public SimpleRestResponse(HttpRequest request, int statusCode, HttpHeaders headers, T body) {
+        this.request = request;
+        this.statusCode = statusCode;
+        this.headers = headers;
+        this.body = body;
+    }
+
+    /**
+     * @return the request which resulted in this RestResponse.
+     */
+    @Override
+    public HttpRequest request() {
+        return request;
+    }
+
+    /**
+     * @return the status code of the HTTP response.
+     */
+    @Override
+    public int statusCode() {
+        return statusCode;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public HttpHeaders headers() {
+        return headers;
+    }
+
+    /**
+     * @return the deserialized body of the HTTP response.
+     */
+    @Override
+    public T body() {
+        return body;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/http/rest/package-info.java b/common/azure-common/src/main/java/com/azure/common/http/rest/package-info.java
new file mode 100644
index 0000000000000..9ed7cfe59057e
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/http/rest/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing REST-related APIs.
+ */
+package com.azure.common.http.rest;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/Base64Url.java b/common/azure-common/src/main/java/com/azure/common/implementation/Base64Url.java
new file mode 100644
index 0000000000000..c478d8dbbbbeb
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/Base64Url.java
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.azure.common.implementation.util.Base64Util;
+
+import java.util.Arrays;
+
+/**
+ * Wrapper over Base64Url encoded byte array used during serialization and deserialization.
+ */
+public final class Base64Url {
+    /**
+     * The Base64Url encoded bytes.
+     */
+    private final byte[] bytes;
+
+    /**
+     * Creates a new Base64Url object with the specified encoded string.
+     *
+     * @param string The encoded string.
+     */
+    public Base64Url(String string) {
+        if (string == null) {
+            this.bytes = null;
+        } else {
+            string = unquote(string);
+            this.bytes = string.getBytes();
+        }
+    }
+
+    /**
+     * Creates a new Base64Url object with the specified encoded bytes.
+     *
+     * @param bytes The encoded bytes.
+     */
+    public Base64Url(byte[] bytes) {
+        this.bytes = unquote(bytes);
+    }
+
+    private static byte[] unquote(byte[] bytes) {
+        if (bytes != null && bytes.length > 1) {
+            bytes = unquote(new String(bytes)).getBytes();
+        }
+        return bytes;
+    }
+
+    private static String unquote(String string) {
+        if (string != null && !string.isEmpty()) {
+            final char firstCharacter = string.charAt(0);
+            if (firstCharacter == '\"' || firstCharacter == '\'') {
+                final int base64UrlStringLength = string.length();
+                final char lastCharacter = string.charAt(base64UrlStringLength - 1);
+                if (lastCharacter == firstCharacter) {
+                    string = string.substring(1, base64UrlStringLength - 1);
+                }
+            }
+        }
+        return string;
+    }
+
+    /**
+     * Encode a byte array into Base64Url encoded bytes.
+     *
+     * @param bytes The byte array to encode.
+     * @return a Base64Url instance
+     */
+    public static Base64Url encode(byte[] bytes) {
+        if (bytes == null) {
+            return new Base64Url((String) null);
+        } else {
+            return new Base64Url(Base64Util.encodeURLWithoutPadding(bytes));
+        }
+    }
+
+    /**
+     * Returns the underlying encoded byte array.
+     *
+     * @return The underlying encoded byte array.
+     */
+    public byte[] encodedBytes() {
+        return bytes;
+    }
+
+    /**
+     * Decode the bytes and return.
+     *
+     * @return The decoded byte array.
+     */
+    public byte[] decodedBytes() {
+        if (this.bytes == null) {
+            return null;
+        }
+
+        final byte[] decodedBytes = Base64Util.decodeURL(bytes);
+        return decodedBytes;
+    }
+
+    @Override
+    public String toString() {
+        return bytes == null ? null : new String(bytes);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(bytes);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
+        if (!(obj instanceof Base64Url)) {
+            return false;
+        }
+
+        Base64Url rhs = (Base64Url) obj;
+        return Arrays.equals(this.bytes, rhs.encodedBytes());
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/CollectionFormat.java b/common/azure-common/src/main/java/com/azure/common/implementation/CollectionFormat.java
new file mode 100644
index 0000000000000..bf257ebd00bd6
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/CollectionFormat.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+/**
+ * Swagger collection format to use for joining {@link java.util.List} parameters in
+ * paths, queries, and headers.
+ * See <a href="https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#fixed-fields-7">https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#fixed-fields-7</a>.
+ */
+public enum CollectionFormat {
+    /**
+     * Comma separated values.
+     * E.g. foo,bar
+     */
+    CSV(","),
+    /**
+     * Space separated values.
+     * E.g. foo bar
+     */
+    SSV(" "),
+    /**
+     * Tab separated values.
+     * E.g. foo\tbar
+     */
+    TSV("\t"),
+    /**
+     * Pipe(|) separated values.
+     * E.g. foo|bar
+     */
+    PIPES("|"),
+    /**
+     * Corresponds to multiple parameter instances instead of multiple values
+     * for a single instance.
+     * E.g. foo=bar&amp;foo=baz
+     */
+    MULTI("&");
+
+    /**
+     * The delimiter separating the values.
+     */
+    private String delimiter;
+
+    /**
+     * Creates CollectionFormat enum.
+     *
+     * @param delimiter the delimiter as a string.
+     */
+    CollectionFormat(String delimiter) {
+        this.delimiter = delimiter;
+    }
+
+    /**
+     * Gets the delimiter used to join a list of parameters.
+     *
+     * @return the delimiter of the current collection format.
+     */
+    public String getDelimiter() {
+        return delimiter;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/DateTimeRfc1123.java b/common/azure-common/src/main/java/com/azure/common/implementation/DateTimeRfc1123.java
new file mode 100644
index 0000000000000..0e9ed137e5f25
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/DateTimeRfc1123.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+/**
+ * Wrapper over java.time.OffsetDateTime used for specifying RFC1123 format during serialization and deserialization.
+ */
+public final class DateTimeRfc1123 {
+    /**
+     * The pattern of the datetime used for RFC1123 datetime format.
+     */
+    private static final DateTimeFormatter RFC1123_DATE_TIME_FORMATTER =
+            DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'").withZone(ZoneId.of("UTC")).withLocale(Locale.US);
+    /**
+     * The actual datetime object.
+     */
+    private final OffsetDateTime dateTime;
+
+    /**
+     * Creates a new DateTimeRfc1123 object with the specified DateTime.
+     * @param dateTime The DateTime object to wrap.
+     */
+    public DateTimeRfc1123(OffsetDateTime dateTime) {
+        this.dateTime = dateTime;
+    }
+
+    /**
+     * Creates a new DateTimeRfc1123 object with the specified DateTime.
+     * @param formattedString The datetime string in RFC1123 format
+     */
+    public DateTimeRfc1123(String formattedString) {
+        this.dateTime = OffsetDateTime.parse(formattedString, DateTimeFormatter.RFC_1123_DATE_TIME);
+    }
+
+    /**
+     * Returns the underlying DateTime.
+     * @return The underlying DateTime.
+     */
+    public OffsetDateTime dateTime() {
+        if (this.dateTime == null) {
+            return null;
+        }
+        return this.dateTime;
+    }
+
+    @Override
+    public String toString() {
+        return RFC1123_DATE_TIME_FORMATTER.format(this.dateTime);
+    }
+
+    @Override
+    public int hashCode() {
+        return this.dateTime.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
+        if (!(obj instanceof DateTimeRfc1123)) {
+            return false;
+        }
+
+        DateTimeRfc1123 rhs = (DateTimeRfc1123) obj;
+        return this.dateTime.equals(rhs.dateTime());
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/EncodedParameter.java b/common/azure-common/src/main/java/com/azure/common/implementation/EncodedParameter.java
new file mode 100644
index 0000000000000..08fe66508588c
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/EncodedParameter.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+/**
+ * Type representing result of encoding a query parameter or header name/value pair for a
+ * HTTP request. It contains the query parameter or header name, plus the query parameter's value or
+ * header's value.
+ */
+class EncodedParameter {
+    private final String name;
+    private final String encodedValue;
+
+    /**
+     * Create a EncodedParameter using the provided parameter name and encoded value.
+     *
+     * @param name the name of the new parameter
+     * @param encodedValue the encoded value of the new parameter
+     */
+    EncodedParameter(String name, String encodedValue) {
+        this.name = name;
+        this.encodedValue = encodedValue;
+    }
+
+    /**
+     * Get this parameter's name.
+     *
+     * @return the name of this parameter
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Get the encoded value for this parameter.
+     *
+     * @return the encoded value for this parameter
+     */
+    public String encodedValue() {
+        return encodedValue;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/OperationDescription.java b/common/azure-common/src/main/java/com/azure/common/implementation/OperationDescription.java
new file mode 100644
index 0000000000000..9fe92687905cc
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/OperationDescription.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.azure.common.http.HttpRequest;
+
+import java.io.Serializable;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * Type that holds composes data from an originating operation
+ * that can be used to resume the polling of the original operation.
+ */
+public class OperationDescription implements Serializable {
+    private Serializable pollStrategyData;
+    private Map<String, String> headers;
+    private String httpMethod;
+    private URL url;
+    private String fullyQualifiedMethodName;
+
+    /**
+     * Create OperationDescription.
+     */
+    public OperationDescription() {
+        this.fullyQualifiedMethodName = null;
+        this.pollStrategyData = null;
+        this.headers = null;
+        this.url = null;
+        this.httpMethod = null;
+    }
+
+    /**
+     * Create a new Substitution.
+     *
+     * @param fullyQualifiedMethodName the fully qualified method name from the originating call
+     * @param pollStrategyData the data for the originating methods polling strategy
+     * @param originalHttpRequest the initial http request from the originating call
+     */
+    public OperationDescription(String fullyQualifiedMethodName,
+                                Serializable pollStrategyData,
+                                HttpRequest originalHttpRequest) {
+        this.fullyQualifiedMethodName = fullyQualifiedMethodName;
+        this.pollStrategyData = pollStrategyData;
+        this.headers = originalHttpRequest.headers().toMap();
+        this.url = originalHttpRequest.url();
+        this.httpMethod = originalHttpRequest.httpMethod().toString();
+    }
+
+    /**
+     * Get the Serializable poll strategy data.
+     *
+     * @return the Serializable poll strategy data
+     */
+    public Serializable pollStrategyData() {
+        return this.pollStrategyData;
+    }
+
+    /**
+     * Get the originating requests url.
+     *
+     * @return the originating requests url
+     */
+    public URL url() {
+        return this.url;
+    }
+
+    /**
+     * @return the originating requests http method.
+     */
+    public String httpMethod() {
+        return this.httpMethod;
+    }
+
+    /**
+     * Get the originating requests headers.
+     *
+     * @return the originating requests headers
+     */
+    public Map<String, String> headers() {
+        return this.headers;
+    }
+
+    /**
+     * Get the originating method name.
+     *
+     * @return the originating method name
+     */
+    String methodName() {
+        int lastIndex = this.fullyQualifiedMethodName.lastIndexOf(".");
+        return this.fullyQualifiedMethodName.substring(lastIndex + 1);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/PercentEscaper.java b/common/azure-common/src/main/java/com/azure/common/implementation/PercentEscaper.java
new file mode 100644
index 0000000000000..a2b81844bb4f7
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/PercentEscaper.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An escaper that escapes URL data through percent encoding.
+ */
+final class PercentEscaper {
+
+    private static final String[] HEX = {
+            "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
+            "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
+            "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
+            "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
+            "%20", "%21", "%22", "%23", "%24", "%25", "%26", "%27",
+            "%28", "%29", "%2a", "%2b", "%2c", "%2d", "%2e", "%2f",
+            "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37",
+            "%38", "%39", "%3a", "%3b", "%3c", "%3d", "%3e", "%3f",
+            "%40", "%41", "%42", "%43", "%44", "%45", "%46", "%47",
+            "%48", "%49", "%4a", "%4b", "%4c", "%4d", "%4e", "%4f",
+            "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57",
+            "%58", "%59", "%5a", "%5b", "%5c", "%5d", "%5e", "%5f",
+            "%60", "%61", "%62", "%63", "%64", "%65", "%66", "%67",
+            "%68", "%69", "%6a", "%6b", "%6c", "%6d", "%6e", "%6f",
+            "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77",
+            "%78", "%79", "%7a", "%7b", "%7c", "%7d", "%7e", "%7f",
+            "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
+            "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
+            "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
+            "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
+            "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
+            "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
+            "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
+            "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
+            "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
+            "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
+            "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
+            "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
+            "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
+            "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
+            "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
+            "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
+    };
+
+    private final boolean usePlusForSpace;
+
+    private final List<Character> safeChars = new ArrayList<>();
+
+    /**
+     * Creates a percent escaper.
+     * @param safeChars a collection of characters that will not be escaped
+     * @param usePlusForSpace escape ' ' as '+' if true, "%20" otherwise
+     */
+    PercentEscaper(String safeChars, boolean usePlusForSpace) {
+        for (int i = 0; i != safeChars.length(); i++) {
+            this.safeChars.add(safeChars.charAt(i));
+        }
+        this.usePlusForSpace = usePlusForSpace;
+    }
+
+    /**
+     * Creates a percent escaper with default settings and encode ' ' as "%20".
+     */
+    PercentEscaper() {
+        this("-._~", false);
+    }
+
+    /**
+     * Escapes a string with the current settings on the escaper.
+     * @param original the origin string to escape
+     * @return the escaped string
+     */
+    public String escape(String original) {
+        StringBuilder output = new StringBuilder();
+        for (int i = 0; i != utf16ToAscii(original).length(); i++) {
+            char c = original.charAt(i);
+            if (c == ' ') {
+                output.append(usePlusForSpace ? "+" : HEX[' ']);
+            } else if (c >= 'a' && c <= 'z') {
+                output.append(c);
+            } else if (c >= 'A' && c <= 'Z') {
+                output.append(c);
+            } else if (c >= '0' && c <= '9') {
+                output.append(c);
+            } else if (safeChars.contains(c)) {
+                output.append(c);
+            } else {
+                output.append(HEX[c]);
+            }
+        }
+        return output.toString();
+    }
+
+    private String utf16ToAscii(String input) {
+        byte[] ascii = new byte[input.length()];
+        for (int i = 0; i < input.length(); i++) {
+            ascii[i] = (byte) input.charAt(i);
+        }
+        return new String(ascii);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/RestProxy.java b/common/azure-common/src/main/java/com/azure/common/implementation/RestProxy.java
new file mode 100644
index 0000000000000..7416a3dbc5b47
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/RestProxy.java
@@ -0,0 +1,625 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.azure.common.http.rest.RestException;
+import com.azure.common.ServiceClient;
+import com.azure.common.annotations.ResumeOperation;
+import com.azure.common.credentials.ServiceClientCredentials;
+import com.azure.common.implementation.http.ContentType;
+import com.azure.common.http.ContextData;
+import com.azure.common.http.HttpHeader;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.policy.HttpPipelinePolicy;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.implementation.http.UrlBuilder;
+import com.azure.common.http.policy.CookiePolicy;
+import com.azure.common.http.policy.CredentialsPolicy;
+import com.azure.common.http.policy.RetryPolicy;
+import com.azure.common.http.policy.UserAgentPolicy;
+import com.azure.common.http.rest.RestResponse;
+import com.azure.common.http.rest.RestResponseBase;
+import com.azure.common.implementation.serializer.HttpResponseDecoder;
+import com.azure.common.implementation.serializer.HttpResponseDecoder.HttpDecodedResponse;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import com.azure.common.implementation.util.FluxUtil;
+import com.azure.common.implementation.util.TypeUtil;
+import io.netty.buffer.ByteBuf;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * Type to create a proxy implementation for an interface describing REST API methods.
+ *
+ * RestProxy can create proxy implementations for interfaces with methods that return
+ * deserialized Java objects as well as asynchronous Single objects that resolve to a
+ * deserialized Java object.
+ */
+public class RestProxy implements InvocationHandler {
+    private final HttpPipeline httpPipeline;
+    private final SerializerAdapter serializer;
+    private final SwaggerInterfaceParser interfaceParser;
+    private final HttpResponseDecoder decoder;
+
+    /**
+     * Create a RestProxy.
+     *
+     * @param httpPipeline the HttpPipelinePolicy and HttpClient httpPipeline that will be used to send HTTP
+     *                 requests.
+     * @param serializer the serializer that will be used to convert response bodies to POJOs.
+     * @param interfaceParser the parser that contains information about the interface describing REST API methods
+     *                        that this RestProxy "implements".
+     */
+    public RestProxy(HttpPipeline httpPipeline, SerializerAdapter serializer, SwaggerInterfaceParser interfaceParser) {
+        this.httpPipeline = httpPipeline;
+        this.serializer = serializer;
+        this.interfaceParser = interfaceParser;
+        this.decoder = new HttpResponseDecoder(this.serializer);
+    }
+
+    /**
+     * Get the SwaggerMethodParser for the provided method. The Method must exist on the Swagger
+     * interface that this RestProxy was created to "implement".
+     *
+     * @param method the method to get a SwaggerMethodParser for
+     * @return the SwaggerMethodParser for the provided method
+     */
+    private SwaggerMethodParser methodParser(Method method) {
+        return interfaceParser.methodParser(method);
+    }
+
+    /**
+     * Get the SerializerAdapter used by this RestProxy.
+     *
+     * @return The SerializerAdapter used by this RestProxy
+     */
+    public SerializerAdapter serializer() {
+        return serializer;
+    }
+
+    /**
+     * Send the provided request asynchronously, applying any request policies provided to the HttpClient instance.
+     *
+     * @param request the HTTP request to send
+     * @param contextData the context
+     * @return a {@link Mono} that emits HttpResponse asynchronously
+     */
+    public Mono<HttpResponse> send(HttpRequest request, ContextData contextData) {
+        return httpPipeline.send(httpPipeline.newContext(request, contextData));
+    }
+
+    @Override
+    public Object invoke(Object proxy, final Method method, Object[] args) {
+        try {
+            final SwaggerMethodParser methodParser;
+            final HttpRequest request;
+            if (method.isAnnotationPresent(ResumeOperation.class)) {
+                OperationDescription opDesc = (OperationDescription) args[0];
+                Method resumeMethod = null;
+                Method[] methods = method.getDeclaringClass().getMethods();
+                for (Method origMethod : methods) {
+                    if (origMethod.getName().equals(opDesc.methodName())) {
+                        resumeMethod = origMethod;
+                        break;
+                    }
+                }
+
+                methodParser = methodParser(resumeMethod);
+                request = createHttpRequest(opDesc, methodParser, args);
+                final Type returnType = methodParser.returnType();
+                return handleResumeOperation(request, opDesc, methodParser, returnType);
+
+            } else {
+                methodParser = methodParser(method);
+                request = createHttpRequest(methodParser, args);
+                final Mono<HttpResponse> asyncResponse = send(request, methodParser.contextData(args).addData("caller-method", methodParser.fullyQualifiedMethodName()));
+                //
+                Mono<HttpDecodedResponse> asyncDecodedResponse = this.decoder.decode(asyncResponse, methodParser);
+                //
+                return handleHttpResponse(request, asyncDecodedResponse, methodParser, methodParser.returnType());
+            }
+
+        } catch (Exception e) {
+            throw Exceptions.propagate(e);
+        }
+    }
+
+    /**
+     * Create a HttpRequest for the provided Swagger method using the provided arguments.
+     *
+     * @param methodParser the Swagger method parser to use
+     * @param args the arguments to use to populate the method's annotation values
+     * @return a HttpRequest
+     * @throws IOException thrown if the body contents cannot be serialized
+     */
+    @SuppressWarnings("unchecked")
+    private HttpRequest createHttpRequest(SwaggerMethodParser methodParser, Object[] args) throws IOException {
+        UrlBuilder urlBuilder;
+
+        // Sometimes people pass in a full URL for the value of their PathParam annotated argument.
+        // This definitely happens in paging scenarios. In that case, just use the full URL and
+        // ignore the Host annotation.
+        final String path = methodParser.path(args);
+        final UrlBuilder pathUrlBuilder = UrlBuilder.parse(path);
+        if (pathUrlBuilder.scheme() != null) {
+            urlBuilder = pathUrlBuilder;
+        }
+        else {
+            urlBuilder = new UrlBuilder();
+
+            // We add path to the UrlBuilder first because this is what is
+            // provided to the HTTP Method annotation. Any path substitutions
+            // from other substitution annotations will overwrite this.
+            urlBuilder.withPath(path);
+
+            final String scheme = methodParser.scheme(args);
+            urlBuilder.withScheme(scheme);
+
+            final String host = methodParser.host(args);
+            urlBuilder.withHost(host);
+        }
+
+        for (final EncodedParameter queryParameter : methodParser.encodedQueryParameters(args)) {
+            urlBuilder.setQueryParameter(queryParameter.name(), queryParameter.encodedValue());
+        }
+
+        final URL url = urlBuilder.toURL();
+        final HttpRequest request = configRequest(new HttpRequest(methodParser.httpMethod(), url), methodParser, args);
+
+        // Headers from Swagger method arguments always take precedence over inferred headers from body types
+        for (final HttpHeader header : methodParser.headers(args)) {
+            request.withHeader(header.name(), header.value());
+        }
+
+        return request;
+    }
+
+    /**
+     * Create a HttpRequest for the provided Swagger method using the provided arguments.
+     *
+     * @param methodParser the Swagger method parser to use
+     * @param args the arguments to use to populate the method's annotation values
+     * @return a HttpRequest
+     * @throws IOException thrown if the body contents cannot be serialized
+     */
+    @SuppressWarnings("unchecked")
+    private HttpRequest createHttpRequest(OperationDescription operationDescription, SwaggerMethodParser methodParser, Object[] args) throws IOException {
+        final HttpRequest request = configRequest(new HttpRequest(methodParser.httpMethod(), operationDescription.url()), methodParser, args);
+
+        // Headers from Swagger method arguments always take precedence over inferred headers from body types
+        for (final String headerName : operationDescription.headers().keySet()) {
+            request.withHeader(headerName, operationDescription.headers().get(headerName));
+        }
+
+        return request;
+    }
+
+    private HttpRequest configRequest(HttpRequest request, SwaggerMethodParser methodParser, Object[] args) throws IOException {
+        final Object bodyContentObject = methodParser.body(args);
+        if (bodyContentObject == null) {
+            request.headers().set("Content-Length", "0");
+        } else {
+            String contentType = methodParser.bodyContentType();
+            if (contentType == null || contentType.isEmpty()) {
+                if (bodyContentObject instanceof byte[] || bodyContentObject instanceof String) {
+                    contentType = ContentType.APPLICATION_OCTET_STREAM;
+                }
+                else {
+                    contentType = ContentType.APPLICATION_JSON;
+                }
+            }
+
+            request.headers().set("Content-Type", contentType);
+
+            boolean isJson = false;
+            final String[] contentTypeParts = contentType.split(";");
+            for (String contentTypePart : contentTypeParts) {
+                if (contentTypePart.trim().equalsIgnoreCase(ContentType.APPLICATION_JSON)) {
+                    isJson = true;
+                    break;
+                }
+            }
+
+            if (isJson) {
+                final String bodyContentString = serializer.serialize(bodyContentObject, SerializerEncoding.JSON);
+                request.withBody(bodyContentString);
+            }
+            else if (FluxUtil.isFluxByteBuf(methodParser.bodyJavaType())) {
+                // Content-Length or Transfer-Encoding: chunked must be provided by a user-specified header when a Flowable<byte[]> is given for the body.
+                //noinspection ConstantConditions
+                request.withBody((Flux<ByteBuf>) bodyContentObject);
+            }
+            else if (bodyContentObject instanceof byte[]) {
+                request.withBody((byte[]) bodyContentObject);
+            }
+            else if (bodyContentObject instanceof String) {
+                final String bodyContentString = (String) bodyContentObject;
+                if (!bodyContentString.isEmpty()) {
+                    request.withBody(bodyContentString);
+                }
+            }
+            else {
+                final String bodyContentString = serializer.serialize(bodyContentObject, SerializerEncoding.fromHeaders(request.headers()));
+                request.withBody(bodyContentString);
+            }
+        }
+
+        return request;
+    }
+
+    private Mono<HttpDecodedResponse> ensureExpectedStatus(Mono<HttpDecodedResponse> asyncDecodedResponse, final SwaggerMethodParser methodParser) {
+        return asyncDecodedResponse
+                .flatMap(decodedHttpResponse -> ensureExpectedStatus(decodedHttpResponse, methodParser, null));
+    }
+
+    private static Exception instantiateUnexpectedException(Class<? extends RestException> exceptionType,
+                                                            Class<?> exceptionBodyType,
+                                                            HttpResponse httpResponse,
+                                                            String responseContent,
+                                                            Object responseDecodedContent) {
+        final int responseStatusCode = httpResponse.statusCode();
+        String contentType = httpResponse.headerValue("Content-Type");
+        String bodyRepresentation;
+        if ("application/octet-stream".equalsIgnoreCase(contentType)) {
+            bodyRepresentation = "(" + httpResponse.headerValue("Content-Length") + "-byte body)";
+        } else {
+            bodyRepresentation = responseContent.isEmpty() ? "(empty body)" : "\"" + responseContent + "\"";
+        }
+
+        Exception result;
+        try {
+            final Constructor<? extends RestException> exceptionConstructor = exceptionType.getConstructor(String.class, HttpResponse.class, exceptionBodyType);
+            result = exceptionConstructor.newInstance("Status code " + responseStatusCode + ", " + bodyRepresentation,
+                    httpResponse,
+                    responseDecodedContent);
+        } catch (ReflectiveOperationException e) {
+            String message = "Status code " + responseStatusCode + ", but an instance of "
+                    + exceptionType.getCanonicalName() + " cannot be created."
+                    + " Response body: " + bodyRepresentation;
+            //
+            result = new IOException(message, e);
+        }
+        return result;
+    }
+
+    /**
+     * Create a publisher that (1) emits error if the provided response {@code decodedResponse} has
+     * 'disallowed status code' OR (2) emits provided response if it's status code ia allowed.
+     *
+     * 'disallowed status code' is one of the status code defined in the provided SwaggerMethodParser
+     *  or is in the int[] of additional allowed status codes.
+     *
+     * @param decodedResponse The HttpResponse to check.
+     * @param methodParser The method parser that contains information about the service interface
+     *                     method that initiated the HTTP request.
+     * @param additionalAllowedStatusCodes Additional allowed status codes that are permitted based
+     *                                     on the context of the HTTP request.
+     * @return An async-version of the provided decodedResponse.
+     */
+    public Mono<HttpDecodedResponse> ensureExpectedStatus(final HttpDecodedResponse decodedResponse, final SwaggerMethodParser methodParser, int[] additionalAllowedStatusCodes) {
+        final int responseStatusCode = decodedResponse.sourceResponse().statusCode();
+        final Mono<HttpDecodedResponse> asyncResult;
+        if (!methodParser.isExpectedResponseStatusCode(responseStatusCode, additionalAllowedStatusCodes)) {
+            Mono<String> bodyAsString = decodedResponse.sourceResponse().bodyAsString();
+            //
+            asyncResult = bodyAsString.flatMap((Function<String, Mono<HttpDecodedResponse>>) responseContent -> {
+                // bodyAsString() emits non-empty string, now look for decoded version of same string
+                Mono<Object> decodedErrorBody = decodedResponse.decodedBody();
+                //
+                return decodedErrorBody.flatMap((Function<Object, Mono<HttpDecodedResponse>>) responseDecodedErrorObject -> {
+                    // decodedBody() emits 'responseDecodedErrorObject' the successfully decoded exception body object
+                    Throwable exception = instantiateUnexpectedException(methodParser.exceptionType(),
+                            methodParser.exceptionBodyType(),
+                            decodedResponse.sourceResponse(),
+                            responseContent,
+                            responseDecodedErrorObject);
+                    return Mono.error(exception);
+                    //
+                }).switchIfEmpty(Mono.defer((Supplier<Mono<HttpDecodedResponse>>) () -> {
+                    // decodedBody() emits empty, indicate unable to decode 'responseContent',
+                    // create exception with un-decodable content string and without exception body object.
+                    Throwable exception = instantiateUnexpectedException(methodParser.exceptionType(),
+                            methodParser.exceptionBodyType(),
+                            decodedResponse.sourceResponse(),
+                            responseContent,
+                            null);
+                    return Mono.error(exception);
+                    //
+                }));
+            }).switchIfEmpty(Mono.defer((Supplier<Mono<HttpDecodedResponse>>) () -> {
+                // bodyAsString() emits empty, indicate no body, create exception empty content string no exception body object.
+                Throwable exception = instantiateUnexpectedException(methodParser.exceptionType(),
+                        methodParser.exceptionBodyType(),
+                        decodedResponse.sourceResponse(),
+                        "",
+                        null);
+                return Mono.error(exception);
+                //
+            }));
+        } else {
+            asyncResult = Mono.just(decodedResponse);
+        }
+        return asyncResult;
+    }
+
+    private Mono<?> handleRestResponseReturnType(HttpDecodedResponse response, SwaggerMethodParser methodParser, Type entityType) {
+        Mono<?> asyncResult;
+
+        if (TypeUtil.isTypeOrSubTypeOf(entityType, RestResponse.class)) {
+            Type bodyType = TypeUtil.getRestResponseBodyType(entityType);
+
+            if (TypeUtil.isTypeOrSubTypeOf(bodyType, Void.class)) {
+                asyncResult = response.sourceResponse().body().ignoreElements()
+                        .then(Mono.just(createResponse(response, entityType, null)));
+            } else {
+                asyncResult = handleBodyReturnType(response, methodParser, bodyType)
+                        .map((Function<Object, RestResponse<?>>) bodyAsObject -> createResponse(response, entityType, bodyAsObject))
+                        .switchIfEmpty(Mono.defer((Supplier<Mono<RestResponse<?>>>) () -> Mono.just(createResponse(response, entityType, null))));
+            }
+        } else {
+            // For now we're just throwing if the Maybe didn't emit a value.
+            asyncResult = handleBodyReturnType(response, methodParser, entityType);
+        }
+
+        return asyncResult;
+    }
+
+    private RestResponse<?> createResponse(HttpDecodedResponse response, Type entityType, Object bodyAsObject) {
+        final HttpResponse httpResponse = response.sourceResponse();
+        final HttpRequest httpRequest = httpResponse.request();
+        final int responseStatusCode = httpResponse.statusCode();
+        final HttpHeaders responseHeaders = httpResponse.headers();
+
+        // determine the type of response class. If the type is the 'RestResponse' interface, we will use the
+        // 'RestResponseBase' class instead.
+        Class<? extends RestResponse<?>> cls = (Class<? extends RestResponse<?>>) TypeUtil.getRawClass(entityType);
+        if (cls.equals(RestResponse.class)) {
+            cls = (Class<? extends RestResponse<?>>) (Object) RestResponseBase.class;
+        }
+
+        // we try to find the most specific constructor, which we do in the following order:
+        // 1) (HttpRequest httpRequest, int statusCode, HttpHeaders headers, Object body, Object deserializedHeaders)
+        // 2) (HttpRequest httpRequest, int statusCode, HttpHeaders headers, Object body)
+        // 3) (HttpRequest httpRequest, int statusCode, HttpHeaders headers)
+        List<Constructor<?>> ctors = Arrays.stream(cls.getDeclaredConstructors())
+                                             .filter(ctor -> {
+                                                 int paramCount = ctor.getParameterCount();
+                                                 return paramCount >= 3 && paramCount <= 5;
+                                             })
+                                             .sorted(Comparator.comparingInt(Constructor::getParameterCount))
+                                             .collect(Collectors.toList());
+
+        if (ctors.isEmpty()) {
+            throw new RuntimeException("Cannot find suitable constructor for class " + cls);
+        }
+
+        // try to create an instance using our list of potential candidates
+        for (int i = 0; i < ctors.size(); i++) {
+            final Constructor<? extends RestResponse<?>> ctor = (Constructor<? extends RestResponse<?>>) ctors.get(i);
+
+            try {
+                final int paramCount = ctor.getParameterCount();
+
+                switch (paramCount) {
+                    case 3:
+                        return ctor.newInstance(httpRequest, responseStatusCode, responseHeaders);
+                    case 4:
+                        return ctor.newInstance(httpRequest, responseStatusCode, responseHeaders, bodyAsObject);
+                    case 5:
+                        return ctor.newInstance(httpRequest, responseStatusCode, responseHeaders, bodyAsObject, response.decodedHeaders().block());
+                    default:
+                        throw new IllegalStateException("Response constructor with expected parameters not found.");
+                }
+            } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+                throw reactor.core.Exceptions.propagate(e);
+            }
+        }
+        // error
+        throw new RuntimeException("Cannot find suitable constructor for class " + cls);
+    }
+
+    protected final Mono<?> handleBodyReturnType(final HttpDecodedResponse response, final SwaggerMethodParser methodParser, final Type entityType) {
+        final int responseStatusCode = response.sourceResponse().statusCode();
+        final HttpMethod httpMethod = methodParser.httpMethod();
+        final Type returnValueWireType = methodParser.returnValueWireType();
+
+        final Mono<?> asyncResult;
+        if (httpMethod == HttpMethod.HEAD
+                && (TypeUtil.isTypeOrSubTypeOf(entityType, Boolean.TYPE) || TypeUtil.isTypeOrSubTypeOf(entityType, Boolean.class))) {
+            boolean isSuccess = (responseStatusCode / 100) == 2;
+            asyncResult = Mono.just(isSuccess);
+        } else if (TypeUtil.isTypeOrSubTypeOf(entityType, byte[].class)) {
+            // Mono<byte[]>
+            Mono<byte[]> responseBodyBytesAsync = response.sourceResponse().bodyAsByteArray();
+            if (returnValueWireType == Base64Url.class) {
+                // Mono<Base64Url>
+                responseBodyBytesAsync = responseBodyBytesAsync.map(base64UrlBytes -> new Base64Url(base64UrlBytes).decodedBytes());
+            }
+            asyncResult = responseBodyBytesAsync;
+        } else if (FluxUtil.isFluxByteBuf(entityType)) {
+            // Mono<Flux<ByteBuf>>
+            asyncResult = Mono.just(response.sourceResponse().body());
+        } else {
+            // Mono<Object>
+            asyncResult = response.decodedBody();
+        }
+        return asyncResult;
+    }
+
+    protected Object handleHttpResponse(final HttpRequest httpRequest, Mono<HttpDecodedResponse> asyncDecodedHttpResponse, SwaggerMethodParser methodParser, Type returnType) {
+        return handleRestReturnType(asyncDecodedHttpResponse, methodParser, returnType);
+    }
+
+    protected Object handleResumeOperation(HttpRequest httpRequest, OperationDescription operationDescription, SwaggerMethodParser methodParser, Type returnType)
+        throws Exception {
+        throw new Exception("The resume operation is not available in the base RestProxy class.");
+    }
+
+    /**
+     * Handle the provided asynchronous HTTP response and return the deserialized value.
+     *
+     * @param asyncHttpDecodedResponse the asynchronous HTTP response to the original HTTP request
+     * @param methodParser the SwaggerMethodParser that the request originates from
+     * @param returnType the type of value that will be returned
+     * @return the deserialized result
+     */
+    public final Object handleRestReturnType(Mono<HttpDecodedResponse> asyncHttpDecodedResponse, final SwaggerMethodParser methodParser, final Type returnType) {
+        final Mono<HttpDecodedResponse> asyncExpectedResponse = ensureExpectedStatus(asyncHttpDecodedResponse, methodParser);
+        final Object result;
+        if (TypeUtil.isTypeOrSubTypeOf(returnType, Mono.class)) {
+            final Type monoTypeParam = TypeUtil.getTypeArgument(returnType);
+            if (TypeUtil.isTypeOrSubTypeOf(monoTypeParam, Void.class)) {
+                // ProxyMethod ReturnType: Mono<Void>
+                result = asyncExpectedResponse.then();
+            } else {
+                // ProxyMethod ReturnType: Mono<? extends RestResponseBase<?, ?>>
+                result = asyncExpectedResponse.flatMap(response ->
+                        handleRestResponseReturnType(response, methodParser, monoTypeParam));
+            }
+        } else if (FluxUtil.isFluxByteBuf(returnType)) {
+            // ProxyMethod ReturnType: Flux<ByteBuf>
+            result = asyncExpectedResponse.flatMapMany(ar -> ar.sourceResponse().body());
+        } else if (TypeUtil.isTypeOrSubTypeOf(returnType, void.class) || TypeUtil.isTypeOrSubTypeOf(returnType, Void.class)) {
+            // ProxyMethod ReturnType: Void
+            asyncExpectedResponse.block();
+            result = null;
+        } else {
+            // ProxyMethod ReturnType: T where T != async (Mono, Flux) or sync Void
+            // Block the deserialization until a value T is received
+            result = asyncExpectedResponse
+                    .flatMap(httpResponse -> handleRestResponseReturnType(httpResponse, methodParser, returnType))
+                    .block();
+        }
+        return result;
+    }
+
+    /**
+     * Create an instance of the default serializer.
+     *
+     * @return the default serializer
+     */
+    public static SerializerAdapter createDefaultSerializer() {
+        return new JacksonAdapter();
+    }
+
+    /**
+     * Create the default HttpPipeline.
+     *
+     * @return the default HttpPipeline
+     */
+    public static HttpPipeline createDefaultPipeline() {
+        return createDefaultPipeline((HttpPipelinePolicy) null);
+    }
+
+    /**
+     * Create the default HttpPipeline.
+     *
+     * @param credentials the credentials to use to apply authentication to the pipeline
+     * @return the default HttpPipeline
+     */
+    public static HttpPipeline createDefaultPipeline(ServiceClientCredentials credentials) {
+        return createDefaultPipeline(new CredentialsPolicy(credentials));
+    }
+
+    /**
+     * Create the default HttpPipeline.
+     * @param credentialsPolicy the credentials policy factory to use to apply authentication to the
+     *                          pipeline
+     * @return the default HttpPipeline
+     */
+    public static HttpPipeline createDefaultPipeline(HttpPipelinePolicy credentialsPolicy) {
+        List<HttpPipelinePolicy> policies = new ArrayList<>();
+        policies.add(new UserAgentPolicy());
+        policies.add(new RetryPolicy());
+        policies.add(new CookiePolicy());
+        if (credentialsPolicy != null) {
+            policies.add(credentialsPolicy);
+        }
+        return new HttpPipeline(policies.toArray(new HttpPipelinePolicy[policies.size()]));
+    }
+
+    /**
+     * Create a proxy implementation of the provided Swagger interface.
+     *
+     * @param swaggerInterface the Swagger interface to provide a proxy implementation for
+     * @param <A> the type of the Swagger interface
+     * @return a proxy implementation of the provided Swagger interface
+     */
+    @SuppressWarnings("unchecked")
+    public static <A> A create(Class<A> swaggerInterface) {
+        return create(swaggerInterface, createDefaultPipeline(), createDefaultSerializer());
+    }
+
+    /**
+     * Create a proxy implementation of the provided Swagger interface.
+     *
+     * @param swaggerInterface the Swagger interface to provide a proxy implementation for
+     *
+     * @param httpPipeline the HttpPipelinePolicy and HttpClient pipline that will be used to send Http
+     *                 requests
+     * @param <A> the type of the Swagger interface
+     * @return a proxy implementation of the provided Swagger interface
+     */
+    @SuppressWarnings("unchecked")
+    public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline) {
+        return create(swaggerInterface, httpPipeline, createDefaultSerializer());
+    }
+
+    /**
+     * Create a proxy implementation of the provided Swagger interface.
+     *
+     * @param swaggerInterface the Swagger interface to provide a proxy implementation for
+     * @param serviceClient the ServiceClient that contains the details to use to create the
+     *                      RestProxy implementation of the swagger interface
+     * @param <A> the type of the Swagger interface
+     * @return a proxy implementation of the provided Swagger interface
+     */
+    @SuppressWarnings("unchecked")
+    public static <A> A create(Class<A> swaggerInterface, ServiceClient serviceClient) {
+        return create(swaggerInterface, serviceClient.httpPipeline(), serviceClient.serializerAdapter());
+    }
+
+    /**
+     * Create a proxy implementation of the provided Swagger interface.
+     *
+     * @param swaggerInterface the Swagger interface to provide a proxy implementation for
+     * @param httpPipeline the HttpPipelinePolicy and HttpClient pipline that will be used to send Http
+     *                 requests
+     * @param serializer the serializer that will be used to convert POJOs to and from request and
+     *                   response bodies
+     * @param <A> the type of the Swagger interface.
+     * @return a proxy implementation of the provided Swagger interface
+     */
+    @SuppressWarnings("unchecked")
+    public static <A> A create(Class<A> swaggerInterface, HttpPipeline httpPipeline, SerializerAdapter serializer) {
+        final SwaggerInterfaceParser interfaceParser = new SwaggerInterfaceParser(swaggerInterface, serializer);
+        final RestProxy restProxy = new RestProxy(httpPipeline, serializer, interfaceParser);
+        return (A) Proxy.newProxyInstance(swaggerInterface.getClassLoader(), new Class[]{swaggerInterface}, restProxy);
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/Substitution.java b/common/azure-common/src/main/java/com/azure/common/implementation/Substitution.java
new file mode 100644
index 0000000000000..11ad72f490f44
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/Substitution.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+/**
+ * A Substitution is a value that can be used to replace placeholder values in a URL. Placeholders
+ * look like: "http://{host}.com/{fileName}.html", where "{host}" and "{fileName}" are the
+ * placeholders.
+ */
+class Substitution {
+    private final String urlParameterName;
+    private final int methodParameterIndex;
+    private final boolean shouldEncode;
+
+    /**
+     * Create a new Substitution.
+     * @param urlParameterName The name that is used between curly quotes as a placeholder in the
+     *                         target URL.
+     * @param methodParameterIndex The index of the parameter in the original interface method where
+     *                             the value for the placeholder is.
+     * @param shouldEncode Whether or not the value from the method's argument should be encoded
+     *                     when the substitution is taking place.
+     */
+    Substitution(String urlParameterName, int methodParameterIndex, boolean shouldEncode) {
+        this.urlParameterName = urlParameterName;
+        this.methodParameterIndex = methodParameterIndex;
+        this.shouldEncode = shouldEncode;
+    }
+
+    /**
+     * Get the placeholder's name.
+     * @return The name of the placeholder.
+     */
+    public String urlParameterName() {
+        return urlParameterName;
+    }
+
+    /**
+     * Get the index of the method parameter where the replacement value is.
+     * @return The index of the method parameter where the replacement value is.
+     */
+    public int methodParameterIndex() {
+        return methodParameterIndex;
+    }
+
+    /**
+     * Get whether or not the replacement value from the method argument needs to be encoded when the
+     * substitution is taking place.
+     * @return Whether or not the replacement value from the method argument needs to be encoded
+     * when the substitution is taking place.
+     */
+    public boolean shouldEncode() {
+        return shouldEncode;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/SwaggerInterfaceParser.java b/common/azure-common/src/main/java/com/azure/common/implementation/SwaggerInterfaceParser.java
new file mode 100644
index 0000000000000..443c2a3c7e598
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/SwaggerInterfaceParser.java
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.azure.common.implementation.exception.MissingRequiredAnnotationException;
+import com.azure.common.annotations.Host;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The type responsible for creating individual Swagger interface method parsers from a Swagger
+ * interface.
+ */
+public class SwaggerInterfaceParser {
+    private final SerializerAdapter serializer;
+    private final String host;
+    private final Map<Method, SwaggerMethodParser> methodParsers = new HashMap<>();
+
+    /**
+     * Create a SwaggerInterfaceParser object with the provided fully qualified interface
+     * name.
+     * @param swaggerInterface The interface that will be parsed.
+     * @param serializer The serializer that will be used to serialize non-String header values and query values.
+     */
+    public SwaggerInterfaceParser(Class<?> swaggerInterface, SerializerAdapter serializer) {
+        this(swaggerInterface, serializer, null);
+    }
+
+    /**
+     * Create a SwaggerInterfaceParser object with the provided fully qualified interface
+     * name.
+     * @param swaggerInterface The interface that will be parsed.
+     * @param serializer The serializer that will be used to serialize non-String header values and query values.
+     * @param host The host of URLs that this Swagger interface targets.
+     */
+    public SwaggerInterfaceParser(Class<?> swaggerInterface, SerializerAdapter serializer, String host) {
+        this.serializer = serializer;
+
+        if (host != null && !host.isEmpty()) {
+            this.host = host;
+        }
+        else {
+            final Host hostAnnotation = swaggerInterface.getAnnotation(Host.class);
+            if (hostAnnotation != null && !hostAnnotation.value().isEmpty()) {
+                this.host = hostAnnotation.value();
+            }
+            else {
+                throw new MissingRequiredAnnotationException(Host.class, swaggerInterface);
+            }
+        }
+    }
+
+    /**
+     * Get the method parser that is associated with the provided swaggerMethod. The method parser
+     * can be used to get details about the Swagger REST API call.
+     *
+     * @param swaggerMethod the method to generate a parser for
+     * @return the SwaggerMethodParser associated with the provided swaggerMethod
+     */
+    public SwaggerMethodParser methodParser(Method swaggerMethod) {
+        SwaggerMethodParser result = methodParsers.get(swaggerMethod);
+        if (result == null) {
+            result = new SwaggerMethodParser(swaggerMethod, serializer, host());
+            methodParsers.put(swaggerMethod, result);
+        }
+        return result;
+    }
+
+    /**
+     * Get the desired host that the provided Swagger interface will target with its REST API
+     * calls. This value is retrieved from the @Host annotation placed on the Swagger interface.
+     * @return The value of the @Host annotation.
+     */
+    String host() {
+        return host;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/SwaggerMethodParser.java b/common/azure-common/src/main/java/com/azure/common/implementation/SwaggerMethodParser.java
new file mode 100644
index 0000000000000..98a57d4a49b6b
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/SwaggerMethodParser.java
@@ -0,0 +1,537 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.azure.common.implementation.exception.MissingRequiredAnnotationException;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.annotations.BodyParam;
+import com.azure.common.annotations.DELETE;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.HEAD;
+import com.azure.common.annotations.HeaderParam;
+import com.azure.common.annotations.Headers;
+import com.azure.common.annotations.HostParam;
+import com.azure.common.annotations.PATCH;
+import com.azure.common.annotations.POST;
+import com.azure.common.annotations.PUT;
+import com.azure.common.annotations.PathParam;
+import com.azure.common.annotations.QueryParam;
+import com.azure.common.annotations.ReturnValueWireType;
+import com.azure.common.annotations.UnexpectedResponseExceptionType;
+import com.azure.common.http.ContextData;
+import com.azure.common.http.HttpHeader;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.rest.RestResponse;
+import com.azure.common.implementation.serializer.HttpResponseDecodeData;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.util.TypeUtil;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The type to parse details of a specific Swagger REST API call from a provided Swagger interface
+ * method.
+ */
+public class SwaggerMethodParser implements HttpResponseDecodeData {
+    private final SerializerAdapter serializer;
+    private final String rawHost;
+    private final String fullyQualifiedMethodName;
+    private HttpMethod httpMethod;
+    private String relativePath;
+    private final List<Substitution> hostSubstitutions = new ArrayList<>();
+    private final List<Substitution> pathSubstitutions = new ArrayList<>();
+    private final List<Substitution> querySubstitutions = new ArrayList<>();
+    private final List<Substitution> headerSubstitutions = new ArrayList<>();
+    private final HttpHeaders headers = new HttpHeaders();
+    private Integer bodyContentMethodParameterIndex;
+    private String bodyContentType;
+    private Type bodyJavaType;
+    private int[] expectedStatusCodes;
+    private Type returnType;
+    private Type returnValueWireType;
+    private Class<? extends RestException> exceptionType;
+    private Class<?> exceptionBodyType;
+
+    /**
+     * Create a SwaggerMethodParser object using the provided fully qualified method name.
+     *
+     * @param swaggerMethod the Swagger method to parse.
+     * @param rawHost the raw host value from the @Host annotation. Before this can be used as the
+     *                host value in an HTTP request, it must be processed through the possible host
+     *                substitutions.
+     */
+    SwaggerMethodParser(Method swaggerMethod, SerializerAdapter serializer, String rawHost) {
+        this.serializer = serializer;
+        this.rawHost = rawHost;
+
+        final Class<?> swaggerInterface = swaggerMethod.getDeclaringClass();
+
+        fullyQualifiedMethodName = swaggerInterface.getName() + "." + swaggerMethod.getName();
+
+        if (swaggerMethod.isAnnotationPresent(GET.class)) {
+            setHttpMethodAndRelativePath(HttpMethod.GET, swaggerMethod.getAnnotation(GET.class).value());
+        }
+        else if (swaggerMethod.isAnnotationPresent(PUT.class)) {
+            setHttpMethodAndRelativePath(HttpMethod.PUT, swaggerMethod.getAnnotation(PUT.class).value());
+        }
+        else if (swaggerMethod.isAnnotationPresent(HEAD.class)) {
+            setHttpMethodAndRelativePath(HttpMethod.HEAD, swaggerMethod.getAnnotation(HEAD.class).value());
+        }
+        else if (swaggerMethod.isAnnotationPresent(DELETE.class)) {
+            setHttpMethodAndRelativePath(HttpMethod.DELETE, swaggerMethod.getAnnotation(DELETE.class).value());
+        }
+        else if (swaggerMethod.isAnnotationPresent(POST.class)) {
+            setHttpMethodAndRelativePath(HttpMethod.POST, swaggerMethod.getAnnotation(POST.class).value());
+        }
+        else if (swaggerMethod.isAnnotationPresent(PATCH.class)) {
+            setHttpMethodAndRelativePath(HttpMethod.PATCH, swaggerMethod.getAnnotation(PATCH.class).value());
+        }
+        else {
+            final ArrayList<Class<? extends Annotation>> requiredAnnotationOptions = new ArrayList<>();
+            requiredAnnotationOptions.add(GET.class);
+            requiredAnnotationOptions.add(PUT.class);
+            requiredAnnotationOptions.add(HEAD.class);
+            requiredAnnotationOptions.add(DELETE.class);
+            requiredAnnotationOptions.add(POST.class);
+            requiredAnnotationOptions.add(PATCH.class);
+            throw new MissingRequiredAnnotationException(requiredAnnotationOptions, swaggerMethod);
+        }
+
+        returnType = swaggerMethod.getGenericReturnType();
+
+        final ReturnValueWireType returnValueWireTypeAnnotation = swaggerMethod.getAnnotation(ReturnValueWireType.class);
+        if (returnValueWireTypeAnnotation != null) {
+            Class<?> returnValueWireType = returnValueWireTypeAnnotation.value();
+            if (returnValueWireType == Base64Url.class || returnValueWireType == UnixTime.class || returnValueWireType == DateTimeRfc1123.class) {
+                this.returnValueWireType = returnValueWireType;
+            }
+            else {
+                if (TypeUtil.isTypeOrSubTypeOf(returnValueWireType, List.class)) {
+                    this.returnValueWireType = returnValueWireType.getGenericInterfaces()[0];
+                }
+            }
+        }
+
+        if (swaggerMethod.isAnnotationPresent(Headers.class)) {
+            final Headers headersAnnotation = swaggerMethod.getAnnotation(Headers.class);
+            final String[] headers = headersAnnotation.value();
+            for (final String header : headers) {
+                final int colonIndex = header.indexOf(":");
+                if (colonIndex >= 0) {
+                    final String headerName = header.substring(0, colonIndex).trim();
+                    if (!headerName.isEmpty()) {
+                        final String headerValue = header.substring(colonIndex + 1).trim();
+                        if (!headerValue.isEmpty()) {
+                            this.headers.set(headerName, headerValue);
+                        }
+                    }
+                }
+            }
+        }
+
+        final ExpectedResponses expectedResponses = swaggerMethod.getAnnotation(ExpectedResponses.class);
+        if (expectedResponses != null) {
+            expectedStatusCodes = expectedResponses.value();
+        }
+
+        final UnexpectedResponseExceptionType unexpectedResponseExceptionType = swaggerMethod.getAnnotation(UnexpectedResponseExceptionType.class);
+        if (unexpectedResponseExceptionType == null) {
+            exceptionType = RestException.class;
+        }
+        else {
+            exceptionType = unexpectedResponseExceptionType.value();
+        }
+
+        try {
+            final Method exceptionBodyMethod = exceptionType.getDeclaredMethod("body");
+            exceptionBodyType = exceptionBodyMethod.getReturnType();
+        } catch (NoSuchMethodException e) {
+            // Should always have a body() method. Register Object as a fallback plan.
+            exceptionBodyType = Object.class;
+        }
+
+        final Annotation[][] allParametersAnnotations = swaggerMethod.getParameterAnnotations();
+        for (int parameterIndex = 0; parameterIndex < allParametersAnnotations.length; ++parameterIndex) {
+            final Annotation[] parameterAnnotations = swaggerMethod.getParameterAnnotations()[parameterIndex];
+            for (final Annotation annotation : parameterAnnotations) {
+                final Class<? extends Annotation> annotationType = annotation.annotationType();
+                if (annotationType.equals(HostParam.class)) {
+                    final HostParam hostParamAnnotation = (HostParam) annotation;
+                    hostSubstitutions.add(new Substitution(hostParamAnnotation.value(), parameterIndex, !hostParamAnnotation.encoded()));
+                }
+                else if (annotationType.equals(PathParam.class)) {
+                    final PathParam pathParamAnnotation = (PathParam) annotation;
+                    pathSubstitutions.add(new Substitution(pathParamAnnotation.value(), parameterIndex, !pathParamAnnotation.encoded()));
+                }
+                else if (annotationType.equals(QueryParam.class)) {
+                    final QueryParam queryParamAnnotation = (QueryParam) annotation;
+                    querySubstitutions.add(new Substitution(queryParamAnnotation.value(), parameterIndex, !queryParamAnnotation.encoded()));
+                }
+                else if (annotationType.equals(HeaderParam.class)) {
+                    final HeaderParam headerParamAnnotation = (HeaderParam) annotation;
+                    headerSubstitutions.add(new Substitution(headerParamAnnotation.value(), parameterIndex, false));
+                }
+                else if (annotationType.equals(BodyParam.class)) {
+                    final BodyParam bodyParamAnnotation = (BodyParam) annotation;
+                    bodyContentMethodParameterIndex = parameterIndex;
+                    bodyContentType = bodyParamAnnotation.value();
+                    bodyJavaType = swaggerMethod.getGenericParameterTypes()[parameterIndex];
+                }
+            }
+        }
+    }
+
+    /**
+     * Get the fully qualified method that was called to invoke this HTTP request.
+     *
+     * @return the fully qualified method that was called to invoke this HTTP request
+     */
+    public String fullyQualifiedMethodName() {
+        return fullyQualifiedMethodName;
+    }
+
+    /**
+     * Get the HTTP method that will be used to complete the Swagger method's request.
+     *
+     * @return the HTTP method that will be used to complete the Swagger method's request
+     */
+    public HttpMethod httpMethod() {
+        return httpMethod;
+    }
+
+    /**
+     * Get the HTTP response status codes that are expected when a request is sent out for this
+     * Swagger method. If the returned int[] is null, then all status codes less than 400 are
+     * allowed.
+     *
+     * @return the expected HTTP response status codes for this Swagger method or null if all status
+     * codes less than 400 are allowed.
+     */
+    @Override
+    public int[] expectedStatusCodes() {
+        return expectedStatusCodes;
+    }
+
+    /**
+     * Get the scheme to use for HTTP requests for this Swagger method.
+     *
+     * @param swaggerMethodArguments the arguments to use for scheme/host substitutions.
+     * @return the final host to use for HTTP requests for this Swagger method.
+     */
+    public String scheme(Object[] swaggerMethodArguments) {
+        final String substitutedHost = applySubstitutions(rawHost, hostSubstitutions, swaggerMethodArguments, UrlEscapers.PATH_ESCAPER);
+        final String[] substitutedHostParts = substitutedHost.split("://");
+        return substitutedHostParts.length < 1 ? null : substitutedHostParts[0];
+    }
+
+    /**
+     * Get the host to use for HTTP requests for this Swagger method.
+     *
+     * @param swaggerMethodArguments the arguments to use for host substitutions
+     * @return the final host to use for HTTP requests for this Swagger method
+     */
+    public String host(Object[] swaggerMethodArguments) {
+        final String substitutedHost = applySubstitutions(rawHost, hostSubstitutions, swaggerMethodArguments, UrlEscapers.PATH_ESCAPER);
+        final String[] substitutedHostParts = substitutedHost.split("://");
+        return substitutedHostParts.length < 2 ? substitutedHost : substitutedHost.split("://")[1];
+    }
+
+    /**
+     * Get the path that will be used to complete the Swagger method's request.
+     *
+     * @param methodArguments the method arguments to use with the path substitutions
+     * @return the path value with its placeholders replaced by the matching substitutions
+     */
+    public String path(Object[] methodArguments) {
+        return applySubstitutions(relativePath, pathSubstitutions, methodArguments, UrlEscapers.PATH_ESCAPER);
+    }
+
+    /**
+     * Get the encoded query parameters that have been added to this value based on the provided
+     * method arguments.
+     *
+     * @param swaggerMethodArguments the arguments that will be used to create the query parameters'
+     *                               values
+     * @return an Iterable with the encoded query parameters
+     */
+    public Iterable<EncodedParameter> encodedQueryParameters(Object[] swaggerMethodArguments) {
+        final List<EncodedParameter> result = new ArrayList<>();
+        if (querySubstitutions != null) {
+            final PercentEscaper escaper = UrlEscapers.QUERY_ESCAPER;
+
+            for (Substitution querySubstitution : querySubstitutions) {
+                final int parameterIndex = querySubstitution.methodParameterIndex();
+                if (0 <= parameterIndex && parameterIndex < swaggerMethodArguments.length) {
+                    final Object methodArgument = swaggerMethodArguments[querySubstitution.methodParameterIndex()];
+                    String parameterValue = serialize(methodArgument);
+                    if (parameterValue != null) {
+                        if (querySubstitution.shouldEncode() && escaper != null) {
+                            parameterValue = escaper.escape(parameterValue);
+                        }
+
+                        result.add(new EncodedParameter(querySubstitution.urlParameterName(), parameterValue));
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Get the headers that have been added to this value based on the provided method arguments.
+     * @param swaggerMethodArguments The arguments that will be used to create the headers' values.
+     * @return An Iterable with the headers.
+     */
+    public Iterable<HttpHeader> headers(Object[] swaggerMethodArguments) {
+        final HttpHeaders result = new HttpHeaders(headers);
+
+        if (headerSubstitutions != null) {
+            for (Substitution headerSubstitution : headerSubstitutions) {
+                final int parameterIndex = headerSubstitution.methodParameterIndex();
+                if (0 <= parameterIndex && parameterIndex < swaggerMethodArguments.length) {
+                    final Object methodArgument = swaggerMethodArguments[headerSubstitution.methodParameterIndex()];
+                    if (methodArgument instanceof Map) {
+                        final Map<String, ?> headerCollection = (Map<String, ?>) methodArgument;
+                        final String headerCollectionPrefix = headerSubstitution.urlParameterName();
+                        for (final Map.Entry<String, ?> headerCollectionEntry : headerCollection.entrySet()) {
+                            final String headerName = headerCollectionPrefix + headerCollectionEntry.getKey();
+                            final String headerValue = serialize(headerCollectionEntry.getValue());
+                            result.set(headerName, headerValue);
+                        }
+                    } else {
+                        final String headerName = headerSubstitution.urlParameterName();
+                        final String headerValue = serialize(methodArgument);
+                        result.set(headerName, headerValue);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Get the {@link ContextData} passed into the proxy method.
+     *
+     * @param swaggerMethodArguments the arguments passed to the proxy method
+     * @return the context, or null if no context was provided
+     */
+    public ContextData contextData(Object[] swaggerMethodArguments) {
+        Object firstArg = swaggerMethodArguments != null && swaggerMethodArguments.length > 0 ? swaggerMethodArguments[0] : null;
+        if (firstArg instanceof ContextData) {
+            return (ContextData) firstArg;
+        } else {
+            return ContextData.NONE;
+        }
+    }
+
+    /**
+     * Get whether or not the provided response status code is one of the expected status codes for
+     * this Swagger method.
+     *
+     * @param responseStatusCode the status code that was returned in the HTTP response
+     * @param additionalAllowedStatusCodes an additional set of allowed status codes that will be
+     *                                     merged with the existing set of allowed status codes for
+     *                                     this query
+     * @return whether or not the provided response status code is one of the expected status codes
+     * for this Swagger method
+     */
+    public boolean isExpectedResponseStatusCode(int responseStatusCode, int[] additionalAllowedStatusCodes) {
+        boolean result;
+
+        if (expectedStatusCodes == null) {
+            result = (responseStatusCode < 400);
+        }
+        else {
+            result = contains(expectedStatusCodes, responseStatusCode)
+                    || contains(additionalAllowedStatusCodes, responseStatusCode);
+        }
+
+        return result;
+    }
+
+    private static boolean contains(int[] values, int searchValue) {
+        boolean result = false;
+
+        if (values != null && values.length > 0) {
+            for (int value : values) {
+                if (searchValue == value) {
+                    result = true;
+                    break;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the type of RestException that will be thrown if the HTTP response's status code is not
+     * one of the expected status codes.
+     *
+     * @return the type of RestException that will be thrown if the HTTP response's status code is
+     * not one of the expected status codes
+     */
+    public Class<? extends RestException> exceptionType() {
+        return exceptionType;
+    }
+
+    /**
+     * Get the type of body Object that a thrown RestException will contain if the HTTP response's
+     * status code is not one of the expected status codes.
+     *
+     * @return the type of body Object that a thrown RestException will contain if the HTTP
+     * response's status code is not one of the expected status codes
+     */
+    @Override
+    public Class<?> exceptionBodyType() {
+        return exceptionBodyType;
+    }
+
+    /**
+     * Get the object to be used as the body of the HTTP request.
+     *
+     * @param swaggerMethodArguments the method arguments to get the body object from
+     * @return the object that will be used as the body of the HTTP request
+     */
+    public Object body(Object[] swaggerMethodArguments) {
+        Object result = null;
+
+        if (bodyContentMethodParameterIndex != null
+                && swaggerMethodArguments != null
+                && 0 <= bodyContentMethodParameterIndex
+                && bodyContentMethodParameterIndex < swaggerMethodArguments.length) {
+            result = swaggerMethodArguments[bodyContentMethodParameterIndex];
+        }
+
+        return result;
+    }
+
+    /**
+     * Get the Content-Type of the body of this Swagger method.
+     *
+     * @return the Content-Type of the body of this Swagger method
+     */
+    public String bodyContentType() {
+        return bodyContentType;
+    }
+
+    /**
+     * Get the return type for the method that this object describes.
+     *
+     * @return the return type for the method that this object describes.
+     */
+    @Override
+    public Type returnType() {
+        return returnType;
+    }
+
+
+    /**
+     * Get the type of the body parameter to this method, if present.
+     *
+     * @return the return type of the body parameter to this method
+     */
+    public Type bodyJavaType() {
+        return bodyJavaType;
+    }
+
+    /**
+     * Get the type that the return value will be send across the network as. If returnValueWireType
+     * is not null, then the raw HTTP response body will need to parsed to this type and then
+     * converted to the actual returnType.
+     *
+     * @return the type that the raw HTTP response body will be sent as
+     */
+    @Override
+    public Type returnValueWireType() {
+        return returnValueWireType;
+    }
+
+    /**
+     * Checks whether or not the Swagger method expects the response to contain a body.
+     *
+     * @return true if Swagger method expects the response to contain a body, false otherwise
+     */
+    public boolean expectsResponseBody() {
+        boolean result = true;
+
+        if (TypeUtil.isTypeOrSubTypeOf(returnType, Void.class)) {
+            result = false;
+        }
+        else if (TypeUtil.isTypeOrSubTypeOf(returnType, Mono.class) || TypeUtil.isTypeOrSubTypeOf(returnType, Flux.class)) {
+            final ParameterizedType asyncReturnType = (ParameterizedType) returnType;
+            final Type syncReturnType = asyncReturnType.getActualTypeArguments()[0];
+            if (TypeUtil.isTypeOrSubTypeOf(syncReturnType, Void.class)) {
+                result = false;
+            } else if (TypeUtil.isTypeOrSubTypeOf(syncReturnType, RestResponse.class)) {
+                result = TypeUtil.restResponseTypeExpectsBody((ParameterizedType) TypeUtil.getSuperType(syncReturnType, RestResponse.class));
+            }
+        } else if (TypeUtil.isTypeOrSubTypeOf(returnType, RestResponse.class)) {
+            result = TypeUtil.restResponseTypeExpectsBody((ParameterizedType) returnType);
+        }
+
+        return result;
+    }
+
+    /**
+     * Set both the HTTP method and the path that will be used to complete the Swagger method's
+     * request.
+     *
+     * @param httpMethod the HTTP method that will be used to complete the Swagger method's request
+     * @param relativePath the path in the URL that will be used to complete the Swagger method's
+     *                     request
+     */
+    private void setHttpMethodAndRelativePath(HttpMethod httpMethod, String relativePath) {
+        this.httpMethod = httpMethod;
+        this.relativePath = relativePath;
+    }
+
+    String serialize(Object value) {
+        String result = null;
+        if (value != null) {
+            if (value instanceof String) {
+                result = (String) value;
+            }
+            else {
+                result = serializer.serializeRaw(value);
+            }
+        }
+        return result;
+    }
+
+    private String applySubstitutions(String originalValue, Iterable<Substitution> substitutions, Object[] methodArguments, PercentEscaper escaper) {
+        String result = originalValue;
+
+        if (methodArguments != null) {
+            for (Substitution substitution : substitutions) {
+                final int substitutionParameterIndex = substitution.methodParameterIndex();
+                if (0 <= substitutionParameterIndex && substitutionParameterIndex < methodArguments.length) {
+                    final Object methodArgument = methodArguments[substitutionParameterIndex];
+
+                    String substitutionValue = serialize(methodArgument);
+                    if (substitutionValue != null && !substitutionValue.isEmpty() && substitution.shouldEncode() && escaper != null) {
+                        substitutionValue = escaper.escape(substitutionValue);
+                    }
+
+                    result = result.replace("{" + substitution.urlParameterName() + "}", substitutionValue);
+                }
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/UnixTime.java b/common/azure-common/src/main/java/com/azure/common/implementation/UnixTime.java
new file mode 100644
index 0000000000000..e32ada994ca91
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/UnixTime.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+
+/**
+ * A wrapper over java.time.OffsetDateTime used for specifying unix seconds format during serialization and deserialization.
+ */
+public final class UnixTime {
+    /**
+     * The actual datetime object.
+     */
+    private final OffsetDateTime dateTime;
+
+    /**
+     * Creates aUnixTime object with the specified DateTime.
+     *
+     * @param dateTime The DateTime object to wrap
+     */
+    public UnixTime(OffsetDateTime dateTime) {
+        this.dateTime = dateTime;
+    }
+
+    /**
+     * Creates a UnixTime object with the specified DateTime.
+     *
+     * @param unixSeconds The Unix seconds value
+     */
+    public UnixTime(long unixSeconds) {
+        this.dateTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(unixSeconds), ZoneOffset.UTC);
+    }
+
+    /**
+     * Get the underlying DateTime.
+     *
+     * @return The underlying DateTime
+     */
+    public OffsetDateTime dateTime() {
+        if (this.dateTime == null) {
+            return null;
+        }
+        return this.dateTime;
+    }
+
+    @Override
+    public String toString() {
+        return dateTime.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return this.dateTime.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+
+        if (!(obj instanceof UnixTime)) {
+            return false;
+        }
+
+        UnixTime rhs = (UnixTime) obj;
+        return this.dateTime.equals(rhs.dateTime());
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/UrlEscapers.java b/common/azure-common/src/main/java/com/azure/common/implementation/UrlEscapers.java
new file mode 100644
index 0000000000000..7705e954d7cea
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/UrlEscapers.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+/**
+ * Collection of useful URL escapers.
+ */
+final class UrlEscapers {
+
+    private static final String UNRESERVED_SYMBOLS = "-._~";
+    private static final String SUB_DELIMS = "!$&'()*+,;=";
+
+    /** An escaper for escaping path parameters. */
+    public static final PercentEscaper PATH_ESCAPER = new PercentEscaper(UNRESERVED_SYMBOLS + SUB_DELIMS + ":@", false);
+    /** An escaper for escaping query parameters. */
+    public static final PercentEscaper QUERY_ESCAPER = new PercentEscaper(UNRESERVED_SYMBOLS + "/?", false);
+    /** An escaper for escaping form parameters. */
+    public static final PercentEscaper FORM_ESCAPER = new PercentEscaper(UNRESERVED_SYMBOLS, true);
+
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/Validator.java b/common/azure-common/src/main/java/com/azure/common/implementation/Validator.java
new file mode 100644
index 0000000000000..4204d64d23ae1
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/Validator.java
@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.azure.common.annotations.SkipParentValidation;
+import com.azure.common.implementation.util.TypeUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Validates user provided parameters are not null if they are required.
+ */
+public final class Validator {
+    /**
+     * Private Ctr.
+     */
+    private Validator() { }
+
+    /**
+     * Validates a user provided required parameter to be not null.
+     *
+     * An {@link IllegalArgumentException} is thrown if a property fails the validation.
+     *
+     * @param parameter the parameter to validate
+     * @throws IllegalArgumentException thrown when the Validator determines the argument is invalid
+     */
+    public static void validate(Object parameter) {
+        // Validation of top level payload is done outside
+        if (parameter == null) {
+            return;
+        }
+
+        Class<?> type = parameter.getClass();
+        if (type == Double.class
+                || type == Float.class
+                || type == Long.class
+                || type == Integer.class
+                || type == Short.class
+                || type == Character.class
+                || type == Byte.class
+                || type == Boolean.class) {
+            type = wrapperToPrimitive(type);
+        }
+        if (type.isPrimitive()
+                || type.isEnum()
+                || type.isAssignableFrom(Class.class)
+                || type.isAssignableFrom(LocalDate.class)
+                || type.isAssignableFrom(OffsetDateTime.class)
+                || type.isAssignableFrom(String.class)
+                || type.isAssignableFrom(DateTimeRfc1123.class)
+                || type.isAssignableFrom(Duration.class)) {
+            return;
+        }
+
+        Annotation skipParentAnnotation = type.getAnnotation(SkipParentValidation.class);
+        //
+        if (skipParentAnnotation == null) {
+            for (Class<?> c : TypeUtil.getAllClasses(type)) {
+                validateClass(c, parameter);
+            }
+        } else {
+            validateClass(type, parameter);
+        }
+    }
+
+    private static Class<?> wrapperToPrimitive(Class<?> clazz) {
+        if (!clazz.isPrimitive()) {
+            return clazz;
+        }
+
+        if (clazz == Integer.class) {
+            return Integer.TYPE;
+        } else if (clazz == Long.class) {
+            return Long.TYPE;
+        } else if (clazz == Boolean.class) {
+            return Boolean.TYPE;
+        } else if (clazz == Byte.class) {
+            return Byte.TYPE;
+        } else if (clazz == Character.class) {
+            return Character.TYPE;
+        } else if (clazz == Float.class) {
+            return Float.TYPE;
+        } else if (clazz == Double.class) {
+            return Double.TYPE;
+        } else if (clazz == Short.class) {
+            return Short.TYPE;
+        } else if (clazz == Void.class) {
+            return Void.TYPE;
+        }
+
+        return clazz;
+    }
+
+    private static void validateClass(Class<?> c, Object parameter) {
+        // Ignore checks for Object type.
+        if (c.isAssignableFrom(Object.class)) {
+            return;
+        }
+        //
+        for (Field field : c.getDeclaredFields()) {
+            field.setAccessible(true);
+            int mod = field.getModifiers();
+            // Skip static fields since we don't have any, skip final fields since users can't modify them
+            if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) {
+                continue;
+            }
+            JsonProperty annotation = field.getAnnotation(JsonProperty.class);
+            // Skip read-only properties (WRITE_ONLY)
+            if (annotation != null && annotation.access().equals(JsonProperty.Access.WRITE_ONLY)) {
+                continue;
+            }
+            Object property;
+            try {
+                property = field.get(parameter);
+            } catch (IllegalAccessException e) {
+                throw new IllegalArgumentException(e.getMessage(), e);
+            }
+            if (property == null) {
+                if (annotation != null && annotation.required()) {
+                    throw new IllegalArgumentException(field.getName() + " is required and cannot be null.");
+                }
+            } else {
+                try {
+                    Class<?> propertyType = property.getClass();
+                    if (List.class.isAssignableFrom(propertyType)) {
+                        List<?> items = (List<?>) property;
+                        for (Object item : items) {
+                            Validator.validate(item);
+                        }
+                    }
+                    else if (Map.class.isAssignableFrom(propertyType)) {
+                        Map<?, ?> entries = (Map<?, ?>) property;
+                        for (Map.Entry<?, ?> entry : entries.entrySet()) {
+                            Validator.validate(entry.getKey());
+                            Validator.validate(entry.getValue());
+                        }
+                    }
+                    else if (parameter.getClass() != propertyType) {
+                        Validator.validate(property);
+                    }
+                } catch (IllegalArgumentException ex) {
+                    if (ex.getCause() == null) {
+                        // Build property chain
+                        throw new IllegalArgumentException(field.getName() + "." + ex.getMessage());
+                    } else {
+                        throw ex;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/exception/InvalidReturnTypeException.java b/common/azure-common/src/main/java/com/azure/common/implementation/exception/InvalidReturnTypeException.java
new file mode 100644
index 0000000000000..86d6c61029864
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/exception/InvalidReturnTypeException.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.exception;
+
+/**
+ * An exception thrown when a Swagger interface defines a method with an invalid return
+ * type.
+ */
+public class InvalidReturnTypeException extends RuntimeException {
+    /**
+     * Create a new InvalidReturnTypeException with the provided message.
+     * @param message The message for this exception.
+     */
+    public InvalidReturnTypeException(String message) {
+        super(message);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/exception/MissingRequiredAnnotationException.java b/common/azure-common/src/main/java/com/azure/common/implementation/exception/MissingRequiredAnnotationException.java
new file mode 100644
index 0000000000000..e8f40a5797d6c
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/exception/MissingRequiredAnnotationException.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.exception;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/**
+ * An exception thrown when a Swagger interface is parsed and it is missing required
+ * annotations.
+ */
+public class MissingRequiredAnnotationException extends RuntimeException {
+    /**
+     * Create a new MissingRequiredAnnotationException for the provided missing required annotation
+     * on the provided swaggerInterface.
+     * @param requiredAnnotation The annotation that is required.
+     * @param swaggerInterface The swagger interface that is missing the required annotation.
+     */
+    public MissingRequiredAnnotationException(Class<? extends Annotation> requiredAnnotation, Class<?> swaggerInterface) {
+        super("A " + getAnnotationName(requiredAnnotation) + " annotation must be defined on " + swaggerInterface.getName() + ".");
+    }
+
+    /**
+     * Create a new MissingRequiredAnnotationException for the provided missing required annotation
+     * on the provided swaggerInterface method.
+     * @param requiredAnnotation The annotation that is required.
+     * @param swaggerInterfaceMethod The swagger interface method that is missing the required annotation.
+     */
+    public MissingRequiredAnnotationException(Class<? extends Annotation> requiredAnnotation, Method swaggerInterfaceMethod) {
+        super("A " + getAnnotationName(requiredAnnotation) + " annotation must be defined on the method " + methodFullName(swaggerInterfaceMethod) + ".");
+    }
+
+    /**
+     * Create a new MissingRequiredAnnotationException for the provided missing required annotation
+     * options on the provided swaggerInterface method.
+     * @param requiredAnnotationOptions The options for the annotation that is required.
+     * @param swaggerInterfaceMethod The swagger interface method that is missing the required annotation.
+     */
+    public MissingRequiredAnnotationException(List<Class<? extends Annotation>> requiredAnnotationOptions, Method swaggerInterfaceMethod) {
+        super("Either " + optionsToString(requiredAnnotationOptions) + " annotation must be defined on the method " + methodFullName(swaggerInterfaceMethod) + ".");
+    }
+
+    private static String getAnnotationName(Class<? extends Annotation> annotation) {
+        return annotation.getSimpleName();
+    }
+
+    private static String optionsToString(List<Class<? extends Annotation>> requiredAnnotationOptions) {
+        final StringBuilder result = new StringBuilder();
+
+        final int optionCount = requiredAnnotationOptions.size();
+        for (int i = 0; i < optionCount; ++i) {
+            if (1 <= i) {
+                result.append(", ");
+            }
+            if (i == optionCount - 1) {
+                result.append("or ");
+            }
+            result.append(getAnnotationName(requiredAnnotationOptions.get(i)));
+        }
+
+        return result.toString();
+    }
+
+    private static String methodFullName(Method swaggerInterfaceMethod) {
+        return swaggerInterfaceMethod.getDeclaringClass().getName() + "." + swaggerInterfaceMethod.getName() + "()";
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/exception/package-info.java b/common/azure-common/src/main/java/com/azure/common/implementation/exception/package-info.java
new file mode 100644
index 0000000000000..6b37a83df04b7
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/exception/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing implementation-specific exception APIs that should not be used by end-users.
+ */
+package com.azure.common.implementation.exception;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/BufferedHttpResponse.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/BufferedHttpResponse.java
new file mode 100644
index 0000000000000..3c6fcb8f9ce6c
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/BufferedHttpResponse.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.http;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpResponse;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * HTTP response which will buffer the response's body when/if it is read.
+ */
+public final class BufferedHttpResponse extends HttpResponse {
+    private final HttpResponse innerHttpResponse;
+    private final Mono<byte[]> cachedBody;
+
+    /**
+     * Creates a buffered HTTP response.
+     *
+     * @param innerHttpResponse The HTTP response to buffer
+     */
+    public BufferedHttpResponse(HttpResponse innerHttpResponse) {
+        this.innerHttpResponse = innerHttpResponse;
+        this.cachedBody = innerHttpResponse.bodyAsByteArray().cache();
+        this.withRequest(innerHttpResponse.request());
+    }
+
+    @Override
+    public int statusCode() {
+        return innerHttpResponse.statusCode();
+    }
+
+    @Override
+    public String headerValue(String name) {
+        return innerHttpResponse.headerValue(name);
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        return innerHttpResponse.headers();
+    }
+
+    @Override
+    public Mono<byte[]> bodyAsByteArray() {
+        return cachedBody;
+    }
+
+    @Override
+    public Flux<ByteBuf> body() {
+        return bodyAsByteArray().flatMapMany(bytes -> Flux.just(Unpooled.wrappedBuffer(bytes)));
+    }
+
+    @Override
+    public Mono<String> bodyAsString() {
+        return bodyAsByteArray()
+                .map(bytes -> bytes == null ? null : new String(bytes, StandardCharsets.UTF_8));
+    }
+
+    @Override
+    public Mono<String> bodyAsString(Charset charset) {
+        return bodyAsByteArray()
+                .map(bytes -> bytes == null ? null : new String(bytes, charset));
+    }
+
+    @Override
+    public BufferedHttpResponse buffer() {
+        return this;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/ContentType.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/ContentType.java
new file mode 100644
index 0000000000000..1c2dae5cebe88
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/ContentType.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.http;
+
+/**
+ * The different values that commonly used for Content-Type header.
+ */
+public final class ContentType {
+    /**
+     * the default JSON Content-Type header.
+     */
+    public static final String APPLICATION_JSON = "application/json";
+
+    /**
+     * the default binary Content-Type header.
+     */
+    public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
+
+    /**
+     * Private ctr.
+     */
+    private ContentType() {
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlBuilder.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlBuilder.java
new file mode 100644
index 0000000000000..ebed556baeb58
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlBuilder.java
@@ -0,0 +1,327 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.http;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A builder class that is used to create URLs.
+ */
+public final class UrlBuilder {
+    private String scheme;
+    private String host;
+    private String port;
+    private String path;
+
+    // LinkedHashMap preserves insertion order
+    private final Map<String, String> query = new LinkedHashMap<>();
+
+    /**
+     * Set the scheme/protocol that will be used to build the final URL.
+     * @param scheme The scheme/protocol that will be used to build the final URL.
+     * @return This UrlBuilder so that multiple setters can be chained together.
+     */
+    public UrlBuilder withScheme(String scheme) {
+        if (scheme == null || scheme.isEmpty()) {
+            this.scheme = null;
+        }
+        else {
+            with(scheme, UrlTokenizerState.SCHEME);
+        }
+        return this;
+    }
+
+    /**
+     * Get the scheme/protocol that has been assigned to this UrlBuilder.
+     * @return the scheme/protocol that has been assigned to this UrlBuilder.
+     */
+    public String scheme() {
+        return scheme;
+    }
+
+    /**
+     * Set the host that will be used to build the final URL.
+     * @param host The host that will be used to build the final URL.
+     * @return This UrlBuilder so that multiple setters can be chained together.
+     */
+    public UrlBuilder withHost(String host) {
+        if (host == null || host.isEmpty()) {
+            this.host = null;
+        }
+        else {
+            with(host, UrlTokenizerState.SCHEME_OR_HOST);
+        }
+        return this;
+    }
+
+    /**
+     * Get the host that has been assigned to this UrlBuilder.
+     * @return the host that has been assigned to this UrlBuilder.
+     */
+    public String host() {
+        return host;
+    }
+
+    /**
+     * Set the port that will be used to build the final URL.
+     * @param port The port that will be used to build the final URL.
+     * @return This UrlBuilder so that multiple setters can be chained together.
+     */
+    public UrlBuilder withPort(String port) {
+        if (port == null || port.isEmpty()) {
+            this.port = null;
+        }
+        else {
+            with(port, UrlTokenizerState.PORT);
+        }
+        return this;
+    }
+
+    /**
+     * Set the port that will be used to build the final URL.
+     * @param port The port that will be used to build the final URL.
+     * @return This UrlBuilder so that multiple setters can be chained together.
+     */
+    public UrlBuilder withPort(int port) {
+        return withPort(Integer.toString(port));
+    }
+
+    /**
+     * Get the port that has been assigned to this UrlBuilder.
+     * @return the port that has been assigned to this UrlBuilder.
+     */
+    public Integer port() {
+        return port == null ? null : Integer.valueOf(port);
+    }
+
+    /**
+     * Set the path that will be used to build the final URL.
+     * @param path The path that will be used to build the final URL.
+     * @return This UrlBuilder so that multiple setters can be chained together.
+     */
+    public UrlBuilder withPath(String path) {
+        if (path == null || path.isEmpty()) {
+            this.path = null;
+        }
+        else {
+            with(path, UrlTokenizerState.PATH);
+        }
+        return this;
+    }
+
+    /**
+     * Get the path that has been assigned to this UrlBuilder.
+     * @return the path that has been assigned to this UrlBuilder.
+     */
+    public String path() {
+        return path;
+    }
+
+    /**
+     * Set the provided query parameter name and encoded value to query string for the final URL.
+     * @param queryParameterName The name of the query parameter.
+     * @param queryParameterEncodedValue The encoded value of the query parameter.
+     * @return The provided query parameter name and encoded value to query string for the final
+     * URL.
+     */
+    public UrlBuilder setQueryParameter(String queryParameterName, String queryParameterEncodedValue) {
+        query.put(queryParameterName, queryParameterEncodedValue);
+        return this;
+    }
+
+    /**
+     * Set the query that will be used to build the final URL.
+     * @param query The query that will be used to build the final URL.
+     * @return This UrlBuilder so that multiple setters can be chained together.
+     */
+    public UrlBuilder withQuery(String query) {
+        if (query == null || query.isEmpty()) {
+            this.query.clear();
+        }
+        else {
+            with(query, UrlTokenizerState.QUERY);
+        }
+        return this;
+    }
+
+    /**
+     * Get the query that has been assigned to this UrlBuilder.
+     * @return the query that has been assigned to this UrlBuilder.
+     */
+    public Map<String, String> query() {
+        return query;
+    }
+
+    private UrlBuilder with(String text, UrlTokenizerState startState) {
+        final UrlTokenizer tokenizer = new UrlTokenizer(text, startState);
+
+        while (tokenizer.next()) {
+            final UrlToken token = tokenizer.current();
+            final String tokenText = token.text();
+            final UrlTokenType tokenType = token.type();
+            switch (tokenType) {
+                case SCHEME:
+                    scheme = emptyToNull(tokenText);
+                    break;
+
+                case HOST:
+                    host = emptyToNull(tokenText);
+                    break;
+
+                case PORT:
+                    port = emptyToNull(tokenText);
+                    break;
+
+                case PATH:
+                    final String tokenPath = emptyToNull(tokenText);
+                    if (path == null || path.equals("/") || !tokenPath.equals("/")) {
+                        path = tokenPath;
+                    }
+                    break;
+
+                case QUERY:
+                    String queryString = emptyToNull(tokenText);
+                    if (queryString != null) {
+                        if (queryString.startsWith("?")) {
+                            queryString = queryString.substring(1);
+                        }
+
+                        for (String entry : queryString.split("&")) {
+                            String[] nameValue = entry.split("=");
+                            if (nameValue.length == 2) {
+                                setQueryParameter(nameValue[0], nameValue[1]);
+                            } else {
+                                throw new IllegalArgumentException("Malformed query entry: " + entry);
+                            }
+                        }
+                    }
+
+                    break;
+
+                default:
+                    break;
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Get the URL that is being built.
+     * @return The URL that is being built.
+     * @throws MalformedURLException if the URL is not fully formed.
+     */
+    public URL toURL() throws MalformedURLException {
+        return new URL(toString());
+    }
+
+    /**
+     * Get the string representation of the URL that is being built.
+     * @return The string representation of the URL that is being built.
+     */
+    public String toString() {
+        final StringBuilder result = new StringBuilder();
+
+        final boolean isAbsolutePath = path != null && (path.startsWith("http://") || path.startsWith("https://"));
+        if (!isAbsolutePath) {
+            if (scheme != null) {
+                result.append(scheme);
+
+                if (!scheme.endsWith("://")) {
+                    result.append("://");
+                }
+            }
+
+            if (host != null) {
+                result.append(host);
+            }
+        }
+
+        if (port != null) {
+            result.append(":");
+            result.append(port);
+        }
+
+        if (path != null) {
+            if (result.length() != 0 && !path.startsWith("/")) {
+                result.append('/');
+            }
+            result.append(path);
+        }
+
+        if (!query.isEmpty()) {
+            StringBuilder queryBuilder = new StringBuilder("?");
+            for (Map.Entry<String, String> entry : query.entrySet()) {
+                if (queryBuilder.length() > 1) {
+                    queryBuilder.append("&");
+                }
+                queryBuilder.append(entry.getKey());
+                queryBuilder.append("=");
+                queryBuilder.append(entry.getValue());
+            }
+
+            result.append(queryBuilder.toString());
+        }
+
+        return result.toString();
+    }
+
+    /**
+     * Parse a UrlBuilder from the provided URL string.
+     * @param url The string to parse.
+     * @return The UrlBuilder that was parsed from the string.
+     */
+    public static UrlBuilder parse(String url) {
+        final UrlBuilder result = new UrlBuilder();
+        result.with(url, UrlTokenizerState.SCHEME_OR_HOST);
+        return result;
+    }
+
+    /**
+     * Parse a UrlBuilder from the provided URL object.
+     * @param url The URL object to parse.
+     * @return The UrlBuilder that was parsed from the URL object.
+     */
+    public static UrlBuilder parse(URL url) {
+        final UrlBuilder result = new UrlBuilder();
+
+        if (url != null) {
+            final String protocol = url.getProtocol();
+            if (protocol != null && !protocol.isEmpty()) {
+                result.withScheme(protocol);
+            }
+
+            final String host = url.getHost();
+            if (host != null && !host.isEmpty()) {
+                result.withHost(host);
+            }
+
+            final int port = url.getPort();
+            if (port != -1) {
+                result.withPort(port);
+            }
+
+            final String path = url.getPath();
+            if (path != null && !path.isEmpty()) {
+                result.withPath(path);
+            }
+
+            final String query = url.getQuery();
+            if (query != null && !query.isEmpty()) {
+                result.withQuery(query);
+            }
+        }
+
+        return result;
+    }
+
+    private static String emptyToNull(String value) {
+        return value == null || value.isEmpty() ? null : value;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlToken.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlToken.java
new file mode 100644
index 0000000000000..39f336514384b
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlToken.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.http;
+
+class UrlToken {
+    private final String text;
+    private final UrlTokenType type;
+
+    UrlToken(String text, UrlTokenType type) {
+        this.text = text;
+        this.type = type;
+    }
+
+    String text() {
+        return text;
+    }
+
+    UrlTokenType type() {
+        return type;
+    }
+
+    @Override
+    public boolean equals(Object rhs) {
+        return rhs instanceof UrlToken && equals((UrlToken) rhs);
+    }
+
+    public boolean equals(UrlToken rhs) {
+        return rhs != null && text.equals(rhs.text) && type == rhs.type;
+    }
+
+    @Override
+    public String toString() {
+        return "\"" + text + "\" (" + type + ")";
+    }
+
+    @Override
+    public int hashCode() {
+        return (text == null ? 0 : text.hashCode()) ^ type.hashCode();
+    }
+
+    static UrlToken scheme(String text) {
+        return new UrlToken(text, UrlTokenType.SCHEME);
+    }
+
+    static UrlToken host(String text) {
+        return new UrlToken(text, UrlTokenType.HOST);
+    }
+
+    static UrlToken port(String text) {
+        return new UrlToken(text, UrlTokenType.PORT);
+    }
+
+    static UrlToken path(String text) {
+        return new UrlToken(text, UrlTokenType.PATH);
+    }
+
+    static UrlToken query(String text) {
+        return new UrlToken(text, UrlTokenType.QUERY);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenType.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenType.java
new file mode 100644
index 0000000000000..de756e6e9b025
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenType.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.http;
+
+enum UrlTokenType {
+    SCHEME,
+
+    HOST,
+
+    PORT,
+
+    PATH,
+
+    QUERY,
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenizer.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenizer.java
new file mode 100644
index 0000000000000..5044cddab64cf
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenizer.java
@@ -0,0 +1,231 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.http;
+
+class UrlTokenizer {
+    private final String text;
+    private final int textLength;
+    private UrlTokenizerState state;
+    private int currentIndex;
+    private UrlToken currentToken;
+
+    UrlTokenizer(String text) {
+        this(text, UrlTokenizerState.SCHEME_OR_HOST);
+    }
+
+    UrlTokenizer(String text, UrlTokenizerState state) {
+        this.text = text;
+        this.textLength = (text == null ? 0 : text.length());
+        this.state = state;
+        this.currentIndex = 0;
+        this.currentToken = null;
+    }
+
+    private boolean hasCurrentCharacter() {
+        return currentIndex < textLength;
+    }
+
+    private char currentCharacter() {
+        return text.charAt(currentIndex);
+    }
+
+    private void nextCharacter() {
+        nextCharacter(1);
+    }
+
+    private void nextCharacter(int step) {
+        if (hasCurrentCharacter()) {
+            currentIndex += step;
+        }
+    }
+
+    private String peekCharacters(int charactersToPeek) {
+        int endIndex = currentIndex + charactersToPeek;
+        if (textLength < endIndex) {
+            endIndex = textLength;
+        }
+        return text.substring(currentIndex, endIndex);
+    }
+
+    UrlToken current() {
+        return currentToken;
+    }
+
+    boolean next() {
+        if (!hasCurrentCharacter()) {
+            currentToken = null;
+        }
+        else {
+            switch (state) {
+                case SCHEME:
+                    final String scheme = readUntilNotLetterOrDigit();
+                    currentToken = UrlToken.scheme(scheme);
+                    if (!hasCurrentCharacter()) {
+                        state = UrlTokenizerState.DONE;
+                    }
+                    else {
+                        state = UrlTokenizerState.HOST;
+                    }
+                    break;
+
+                case SCHEME_OR_HOST:
+                    final String schemeOrHost = readUntilCharacter(':', '/', '?');
+                    if (!hasCurrentCharacter()) {
+                        currentToken = UrlToken.host(schemeOrHost);
+                        state = UrlTokenizerState.DONE;
+                    }
+                    else if (currentCharacter() == ':') {
+                        if (peekCharacters(3).equals("://")) {
+                            currentToken = UrlToken.scheme(schemeOrHost);
+                            state = UrlTokenizerState.HOST;
+                        }
+                        else {
+                            currentToken = UrlToken.host(schemeOrHost);
+                            state = UrlTokenizerState.PORT;
+                        }
+                    }
+                    else if (currentCharacter() == '/') {
+                        currentToken = UrlToken.host(schemeOrHost);
+                        state = UrlTokenizerState.PATH;
+                    }
+                    else if (currentCharacter() == '?') {
+                        currentToken = UrlToken.host(schemeOrHost);
+                        state = UrlTokenizerState.QUERY;
+                    }
+                    break;
+
+                case HOST:
+                    if (peekCharacters(3).equals("://")) {
+                        nextCharacter(3);
+                    }
+
+                    final String host = readUntilCharacter(':', '/', '?');
+                    currentToken = UrlToken.host(host);
+
+                    if (!hasCurrentCharacter()) {
+                        state = UrlTokenizerState.DONE;
+                    }
+                    else if (currentCharacter() == ':') {
+                        state = UrlTokenizerState.PORT;
+                    }
+                    else if (currentCharacter() == '/') {
+                        state = UrlTokenizerState.PATH;
+                    }
+                    else {
+                        state = UrlTokenizerState.QUERY;
+                    }
+                    break;
+
+                case PORT:
+                    if (currentCharacter() == ':') {
+                        nextCharacter();
+                    }
+
+                    final String port = readUntilCharacter('/', '?');
+                    currentToken = UrlToken.port(port);
+
+                    if (!hasCurrentCharacter()) {
+                        state = UrlTokenizerState.DONE;
+                    }
+                    else if (currentCharacter() == '/') {
+                        state = UrlTokenizerState.PATH;
+                    }
+                    else {
+                        state = UrlTokenizerState.QUERY;
+                    }
+                    break;
+
+                case PATH:
+                    final String path = readUntilCharacter('?');
+                    currentToken = UrlToken.path(path);
+
+                    if (!hasCurrentCharacter()) {
+                        state = UrlTokenizerState.DONE;
+                    }
+                    else {
+                        state = UrlTokenizerState.QUERY;
+                    }
+                    break;
+
+                case QUERY:
+                    if (currentCharacter() == '?') {
+                        nextCharacter();
+                    }
+
+                    final String query = readRemaining();
+                    currentToken = UrlToken.query(query);
+                    state = UrlTokenizerState.DONE;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        return currentToken != null;
+    }
+
+    private String readUntilNotLetterOrDigit() {
+        String result = "";
+
+        if (hasCurrentCharacter()) {
+            final StringBuilder builder = new StringBuilder();
+            while (hasCurrentCharacter()) {
+                final char currentCharacter = currentCharacter();
+                if (!Character.isLetterOrDigit(currentCharacter)) {
+                    break;
+                }
+                else {
+                    builder.append(currentCharacter);
+                    nextCharacter();
+                }
+            }
+            result = builder.toString();
+        }
+
+        return result;
+    }
+
+    private String readUntilCharacter(char... terminatingCharacters) {
+        String result = "";
+
+        if (hasCurrentCharacter()) {
+            final StringBuilder builder = new StringBuilder();
+            boolean foundTerminator = false;
+            while (hasCurrentCharacter()) {
+                final char currentCharacter = currentCharacter();
+
+                for (final char terminatingCharacter : terminatingCharacters) {
+                    if (currentCharacter == terminatingCharacter) {
+                        foundTerminator = true;
+                        break;
+                    }
+                }
+
+                if (foundTerminator) {
+                    break;
+                }
+                else {
+                    builder.append(currentCharacter);
+                    nextCharacter();
+                }
+            }
+            result = builder.toString();
+        }
+
+        return result;
+    }
+
+    private String readRemaining() {
+        String result = "";
+        if (currentIndex < textLength) {
+            result = text.substring(currentIndex, textLength);
+            currentIndex = textLength;
+        }
+        return result;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenizerState.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenizerState.java
new file mode 100644
index 0000000000000..0b4c01d9372d2
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/UrlTokenizerState.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.http;
+
+enum UrlTokenizerState {
+    SCHEME,
+
+    SCHEME_OR_HOST,
+
+    HOST,
+
+    PORT,
+
+    PATH,
+
+    QUERY,
+
+    DONE
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/http/package-info.java b/common/azure-common/src/main/java/com/azure/common/implementation/http/package-info.java
new file mode 100644
index 0000000000000..4ba8707947041
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/http/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing implementation-specific HTTP APIs that should not be used by end-users.
+ */
+package com.azure.common.implementation.http;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/package-info.java b/common/azure-common/src/main/java/com/azure/common/implementation/package-info.java
new file mode 100644
index 0000000000000..add95d19e6407
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing implementation-specific APIs that should not be used by end-users.
+ */
+package com.azure.common.implementation;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseBodyDecoder.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseBodyDecoder.java
new file mode 100644
index 0000000000000..a0882c2ab7ee1
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseBodyDecoder.java
@@ -0,0 +1,403 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+import com.azure.common.implementation.Base64Url;
+import com.azure.common.implementation.DateTimeRfc1123;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.http.rest.RestResponse;
+import com.azure.common.http.rest.RestResponseBase;
+import com.azure.common.implementation.UnixTime;
+import com.azure.common.annotations.ReturnValueWireType;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.rest.SimpleRestResponse;
+import com.azure.common.implementation.util.FluxUtil;
+import com.azure.common.implementation.util.TypeUtil;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Decoder to decode body of HTTP response.
+ */
+final class HttpResponseBodyDecoder {
+    /**
+     * Decodes body of a http response.
+     *
+     * The content reading and decoding happens when caller subscribe to the returned {@code Mono<Object>},
+     * if the response body is not decodable then {@code Mono.empty()} will be returned.
+     *
+     * @param httpResponse the response containing the body to be decoded
+     * @param serializer the adapter to use for decoding
+     * @param decodeData the necessary data required to decode a Http response
+     * @return publisher that emits decoded response body upon subscription if body is decodable,
+     * no emission if the body is not-decodable
+     */
+    static Mono<Object> decode(HttpResponse httpResponse, SerializerAdapter serializer, HttpResponseDecodeData decodeData) {
+        ensureRequestSet(httpResponse);
+        //
+        return Mono.defer(() -> {
+            if (isErrorStatus(httpResponse, decodeData)) {
+                return httpResponse.bodyAsString()
+                        .flatMap(bodyString -> {
+                            try {
+                                final Object decodedErrorEntity = deserializeBody(bodyString,
+                                        decodeData.exceptionBodyType(),
+                                        null,
+                                        serializer,
+                                        SerializerEncoding.fromHeaders(httpResponse.headers()));
+                                return decodedErrorEntity == null ? Mono.empty() : Mono.just(decodedErrorEntity);
+                            } catch (IOException | MalformedValueException ignored) {
+                                // This translates in RestProxy as a RestException with no deserialized body.
+                                // The response content will still be accessible via the .response() member.
+                            }
+                            return Mono.empty();
+                        });
+            } else if (httpResponse.request().httpMethod() == HttpMethod.HEAD) {
+                // RFC: A response to a HEAD method should not have a body. If so, it must be ignored
+                return Mono.empty();
+            } else if (!isReturnTypeDecodable(decodeData)) {
+                return Mono.empty();
+            } else {
+                return httpResponse.bodyAsString()
+                        .flatMap(bodyString -> {
+                            try {
+                                final Object decodedSuccessEntity = deserializeBody(bodyString,
+                                        extractEntityTypeFromReturnType(decodeData),
+                                        decodeData.returnValueWireType(),
+                                        serializer,
+                                        SerializerEncoding.fromHeaders(httpResponse.headers()));
+                                return decodedSuccessEntity == null ? Mono.empty() : Mono.just(decodedSuccessEntity);
+                            } catch (MalformedValueException e) {
+                                return Mono.error(new RestException("HTTP response has a malformed body.", httpResponse, e));
+                            } catch (IOException e) {
+                                return Mono.error(new RestException("Deserialization Failed.", httpResponse, e));
+                            }
+                        });
+            }
+        });
+    }
+
+    /**
+     * @return true if the body is decodable, false otherwise
+     */
+    static boolean isDecodable(HttpResponse httpResponse, HttpResponseDecodeData decodeData) {
+        ensureRequestSet(httpResponse);
+        //
+        if (isErrorStatus(httpResponse, decodeData)) {
+            // For error cases we always try to decode the non-empty response body
+            // either to a strongly typed exception model or to Object
+            return true;
+        } else if (httpResponse.request().httpMethod() == HttpMethod.HEAD) {
+            // RFC: A response to a HEAD method should not have a body. If so, it must be ignored
+            return false;
+        } else {
+            return isReturnTypeDecodable(decodeData);
+        }
+    }
+
+    /**
+     * @return the decoded type used to decode the response body, null if the body is not decodable.
+     */
+    static Type decodedType(HttpResponse httpResponse, HttpResponseDecodeData decodeData) {
+        ensureRequestSet(httpResponse);
+        //
+        if (isErrorStatus(httpResponse, decodeData)) {
+            // For error cases we always try to decode the non-empty response body
+            // either to a strongly typed exception model or to Object
+            return decodeData.exceptionBodyType();
+        } else if (httpResponse.request().httpMethod() == HttpMethod.HEAD) {
+            // RFC: A response to a HEAD method should not have a body. If so, it must be ignored
+            return null;
+        } else if (!isReturnTypeDecodable(decodeData)) {
+            return null;
+        } else {
+            return extractEntityTypeFromReturnType(decodeData);
+        }
+    }
+
+    /**
+     * Checks the response status code is considered as error.
+     *
+     * @param httpResponse the response to check
+     * @param decodeData the response metadata
+     * @return true if the response status code is considered as error, false otherwise.
+     */
+    static boolean isErrorStatus(HttpResponse httpResponse, HttpResponseDecodeData decodeData) {
+        final int[] expectedStatuses = decodeData.expectedStatusCodes();
+        if (expectedStatuses != null) {
+            return !contains(expectedStatuses, httpResponse.statusCode());
+        } else {
+            return httpResponse.statusCode() / 100 != 2;
+        }
+    }
+
+    /**
+     * Deserialize the given string value representing content of a REST API response.
+     *
+     * @param value the string value to deserialize
+     * @param resultType the return type of the java proxy method
+     * @param wireType value of optional {@link ReturnValueWireType} annotation present in java proxy method indicating
+     *                 'entity type' (wireType) of REST API wire response body
+     * @param encoding the encoding format of value
+     * @return Deserialized object
+     * @throws IOException
+     */
+    private static Object deserializeBody(String value, Type resultType, Type wireType, SerializerAdapter serializer, SerializerEncoding encoding) throws IOException {
+        final Object result;
+
+        if (wireType == null) {
+            result = serializer.deserialize(value, resultType, encoding);
+        } else {
+            final Type wireResponseType = constructWireResponseType(resultType, wireType);
+            final Object wireResponse = serializer.deserialize(value, wireResponseType, encoding);
+            result = convertToResultType(wireResponse, resultType, wireType);
+        }
+        return result;
+    }
+
+    /**
+     * Given:
+     * (1). the {@code java.lang.reflect.Type} (resultType) of java proxy method return value
+     * (2). and {@link ReturnValueWireType} annotation value indicating 'entity type' (wireType)
+     *      of same REST API's wire response body
+     * this method construct 'response body Type'.
+     *
+     * Note: When {@link ReturnValueWireType} annotation is applied to a proxy method, then the raw
+     * HTTP response content will need to parsed using the derived 'response body Type' then converted
+     * to actual {@code returnType}.
+     *
+     * @param resultType the {@code java.lang.reflect.Type} of java proxy method return value
+     * @param wireType the {@code java.lang.reflect.Type} of entity in REST API response body
+     * @return the {@code java.lang.reflect.Type} of REST API response body
+     */
+    private static Type constructWireResponseType(Type resultType, Type wireType) {
+        Objects.requireNonNull(resultType);
+        Objects.requireNonNull(wireType);
+        //
+        Type wireResponseType = resultType;
+
+        if (resultType == byte[].class) {
+            if (wireType == Base64Url.class) {
+                wireResponseType = Base64Url.class;
+            }
+        } else if (resultType == OffsetDateTime.class) {
+            if (wireType == DateTimeRfc1123.class) {
+                wireResponseType = DateTimeRfc1123.class;
+            } else if (wireType == UnixTime.class) {
+                wireResponseType = UnixTime.class;
+            }
+        } else {
+            if (TypeUtil.isTypeOrSubTypeOf(resultType, List.class)) {
+                final Type resultElementType = TypeUtil.getTypeArgument(resultType);
+                final Type wireResponseElementType = constructWireResponseType(resultElementType, wireType);
+
+                wireResponseType = TypeUtil.createParameterizedType(
+                        (Class<?>) ((ParameterizedType) resultType).getRawType(), wireResponseElementType);
+            } else if (TypeUtil.isTypeOrSubTypeOf(resultType, Map.class) || TypeUtil.isTypeOrSubTypeOf(resultType, RestResponse.class)) {
+                Type[] typeArguments = TypeUtil.getTypeArguments(resultType);
+                final Type resultValueType = typeArguments[1];
+                final Type wireResponseValueType = constructWireResponseType(resultValueType, wireType);
+
+                wireResponseType = TypeUtil.createParameterizedType(
+                        (Class<?>) ((ParameterizedType) resultType).getRawType(), typeArguments[0], wireResponseValueType);
+            }
+        }
+        return wireResponseType;
+    }
+
+    /**
+     * Converts the object {@code wireResponse} that was deserialized using 'response body Type'
+     * (produced by {@code constructWireResponseType(args)} method) to resultType.
+     *
+     * @param wireResponse the object to convert
+     * @param resultType the {@code java.lang.reflect.Type} to convert wireResponse to
+     * @param wireType the {@code java.lang.reflect.Type} of the wireResponse
+     * @return converted object
+     */
+    private static Object convertToResultType(Object wireResponse, Type resultType, Type wireType) {
+        Object result = wireResponse;
+
+        if (wireResponse != null) {
+            if (resultType == byte[].class) {
+                if (wireType == Base64Url.class) {
+                    result = ((Base64Url) wireResponse).decodedBytes();
+                }
+            } else if (resultType == OffsetDateTime.class) {
+                if (wireType == DateTimeRfc1123.class) {
+                    result = ((DateTimeRfc1123) wireResponse).dateTime();
+                } else if (wireType == UnixTime.class) {
+                    result = ((UnixTime) wireResponse).dateTime();
+                }
+            } else {
+                if (TypeUtil.isTypeOrSubTypeOf(resultType, List.class)) {
+                    final Type resultElementType = TypeUtil.getTypeArgument(resultType);
+
+                    final List<Object> wireResponseList = (List<Object>) wireResponse;
+
+                    final int wireResponseListSize = wireResponseList.size();
+                    for (int i = 0; i < wireResponseListSize; ++i) {
+                        final Object wireResponseElement = wireResponseList.get(i);
+                        final Object resultElement = convertToResultType(wireResponseElement, resultElementType, wireType);
+                        if (wireResponseElement != resultElement) {
+                            wireResponseList.set(i, resultElement);
+                        }
+                    }
+                    //
+                    result = wireResponseList;
+                } else if (TypeUtil.isTypeOrSubTypeOf(resultType, Map.class)) {
+                    final Type resultValueType = TypeUtil.getTypeArguments(resultType)[1];
+
+                    final Map<String, Object> wireResponseMap = (Map<String, Object>) wireResponse;
+
+                    final Set<String> wireResponseKeys = wireResponseMap.keySet();
+                    for (String wireResponseKey : wireResponseKeys) {
+                        final Object wireResponseValue = wireResponseMap.get(wireResponseKey);
+                        final Object resultValue = convertToResultType(wireResponseValue, resultValueType, wireType);
+                        if (wireResponseValue != resultValue) {
+                            wireResponseMap.put(wireResponseKey, resultValue);
+                        }
+                    }
+                    //
+                    result = wireResponseMap;
+                } else if (TypeUtil.isTypeOrSubTypeOf(resultType, RestResponseBase.class)) {
+                    RestResponseBase<?, ?> restResponseBase = (RestResponseBase<?, ?>) wireResponse;
+                    Object wireResponseBody = restResponseBase.body();
+
+                    // TODO: anuchan - RestProxy is always in charge of creating RestResponseBase--so this doesn't seem right
+                    Object resultBody = convertToResultType(wireResponseBody, TypeUtil.getTypeArguments(resultType)[1], wireType);
+                    if (wireResponseBody != resultBody) {
+                        result = new RestResponseBase<>(restResponseBase.request(), restResponseBase.statusCode(), restResponseBase.headers(), resultBody, restResponseBase.deserializedHeaders());
+                    } else {
+                        result = restResponseBase;
+                    }
+                } else if (TypeUtil.isTypeOrSubTypeOf(resultType, RestResponse.class)) {
+                    RestResponse<?> restResponse = (RestResponse<?>) wireResponse;
+                    Object wireResponseBody = restResponse.body();
+
+                    // TODO: anuchan - RestProxy is always in charge of creating RestResponseBase--so this doesn't seem right
+                    Object resultBody = convertToResultType(wireResponseBody, TypeUtil.getTypeArguments(resultType)[1], wireType);
+                    if (wireResponseBody != resultBody) {
+                        result = new SimpleRestResponse<>(restResponse.request(), restResponse.statusCode(), restResponse.headers(), resultBody);
+                    } else {
+                        result = restResponse;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Get the {@link Type} of the REST API 'returned entity'.
+     *
+     * In the declaration of a java proxy method corresponding to the REST API, the 'returned entity' can be:
+     *
+     *      1. emission value of the reactor publisher returned by proxy method
+     *
+     *          e.g. {@code Mono<Foo> getFoo(args);}
+     *               {@code Flux<Foo> getFoos(args);}
+     *          where Foo is the REST API 'returned entity'.
+     *
+     *      2. OR content (body) of {@link RestResponseBase} emitted by the reactor publisher returned from proxy method
+     *
+     *          e.g. {@code Mono<RestResponseBase<headers, Foo>> getFoo(args);}
+     *               {@code Flux<RestResponseBase<headers, Foo>> getFoos(args);}
+     *          where Foo is the REST API return entity.
+     *
+     * @return the entity type.
+     */
+    private static Type extractEntityTypeFromReturnType(HttpResponseDecodeData decodeData) {
+        Type token = decodeData.returnType();
+        if (token != null) {
+            if (TypeUtil.isTypeOrSubTypeOf(token, Mono.class)) {
+                token = TypeUtil.getTypeArgument(token);
+            } else if (TypeUtil.isTypeOrSubTypeOf(token, Flux.class)) {
+                Type t = TypeUtil.getTypeArgument(token);
+                try {
+                    // TODO: anuchan - unwrap OperationStatus a different way
+                    // Check for OperationStatus<?>
+                    if (TypeUtil.isTypeOrSubTypeOf(t, Class.forName("com.azure.common.mgmt.OperationStatus"))) {
+                        token = t;
+                    }
+                } catch (ClassNotFoundException ignored) {
+                }
+            }
+
+            if (TypeUtil.isTypeOrSubTypeOf(token, RestResponse.class)) {
+                token = TypeUtil.getRestResponseBodyType(token);
+            }
+
+            try {
+                // TODO: anuchan - unwrap OperationStatus a different way
+                if (TypeUtil.isTypeOrSubTypeOf(token, Class.forName("com.azure.common.mgmt.OperationStatus"))) {
+                    // Get Type of 'T' from OperationStatus<T>
+                    token = TypeUtil.getTypeArgument(token);
+                }
+            } catch (Exception ignored) {
+            }
+        }
+        return token;
+    }
+
+    /**
+     * Checks the return type represents a decodable type.
+     *
+     * @param decodeData the decode metadata
+     * @return true if decodable, false otherwise.
+     */
+    private static boolean isReturnTypeDecodable(HttpResponseDecodeData decodeData) {
+        Type returnType = decodeData.returnType();
+        if (returnType == null) {
+            return false;
+        } else {
+            return !FluxUtil.isFluxByteBuf(returnType)
+                    && !(TypeUtil.isTypeOrSubTypeOf(returnType, Mono.class) && TypeUtil.isTypeOrSubTypeOf(TypeUtil.getTypeArgument(returnType), Void.class))
+                    && !TypeUtil.isTypeOrSubTypeOf(returnType, byte[].class)
+                    && !TypeUtil.isTypeOrSubTypeOf(returnType, Void.TYPE) && !TypeUtil.isTypeOrSubTypeOf(returnType, Void.class);
+        }
+    }
+
+    /**
+     * Checks an given value exists in an array.
+     *
+     * @param values array of ints
+     * @param searchValue value to check for existence
+     * @return true if value exists in the array, false otherwise
+     */
+    private static boolean contains(int[] values, int searchValue) {
+        Objects.requireNonNull(values);
+        for (int value : values) {
+            if (searchValue == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Ensure that request property and method is set in the response.
+     *
+     * @param httpResponse the response to validate
+     * @return the validated response
+     */
+    private static HttpResponse ensureRequestSet(HttpResponse httpResponse) {
+        Objects.requireNonNull(httpResponse.request());
+        Objects.requireNonNull(httpResponse.request().httpMethod());
+        return httpResponse;
+    }
+}
+
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseDecodeData.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseDecodeData.java
new file mode 100644
index 0000000000000..46efc1bf69486
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseDecodeData.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+import com.azure.common.http.rest.RestResponseBase;
+import com.azure.common.annotations.HeaderCollection;
+import com.azure.common.implementation.util.TypeUtil;
+import reactor.core.publisher.Mono;
+
+import java.lang.reflect.Type;
+
+/**
+ * Type representing necessary information required to decode a specific Http response.
+ */
+public interface HttpResponseDecodeData {
+    /**
+     * Get the type of the entity to deserialize the body.
+     *
+     * @return the return type
+     */
+    Type returnType();
+
+    /**
+     * Get the type of the entity to be used to deserialize 'Matching' headers.
+     *
+     * The 'header entity' is optional and client can choose it when a strongly typed model is needed for headers.
+     *
+     * 'Matching' headers are the HTTP response headers those with:
+     *   1. header names same as name of a properties in the 'header entity'.
+     *   2. header names start with value of {@link HeaderCollection} annotation applied to the properties in the 'header entity'.
+     *
+     * @return headers entity type
+     */
+     default Type headersType() {
+        Type token = this.returnType();
+        Type headersType = null;
+
+        if (TypeUtil.isTypeOrSubTypeOf(token, Mono.class)) {
+            token = TypeUtil.getTypeArgument(token);
+        }
+
+        // Only the RestResponseBase class supports a custom header type. All other RestResponse subclasses do not.
+        if (TypeUtil.isTypeOrSubTypeOf(token, RestResponseBase.class)) {
+            headersType = TypeUtil.getTypeArguments(TypeUtil.getSuperType(token, RestResponseBase.class))[0];
+        }
+
+        return headersType;
+    }
+
+    /**
+     * Get the expected HTTP response status codes.
+     *
+     * 1. If the returned int[] is null, then all 2XX status codes are considered as success code.
+     * 2. If the returned int[] is not-null, only the codes in the array are considered as success code.
+     *
+     * @return the expected HTTP response status codes
+     */
+    default int[] expectedStatusCodes() {
+         return null;
+    }
+
+    /**
+     * Get the type of the 'entity' in HTTP response content.
+     *
+     * When this method return non-null {@code java.lang.reflect.Type} then the raw HTTP response
+     * content will need to parsed to this {@code java.lang.reflect.Type} then converted to actual
+     * {@code returnType}.
+     *
+     * @return the type that the raw HTTP response content will be sent as
+     */
+     default Type returnValueWireType() {
+        return null;
+    }
+
+    /**
+     * Get the type of error body Object to be used for deserializing body when HTTP response status
+     * code is not one of the expected status codes.
+     *
+     * expected status codes are the codes returned by {@code expectedStatusCodes()},
+     * when {@code expectedStatusCodes()} returns null then 2XX are considered as expected status codes
+     *
+     * @return the type of error body Object to be used for deserializing body when HTTP response
+     * status code is not one of the expected status codes
+     */
+    default Class<?> exceptionBodyType() {
+         return Object.class;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseDecoder.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseDecoder.java
new file mode 100644
index 0000000000000..0e76b08906a63
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseDecoder.java
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+import com.azure.common.http.HttpResponse;
+import reactor.core.publisher.Mono;
+
+import java.io.Closeable;
+import java.lang.reflect.Type;
+
+/**
+ * Decode {@link HttpResponse} to {@link HttpDecodedResponse}.
+ */
+public final class HttpResponseDecoder {
+    // The adapter for deserialization
+    private final SerializerAdapter serializer;
+
+    /**
+     * Creates HttpResponseDecoder.
+     *
+     * @param serializer the serializer
+     */
+    public HttpResponseDecoder(SerializerAdapter serializer) {
+        this.serializer = serializer;
+    }
+
+    /**
+     * Asynchronously decodes a {@link HttpResponse}.
+     *
+     * @param response the publisher that emits response to be decoded
+     * @param decodeData the necessary data required to decode the response emitted by {@code response}
+     * @return a publisher that emits decoded HttpResponse upon subscription
+     */
+    public Mono<HttpDecodedResponse> decode(Mono<HttpResponse> response, HttpResponseDecodeData decodeData) {
+        return response.map(r -> new HttpDecodedResponse(r, this.serializer, decodeData));
+    }
+
+    /**
+     * A decorated HTTP response which has subscribable body and headers that supports lazy decoding.
+     *
+     * Subscribing to body kickoff http content reading, it's decoding then emission of decoded object.
+     * Subscribing to header kickoff header decoding and emission of decoded object.
+     */
+    public static final class HttpDecodedResponse implements Closeable {
+        private final HttpResponse response;
+        private final SerializerAdapter serializer;
+        private final HttpResponseDecodeData decodeData;
+        private Mono<Object> bodyCached;
+        private Mono<Object> headersCached;
+
+        /**
+         * Creates HttpDecodedResponse.
+         * Package private Ctr.
+         *
+         * @param response the publisher that emits the raw response upon subscription which needs to be decoded
+         * @param serializer the decoder
+         * @param decodeData the necessary data required to decode a Http response
+         */
+        HttpDecodedResponse(final HttpResponse response, SerializerAdapter serializer, HttpResponseDecodeData decodeData) {
+            if (HttpResponseBodyDecoder.isDecodable(response, decodeData)) {
+                this.response = response.buffer();
+            } else {
+                this.response = response;
+            }
+            this.serializer = serializer;
+            this.decodeData = decodeData;
+        }
+
+        /**
+         * @return get the raw response that this decoded response based on
+         */
+        public HttpResponse sourceResponse() {
+            return this.response;
+        }
+
+        /**
+         * Gets the publisher when subscribed the http content gets read, decoded
+         * and emitted. {@code Mono.empty()} gets emitted if the content is not
+         * decodable.
+         *
+         * @return publisher that emits decoded http content
+         */
+        public Mono<Object> decodedBody() {
+            if (this.bodyCached == null) {
+                this.bodyCached = HttpResponseBodyDecoder.decode(this.response,
+                        this.serializer,
+                        this.decodeData).cache();
+            }
+            return this.bodyCached;
+        }
+
+        /**
+         * Gets the publisher when subscribed the http header gets decoded and emitted.
+         * {@code Mono.empty()} gets emitted if the headers are not decodable.
+         *
+         * @return publisher that emits entity instance representing decoded http headers
+         */
+        public Mono<Object> decodedHeaders() {
+            if (this.headersCached == null) {
+                this.headersCached = HttpResponseHeaderDecoder.decode(this.response,
+                        this.serializer,
+                        this.decodeData).cache();
+            }
+            return this.headersCached;
+        }
+
+        /**
+         * @return the {@code java.lang.reflect.Type} used to decode the response body,
+         * null if the body is not decodable
+         */
+        public Type decodedType() {
+            return HttpResponseBodyDecoder.decodedType(this.response, this.decodeData);
+        }
+
+        /**
+         * @return true if the response status code is considered as error, false otherwise
+         */
+        public boolean isErrorStatus() {
+            return HttpResponseBodyDecoder.isErrorStatus(this.response, this.decodeData);
+        }
+
+        @Override
+        public void close() {
+            this.response.close();
+        }
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseHeaderDecoder.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseHeaderDecoder.java
new file mode 100644
index 0000000000000..f6e9bf0d4a2c4
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/HttpResponseHeaderDecoder.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+import com.azure.common.http.rest.RestException;
+import com.azure.common.http.rest.RestResponseBase;
+import com.azure.common.annotations.HeaderCollection;
+import com.azure.common.http.HttpHeader;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.implementation.util.TypeUtil;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Decoder to decode header of HTTP response.
+ */
+final class HttpResponseHeaderDecoder {
+    /**
+     * Decode headers of the http response.
+     *
+     * The decoding happens when caller subscribed to the returned {@code Mono<Object>},
+     * if the response header is not decodable then {@code Mono.empty()} will be returned.
+     *
+     * @param httpResponse the response containing the headers to be decoded
+     * @param serializer the adapter to use for decoding
+     * @param decodeData the necessary data required to decode a Http response
+     * @return publisher that emits decoded response header upon subscription if header is decodable,
+     * no emission if the header is not-decodable
+     */
+    static Mono<Object> decode(HttpResponse httpResponse, SerializerAdapter serializer, HttpResponseDecodeData decodeData) {
+        Type headerType = decodeData.headersType();
+        if (headerType == null) {
+            return Mono.empty();
+        } else {
+            return Mono.defer(() -> {
+                try {
+                    return Mono.just(deserializeHeaders(httpResponse.headers(), serializer, decodeData));
+                } catch (IOException e) {
+                    return Mono.error(new RestException("HTTP response has malformed headers", httpResponse, e));
+                }
+            });
+        }
+    }
+
+    /**
+     * Deserialize the provided headers returned from a REST API to an entity instance declared as
+     * the model to hold 'Matching' headers.
+     *
+     * 'Matching' headers are the REST API returned headers those with:
+     *      1. header names same as name of a properties in the entity.
+     *      2. header names start with value of {@link HeaderCollection} annotation applied to the properties in the entity.
+     *
+     * When needed, the 'header entity' types must be declared as first generic argument of {@link RestResponseBase} returned
+     * by java proxy method corresponding to the REST API.
+     * e.g.
+     * {@code Mono<RestResponseBase<FooMetadataHeaders, Void>> getMetadata(args);}
+     * {@code
+     *      class FooMetadataHeaders {
+     *          String name;
+     *          @HeaderCollection("header-collection-prefix-")
+     *          Map<String,String> headerCollection;
+     *      }
+     * }
+     *
+     * in the case of above example, this method produces an instance of FooMetadataHeaders from provided {@headers}.
+     *
+     * @param headers the REST API returned headers
+     * @return instance of header entity type created based on provided {@headers}, if header entity model does
+     * not exists then return null
+     * @throws IOException
+     */
+    private static Object deserializeHeaders(HttpHeaders headers, SerializerAdapter serializer, HttpResponseDecodeData decodeData) throws IOException {
+        final Type deserializedHeadersType = decodeData.headersType();
+        if (deserializedHeadersType == null) {
+            return null;
+        } else {
+            final String headersJsonString = serializer.serialize(headers, SerializerEncoding.JSON);
+            Object deserializedHeaders = serializer.deserialize(headersJsonString, deserializedHeadersType, SerializerEncoding.JSON);
+
+            final Class<?> deserializedHeadersClass = TypeUtil.getRawClass(deserializedHeadersType);
+            final Field[] declaredFields = deserializedHeadersClass.getDeclaredFields();
+            for (final Field declaredField : declaredFields) {
+                if (declaredField.isAnnotationPresent(HeaderCollection.class)) {
+                    final Type declaredFieldType = declaredField.getGenericType();
+                    if (TypeUtil.isTypeOrSubTypeOf(declaredField.getType(), Map.class)) {
+                        final Type[] mapTypeArguments = TypeUtil.getTypeArguments(declaredFieldType);
+                        if (mapTypeArguments.length == 2 && mapTypeArguments[0] == String.class && mapTypeArguments[1] == String.class) {
+                            final HeaderCollection headerCollectionAnnotation = declaredField.getAnnotation(HeaderCollection.class);
+                            final String headerCollectionPrefix = headerCollectionAnnotation.value().toLowerCase();
+                            final int headerCollectionPrefixLength = headerCollectionPrefix.length();
+                            if (headerCollectionPrefixLength > 0) {
+                                final Map<String, String> headerCollection = new HashMap<>();
+                                for (final HttpHeader header : headers) {
+                                    final String headerName = header.name();
+                                    if (headerName.toLowerCase().startsWith(headerCollectionPrefix)) {
+                                        headerCollection.put(headerName.substring(headerCollectionPrefixLength), header.value());
+                                    }
+                                }
+
+                                final boolean declaredFieldAccessibleBackup = declaredField.isAccessible();
+                                try {
+                                    if (!declaredFieldAccessibleBackup) {
+                                        declaredField.setAccessible(true);
+                                    }
+                                    declaredField.set(deserializedHeaders, headerCollection);
+                                } catch (IllegalAccessException ignored) {
+                                } finally {
+                                    if (!declaredFieldAccessibleBackup) {
+                                        declaredField.setAccessible(declaredFieldAccessibleBackup);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            return deserializedHeaders;
+        }
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/JsonFlatten.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/JsonFlatten.java
new file mode 100644
index 0000000000000..d682ae5a652c3
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/JsonFlatten.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used for flattening properties separated by '.'.
+ * E.g. a property with JsonProperty value "properties.value"
+ * will have "value" property under the "properties" tree on
+ * the wire.
+ *
+ */
+@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface JsonFlatten {
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/MalformedValueException.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/MalformedValueException.java
new file mode 100644
index 0000000000000..715f9cf80bc52
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/MalformedValueException.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+/**
+ * An exception thrown while parsing an invalid input during serialization or deserialization.
+ */
+public class MalformedValueException extends RuntimeException {
+    /**
+     * Create a MalformedValueException instance.
+     *
+     * @param message the exception message
+     */
+    public MalformedValueException(String message) {
+        super(message);
+    }
+
+    /**
+     * Create a MalformedValueException instance.
+     *
+     * @param message the exception message
+     * @param cause the actual cause
+     */
+    public MalformedValueException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/SerializerAdapter.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/SerializerAdapter.java
new file mode 100644
index 0000000000000..5993991bd9d1d
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/SerializerAdapter.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+import com.azure.common.implementation.CollectionFormat;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * An interface defining the behaviors of a serializer.
+ */
+public interface SerializerAdapter {
+    /**
+     * Serializes an object into a string.
+     *
+     * @param object the object to serialize
+     * @param encoding the encoding to use for serialization
+     * @return the serialized string. Null if the object to serialize is null
+     * @throws IOException exception from serialization
+     */
+    String serialize(Object object, SerializerEncoding encoding) throws IOException;
+
+    /**
+     * Serializes an object into a raw string. The leading and trailing quotes will be trimmed.
+     *
+     * @param object the object to serialize
+     * @return the serialized string. Null if the object to serialize is null
+     */
+    String serializeRaw(Object object);
+
+    /**
+     * Serializes a list into a string with the delimiter specified with the
+     * Swagger collection format joining each individual serialized items in
+     * the list.
+     *
+     * @param list the list to serialize
+     * @param format the Swagger collection format
+     * @return the serialized string
+     */
+    String serializeList(List<?> list, CollectionFormat format);
+
+    /**
+     * Deserializes a string into a {@link U} object.
+     *
+     * @param value the string value to deserialize
+     * @param <U> the type of the deserialized object
+     * @param type the type to deserialize
+     * @param encoding the encoding used in the serialized value
+     * @return the deserialized object
+     * @throws IOException exception from deserialization
+     */
+    <U> U deserialize(String value, Type type, SerializerEncoding encoding) throws IOException;
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/SerializerEncoding.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/SerializerEncoding.java
new file mode 100644
index 0000000000000..97cf623f6a29f
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/SerializerEncoding.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer;
+
+
+import com.azure.common.http.HttpHeaders;
+
+/**
+ * Supported serialization encoding formats.
+ */
+public enum SerializerEncoding {
+    /**
+     * JavaScript Object Notation.
+     */
+    JSON,
+
+    /**
+     * Extensible Markup Language.
+     */
+    XML;
+
+    /**
+     * Determines the serializer encoding to use based on the Content-Type header.
+     *
+     * @param headers the headers to check the encoding for
+     * @return the serializer encoding to use for the body
+     */
+    public static SerializerEncoding fromHeaders(HttpHeaders headers) {
+        String mimeContentType = headers.value("Content-Type");
+        if (mimeContentType != null) {
+            String[] parts = mimeContentType.split(";");
+            if (parts[0].equalsIgnoreCase("application/xml") || parts[0].equalsIgnoreCase("text/xml")) {
+                return XML;
+            }
+        }
+
+        return JSON;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesDeserializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesDeserializer.java
new file mode 100644
index 0000000000000..0f15c4f691e0f
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesDeserializer.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
+import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.azure.common.implementation.util.TypeUtil;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+/**
+ * Custom serializer for deserializing complex types with additional properties.
+ * If a complex type has a property named "additionalProperties" with serialized
+ * name empty ("") of type Map&lt;String, Object&gt;, all extra properties on the
+ * payload will be stored in this map.
+ */
+final class AdditionalPropertiesDeserializer extends StdDeserializer<Object> implements ResolvableDeserializer {
+    /**
+     * The default mapperAdapter for the current type.
+     */
+    private final JsonDeserializer<?> defaultDeserializer;
+
+    /**
+     * The object mapper for default deserializations.
+     */
+    private final ObjectMapper mapper;
+
+    /**
+     * Creates FlatteningDeserializer.
+     * @param vc handled type
+     * @param defaultDeserializer the default JSON mapperAdapter
+     * @param mapper the object mapper for default deserializations
+     */
+    protected AdditionalPropertiesDeserializer(Class<?> vc, JsonDeserializer<?> defaultDeserializer, ObjectMapper mapper) {
+        super(vc);
+        this.defaultDeserializer = defaultDeserializer;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @param mapper the object mapper for default deserializations
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule(final ObjectMapper mapper) {
+        SimpleModule module = new SimpleModule();
+        module.setDeserializerModifier(new BeanDeserializerModifier() {
+            @Override
+            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+                for (Class<?> c : TypeUtil.getAllClasses(beanDesc.getBeanClass())) {
+                    Field[] fields = c.getDeclaredFields();
+                    for (Field field : fields) {
+                        if ("additionalProperties".equalsIgnoreCase(field.getName())) {
+                            JsonProperty property = field.getAnnotation(JsonProperty.class);
+                            if (property != null && property.value().isEmpty()) {
+                                return new AdditionalPropertiesDeserializer(beanDesc.getBeanClass(), deserializer, mapper);
+                            }
+                        }
+                    }
+                }
+                return deserializer;
+            }
+        });
+        return module;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+        ObjectNode root = mapper.readTree(jp);
+        ObjectNode copy = root.deepCopy();
+
+        // compare top level fields and keep only missing fields
+        final Class<?> tClass = this.defaultDeserializer.handledType();
+        for (Class<?> c : TypeUtil.getAllClasses(tClass)) {
+            Field[] fields = c.getDeclaredFields();
+            for (Field field : fields) {
+                JsonProperty property = field.getAnnotation(JsonProperty.class);
+                String key = property.value().split("((?<!\\\\))\\.")[0];
+                if (!key.isEmpty()) {
+                    if (copy.has(key)) {
+                        copy.remove(key);
+                    }
+                }
+            }
+        }
+
+        // put into additional properties
+        root.put("additionalProperties", copy);
+
+        JsonParser parser = new JsonFactory().createParser(root.toString());
+        parser.nextToken();
+        return defaultDeserializer.deserialize(parser, ctxt);
+    }
+
+    @Override
+    public void resolve(DeserializationContext ctxt) throws JsonMappingException {
+        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesSerializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesSerializer.java
new file mode 100644
index 0000000000000..27b9b92fa8e96
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesSerializer.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.azure.common.implementation.util.TypeUtil;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+/**
+ * Custom serializer for serializing complex types with additional properties.
+ * If a complex type has a property named "additionalProperties" with serialized
+ * name empty ("") of type Map&lt;String, Object&gt;, all items in this map will
+ * become top level properties for this complex type.
+ */
+final class AdditionalPropertiesSerializer extends StdSerializer<Object> implements ResolvableSerializer {
+    /**
+     * The default mapperAdapter for the current type.
+     */
+    private final JsonSerializer<?> defaultSerializer;
+
+    /**
+     * The object mapper for default serializations.
+     */
+    private final ObjectMapper mapper;
+
+    /**
+     * Creates an instance of FlatteningSerializer.
+     * @param vc handled type
+     * @param defaultSerializer the default JSON serializer
+     * @param mapper the object mapper for default serializations
+     */
+    protected AdditionalPropertiesSerializer(Class<?> vc, JsonSerializer<?> defaultSerializer, ObjectMapper mapper) {
+        super(vc, false);
+        this.defaultSerializer = defaultSerializer;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @param mapper the object mapper for default serializations
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule(final ObjectMapper mapper) {
+        SimpleModule module = new SimpleModule();
+        module.setSerializerModifier(new BeanSerializerModifier() {
+            @Override
+            public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
+                for (Class<?> c : TypeUtil.getAllClasses(beanDesc.getBeanClass())) {
+                    if (c.isAssignableFrom(Object.class)) {
+                        continue;
+                    }
+                    Field[] fields = c.getDeclaredFields();
+                    for (Field field : fields) {
+                        if ("additionalProperties".equalsIgnoreCase(field.getName())) {
+                            JsonProperty property = field.getAnnotation(JsonProperty.class);
+                            if (property != null && property.value().isEmpty()) {
+                                return new AdditionalPropertiesSerializer(beanDesc.getBeanClass(), serializer, mapper);
+                            }
+                        }
+                    }
+                }
+                return serializer;
+            }
+        });
+        return module;
+    }
+
+    @Override
+    public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        // serialize the original object into JsonNode
+        ObjectNode root = mapper.valueToTree(value);
+        // take additional properties node out
+        Entry<String, JsonNode> additionalPropertiesField = null;
+        Iterator<Entry<String, JsonNode>> fields = root.fields();
+        while (fields.hasNext()) {
+            Entry<String, JsonNode> field = fields.next();
+            if ("additionalProperties".equalsIgnoreCase(field.getKey())) {
+                additionalPropertiesField = field;
+                break;
+            }
+        }
+        if (additionalPropertiesField != null) {
+            root.remove(additionalPropertiesField.getKey());
+            // put each item back in
+            ObjectNode extraProperties = (ObjectNode) additionalPropertiesField.getValue();
+            fields = extraProperties.fields();
+            while (fields.hasNext()) {
+                Entry<String, JsonNode> field = fields.next();
+                root.put(field.getKey(), field.getValue());
+            }
+        }
+
+        jgen.writeTree(root);
+    }
+
+    @Override
+    public void resolve(SerializerProvider provider) throws JsonMappingException {
+        ((ResolvableSerializer) defaultSerializer).resolve(provider);
+    }
+
+    @Override
+    public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSerializer) throws IOException {
+        serialize(value, gen, provider);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/Base64UrlSerializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/Base64UrlSerializer.java
new file mode 100644
index 0000000000000..bf0d1519e43a4
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/Base64UrlSerializer.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.azure.common.implementation.Base64Url;
+
+import java.io.IOException;
+
+/**
+ * Custom serializer for serializing {@code Byte[]} objects into Base64 strings.
+ */
+final class Base64UrlSerializer extends JsonSerializer<Base64Url> {
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule() {
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(Base64Url.class, new Base64UrlSerializer());
+        return module;
+    }
+
+    @Override
+    public void serialize(Base64Url value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        jgen.writeString(value.toString());
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/ByteArraySerializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/ByteArraySerializer.java
new file mode 100644
index 0000000000000..3515bdcd653e1
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/ByteArraySerializer.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+import java.io.IOException;
+
+/**
+ * Custom serializer for serializing {@code Byte[]} objects into Base64 strings.
+ */
+final class ByteArraySerializer extends JsonSerializer<Byte[]> {
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule() {
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(Byte[].class, new ByteArraySerializer());
+        return module;
+    }
+
+    @Override
+    public void serialize(Byte[] value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        byte[] bytes = new byte[value.length];
+        for (int i = 0; i < value.length; i++) {
+            bytes[i] = value[i];
+        }
+        jgen.writeBinary(bytes);
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DateTimeRfc1123Serializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DateTimeRfc1123Serializer.java
new file mode 100644
index 0000000000000..c8ff88a87eed5
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DateTimeRfc1123Serializer.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.azure.common.implementation.DateTimeRfc1123;
+
+import java.io.IOException;
+
+/**
+ * Custom serializer for serializing {@link DateTimeRfc1123} object into RFC1123 formats.
+ */
+final class DateTimeRfc1123Serializer extends JsonSerializer<DateTimeRfc1123> {
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule() {
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(DateTimeRfc1123.class, new DateTimeRfc1123Serializer());
+        return module;
+    }
+
+    @Override
+    public void serialize(DateTimeRfc1123 value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        if (provider.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) {
+            jgen.writeNumber(value.dateTime().toInstant().toEpochMilli());
+        } else {
+            jgen.writeString(value.toString()); //Use the default toString as it is RFC1123.
+        }
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DateTimeSerializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DateTimeSerializer.java
new file mode 100644
index 0000000000000..68a30cd9af5b3
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DateTimeSerializer.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+import java.io.IOException;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Custom serializer for serializing {@link OffsetDateTime} object into ISO8601 formats.
+ */
+final class DateTimeSerializer extends JsonSerializer<OffsetDateTime> {
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule() {
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(OffsetDateTime.class, new DateTimeSerializer());
+        return module;
+    }
+
+    @Override
+    public void serialize(OffsetDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        if (provider.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) {
+            jgen.writeNumber(value.toInstant().toEpochMilli());
+        } else {
+            jgen.writeString(toString(value));
+        }
+    }
+
+    /**
+     * Convert the provided OffsetDateTime to its String representation.
+     * @param offsetDateTime The OffsetDateTime to convert.
+     * @return The String representation of the provided offsetDateTime.
+     */
+    public static String toString(OffsetDateTime offsetDateTime) {
+        String result = null;
+        if (offsetDateTime != null) {
+            offsetDateTime = offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC);
+            result = DateTimeFormatter.ISO_INSTANT.format(offsetDateTime);
+            if (result.startsWith("+")) {
+                result = result.substring(1);
+            }
+        }
+        return result;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DurationSerializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DurationSerializer.java
new file mode 100644
index 0000000000000..97e8c8d5fa664
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/DurationSerializer.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+
+import java.io.IOException;
+import java.time.Duration;
+
+/**
+ * Custom serializer for serializing {@link Duration} object into ISO8601 formats.
+ */
+final class DurationSerializer extends JsonSerializer<Duration> {
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule() {
+        SimpleModule module = new SimpleModule();
+        module.addSerializer(Duration.class, new DurationSerializer());
+        return module;
+    }
+
+    @Override
+    public void serialize(Duration duration, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+        jsonGenerator.writeString(DurationSerializer.toString(duration));
+    }
+
+    /**
+     * Convert to provided Duration to an ISO 8601 String with a days component.
+     * @param duration The Duration to convert.
+     * @return The String representation of the provided Duration.
+     */
+    public static String toString(Duration duration) {
+        String result = null;
+        if (duration != null) {
+            if (duration.isZero()) {
+                result = "PT0S";
+            } else {
+                final StringBuilder builder = new StringBuilder();
+
+                builder.append('P');
+
+                final long days = duration.toDays();
+                if (days > 0) {
+                    builder.append(days);
+                    builder.append('D');
+                    duration = duration.minusDays(days);
+                }
+
+                final long hours = duration.toHours();
+                if (hours > 0) {
+                    builder.append('T');
+                    builder.append(hours);
+                    builder.append('H');
+                    duration = duration.minusHours(hours);
+                }
+
+                final long minutes = duration.toMinutes();
+                if (minutes > 0) {
+                    if (hours == 0) {
+                        builder.append('T');
+                    }
+
+                    builder.append(minutes);
+                    builder.append('M');
+                    duration = duration.minusMinutes(minutes);
+                }
+
+                final long seconds = duration.getSeconds();
+                if (seconds > 0) {
+                    if (hours == 0 && minutes == 0) {
+                        builder.append('T');
+                    }
+
+                    builder.append(seconds);
+                    duration = duration.minusSeconds(seconds);
+                }
+
+                long milliseconds = duration.toMillis();
+                if (milliseconds > 0) {
+                    if (hours == 0 && minutes == 0 && seconds == 0) {
+                        builder.append("T");
+                    }
+
+                    if (seconds == 0) {
+                        builder.append("0");
+                    }
+
+                    builder.append('.');
+
+                    if (milliseconds <= 99) {
+                        builder.append('0');
+
+                        if (milliseconds <= 9) {
+                            builder.append('0');
+                        }
+                    }
+
+                    // Remove trailing zeros.
+                    while (milliseconds % 10 == 0) {
+                        milliseconds /= 10;
+                    }
+                    builder.append(milliseconds);
+                }
+
+                if (seconds > 0 || milliseconds > 0) {
+                    builder.append('S');
+                }
+
+                result = builder.toString();
+            }
+        }
+        return result;
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/FlatteningDeserializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/FlatteningDeserializer.java
new file mode 100644
index 0000000000000..d042daf73f994
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/FlatteningDeserializer.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.DeserializationConfig;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
+import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.azure.common.implementation.serializer.JsonFlatten;
+import com.azure.common.implementation.util.TypeUtil;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+/**
+ * Custom serializer for deserializing complex types with wrapped properties.
+ * For example, a property with annotation @JsonProperty(value = "properties.name")
+ * will be mapped to a top level "name" property in the POJO model.
+ */
+final class FlatteningDeserializer extends StdDeserializer<Object> implements ResolvableDeserializer {
+    /**
+     * The default mapperAdapter for the current type.
+     */
+    private final JsonDeserializer<?> defaultDeserializer;
+
+    /**
+     * The object mapper for default deserializations.
+     */
+    private final ObjectMapper mapper;
+
+    /**
+     * Creates an instance of FlatteningDeserializer.
+     * @param vc handled type
+     * @param defaultDeserializer the default JSON mapperAdapter
+     * @param mapper the object mapper for default deserializations
+     */
+    protected FlatteningDeserializer(Class<?> vc, JsonDeserializer<?> defaultDeserializer, ObjectMapper mapper) {
+        super(vc);
+        this.defaultDeserializer = defaultDeserializer;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @param mapper the object mapper for default deserializations
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule(final ObjectMapper mapper) {
+        SimpleModule module = new SimpleModule();
+        module.setDeserializerModifier(new BeanDeserializerModifier() {
+            @Override
+            public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
+                if (beanDesc.getBeanClass().getAnnotation(JsonFlatten.class) != null) {
+                    return new FlatteningDeserializer(beanDesc.getBeanClass(), deserializer, mapper);
+                }
+                return deserializer;
+            }
+        });
+        return module;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
+        JsonNode root = mapper.readTree(jp);
+        final Class<?> tClass = this.defaultDeserializer.handledType();
+        for (Class<?> c : TypeUtil.getAllClasses(tClass)) {
+            // Ignore checks for Object type.
+            if (c.isAssignableFrom(Object.class)) {
+                continue;
+            }
+            for (Field field : c.getDeclaredFields()) {
+                JsonNode node = root;
+                JsonProperty property = field.getAnnotation(JsonProperty.class);
+                if (property != null) {
+                    String value = property.value();
+                    if (value.matches(".+[^\\\\]\\..+")) {
+                        String[] values = value.split("((?<!\\\\))\\.");
+                        for (String val : values) {
+                            val = val.replace("\\.", ".");
+                            node = node.get(val);
+                            if (node == null) {
+                                break;
+                            }
+                        }
+                        ((ObjectNode) root).put(value, node);
+                    }
+                }
+            }
+        }
+        JsonParser parser = new JsonFactory().createParser(root.toString());
+        parser.nextToken();
+        return defaultDeserializer.deserialize(parser, ctxt);
+    }
+
+    @Override
+    public void resolve(DeserializationContext ctxt) throws JsonMappingException {
+        ((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/FlatteningSerializer.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/FlatteningSerializer.java
new file mode 100644
index 0000000000000..4b023ad8cc48c
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/FlatteningSerializer.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+import com.fasterxml.jackson.databind.ser.ResolvableSerializer;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.azure.common.implementation.serializer.JsonFlatten;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.time.Duration;
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Custom serializer for serializing types with wrapped properties.
+ * For example, a property with annotation @JsonProperty(value = "properties.name")
+ * will be mapped from a top level "name" property in the POJO model to
+ * {'properties' : { 'name' : 'my_name' }} in the serialized payload.
+ */
+class FlatteningSerializer extends StdSerializer<Object> implements ResolvableSerializer {
+    /**
+     * The default mapperAdapter for the current type.
+     */
+    private final JsonSerializer<?> defaultSerializer;
+
+    /**
+     * The object mapper for default serializations.
+     */
+    private final ObjectMapper mapper;
+
+    /**
+     * Creates an instance of FlatteningSerializer.
+     * @param vc handled type
+     * @param defaultSerializer the default JSON serializer
+     * @param mapper the object mapper for default serializations
+     */
+    protected FlatteningSerializer(Class<?> vc, JsonSerializer<?> defaultSerializer, ObjectMapper mapper) {
+        super(vc, false);
+        this.defaultSerializer = defaultSerializer;
+        this.mapper = mapper;
+    }
+
+    /**
+     * Gets a module wrapping this serializer as an adapter for the Jackson
+     * ObjectMapper.
+     *
+     * @param mapper the object mapper for default serializations
+     * @return a simple module to be plugged onto Jackson ObjectMapper.
+     */
+    public static SimpleModule getModule(final ObjectMapper mapper) {
+        SimpleModule module = new SimpleModule();
+        module.setSerializerModifier(new BeanSerializerModifier() {
+            @Override
+            public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer<?> serializer) {
+                if (beanDesc.getBeanClass().getAnnotation(JsonFlatten.class) != null) {
+                    return new FlatteningSerializer(beanDesc.getBeanClass(), serializer, mapper);
+                }
+                return serializer;
+            }
+        });
+        return module;
+    }
+
+    private List<Field> getAllDeclaredFields(Class<?> clazz) {
+        List<Field> fields = new ArrayList<Field>();
+        while (clazz != null && !clazz.equals(Object.class)) {
+            for (Field f : clazz.getDeclaredFields()) {
+                int mod = f.getModifiers();
+                if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod)) {
+                    fields.add(f);
+                }
+            }
+            clazz = clazz.getSuperclass();
+        }
+        return fields;
+    }
+
+    @SuppressWarnings("unchecked")
+    private void escapeMapKeys(Object value) {
+        if (value == null) {
+            return;
+        }
+
+        if (value.getClass().isPrimitive()
+                || value.getClass().isEnum()
+                || value instanceof OffsetDateTime
+                || value instanceof Duration
+                || value instanceof String) {
+            return;
+        }
+
+        int mod = value.getClass().getModifiers();
+        if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) {
+            return;
+        }
+
+        if (value instanceof List<?>) {
+            for (Object val : ((List<?>) value)) {
+                escapeMapKeys(val);
+            }
+            return;
+        }
+
+        if (value instanceof Map<?, ?>) {
+            for (String key : new HashSet<>(((Map<String, Object>) value).keySet())) {
+                if (key.contains(".")) {
+                    String newKey = key.replaceAll("((?<!\\\\))\\.", "\\\\.");
+                    Object val = ((Map<String, Object>) value).remove(key);
+                    ((Map<String, Object>) value).put(newKey, val);
+                }
+            }
+            for (Object val : ((Map<?, ?>) value).values()) {
+                escapeMapKeys(val);
+            }
+            return;
+        }
+
+        for (Field f : getAllDeclaredFields(value.getClass())) {
+            f.setAccessible(true);
+            try {
+                escapeMapKeys(f.get(value));
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
+        if (value == null) {
+            jgen.writeNull();
+            return;
+        }
+        escapeMapKeys(value);
+        // BFS for all collapsed properties
+        ObjectNode root = mapper.valueToTree(value);
+        ObjectNode res = root.deepCopy();
+        Queue<ObjectNode> source = new LinkedBlockingQueue<ObjectNode>();
+        Queue<ObjectNode> target = new LinkedBlockingQueue<ObjectNode>();
+        source.add(root);
+        target.add(res);
+        while (!source.isEmpty()) {
+            ObjectNode current = source.poll();
+            ObjectNode resCurrent = target.poll();
+            Iterator<Map.Entry<String, JsonNode>> fields = current.fields();
+            while (fields.hasNext()) {
+                Map.Entry<String, JsonNode> field = fields.next();
+                ObjectNode node = resCurrent;
+                String key = field.getKey();
+                JsonNode outNode = resCurrent.get(key);
+                if (key.matches(".+[^\\\\]\\..+")) {
+                    // Handle flattening properties
+                    //
+                    String[] values = key.split("((?<!\\\\))\\.");
+                    for (int i = 0; i < values.length; ++i) {
+                        values[i] = values[i].replace("\\.", ".");
+                        if (i == values.length - 1) {
+                            break;
+                        }
+                        String val = values[i];
+                        if (node.has(val)) {
+                            node = (ObjectNode) node.get(val);
+                        } else {
+                            ObjectNode child = new ObjectNode(JsonNodeFactory.instance);
+                            node.put(val, child);
+                            node = child;
+                        }
+                    }
+                    node.set(values[values.length - 1], resCurrent.get(key));
+                    resCurrent.remove(key);
+                    outNode = node.get(values[values.length - 1]);
+                } else if (key.matches(".*[^\\\\]\\\\..+")) {
+                    // Handle escaped map key
+                    //
+                    String originalKey = key.replaceAll("\\\\.", ".");
+                    resCurrent.remove(key);
+                    resCurrent.put(originalKey, outNode);
+                }
+                if (field.getValue() instanceof ObjectNode) {
+                    source.add((ObjectNode) field.getValue());
+                    target.add((ObjectNode) outNode);
+                } else if (field.getValue() instanceof ArrayNode
+                        && (field.getValue()).size() > 0
+                        && (field.getValue()).get(0) instanceof ObjectNode) {
+                    Iterator<JsonNode> sourceIt = field.getValue().elements();
+                    Iterator<JsonNode> targetIt = outNode.elements();
+                    while (sourceIt.hasNext()) {
+                        source.add((ObjectNode) sourceIt.next());
+                        target.add((ObjectNode) targetIt.next());
+                    }
+                }
+            }
+        }
+        jgen.writeTree(res);
+    }
+
+    @Override
+    public void resolve(SerializerProvider provider) throws JsonMappingException {
+        ((ResolvableSerializer) defaultSerializer).resolve(provider);
+    }
+
+    @Override
+    public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider, TypeSerializer typeSerializer) throws IOException {
+        serialize(value, gen, provider);
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/JacksonAdapter.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/JacksonAdapter.java
new file mode 100644
index 0000000000000..fedd95fb1b19b
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/JacksonAdapter.java
@@ -0,0 +1,191 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.azure.common.implementation.CollectionFormat;
+import com.azure.common.implementation.serializer.MalformedValueException;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of {@link SerializerAdapter} for Jackson.
+ */
+public class JacksonAdapter implements SerializerAdapter {
+    /**
+     * An instance of {@link ObjectMapper} to serialize/deserialize objects.
+     */
+    private final ObjectMapper mapper;
+
+    /**
+     * An instance of {@link ObjectMapper} that does not do flattening.
+     */
+    private final ObjectMapper simpleMapper;
+
+    private final XmlMapper xmlMapper;
+
+    /**
+     * Creates a new JacksonAdapter instance with default mapper settings.
+     */
+    public JacksonAdapter() {
+        simpleMapper = initializeObjectMapper(new ObjectMapper());
+        xmlMapper = initializeObjectMapper(new XmlMapper());
+        xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true);
+        xmlMapper.setDefaultUseWrapper(false);
+        ObjectMapper flatteningMapper = initializeObjectMapper(new ObjectMapper())
+                .registerModule(FlatteningSerializer.getModule(simpleMapper()))
+                .registerModule(FlatteningDeserializer.getModule(simpleMapper()));
+        mapper = initializeObjectMapper(new ObjectMapper())
+                // Order matters: must register in reverse order of hierarchy
+                .registerModule(AdditionalPropertiesSerializer.getModule(flatteningMapper))
+                .registerModule(AdditionalPropertiesDeserializer.getModule(flatteningMapper))
+                .registerModule(FlatteningSerializer.getModule(simpleMapper()))
+                .registerModule(FlatteningDeserializer.getModule(simpleMapper()));    }
+
+    /**
+     * Gets a static instance of {@link ObjectMapper} that doesn't handle flattening.
+     *
+     * @return an instance of {@link ObjectMapper}.
+     */
+    protected ObjectMapper simpleMapper() {
+        return simpleMapper;
+    }
+
+    /**
+     * @return the original serializer type
+     */
+    public ObjectMapper serializer() {
+        return mapper;
+    }
+
+    @Override
+    public String serialize(Object object, SerializerEncoding encoding) throws IOException {
+        if (object == null) {
+            return null;
+        }
+        StringWriter writer = new StringWriter();
+        if (encoding == SerializerEncoding.XML) {
+            xmlMapper.writeValue(writer, object);
+        } else {
+            serializer().writeValue(writer, object);
+        }
+
+        return writer.toString();
+    }
+
+    @Override
+    public String serializeRaw(Object object) {
+        if (object == null) {
+            return null;
+        }
+        try {
+            return serialize(object, SerializerEncoding.JSON).replaceAll("^\"*", "").replaceAll("\"*$", "");
+        } catch (IOException ex) {
+            return null;
+        }
+    }
+
+    @Override
+    public String serializeList(List<?> list, CollectionFormat format) {
+        if (list == null) {
+            return null;
+        }
+        List<String> serialized = new ArrayList<>();
+        for (Object element : list) {
+            String raw = serializeRaw(element);
+            serialized.add(raw != null ? raw : "");
+        }
+        return String.join(format.getDelimiter(), serialized);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T deserialize(String value, final Type type, SerializerEncoding encoding) throws IOException {
+        if (value == null || value.isEmpty()) {
+            return null;
+        }
+
+        final JavaType javaType = createJavaType(type);
+        try {
+            if (encoding == SerializerEncoding.XML) {
+                return (T) xmlMapper.readValue(value, javaType);
+            } else {
+                return (T) serializer().readValue(value, javaType);
+            }
+        } catch (JsonParseException jpe) {
+            throw new MalformedValueException(jpe.getMessage(), jpe);
+        }
+    }
+
+    /**
+     * Initializes an instance of JacksonMapperAdapter with default configurations
+     * applied to the object mapper.
+     *
+     * @param mapper the object mapper to use.
+     */
+    private static <T extends ObjectMapper> T initializeObjectMapper(T mapper) {
+        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
+                .configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true)
+                .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
+                .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true)
+                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+                .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
+                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
+                .registerModule(new JavaTimeModule())
+                .registerModule(ByteArraySerializer.getModule())
+                .registerModule(Base64UrlSerializer.getModule())
+                .registerModule(DateTimeSerializer.getModule())
+                .registerModule(DateTimeRfc1123Serializer.getModule())
+                .registerModule(DurationSerializer.getModule());
+        mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
+                .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
+                .withSetterVisibility(JsonAutoDetect.Visibility.NONE)
+                .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
+                .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
+        return mapper;
+    }
+
+    private JavaType createJavaType(Type type) {
+        JavaType result;
+        if (type == null) {
+            result = null;
+        }
+        else if (type instanceof JavaType) {
+            result = (JavaType) type;
+        }
+        else if (type instanceof ParameterizedType) {
+            final ParameterizedType parameterizedType = (ParameterizedType) type;
+            final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
+            JavaType[] javaTypeArguments = new JavaType[actualTypeArguments.length];
+            for (int i = 0; i != actualTypeArguments.length; i++) {
+                javaTypeArguments[i] = createJavaType(actualTypeArguments[i]);
+            }
+            result = mapper.getTypeFactory().constructParametricType((Class<?>) parameterizedType.getRawType(), javaTypeArguments);
+        }
+        else {
+            result = mapper.getTypeFactory().constructType(type);
+        }
+        return result;
+    }
+
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/package-info.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/package-info.java
new file mode 100644
index 0000000000000..6008e2604b08b
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/jackson/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing serialization and deserialization implementation using JSON library for Java (Jackson).
+ */
+package com.azure.common.implementation.serializer.jackson;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/serializer/package-info.java b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/package-info.java
new file mode 100644
index 0000000000000..4d312dbefc673
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/serializer/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing interfaces describing serialization and deserialization contract.
+ */
+package com.azure.common.implementation.serializer;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/util/Base64Util.java b/common/azure-common/src/main/java/com/azure/common/implementation/util/Base64Util.java
new file mode 100644
index 0000000000000..ffca5e3bfcaa3
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/util/Base64Util.java
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.util;
+
+import java.util.Base64;
+
+/**
+ * Utility type exposing Base64 encoding and decoding methods.
+ */
+public final class Base64Util {
+    /**
+     * Encodes a byte array to base64.
+     * @param src the byte array to encode
+     * @return the base64 encoded bytes
+     */
+    public static byte[] encode(byte[] src) {
+        return src == null ? null : Base64.getEncoder().encode(src);
+    }
+
+    /**
+     * Encodes a byte array to base64 URL format.
+     * @param src the byte array to encode
+     * @return the base64 URL encoded bytes
+     */
+    public static byte[] encodeURLWithoutPadding(byte[] src) {
+        return src == null ? null : Base64.getUrlEncoder().withoutPadding().encode(src);
+    }
+
+    /**
+     * Encodes a byte array to a base 64 string.
+     * @param src the byte array to encode
+     * @return the base64 encoded string
+     */
+    public static String encodeToString(byte[] src) {
+        return src == null ? null : Base64.getEncoder().encodeToString(src);
+    }
+
+    /**
+     * Decodes a base64 encoded byte array.
+     * @param encoded the byte array to decode
+     * @return the decoded byte array
+     */
+    public static byte[] decode(byte[] encoded) {
+        return encoded == null ? null : Base64.getDecoder().decode(encoded);
+    }
+
+    /**
+     * Decodes a byte array in base64 URL format.
+     * @param src the byte array to decode
+     * @return the decoded byte array
+     */
+    public static byte[] decodeURL(byte[] src) {
+        return src == null ? null : Base64.getUrlDecoder().decode(src);
+    }
+
+    /**
+     * Decodes a base64 encoded string.
+     * @param encoded the string to decode
+     * @return the decoded byte array
+     */
+    public static byte[] decodeString(String encoded) {
+        return encoded == null ? null : Base64.getDecoder().decode(encoded);
+    }
+
+    // Private Ctr
+    private Base64Util() {
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/util/FluxUtil.java b/common/azure-common/src/main/java/com/azure/common/implementation/util/FluxUtil.java
new file mode 100644
index 0000000000000..ca732b4c5d3c0
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/util/FluxUtil.java
@@ -0,0 +1,407 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.util;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.ReferenceCountUtil;
+import org.reactivestreams.Subscriber;
+import org.reactivestreams.Subscription;
+import reactor.core.CoreSubscriber;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.Operators;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+
+/**
+ * Utility type exposing methods to deal with {@link Flux}.
+ */
+public final class FluxUtil {
+    /**
+     * Checks if a type is Flux&lt;ByteBuf&gt;.
+     *
+     * @param entityType the type to check
+     * @return whether the type represents a Flux that emits ByteBuf
+     */
+    public static boolean isFluxByteBuf(Type entityType) {
+        if (TypeUtil.isTypeOrSubTypeOf(entityType, Flux.class)) {
+            final Type innerType = TypeUtil.getTypeArguments(entityType)[0];
+            if (TypeUtil.isTypeOrSubTypeOf(innerType, ByteBuf.class)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Collects ByteBuf emitted by a Flux into a byte array.
+     * @param stream A stream which emits ByteBuf instances.
+     * @param autoReleaseEnabled if ByteBuf instances in stream gets automatically released as they consumed
+     * @return A Mono which emits the concatenation of all the ByteBuf instances given by the source Flux.
+     */
+    public static Mono<byte[]> collectBytesInByteBufStream(Flux<ByteBuf> stream, boolean autoReleaseEnabled) {
+        if (autoReleaseEnabled) {
+            // A stream is auto-release enabled means - the ByteBuf chunks in the stream get
+            // released as consumer consumes each chunk.
+            return Mono.using(Unpooled::compositeBuffer,
+                    cbb -> stream.collect(() -> cbb,
+                            (cbb1, buffer) -> cbb1.addComponent(true, Unpooled.wrappedBuffer(buffer).retain())),
+                    ReferenceCountUtil::release)
+                    .filter((CompositeByteBuf cbb) -> cbb.isReadable())
+                    .map(FluxUtil::byteBufToArray);
+        } else {
+            return stream.collect(Unpooled::compositeBuffer,
+                    (cbb1, buffer) -> cbb1.addComponent(true, Unpooled.wrappedBuffer(buffer)))
+                    .filter((CompositeByteBuf cbb) -> cbb.isReadable())
+                    .map(FluxUtil::byteBufToArray);
+        }
+    }
+
+    /**
+     * Splits a ByteBuf into ByteBuf chunks.
+     *
+     * @param whole the ByteBuf to split
+     * @param chunkSize the maximum size of each ByteBuf chunk
+     * @return A stream that emits chunks of the original whole ByteBuf
+     */
+    public static Flux<ByteBuf> split(final ByteBuf whole, final int chunkSize) {
+        return Flux.generate(whole::readerIndex, (readFromIndex, synchronousSync) -> {
+            final int writerIndex = whole.writerIndex();
+            //
+            if (readFromIndex >= writerIndex) {
+                synchronousSync.complete();
+                return writerIndex;
+            } else {
+                int readSize = Math.min(writerIndex - readFromIndex, chunkSize);
+                // Netty slice operation will not increment the ref count.
+                //
+                // Here we invoke 'retain' on each slice, since
+                // consumer of the returned Flux stream is responsible for
+                // releasing each chunk as it gets consumed.
+                //
+                synchronousSync.next(whole.slice(readFromIndex, readSize).retain());
+                return readFromIndex + readSize;
+            }
+        });
+    }
+
+    /**
+     * Gets the content of the provided ByteBuf as a byte array.
+     * This method will create a new byte array even if the ByteBuf can
+     * have optionally backing array.
+     *
+     *
+     * @param byteBuf the byte buffer
+     * @return the byte array
+     */
+    public static byte[] byteBufToArray(ByteBuf byteBuf) {
+        int length = byteBuf.readableBytes();
+        byte[] byteArray = new byte[length];
+        byteBuf.getBytes(byteBuf.readerIndex(), byteArray);
+        return byteArray;
+    }
+
+    /**
+     * Collects byte buffers emitted by a Flux into a ByteBuf.
+     *
+     * @param stream A stream which emits ByteBuf instances.
+     * @param autoReleaseEnabled if ByteBuf instances in stream gets automatically released as they consumed
+     * @return A Mono which emits the concatenation of all the byte buffers given by the source Flux.
+     */
+    public static Mono<ByteBuf> collectByteBufStream(Flux<ByteBuf> stream, boolean autoReleaseEnabled) {
+        if (autoReleaseEnabled) {
+            Mono<ByteBuf> mergedCbb = Mono.using(
+                    // Resource supplier
+                    () -> {
+                        CompositeByteBuf initialCbb = Unpooled.compositeBuffer();
+                        return initialCbb;
+                    },
+                    // source Mono creator
+                    (CompositeByteBuf initialCbb) -> {
+                        Mono<CompositeByteBuf> reducedCbb = stream.reduce(initialCbb, (CompositeByteBuf currentCbb, ByteBuf nextBb) -> {
+                            CompositeByteBuf updatedCbb = currentCbb.addComponent(nextBb.retain());
+                            return updatedCbb;
+                        });
+                        //
+                        return reducedCbb
+                                .doOnNext((CompositeByteBuf cbb) -> cbb.writerIndex(cbb.capacity()))
+                                .filter((CompositeByteBuf cbb) -> cbb.isReadable());
+                    },
+                    // Resource cleaner
+                    (CompositeByteBuf finalCbb) -> finalCbb.release());
+            return mergedCbb;
+        } else {
+            return stream.collect(Unpooled::compositeBuffer,
+                    (cbb1, buffer) -> cbb1.addComponent(true, Unpooled.wrappedBuffer(buffer)))
+                    .filter((CompositeByteBuf cbb) -> cbb.isReadable())
+                    .map(bb -> bb);
+        }
+    }
+
+    private static final int DEFAULT_CHUNK_SIZE = 1024 * 64;
+
+    //region Utility methods to create Flux<ByteBuf> that read and emits chunks from AsynchronousFileChannel.
+
+    /**
+     * Creates a {@link Flux} from an {@link AsynchronousFileChannel}
+     * which reads part of a file into chunks of the given size.
+     *
+     * @param fileChannel The file channel.
+     * @param chunkSize the size of file chunks to read.
+     * @param offset The offset in the file to begin reading.
+     * @param length The number of bytes to read from the file.
+     * @return the Flowable.
+     */
+    public static Flux<ByteBuf> byteBufStreamFromFile(AsynchronousFileChannel fileChannel, int chunkSize, long offset, long length) {
+        return new ByteBufStreamFromFile(fileChannel, chunkSize, offset, length);
+    }
+
+    /**
+     * Creates a {@link Flux} from an {@link AsynchronousFileChannel}
+     * which reads part of a file.
+     *
+     * @param fileChannel The file channel.
+     * @param offset The offset in the file to begin reading.
+     * @param length The number of bytes to read from the file.
+     * @return the Flowable.
+     */
+    public static Flux<ByteBuf> byteBufStreamFromFile(AsynchronousFileChannel fileChannel, long offset, long length) {
+        return byteBufStreamFromFile(fileChannel, DEFAULT_CHUNK_SIZE, offset, length);
+    }
+
+    /**
+     * Creates a {@link Flux} from an {@link AsynchronousFileChannel}
+     * which reads the entire file.
+     *
+     * @param fileChannel The file channel.
+     * @return The AsyncInputStream.
+     */
+    public static Flux<ByteBuf> byteBufStreamFromFile(AsynchronousFileChannel fileChannel) {
+        try {
+            long size = fileChannel.size();
+            return byteBufStreamFromFile(fileChannel, DEFAULT_CHUNK_SIZE, 0, size);
+        } catch (IOException e) {
+            return Flux.error(e);
+        }
+    }
+    //endregion
+
+    //region ByteBufStreamFromFile implementation
+    private static final class ByteBufStreamFromFile extends Flux<ByteBuf> {
+        private final ByteBufAllocator alloc;
+        private final AsynchronousFileChannel fileChannel;
+        private final int chunkSize;
+        private final long offset;
+        private final long length;
+
+        ByteBufStreamFromFile(AsynchronousFileChannel fileChannel, int chunkSize, long offset, long length) {
+            this.alloc = ByteBufAllocator.DEFAULT;
+            this.fileChannel = fileChannel;
+            this.chunkSize = chunkSize;
+            this.offset = offset;
+            this.length = length;
+        }
+
+        @Override
+        public void subscribe(CoreSubscriber<? super ByteBuf> actual) {
+            FileReadSubscription subscription = new FileReadSubscription(actual, fileChannel, alloc, chunkSize, offset, length);
+            actual.onSubscribe(subscription);
+        }
+
+        static final class FileReadSubscription implements Subscription, CompletionHandler<Integer, ByteBuf> {
+            private static final int NOT_SET = -1;
+            private static final long serialVersionUID = -6831808726875304256L;
+            //
+            private final Subscriber<? super ByteBuf> subscriber;
+            private volatile long position;
+            //
+            private final AsynchronousFileChannel fileChannel;
+            private final ByteBufAllocator alloc;
+            private final int chunkSize;
+            private final long offset;
+            private final long length;
+            //
+            private volatile boolean done;
+            private Throwable error;
+            private volatile ByteBuf next;
+            private volatile boolean cancelled;
+            //
+            volatile int wip;
+            @SuppressWarnings("rawtypes")
+            static final AtomicIntegerFieldUpdater<FileReadSubscription> WIP = AtomicIntegerFieldUpdater.newUpdater(FileReadSubscription.class, "wip");
+            volatile long requested;
+            @SuppressWarnings("rawtypes")
+            static final AtomicLongFieldUpdater<FileReadSubscription> REQUESTED = AtomicLongFieldUpdater.newUpdater(FileReadSubscription.class, "requested");
+            //
+
+            FileReadSubscription(Subscriber<? super ByteBuf> subscriber, AsynchronousFileChannel fileChannel, ByteBufAllocator alloc, int chunkSize, long offset, long length) {
+                this.subscriber = subscriber;
+                //
+                this.fileChannel = fileChannel;
+                this.alloc = alloc;
+                this.chunkSize = chunkSize;
+                this.offset = offset;
+                this.length = length;
+                //
+                this.position = NOT_SET;
+            }
+
+            //region Subscription implementation
+
+            @Override
+            public void request(long n) {
+                if (Operators.validate(n)) {
+                    Operators.addCap(REQUESTED, this, n);
+                    drain();
+                }
+            }
+
+            @Override
+            public void cancel() {
+                this.cancelled = true;
+            }
+
+            //endregion
+
+            //region CompletionHandler implementation
+
+            @Override
+            public void completed(Integer bytesRead, ByteBuf buffer) {
+                if (!cancelled) {
+                    if (bytesRead == -1) {
+                        done = true;
+                    } else {
+                        // use local variable to perform fewer volatile reads
+                        long pos = position;
+                        //
+                        int bytesWanted = (int) Math.min(bytesRead, maxRequired(pos));
+                        buffer.writerIndex(bytesWanted);
+                        long position2 = pos + bytesWanted;
+                        //noinspection NonAtomicOperationOnVolatileField
+                        position = position2;
+                        next = buffer;
+                        if (position2 >= offset + length) {
+                            done = true;
+                        }
+                    }
+                    drain();
+                }
+            }
+
+            @Override
+            public void failed(Throwable exc, ByteBuf attachment) {
+                if (!cancelled) {
+                    // must set error before setting done to true
+                    // so that is visible in drain loop
+                    error = exc;
+                    done = true;
+                    drain();
+                }
+            }
+
+            //endregion
+
+            private void drain() {
+                if (WIP.getAndIncrement(this) != 0) {
+                    return;
+                }
+                // on first drain (first request) we initiate the first read
+                if (position == NOT_SET) {
+                    position = offset;
+                    doRead();
+                }
+                int missed = 1;
+                for (;;) {
+                    if (cancelled) {
+                        return;
+                    }
+                    if (REQUESTED.get(this) > 0) {
+                        boolean emitted = false;
+                        // read d before next to avoid race
+                        boolean d = done;
+                        ByteBuf bb = next;
+                        if (bb != null) {
+                            next = null;
+                            //
+                            // try {
+                            subscriber.onNext(bb);
+                            // } finally {
+                                // Note: Don't release here, we follow netty disposal pattern
+                                // it's consumers responsiblity to release chunks after consumption.
+                                //
+                                // ReferenceCountUtil.release(bb);
+                            // }
+                            //
+                            emitted = true;
+                        } else {
+                            emitted = false;
+                        }
+                        if (d) {
+                            if (error != null) {
+                                subscriber.onError(error);
+                                // exit without reducing wip so that further drains will be NOOP
+                                return;
+                            } else {
+                                subscriber.onComplete();
+                                // exit without reducing wip so that further drains will be NOOP
+                                return;
+                            }
+                        }
+                        if (emitted) {
+                            // do this after checking d to avoid calling read
+                            // when done
+                            Operators.produced(REQUESTED, this, 1);
+                            //
+                            doRead();
+                        }
+                    }
+                    missed = WIP.addAndGet(this, -missed);
+                    if (missed == 0) {
+                        return;
+                    }
+                }
+            }
+
+            private void doRead() {
+                // use local variable to limit volatile reads
+                long pos = position;
+                int readSize = Math.min(chunkSize, maxRequired(pos));
+                ByteBuf innerBuf = alloc.buffer(readSize, readSize);
+                fileChannel.read(innerBuf.nioBuffer(0, readSize), pos, innerBuf, this);
+            }
+
+            private int maxRequired(long pos) {
+                long maxRequired = offset + length - pos;
+                if (maxRequired <= 0) {
+                    return 0;
+                } else {
+                    int m = (int) (maxRequired);
+                    // support really large files by checking for overflow
+                    if (m < 0) {
+                        return Integer.MAX_VALUE;
+                    } else {
+                        return m;
+                    }
+                }
+            }
+        }
+    }
+
+    //endregion
+
+    // Private Ctr
+    private FluxUtil() {
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/util/TypeUtil.java b/common/azure-common/src/main/java/com/azure/common/implementation/util/TypeUtil.java
new file mode 100644
index 0000000000000..bdae35c928e46
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/util/TypeUtil.java
@@ -0,0 +1,207 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.util;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility type exposing methods to deal with {@link Type}.
+ */
+public final class TypeUtil {
+    /**
+     * Find all super classes including provided class.
+     *
+     * @param clazz the raw class to find super types for
+     * @return the list of super classes
+     */
+    public static List<Class<?>> getAllClasses(Class<?> clazz) {
+        List<Class<?>> types = new ArrayList<>();
+        while (clazz != null) {
+            types.add(clazz);
+            clazz = clazz.getSuperclass();
+        }
+        return types;
+    }
+
+    /**
+     * Get the generic arguments for a type.
+     *
+     * @param type the type to get arguments
+     * @return the generic arguments, empty if type is not parameterized
+     */
+    public static Type[] getTypeArguments(Type type) {
+        if (!(type instanceof ParameterizedType)) {
+            return new Type[0];
+        }
+        return ((ParameterizedType) type).getActualTypeArguments();
+    }
+
+    /**
+     * Get the generic argument, or the first if the type has more than one.
+     *
+     * @param type the type to get arguments
+     * @return the generic argument, null if type is not parameterized
+     */
+    public static Type getTypeArgument(Type type) {
+        if (!(type instanceof ParameterizedType)) {
+            return null;
+        }
+        return ((ParameterizedType) type).getActualTypeArguments()[0];
+    }
+
+    /**
+     * Get the raw class for a given type.
+     *
+     * @param type the input type
+     * @return the raw class
+     */
+    public static Class<?> getRawClass(Type type) {
+        if (type instanceof ParameterizedType) {
+            return (Class<?>) ((ParameterizedType) type).getRawType();
+        } else {
+            return (Class<?>) type;
+        }
+    }
+
+    /**
+     * Get the super type for a given type.
+     *
+     * @param type the input type
+     * @return the direct super type
+     */
+    public static Type getSuperType(Type type) {
+        if (type instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) type;
+            Type genericSuperClass = ((Class<?>) parameterizedType.getRawType()).getGenericSuperclass();
+            if (genericSuperClass instanceof ParameterizedType) {
+                /*
+                 * Find erased generic types for the super class and replace
+                 * with actual type arguments from the parameterized type
+                 */
+                Type[] superTypeArguments = getTypeArguments(genericSuperClass);
+                List<Type> typeParameters = Arrays.asList(((Class<?>) parameterizedType.getRawType()).getTypeParameters());
+                int j = 0;
+                for (int i = 0; i != superTypeArguments.length; i++) {
+                    if (typeParameters.contains(superTypeArguments[i])) {
+                        superTypeArguments[i] = parameterizedType.getActualTypeArguments()[j++];
+                    }
+                }
+                return new ParameterizedType() {
+                    @Override
+                    public Type[] getActualTypeArguments() {
+                        return superTypeArguments;
+                    }
+
+                    @Override
+                    public Type getRawType() {
+                        return ((ParameterizedType) genericSuperClass).getRawType();
+                    }
+
+                    @Override
+                    public Type getOwnerType() {
+                        return null;
+                    }
+                };
+            } else {
+                return genericSuperClass;
+            }
+        } else {
+            return ((Class<?>) type).getGenericSuperclass();
+        }
+    }
+
+    /**
+     * Get the super type for a type in its super type chain, which has
+     * a raw class that matches the specified class.
+     *
+     * @param subType the sub type to find super type for
+     * @param rawSuperType the raw class for the super type
+     * @return the super type that matches the requirement
+     */
+    public static Type getSuperType(Type subType, Class<?> rawSuperType) {
+        while (subType != null && getRawClass(subType) != rawSuperType) {
+            subType = getSuperType(subType);
+        }
+        return subType;
+    }
+
+    /**
+     * Determines if a type is the same or a subtype for another type.
+     * 
+     * @param subType the supposed sub type
+     * @param superType the supposed super type
+     * @return true if the first type is the same or a subtype for the second type
+     */
+    public static boolean isTypeOrSubTypeOf(Type subType, Type superType) {
+        Class<?> sub = getRawClass(subType);
+        Class<?> sup = getRawClass(superType);
+
+        return sup.isAssignableFrom(sub);
+    }
+
+    /**
+     * Create a parameterized type from a raw class and its type arguments.
+     *
+     * @param rawClass the raw class to construct the parameterized type
+     * @param genericTypes the generic arguments
+     * @return the parameterized type
+     */
+    public static ParameterizedType createParameterizedType(Class<?> rawClass, Type... genericTypes) {
+        return new ParameterizedType() {
+            @Override
+            public Type[] getActualTypeArguments() {
+                return genericTypes;
+            }
+
+            @Override
+            public Type getRawType() {
+                return rawClass;
+            }
+
+            @Override
+            public Type getOwnerType() {
+                return null;
+            }
+        };
+    }
+
+    /**
+     * Returns whether the rest response expects to have any body (by checking if the body parameter type is set to Void,
+     * in which case no body is expected).
+     *
+     * @param restResponseReturnType The RestResponse subtype containing the type arguments we are inspecting.
+     * @return True if a body is expected, false if a Void body is expected.
+     */
+    public static boolean restResponseTypeExpectsBody(ParameterizedType restResponseReturnType) {
+        return getRestResponseBodyType(restResponseReturnType) != Void.class;
+    }
+
+    /**
+     * Returns the body type expected in the rest response.
+     *
+     * @param restResponseReturnType The RestResponse subtype containing the type arguments we are inspecting.
+     * @return The type of the body.
+     */
+    public static Type getRestResponseBodyType(Type restResponseReturnType) {
+        // if this type has type arguments, then we look at the last one to determine if it expects a body
+        final Type[] restResponseTypeArguments = TypeUtil.getTypeArguments(restResponseReturnType);
+        if (restResponseTypeArguments != null && restResponseTypeArguments.length > 0) {
+            return restResponseTypeArguments[restResponseTypeArguments.length - 1];
+        } else {
+            // no generic type on this RestResponse sub-type, so we go up to parent
+            return getRestResponseBodyType(TypeUtil.getSuperType(restResponseReturnType));
+        }
+    }
+
+    // Private Ctr
+    private TypeUtil() {
+    }
+}
diff --git a/common/azure-common/src/main/java/com/azure/common/implementation/util/package-info.java b/common/azure-common/src/main/java/com/azure/common/implementation/util/package-info.java
new file mode 100644
index 0000000000000..1e7bba742b3bf
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/implementation/util/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing utility classes with helper methods for the runtime.
+ */
+package com.azure.common.implementation.util;
\ No newline at end of file
diff --git a/common/azure-common/src/main/java/com/azure/common/package-info.java b/common/azure-common/src/main/java/com/azure/common/package-info.java
new file mode 100644
index 0000000000000..657249cf826cf
--- /dev/null
+++ b/common/azure-common/src/main/java/com/azure/common/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Package containing the types for client side http communication with a REST endpoint.
+ */
+package com.azure.common;
\ No newline at end of file
diff --git a/common/azure-common/src/test/java/com/azure/common/CredentialsTests.java b/common/azure-common/src/test/java/com/azure/common/CredentialsTests.java
new file mode 100644
index 0000000000000..0b8cc3458f0c9
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/CredentialsTests.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common;
+
+import com.azure.common.credentials.BasicAuthenticationCredentials;
+import com.azure.common.credentials.TokenCredentials;
+
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.policy.CredentialsPolicy;
+import com.azure.common.http.policy.HttpPipelinePolicy;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.MockHttpClient;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.net.URL;
+
+public class CredentialsTests {
+
+    @Test
+    public void basicCredentialsTest() throws Exception {
+        BasicAuthenticationCredentials credentials = new BasicAuthenticationCredentials("user", "pass");
+
+        HttpPipelinePolicy auditorPolicy =  (context, next) -> {
+            String headerValue = context.httpRequest().headers().value("Authorization");
+            Assert.assertEquals("Basic dXNlcjpwYXNz", headerValue);
+            return next.process();
+        };
+        //
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient(),
+                new CredentialsPolicy(credentials),
+                auditorPolicy);
+
+
+        HttpRequest request = new HttpRequest(HttpMethod.GET, new URL("http://localhost"));
+        pipeline.send(request).block();
+    }
+
+    @Test
+    public void tokenCredentialsTest() throws Exception {
+        TokenCredentials credentials = new TokenCredentials(null, "this_is_a_token");
+
+        HttpPipelinePolicy auditorPolicy =  (context, next) -> {
+            String headerValue = context.httpRequest().headers().value("Authorization");
+            Assert.assertEquals("Bearer this_is_a_token", headerValue);
+            return next.process();
+        };
+
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient(),
+                new CredentialsPolicy(credentials),
+                auditorPolicy);
+
+        HttpRequest request = new HttpRequest(HttpMethod.GET, new URL("http://localhost"));
+        pipeline.send(request).block();
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/MockServer.java b/common/azure-common/src/test/java/com/azure/common/MockServer.java
new file mode 100644
index 0000000000000..01cec3f887d27
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/MockServer.java
@@ -0,0 +1,103 @@
+package com.azure.common;
+
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.Random;
+
+public class MockServer {
+    private static class TestHandler extends HandlerWrapper {
+        @Override
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
+            LoggerFactory.getLogger(getClass()).info("Received request for " + baseRequest.getRequestURL());
+            baseRequest.setHandled(true);
+            Random random = new Random();
+
+            byte[] buf = new byte[8192];
+            InputStream is = request.getInputStream();
+            MessageDigest md5;
+            try {
+                md5 = MessageDigest.getInstance("MD5");
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(e);
+            }
+
+            while (true) {
+                int bytesRead = is.read(buf);
+                if (bytesRead == -1) {
+                    break;
+                }
+                md5.update(buf, 0, bytesRead);
+
+                int randomNumber = random.nextInt(100000);
+                if (randomNumber == 12345) {
+                    LoggerFactory.getLogger(getClass()).info("Server had a transient error.");
+                    response.setStatus(503);
+                    response.getWriter().println("Error! Please try again.");
+
+                    // Appears to be necessary to read all the request content to prevent hangs
+                    // Would like to be able to test scenarios where the server drops the connection
+                    // when we're in the middle of sending request content.
+                    while (is.read(buf) != -1) ;
+
+                    return;
+                }
+            }
+
+            byte[] md5Digest = md5.digest();
+            String encodedMD5 = Base64.getEncoder().encodeToString(md5Digest);
+            if (request.getMethod().equals("DELETE")) {
+                response.setStatus(202);
+            } else {
+                response.setStatus(201);
+            }
+            response.setHeader("Content-MD5", encodedMD5);
+            LoggerFactory.getLogger(getClass()).info("Finished handling request " + baseRequest.getRequestURL());
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        int port = 8080;
+        String portString = System.getenv("JAVA_SDK_TEST_PORT");
+        if (portString != null) {
+            port = Integer.parseInt(portString, 10);
+        }
+
+        Server server = new Server(port);
+        ResourceHandler resourceHandler = new ResourceHandler();
+        resourceHandler.setDirectoriesListed(true);
+
+        String tempPath = System.getenv("JAVA_STRESS_TEST_TEMP_PATH");
+        if (tempPath == null || tempPath.isEmpty()) {
+            tempPath = "client-runtime/temp";
+        }
+
+        resourceHandler.setResourceBase(tempPath);
+        ContextHandler ch = new ContextHandler("/javasdktest/upload");
+        ch.setHandler(resourceHandler);
+
+        HandlerList handlers = new HandlerList();
+        handlers.addHandler(ch);
+        handlers.addHandler(new TestHandler());
+
+        server.setHandler(handlers);
+
+        System.out.println("Starting MockServer");
+        server.start();
+        server.join();
+        System.out.println("Shutting down MockServer");
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/MyRestException.java b/common/azure-common/src/test/java/com/azure/common/MyRestException.java
new file mode 100644
index 0000000000000..0e2b9ef8ad86d
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/MyRestException.java
@@ -0,0 +1,16 @@
+package com.azure.common;
+
+import com.azure.common.entities.HttpBinJSON;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.rest.RestException;
+
+public class MyRestException extends RestException {
+    public MyRestException(String message, HttpResponse response, HttpBinJSON body) {
+        super(message, response, body);
+    }
+
+    @Override
+    public HttpBinJSON body() {
+        return (HttpBinJSON) super.body();
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/test/java/com/azure/common/UserAgentTests.java b/common/azure-common/src/test/java/com/azure/common/UserAgentTests.java
new file mode 100644
index 0000000000000..ad8f498be55ba
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/UserAgentTests.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common;
+
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.MockHttpClient;
+import com.azure.common.http.MockHttpResponse;
+import com.azure.common.http.policy.UserAgentPolicy;
+import org.junit.Assert;
+import org.junit.Test;
+
+import reactor.core.publisher.Mono;
+
+import java.net.URL;
+
+public class UserAgentTests {
+    @Test
+    public void defaultUserAgentTests() throws Exception {
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient() {
+                @Override
+                public Mono<HttpResponse> send(HttpRequest request) {
+                    Assert.assertEquals(
+                            request.headers().value("User-Agent"),
+                            "AutoRest-Java");
+                    return Mono.<HttpResponse>just(new MockHttpResponse(request, 200));
+                }
+            },
+            new UserAgentPolicy("AutoRest-Java"));
+
+        HttpResponse response = pipeline.send(new HttpRequest(
+                HttpMethod.GET, new URL("http://localhost"))).block();
+
+        Assert.assertEquals(200, response.statusCode());
+    }
+
+    @Test
+    public void customUserAgentTests() throws Exception {
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient() {
+            @Override
+                public Mono<HttpResponse> send(HttpRequest request) {
+                    String header = request.headers().value("User-Agent");
+                    Assert.assertEquals("Awesome", header);
+                    return Mono.<HttpResponse>just(new MockHttpResponse(request, 200));
+                }
+            },
+            new UserAgentPolicy("Awesome"));
+
+        HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET,
+                new URL("http://localhost"))).block();
+        Assert.assertEquals(200, response.statusCode());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/entities/AccessPolicy.java b/common/azure-common/src/test/java/com/azure/common/entities/AccessPolicy.java
new file mode 100644
index 0000000000000..9b344f77bd48c
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/entities/AccessPolicy.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ *
+ * Code generated by Microsoft (R) AutoRest Code Generator.
+ * Changes may cause incorrect behavior and will be lost if the code is
+ * regenerated.
+ */
+
+package com.azure.common.entities;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.OffsetDateTime;
+
+/**
+ * An Access policy.
+ */
+public class AccessPolicy {
+    /**
+     * the date-time the policy is active.
+     */
+    @JsonProperty(value = "Start")
+    private OffsetDateTime start;
+
+    /**
+     * the date-time the policy expires.
+     */
+    @JsonProperty(value = "Expiry")
+    private OffsetDateTime expiry;
+
+    /**
+     * the permissions for the acl policy.
+     */
+    @JsonProperty(value = "Permission")
+    private String permission;
+
+    /**
+     * Get the start value.
+     *
+     * @return the start value
+     */
+    public OffsetDateTime start() {
+        return this.start;
+    }
+
+    /**
+     * Set the start value.
+     *
+     * @param start the start value to set
+     * @return the AccessPolicy object itself.
+     */
+    public AccessPolicy withStart(OffsetDateTime start) {
+        this.start = start;
+        return this;
+    }
+
+    /**
+     * Get the expiry value.
+     *
+     * @return the expiry value
+     */
+    public OffsetDateTime expiry() {
+        return this.expiry;
+    }
+
+    /**
+     * Set the expiry value.
+     *
+     * @param expiry the expiry value to set
+     * @return the AccessPolicy object itself.
+     */
+    public AccessPolicy withExpiry(OffsetDateTime expiry) {
+        this.expiry = expiry;
+        return this;
+    }
+
+    /**
+     * Get the permission value.
+     *
+     * @return the permission value
+     */
+    public String permission() {
+        return this.permission;
+    }
+
+    /**
+     * Set the permission value.
+     *
+     * @param permission the permission value to set
+     * @return the AccessPolicy object itself.
+     */
+    public AccessPolicy withPermission(String permission) {
+        this.permission = permission;
+        return this;
+    }
+
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/entities/HttpBinHeaders.java b/common/azure-common/src/test/java/com/azure/common/entities/HttpBinHeaders.java
new file mode 100644
index 0000000000000..47a1ad371c33e
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/entities/HttpBinHeaders.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ *
+ * Code generated by Microsoft (R) AutoRest Code Generator.
+ */
+
+package com.azure.common.entities;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.azure.common.implementation.DateTimeRfc1123;
+
+/**
+ * Defines headers for httpbin.org operations.
+ */
+public class HttpBinHeaders {
+    @JsonProperty(value = "Date")
+    public DateTimeRfc1123 date;
+
+    @JsonProperty(value = "Via")
+    public String via;
+
+    @JsonProperty(value = "Connection")
+    public String connection;
+
+    @JsonProperty(value = "X-Processed-Time")
+    public double xProcessedTime;
+
+    @JsonProperty(value = "Access-Control-Allow-Credentials")
+    public boolean accessControlAllowCredentials;
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/entities/HttpBinJSON.java b/common/azure-common/src/test/java/com/azure/common/entities/HttpBinJSON.java
new file mode 100644
index 0000000000000..8f497aa576643
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/entities/HttpBinJSON.java
@@ -0,0 +1,12 @@
+package com.azure.common.entities;
+
+import java.util.Map;
+
+/**
+ * Maps to the JSON return values from http://httpbin.org.
+ */
+public class HttpBinJSON {
+    public String url;
+    public Map<String,String> headers;
+    public Object data;
+}
\ No newline at end of file
diff --git a/common/azure-common/src/test/java/com/azure/common/entities/SignedIdentifierInner.java b/common/azure-common/src/test/java/com/azure/common/entities/SignedIdentifierInner.java
new file mode 100644
index 0000000000000..e9ce9e558a892
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/entities/SignedIdentifierInner.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ *
+ * Code generated by Microsoft (R) AutoRest Code Generator.
+ * Changes may cause incorrect behavior and will be lost if the code is
+ * regenerated.
+ */
+
+package com.azure.common.entities;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * signed identifier.
+ */
+public class SignedIdentifierInner {
+    /**
+     * a unique id.
+     */
+    @JsonProperty(value = "Id", required = true)
+    private String id;
+
+    /**
+     * The access policy.
+     */
+    @JsonProperty(value = "AccessPolicy", required = true)
+    private AccessPolicy accessPolicy;
+
+    /**
+     * Get the id value.
+     *
+     * @return the id value
+     */
+    public String id() {
+        return this.id;
+    }
+
+    /**
+     * Set the id value.
+     *
+     * @param id the id value to set
+     * @return the SignedIdentifierInner object itself.
+     */
+    public SignedIdentifierInner withId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    /**
+     * Get the accessPolicy value.
+     *
+     * @return the accessPolicy value
+     */
+    public AccessPolicy accessPolicy() {
+        return this.accessPolicy;
+    }
+
+    /**
+     * Set the accessPolicy value.
+     *
+     * @param accessPolicy the accessPolicy value to set
+     * @return the SignedIdentifierInner object itself.
+     */
+    public SignedIdentifierInner withAccessPolicy(AccessPolicy accessPolicy) {
+        this.accessPolicy = accessPolicy;
+        return this;
+    }
+
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/entities/SignedIdentifiersWrapper.java b/common/azure-common/src/test/java/com/azure/common/entities/SignedIdentifiersWrapper.java
new file mode 100644
index 0000000000000..12ddcc5493f76
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/entities/SignedIdentifiersWrapper.java
@@ -0,0 +1,26 @@
+package com.azure.common.entities;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
+import java.util.List;
+
+@JacksonXmlRootElement(localName = "SignedIdentifiers")
+public class SignedIdentifiersWrapper {
+    @JacksonXmlProperty(localName = "SignedIdentifier")
+    private final List<SignedIdentifierInner> signedIdentifiers;
+    @JsonCreator
+    public SignedIdentifiersWrapper(@JsonProperty("signedIdentifiers") List<SignedIdentifierInner> signedIdentifiers) {
+        this.signedIdentifiers = signedIdentifiers;
+    }
+    /**
+     * Get the SignedIdentifiers value.
+     *
+     * @return the SignedIdentifiers value
+     */
+    public List<SignedIdentifierInner> signedIdentifiers() {
+        return signedIdentifiers;
+    }
+}
\ No newline at end of file
diff --git a/common/azure-common/src/test/java/com/azure/common/entities/Slide.java b/common/azure-common/src/test/java/com/azure/common/entities/Slide.java
new file mode 100644
index 0000000000000..09020f59d5180
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/entities/Slide.java
@@ -0,0 +1,15 @@
+package com.azure.common.entities;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Slide {
+    @JacksonXmlProperty(localName = "type", isAttribute = true)
+    public String type;
+
+    @JsonProperty("title")
+    public String title;
+
+    @JsonProperty("item")
+    public String[] items;
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/entities/Slideshow.java b/common/azure-common/src/test/java/com/azure/common/entities/Slideshow.java
new file mode 100644
index 0000000000000..0ddda5e271683
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/entities/Slideshow.java
@@ -0,0 +1,18 @@
+package com.azure.common.entities;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+
+public class Slideshow {
+    @JacksonXmlProperty(localName = "title", isAttribute = true)
+    public String title;
+
+    @JacksonXmlProperty(localName = "date", isAttribute = true)
+    public String date;
+
+    @JacksonXmlProperty(localName = "author", isAttribute = true)
+    public String author;
+
+    @JsonProperty("slide")
+    public Slide[] slides;
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/HttpHeaderTests.java b/common/azure-common/src/test/java/com/azure/common/http/HttpHeaderTests.java
new file mode 100644
index 0000000000000..e8d6811b2030a
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/HttpHeaderTests.java
@@ -0,0 +1,15 @@
+package com.azure.common.http;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class HttpHeaderTests {
+    @Test
+    public void addValue()
+    {
+        final HttpHeader header = new HttpHeader("a", "b");
+        header.addValue("c");
+        assertEquals("a:b,c", header.toString());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/HttpHeadersTests.java b/common/azure-common/src/test/java/com/azure/common/http/HttpHeadersTests.java
new file mode 100644
index 0000000000000..c5b06e446faf8
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/HttpHeadersTests.java
@@ -0,0 +1,31 @@
+package com.azure.common.http;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class HttpHeadersTests {
+    @Test
+    public void testSet()
+    {
+        final HttpHeaders headers = new HttpHeaders();
+
+        headers.set("a", "b");
+        assertEquals("b", headers.value("a"));
+
+        headers.set("a", "c");
+        assertEquals("c", headers.value("a"));
+
+        headers.set("a", null);
+        assertNull(headers.value("a"));
+
+        headers.set("A", "");
+        assertEquals("", headers.value("a"));
+
+        headers.set("A", "b");
+        assertEquals("b", headers.value("A"));
+
+        headers.set("a", null);
+        assertNull(headers.value("a"));
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/HttpMethodTests.java b/common/azure-common/src/test/java/com/azure/common/http/HttpMethodTests.java
new file mode 100644
index 0000000000000..c4ab802e5b33f
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/HttpMethodTests.java
@@ -0,0 +1,43 @@
+package com.azure.common.http;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class HttpMethodTests {
+    @Test
+    public void GET()
+    {
+        assertEquals("GET", HttpMethod.GET.toString());
+    }
+
+    @Test
+    public void PUT()
+    {
+        assertEquals("PUT", HttpMethod.PUT.toString());
+    }
+
+    @Test
+    public void POST()
+    {
+        assertEquals("POST", HttpMethod.POST.toString());
+    }
+
+    @Test
+    public void PATCH()
+    {
+        assertEquals("PATCH", HttpMethod.PATCH.toString());
+    }
+
+    @Test
+    public void DELETE()
+    {
+        assertEquals("DELETE", HttpMethod.DELETE.toString());
+    }
+
+    @Test
+    public void HEAD()
+    {
+        assertEquals("HEAD", HttpMethod.HEAD.toString());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/HttpPipelineTests.java b/common/azure-common/src/test/java/com/azure/common/http/HttpPipelineTests.java
new file mode 100644
index 0000000000000..b1800c35228e2
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/HttpPipelineTests.java
@@ -0,0 +1,139 @@
+package com.azure.common.http;
+
+import com.azure.common.http.policy.PortPolicy;
+import com.azure.common.http.policy.ProtocolPolicy;
+import com.azure.common.http.policy.RequestIdPolicy;
+import com.azure.common.http.policy.RetryPolicy;
+import com.azure.common.http.policy.UserAgentPolicy;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.*;
+
+public class HttpPipelineTests {
+    @Test
+    public void constructorWithNoArguments() {
+        HttpPipeline pipeline = new HttpPipeline();
+        assertEquals(0, pipeline.pipelinePolicies().length);
+        assertNotNull(pipeline.httpClient());
+        assertTrue(pipeline.httpClient() instanceof ReactorNettyClient);
+    }
+
+    @Test
+    public void withRequestPolicy() {
+        HttpPipeline pipeline = new HttpPipeline(new PortPolicy(80, true),
+                new ProtocolPolicy("ftp", true),
+                new RetryPolicy());
+
+        assertEquals(3, pipeline.pipelinePolicies().length);
+        assertEquals(PortPolicy.class, pipeline.pipelinePolicies()[0].getClass());
+        assertEquals(ProtocolPolicy.class, pipeline.pipelinePolicies()[1].getClass());
+        assertEquals(RetryPolicy.class, pipeline.pipelinePolicies()[2].getClass());
+        assertNotNull(pipeline.httpClient());
+        assertTrue(pipeline.httpClient() instanceof ReactorNettyClient);
+    }
+
+    @Test
+    public void withRequestOptions() throws MalformedURLException {
+        HttpPipeline pipeline = new HttpPipeline(new PortPolicy(80, true),
+                new ProtocolPolicy("ftp", true),
+                new RetryPolicy());
+
+        HttpPipelineCallContext context = pipeline.newContext(new HttpRequest(HttpMethod.GET, new URL("http://foo.com")));
+        assertNotNull(context);
+        assertNotNull(pipeline.httpClient());
+        assertTrue(pipeline.httpClient() instanceof ReactorNettyClient);
+    }
+
+    @Test
+    public void withNoRequestPolicies() throws MalformedURLException {
+        final HttpMethod expectedHttpMethod = HttpMethod.GET;
+        final URL expectedUrl = new URL("http://my.site.com");
+        final HttpPipeline httpPipeline = new HttpPipeline(new MockHttpClient() {
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                assertEquals(0, request.headers().size());
+                assertEquals(expectedHttpMethod, request.httpMethod());
+                assertEquals(expectedUrl, request.url());
+                return Mono.<HttpResponse>just(new MockHttpResponse(request, 200));
+            }
+        });
+
+        final HttpResponse response = httpPipeline.send(new HttpRequest(expectedHttpMethod, expectedUrl)).block();
+        assertNotNull(response);
+        assertEquals(200, response.statusCode());
+    }
+
+    @Test
+    public void withUserAgentRequestPolicy() throws MalformedURLException {
+        final HttpMethod expectedHttpMethod = HttpMethod.GET;
+        final URL expectedUrl = new URL("http://my.site.com/1");
+        final String expectedUserAgent = "my-user-agent";
+        final HttpClient httpClient = new MockHttpClient() {
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                assertEquals(1, request.headers().size());
+                assertEquals(expectedUserAgent, request.headers().value("User-Agent"));
+                assertEquals(expectedHttpMethod, request.httpMethod());
+                assertEquals(expectedUrl, request.url());
+                return Mono.<HttpResponse>just(new MockHttpResponse(request, 200));
+            }
+        };
+
+        final HttpPipeline httpPipeline = new HttpPipeline(httpClient,
+                new UserAgentPolicy(expectedUserAgent));
+
+        final HttpResponse response = httpPipeline.send(new HttpRequest(expectedHttpMethod, expectedUrl)).block();
+        assertNotNull(response);
+        assertEquals(200, response.statusCode());
+    }
+
+    @Test
+    public void withRequestIdRequestPolicy() throws MalformedURLException {
+        final HttpMethod expectedHttpMethod = HttpMethod.GET;
+        final URL expectedUrl = new URL("http://my.site.com/1");
+        final HttpPipeline httpPipeline = new HttpPipeline(new MockHttpClient() {
+                @Override
+                public Mono<HttpResponse> send(HttpRequest request) {
+                    assertEquals(1, request.headers().size());
+                    final String requestId = request.headers().value("x-ms-client-request-id");
+                    assertNotNull(requestId);
+                    assertFalse(requestId.isEmpty());
+
+                    assertEquals(expectedHttpMethod, request.httpMethod());
+                    assertEquals(expectedUrl, request.url());
+                    return Mono.<HttpResponse>just(new MockHttpResponse(request, 200));
+                }
+            },
+            new RequestIdPolicy());
+
+        final HttpResponse response = httpPipeline.send(new HttpRequest(expectedHttpMethod, expectedUrl)).block();
+        assertNotNull(response);
+        assertEquals(200, response.statusCode());
+    }
+
+    private static abstract class MockHttpClient implements HttpClient {
+
+        @Override
+        public abstract Mono<HttpResponse> send(HttpRequest request);
+
+        @Override
+        public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+            throw new IllegalStateException("MockHttpClient.proxy");
+        }
+
+        @Override
+        public HttpClient wiretap(boolean enableWiretap) {
+            throw new IllegalStateException("MockHttpClient.wiretap");
+        }
+
+        @Override
+        public HttpClient port(int port) {
+            throw new IllegalStateException("MockHttpClient.port");
+        }
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/HttpRequestTests.java b/common/azure-common/src/test/java/com/azure/common/http/HttpRequestTests.java
new file mode 100644
index 0000000000000..ed3fe4c031df8
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/HttpRequestTests.java
@@ -0,0 +1,52 @@
+package com.azure.common.http;
+
+import io.netty.buffer.Unpooled;
+import org.junit.Test;
+import reactor.core.publisher.Flux;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import static org.junit.Assert.*;
+
+public class HttpRequestTests {
+    @Test
+    public void constructor() throws MalformedURLException {
+        final HttpRequest request = new HttpRequest(HttpMethod.POST, new URL("http://request.url"));
+        assertEquals(HttpMethod.POST, request.httpMethod());
+        assertEquals(new URL("http://request.url"), request.url());
+    }
+
+    @Test
+    public void testClone() throws IOException {
+        final HttpHeaders headers = new HttpHeaders();
+        headers.set("my-header", "my-value");
+        headers.set("other-header", "other-value");
+
+        final HttpRequest request = new HttpRequest(
+                HttpMethod.PUT,
+                new URL("http://request.url"),
+                headers,
+                Flux.just(Unpooled.buffer(0, 0)));
+
+        final HttpRequest bufferedRequest = request.buffer();
+
+        assertNotSame(request, bufferedRequest);
+
+        assertEquals(request.httpMethod(), bufferedRequest.httpMethod());
+        assertEquals(request.url(), bufferedRequest.url());
+
+        assertNotSame(request.headers(), bufferedRequest.headers());
+        assertEquals(request.headers().toMap().size(), bufferedRequest.headers().toMap().size());
+        for (HttpHeader clonedHeader : bufferedRequest.headers()) {
+            for (HttpHeader originalHeader : request.headers()) {
+                assertNotSame(clonedHeader, originalHeader);
+            }
+
+            assertEquals(clonedHeader.value(), request.headers().value(clonedHeader.name()));
+        }
+
+        assertSame(request.body(), bufferedRequest.body());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/MockHttpClient.java b/common/azure-common/src/test/java/com/azure/common/http/MockHttpClient.java
new file mode 100644
index 0000000000000..3341ee7079201
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/MockHttpClient.java
@@ -0,0 +1,224 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import com.azure.common.implementation.Base64Url;
+import com.azure.common.implementation.DateTimeRfc1123;
+import com.azure.common.entities.HttpBinJSON;
+import com.azure.common.implementation.util.FluxUtil;
+import reactor.core.publisher.Mono;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * This HttpClient attempts to mimic the behavior of http://httpbin.org without ever making a network call.
+ */
+public class MockHttpClient implements HttpClient {
+    private static final HttpHeaders responseHeaders = new HttpHeaders()
+            .set("Date", "Fri, 13 Oct 2017 20:33:09 GMT")
+            .set("Via", "1.1 vegur")
+            .set("Connection", "keep-alive")
+            .set("X-Processed-Time", "1.0")
+            .set("Access-Control-Allow-Credentials", "true")
+            .set("Content-Type", "application/json");
+
+    @Override
+    public Mono<HttpResponse> send(HttpRequest request) {
+        HttpResponse response = null;
+
+        try {
+            final URL requestUrl = request.url();
+            final String requestHost = requestUrl.getHost();
+            if ("httpbin.org".equalsIgnoreCase(requestHost)) {
+                final String requestPath = requestUrl.getPath();
+                final String requestPathLower = requestPath.toLowerCase();
+                if (requestPathLower.equals("/anything") || requestPathLower.startsWith("/anything/")) {
+                    if ("HEAD".equals(request.httpMethod())) {
+                        response = new MockHttpResponse(request, 200, "");
+                    } else {
+                        final HttpBinJSON json = new HttpBinJSON();
+                        json.url = request.url().toString()
+                                // This is just to mimic the behavior we've seen with httpbin.org.
+                                .replace("%20", " ");
+                        json.headers = toMap(request.headers());
+                        response = new MockHttpResponse(request, 200, json);
+                    }
+                }
+                else if (requestPathLower.startsWith("/bytes/")) {
+                    final String byteCountString = requestPath.substring("/bytes/".length());
+                    final int byteCount = Integer.parseInt(byteCountString);
+                    HttpHeaders newHeaders = new HttpHeaders(responseHeaders)
+                            .set("Content-Type", "application/octet-stream")
+                            .set("Content-Length", Integer.toString(byteCount));
+                    response = new MockHttpResponse(request, 200, newHeaders, byteCount == 0 ? null : new byte[byteCount]);
+                }
+                else if (requestPathLower.startsWith("/base64urlbytes/")) {
+                    final String byteCountString = requestPath.substring("/base64urlbytes/".length());
+                    final int byteCount = Integer.parseInt(byteCountString);
+                    final byte[] bytes = new byte[byteCount];
+                    for (int i = 0; i < byteCount; ++i) {
+                        bytes[i] = (byte)i;
+                    }
+                    final Base64Url base64EncodedBytes = bytes.length == 0 ? null : Base64Url.encode(bytes);
+                    response = new MockHttpResponse(request, 200, responseHeaders, base64EncodedBytes);
+                }
+                else if (requestPathLower.equals("/base64urllistofbytes")) {
+                    final List<String> base64EncodedBytesList = new ArrayList<>();
+                    for (int i = 0; i < 3; ++i) {
+                        final int byteCount = (i + 1) * 10;
+                        final byte[] bytes = new byte[byteCount];
+                        for (int j = 0; j < byteCount; ++j) {
+                            bytes[j] = (byte)j;
+                        }
+                        final Base64Url base64UrlEncodedBytes = Base64Url.encode(bytes);
+                        base64EncodedBytesList.add(base64UrlEncodedBytes.toString());
+                    }
+                    response = new MockHttpResponse(request, 200, responseHeaders, base64EncodedBytesList);
+                }
+                else if (requestPathLower.equals("/base64urllistoflistofbytes")) {
+                    final List<List<String>> result = new ArrayList<>();
+                    for (int i = 0; i < 2; ++i) {
+                        final List<String> innerList = new ArrayList<>();
+                        for (int j = 0; j < (i + 1) * 2; ++j) {
+                            final int byteCount = (j + 1) * 5;
+                            final byte[] bytes = new byte[byteCount];
+                            for (int k = 0; k < byteCount; ++k) {
+                                bytes[k] = (byte)k;
+                            }
+
+                            final Base64Url base64UrlEncodedBytes = Base64Url.encode(bytes);
+                            innerList.add(base64UrlEncodedBytes.toString());
+                        }
+                        result.add(innerList);
+                    }
+                    response = new MockHttpResponse(request, 200, responseHeaders, result);
+                }
+                else if (requestPathLower.equals("/base64urlmapofbytes")) {
+                    final Map<String,String> result = new HashMap<>();
+                    for (int i = 0; i < 2; ++i) {
+                        final String key = Integer.toString(i);
+
+                        final int byteCount = (i + 1) * 10;
+                        final byte[] bytes = new byte[byteCount];
+                        for (int j = 0; j < byteCount; ++j) {
+                            bytes[j] = (byte)j;
+                        }
+
+                        final Base64Url base64UrlEncodedBytes = Base64Url.encode(bytes);
+                        result.put(key, base64UrlEncodedBytes.toString());
+                    }
+                    response = new MockHttpResponse(request, 200, responseHeaders, result);
+                }
+                else if (requestPathLower.equals("/datetimerfc1123")) {
+                    final DateTimeRfc1123 now = new DateTimeRfc1123(OffsetDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneOffset.UTC));
+                    final String result = now.toString();
+                    response = new MockHttpResponse(request, 200, responseHeaders, result);
+                }
+                else if (requestPathLower.equals("/unixtime")) {
+                    response = new MockHttpResponse(request, 200, responseHeaders, 0);
+                }
+                else if (requestPathLower.equals("/delete")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = createHttpBinResponseDataForRequest(request);
+                    response = new MockHttpResponse(request, 200, json);
+                }
+                else if (requestPathLower.equals("/get")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.headers = toMap(request.headers());
+                    response = new MockHttpResponse(request, 200, json);
+                }
+                else if (requestPathLower.equals("/patch")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = createHttpBinResponseDataForRequest(request);
+                    response = new MockHttpResponse(request, 200, json);
+                }
+                else if (requestPathLower.equals("/post")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = createHttpBinResponseDataForRequest(request);
+                    json.headers = toMap(request.headers());
+                    response = new MockHttpResponse(request, 200, json);
+                }
+                else if (requestPathLower.equals("/put")) {
+                    final HttpBinJSON json = new HttpBinJSON();
+                    json.url = request.url().toString();
+                    json.data = createHttpBinResponseDataForRequest(request);
+                    json.headers = toMap(request.headers());
+                    response = new MockHttpResponse(request, 200, responseHeaders, json);
+                }
+                else if (requestPathLower.startsWith("/status/")) {
+                    final String statusCodeString = requestPathLower.substring("/status/".length());
+                    final int statusCode = Integer.valueOf(statusCodeString);
+                    response = new MockHttpResponse(request, statusCode);
+                }
+            }
+        }
+        catch (Exception ex) {
+            return Mono.error(ex);
+        }
+
+        if (response == null) {
+            response = new MockHttpResponse(request, 500);
+        }
+
+        return Mono.just(response);
+    }
+
+    @Override
+    public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+        throw new IllegalStateException("MockHttpClient.proxy");
+    }
+
+    @Override
+    public HttpClient wiretap(boolean enableWiretap) {
+        throw new IllegalStateException("MockHttpClient.wiretap");
+    }
+
+    @Override
+    public HttpClient port(int port) {
+        throw new IllegalStateException("MockHttpClient.port");
+    }
+
+    private static String createHttpBinResponseDataForRequest(HttpRequest request) {
+        String body = bodyToString(request);
+        if (body == null) {
+            return "";
+        } else {
+            return body;
+        }
+    }
+
+    private static String bodyToString(HttpRequest request) {
+        String body = "";
+        if (request.body() != null) {
+            Mono<String> asyncString = FluxUtil.collectBytesInByteBufStream(request.body(), true)
+                    .map(bytes -> new String(bytes, StandardCharsets.UTF_8));
+            body = asyncString.block();
+        }
+        return body;
+    }
+
+    private static Map<String, String> toMap(HttpHeaders headers) {
+        final Map<String, String> result = new HashMap<>();
+        for (final HttpHeader header : headers) {
+            result.put(header.name(), header.value());
+        }
+        return result;
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/MockHttpResponse.java b/common/azure-common/src/test/java/com/azure/common/http/MockHttpResponse.java
new file mode 100644
index 0000000000000..25442b76fdfc0
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/MockHttpResponse.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http;
+
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public class MockHttpResponse extends HttpResponse {
+    private final static SerializerAdapter serializer = new JacksonAdapter();
+
+    private final int statusCode;
+
+    private final HttpHeaders headers;
+
+    private final byte[] bodyBytes;
+
+    public MockHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, byte[] bodyBytes) {
+        this.statusCode = statusCode;
+        this.headers = headers;
+        this.bodyBytes = bodyBytes;
+        this.withRequest(request);
+    }
+
+    public MockHttpResponse(HttpRequest request, int statusCode, byte[] bodyBytes) {
+        this(request, statusCode, new HttpHeaders(), bodyBytes);
+    }
+
+    public MockHttpResponse(HttpRequest request, int statusCode) {
+        this(request, statusCode, new byte[0]);
+    }
+
+    public MockHttpResponse(HttpRequest request, int statusCode, String string) {
+        this(request, statusCode, new HttpHeaders(), string == null ? new byte[0] : string.getBytes());
+    }
+
+    public MockHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers) {
+        this(request, statusCode, headers, new byte[0]);
+    }
+
+    public MockHttpResponse(HttpRequest request, int statusCode, HttpHeaders headers, Object serializable) {
+        this(request, statusCode, headers, serialize(serializable));
+    }
+
+    public MockHttpResponse(HttpRequest request, int statusCode, Object serializable) {
+        this(request, statusCode, new HttpHeaders(), serialize(serializable));
+    }
+
+    private static byte[] serialize(Object serializable) {
+        byte[] result = null;
+        try {
+            final String serializedString = serializer.serialize(serializable, SerializerEncoding.JSON);
+            result = serializedString == null ? null : serializedString.getBytes();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    @Override
+    public int statusCode() {
+        return statusCode;
+    }
+
+    @Override
+    public String headerValue(String name) {
+        return headers.value(name);
+    }
+
+    @Override
+    public HttpHeaders headers() {
+        return new HttpHeaders(headers);
+    }
+
+    @Override
+    public Mono<byte[]> bodyAsByteArray() {
+        if (bodyBytes == null) {
+            return Mono.empty();
+        } else {
+            return Mono.just(bodyBytes);
+        }
+    }
+
+    @Override
+    public Flux<ByteBuf> body() {
+        if (bodyBytes == null) {
+            return Flux.empty();
+        } else {
+            return Flux.just(Unpooled.wrappedBuffer(bodyBytes));
+        }
+    }
+
+    @Override
+    public Mono<String> bodyAsString() {
+        if (bodyBytes == null) {
+            return Mono.empty();
+        } else {
+            return Mono.just(new String(bodyBytes, StandardCharsets.UTF_8));
+        }
+    }
+
+    @Override
+    public Mono<String> bodyAsString(Charset charset) {
+        if (bodyBytes == null) {
+            return Mono.empty();
+        } else {
+            return Mono.just(new String(bodyBytes, charset));
+        }
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/ReactorNettyClientTests.java b/common/azure-common/src/test/java/com/azure/common/http/ReactorNettyClientTests.java
new file mode 100644
index 0000000000000..796d8010b42e9
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/ReactorNettyClientTests.java
@@ -0,0 +1,340 @@
+package com.azure.common.http;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.ReferenceCountUtil;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+
+import io.reactivex.Completable;
+import io.reactivex.schedulers.Schedulers;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+import reactor.test.StepVerifierOptions;
+
+public class ReactorNettyClientTests {
+
+    private static final String SHORT_BODY = "hi there";
+    private static final String LONG_BODY = createLongBody();
+
+    private static WireMockServer server;
+
+    @BeforeClass
+    public static void beforeClass() {
+        server = new WireMockServer(WireMockConfiguration.options().dynamicPort().disableRequestJournal());
+        server.stubFor(
+                WireMock.get("/short").willReturn(WireMock.aResponse().withBody(SHORT_BODY)));
+        server.stubFor(WireMock.get("/long").willReturn(WireMock.aResponse().withBody(LONG_BODY)));
+        server.stubFor(WireMock.get("/error")
+                .willReturn(WireMock.aResponse().withBody("error").withStatus(500)));
+        server.stubFor(
+                WireMock.post("/shortPost").willReturn(WireMock.aResponse().withBody(SHORT_BODY)));
+        server.start();
+        // ResourceLeakDetector.setLevel(Level.PARANOID);
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        if (server != null) {
+            server.shutdown();
+        }
+    }
+
+    @Test
+    public void testFlowableResponseShortBodyAsByteArrayAsync() {
+        checkBodyReceived(SHORT_BODY, "/short");
+    }
+
+    @Test
+    public void testFlowableResponseLongBodyAsByteArrayAsync() {
+        checkBodyReceived(LONG_BODY, "/long");
+    }
+
+
+    @Test
+    public void testMultipleSubscriptionsEmitsError() {
+        HttpResponse response = getResponse("/short");
+        // Subscription:1
+        response.bodyAsByteArray().block();
+        // Subscription:2
+        StepVerifier.create(response.bodyAsByteArray())
+                .expectNextCount(0) // TODO: Check with smaldini, what is the verifier operator equivalent to .awaitDone(20, TimeUnit.SECONDS)
+                .verifyError(IllegalStateException.class);
+
+    }
+
+    @Test
+    public void testDispose() throws InterruptedException {
+        HttpResponse response = getResponse("/long");
+        response.body().subscribe().dispose();
+        // Wait for scheduled connection disposal action to execute on netty event-loop
+        Thread.sleep(5000);
+        Assert.assertTrue(response.internConnection().isDisposed());
+    }
+
+
+
+    @Test
+    public void testCancel() {
+        HttpResponse response = getResponse("/long");
+        //
+        StepVerifierOptions stepVerifierOptions = StepVerifierOptions.create();
+        stepVerifierOptions.initialRequest(0);
+        //
+        StepVerifier.create(response.body(), stepVerifierOptions)
+                .expectNextCount(0)
+                .thenRequest(1)
+                .expectNextCount(1)
+                .thenCancel()
+                .verify();
+        Assert.assertTrue(response.internConnection().isDisposed());
+    }
+
+    @Test
+    public void testFlowableWhenServerReturnsBodyAndNoErrorsWhenHttp500Returned() {
+        HttpResponse response = getResponse("/error");
+        StepVerifier.create(response.bodyAsString())
+                .expectNext("error") // TODO: .awaitDone(20, TimeUnit.SECONDS) [See previous todo]
+                .verifyComplete();
+        assertEquals(500, response.statusCode());
+    }
+
+    @Test
+    @Ignore("Not working accurately at present")
+    public void testFlowableBackpressure() {
+        HttpResponse response = getResponse("/long");
+        //
+        StepVerifierOptions stepVerifierOptions = StepVerifierOptions.create();
+        stepVerifierOptions.initialRequest(0);
+        //
+        StepVerifier.create(response.body(), stepVerifierOptions)
+                .expectNextCount(0)
+                .thenRequest(1)
+                .expectNextCount(1)
+                .thenRequest(3)
+                .expectNextCount(3)
+                .thenRequest(Long.MAX_VALUE)// TODO: Check with smaldini, what is the verifier operator to ignore all next emissions
+                .expectNextCount(1507)
+                .verifyComplete();
+    }
+
+    @Test
+    public void testRequestBodyIsErrorShouldPropagateToResponse() {
+        HttpClient client = HttpClient.createDefault();
+        HttpRequest request = new HttpRequest(HttpMethod.POST, url(server, "/shortPost"))
+                .withHeader("Content-Length", "123")
+                .withBody(Flux.error(new RuntimeException("boo")));
+
+        StepVerifier.create(client.send(request))
+                .expectErrorMessage("boo")
+                .verify();
+    }
+
+    @Test
+    public void testRequestBodyEndsInErrorShouldPropagateToResponse() {
+        HttpClient client = HttpClient.createDefault();
+        String contentChunk = "abcdefgh";
+        int repetitions = 1000;
+        HttpRequest request = new HttpRequest(HttpMethod.POST, url(server, "/shortPost"))
+                .withHeader("Content-Length", String.valueOf(contentChunk.length() * repetitions))
+                .withBody(Flux.just(contentChunk)
+                        .repeat(repetitions)
+                        .map(s -> Unpooled.wrappedBuffer(s.getBytes(StandardCharsets.UTF_8)))
+                        .concatWith(Flux.error(new RuntimeException("boo"))));
+        StepVerifier.create(client.send(request))
+                // .awaitDone(10, TimeUnit.SECONDS)
+                .expectErrorMessage("boo")
+                .verify();
+    }
+
+    @Test(timeout = 5000)
+    public void testServerShutsDownSocketShouldPushErrorToContentFlowable()
+            throws IOException, InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Socket> sock = new AtomicReference<>();
+        ServerSocket ss = new ServerSocket(0);
+        try {
+            Completable.fromCallable(() -> {
+                latch.countDown();
+                Socket socket = ss.accept();
+                sock.set(socket);
+                // give the client time to get request across
+                Thread.sleep(500);
+                // respond but don't send the complete response
+                byte[] bytes = new byte[1024];
+                int n = socket.getInputStream().read(bytes);
+                System.out.println(new String(bytes, 0, n, StandardCharsets.UTF_8));
+                String response = "HTTP/1.1 200 OK\r\n" //
+                        + "Content-Type: text/plain\r\n" //
+                        + "Content-Length: 10\r\n" //
+                        + "\r\n" //
+                        + "zi";
+                OutputStream out = socket.getOutputStream();
+                out.write(response.getBytes());
+                out.flush();
+                // kill the socket with HTTP response body incomplete
+                socket.close();
+                return 1;
+            })
+            .subscribeOn(Schedulers.io())
+            .subscribe();
+            //
+            latch.await();
+            HttpClient client = HttpClient.createDefault();
+            HttpRequest request = new HttpRequest(HttpMethod.GET,
+                    new URL("http://localhost:" + ss.getLocalPort() + "/get"));
+            HttpResponse response = client.send(request).block();
+            assertEquals(200, response.statusCode());
+            System.out.println("reading body");
+            //
+            StepVerifier.create(response.bodyAsByteArray())
+                    // .awaitDone(20, TimeUnit.SECONDS)
+                    .verifyError(IOException.class);
+        } finally {
+            ss.close();
+        }
+    }
+
+    @Test
+    public void testConcurrentRequests() throws NoSuchAlgorithmException {
+        long t = System.currentTimeMillis();
+        int numRequests = 100; // 100 = 1GB of data read
+        long timeoutSeconds = 60;
+        HttpClient client = HttpClient.createDefault();
+        byte[] expectedDigest = digest(LONG_BODY);
+
+        Mono<Long> numBytesMono = Flux.range(1, numRequests)
+                .parallel(10)
+                .runOn(reactor.core.scheduler.Schedulers.newElastic("io", 30))
+                .flatMap(n -> Mono.fromCallable(() -> getResponse(client, "/long")).flatMapMany(response -> {
+                    MessageDigest md = md5Digest();
+                    return response.body()
+                            .doOnNext(bb -> {
+                                bb.retain();
+                                if (bb.hasArray()) {
+                                    // Heap buffer
+                                    md.update(bb.array());
+                                } else {
+                                    // Direct buffer
+                                    int len = bb.readableBytes();
+                                    byte[] array = new byte[len];
+                                    bb.getBytes(bb.readerIndex(), array);
+                                    md.update(array);
+                                }
+                            })
+                            .map(bb -> new NumberedByteBuf(n, bb))
+//                          .doOnComplete(() -> System.out.println("completed " + n))
+                            .doOnComplete(() -> Assert.assertArrayEquals("wrong digest!", expectedDigest,
+                                    md.digest()));
+                }))
+                .sequential()
+                // enable the doOnNext call to see request numbers and thread names
+                // .doOnNext(g -> System.out.println(g.n + " " +
+                // Thread.currentThread().getName()))
+                .map(nbb -> {
+                    long bytesCount = (long) nbb.bb.readableBytes();
+                    ReferenceCountUtil.release(nbb.bb);
+                    return bytesCount;
+                })
+                .reduce((x, y) -> x + y)
+                .subscribeOn(reactor.core.scheduler.Schedulers.newElastic("io", 30))
+                .publishOn(reactor.core.scheduler.Schedulers.newElastic("io", 30));
+
+        StepVerifier.create(numBytesMono)
+//              .awaitDone(timeoutSeconds, TimeUnit.SECONDS)
+                .expectNext((long)(numRequests * LONG_BODY.getBytes(StandardCharsets.UTF_8).length))
+                .verifyComplete();
+//
+//        long numBytes = numBytesMono.block();
+//        t = System.currentTimeMillis() - t;
+//        System.out.println("totalBytesRead=" + numBytes / 1024 / 1024 + "MB in " + t / 1000.0 + "s");
+//        assertEquals(numRequests * LONG_BODY.getBytes(StandardCharsets.UTF_8).length, numBytes);
+    }
+
+    private static MessageDigest md5Digest() {
+        try {
+            return MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static byte[] digest(String s) throws NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        md.update(s.getBytes(StandardCharsets.UTF_8));
+        byte[] expectedDigest = md.digest();
+        return expectedDigest;
+    }
+
+    private static final class NumberedByteBuf {
+        final long n;
+        final ByteBuf bb;
+
+        NumberedByteBuf(long n, ByteBuf bb) {
+            this.n = n;
+            this.bb = bb;
+        }
+    }
+
+    private static HttpResponse getResponse(String path) {
+        HttpClient client = HttpClient.createDefault();
+        return getResponse(client, path);
+    }
+
+    private static HttpResponse getResponse(HttpClient client, String path) {
+        HttpRequest request = new HttpRequest(HttpMethod.GET, url(server, path));
+        return client.send(request).block();
+    }
+
+    private static URL url(WireMockServer server, String path) {
+        try {
+            return new URL("http://localhost:" + server.port() + path);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static String createLongBody() {
+        StringBuilder s = new StringBuilder(10000000);
+        for (int i = 0; i < 1000000; i++) {
+            s.append("abcdefghijk");
+        }
+        return s.toString();
+    }
+
+    private void checkBodyReceived(String expectedBody, String path) {
+        HttpClient client = HttpClient.createDefault();
+        HttpResponse response = doRequest(client, path);
+        String s = new String(response.bodyAsByteArray().block(),
+                StandardCharsets.UTF_8);
+        assertEquals(expectedBody, s);
+    }
+
+    private HttpResponse doRequest(HttpClient client, String path) {
+        HttpRequest request = new HttpRequest(HttpMethod.GET, url(server, path));
+        HttpResponse response = client.send(request).block();
+        return response;
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/policy/HostPolicyTests.java b/common/azure-common/src/test/java/com/azure/common/http/policy/HostPolicyTests.java
new file mode 100644
index 0000000000000..46876bba7fad3
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/policy/HostPolicyTests.java
@@ -0,0 +1,69 @@
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.ProxyOptions;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+
+public class HostPolicyTests {
+    @Test
+    public void withNoPort() throws MalformedURLException {
+        final HttpPipeline pipeline = createPipeline("localhost", "ftp://localhost");
+        pipeline.send(createHttpRequest("ftp://www.example.com")).block();
+    }
+
+    @Test
+    public void withPort() throws MalformedURLException {
+        final HttpPipeline pipeline = createPipeline("localhost", "ftp://localhost:1234");
+        pipeline.send(createHttpRequest("ftp://www.example.com:1234"));
+    }
+
+    private static HttpPipeline createPipeline(String host, String expectedUrl) {
+        return new HttpPipeline(new MockHttpClient() {
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                return Mono.empty(); // NOP
+            }
+        },
+        new HostPolicy(host),
+        (context, next) -> {
+            assertEquals(expectedUrl, context.httpRequest().url().toString());
+            return next.process();
+        });
+    }
+
+    private static HttpRequest createHttpRequest(String url) throws MalformedURLException {
+        return new HttpRequest(HttpMethod.GET, new URL(url));
+    }
+
+    private static abstract class MockHttpClient implements HttpClient {
+
+        @Override
+        public abstract Mono<HttpResponse> send(HttpRequest request);
+
+        @Override
+        public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+            throw new IllegalStateException("MockHttpClient.proxy");
+        }
+
+        @Override
+        public HttpClient wiretap(boolean enableWiretap) {
+            throw new IllegalStateException("MockHttpClient.wiretap");
+        }
+
+        @Override
+        public HttpClient port(int port) {
+            throw new IllegalStateException("MockHttpClient.port");
+        }
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/policy/ProtocolPolicyTests.java b/common/azure-common/src/test/java/com/azure/common/http/policy/ProtocolPolicyTests.java
new file mode 100644
index 0000000000000..2957fe2777e93
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/policy/ProtocolPolicyTests.java
@@ -0,0 +1,83 @@
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.ProxyOptions;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+
+public class ProtocolPolicyTests {
+
+    @Test
+    public void withOverwrite() throws MalformedURLException {
+        final HttpPipeline pipeline = createPipeline("ftp", "ftp://www.bing.com");
+        pipeline.send(createHttpRequest("http://www.bing.com"));
+    }
+
+    @Test
+    public void withNoOverwrite() throws MalformedURLException {
+        final HttpPipeline pipeline = createPipeline("ftp", false, "https://www.bing.com");
+        pipeline.send(createHttpRequest("https://www.bing.com"));
+    }
+    private static HttpPipeline createPipeline(String protocol, String expectedUrl) {
+        return new HttpPipeline(new MockHttpClient() {
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                return Mono.empty(); // NOP
+            }
+        },
+        new ProtocolPolicy(protocol, true),
+        (context, next) -> {
+            assertEquals(expectedUrl, context.httpRequest().url().toString());
+            return next.process();
+        });
+    }
+
+    private static HttpPipeline createPipeline(String protocol, boolean overwrite, String expectedUrl) {
+        return new HttpPipeline(new MockHttpClient() {
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                return Mono.empty(); // NOP
+            }
+        },
+        new ProtocolPolicy(protocol, overwrite),
+        (context, next) -> {
+            assertEquals(expectedUrl, context.httpRequest().url().toString());
+            return next.process();
+        });
+    }
+
+    private static HttpRequest createHttpRequest(String url) throws MalformedURLException {
+        return new HttpRequest(HttpMethod.GET, new URL(url));
+    }
+
+    private static abstract class MockHttpClient implements HttpClient {
+
+        @Override
+        public abstract Mono<HttpResponse> send(HttpRequest request);
+
+        @Override
+        public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+            throw new IllegalStateException("MockHttpClient.proxy");
+        }
+
+        @Override
+        public HttpClient wiretap(boolean enableWiretap) {
+            throw new IllegalStateException("MockHttpClient.wiretap");
+        }
+
+        @Override
+        public HttpClient port(int port) {
+            throw new IllegalStateException("MockHttpClient.port");
+        }
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/policy/ProxyAuthenticationPolicyTests.java b/common/azure-common/src/test/java/com/azure/common/http/policy/ProxyAuthenticationPolicyTests.java
new file mode 100644
index 0000000000000..fa829da39ef54
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/policy/ProxyAuthenticationPolicyTests.java
@@ -0,0 +1,38 @@
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.MockHttpClient;
+import org.junit.Test;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class ProxyAuthenticationPolicyTests {
+    @Test
+    public void test() throws MalformedURLException {
+        final AtomicBoolean auditorVisited = new AtomicBoolean(false);
+        final String username = "testuser";
+        final String password = "testpass";
+        //
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient(),
+                new ProxyAuthenticationPolicy(username, password),
+                (context, next) -> {
+                    assertEquals("Basic dGVzdHVzZXI6dGVzdHBhc3M=", context.httpRequest().headers().value("Proxy-Authentication"));
+                    auditorVisited.set(true);
+                    return next.process();
+                });
+
+        pipeline.send(new HttpRequest(HttpMethod.GET, new URL("http://localhost")))
+                .block();
+
+        if (!auditorVisited.get()) {
+            fail();
+        }
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/policy/RequestIdPolicyTests.java b/common/azure-common/src/test/java/com/azure/common/http/policy/RequestIdPolicyTests.java
new file mode 100644
index 0000000000000..4f3fef6151cbe
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/policy/RequestIdPolicyTests.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.MockHttpClient;
+import io.netty.buffer.ByteBuf;
+import org.junit.Assert;
+import org.junit.Test;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+public class RequestIdPolicyTests {
+    private final HttpResponse mockResponse = new HttpResponse() {
+        @Override
+        public int statusCode() {
+            return 500;
+        }
+
+        @Override
+        public String headerValue(String name) {
+            return null;
+        }
+
+        @Override
+        public HttpHeaders headers() {
+            return new HttpHeaders();
+        }
+
+        @Override
+        public Mono<byte[]> bodyAsByteArray() {
+            return Mono.empty();
+        }
+
+        @Override
+        public Flux<ByteBuf> body() {
+            return Flux.empty();
+        }
+
+        @Override
+        public Mono<String> bodyAsString() {
+            return Mono.empty();
+        }
+
+        @Override
+        public Mono<String> bodyAsString(Charset charset) {
+            return Mono.empty();
+        }
+    };
+
+    private static final String REQUEST_ID_HEADER = "x-ms-client-request-id";
+
+    @Test
+    public void newRequestIdForEachCall() throws Exception {
+        HttpPipeline pipeline = new HttpPipeline(new MockHttpClient() {
+            String firstRequestId = null;
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                if (firstRequestId != null) {
+                    String newRequestId = request.headers().value(REQUEST_ID_HEADER);
+                    Assert.assertNotNull(newRequestId);
+                    Assert.assertNotEquals(newRequestId, firstRequestId);
+                }
+
+                firstRequestId = request.headers().value(REQUEST_ID_HEADER);
+                if (firstRequestId == null) {
+                    Assert.fail();
+                }
+                return Mono.just(mockResponse);
+            }
+        },
+        new RequestIdPolicy());
+
+        pipeline.send(new HttpRequest(HttpMethod.GET, new URL("http://localhost/"))).block();
+        pipeline.send(new HttpRequest(HttpMethod.GET, new URL("http://localhost/"))).block();
+    }
+
+    @Test
+    public void sameRequestIdForRetry() throws Exception {
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient() {
+            String firstRequestId = null;
+
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                if (firstRequestId != null) {
+                    String newRequestId = request.headers().value(REQUEST_ID_HEADER);
+                    Assert.assertNotNull(newRequestId);
+                    Assert.assertEquals(newRequestId, firstRequestId);
+                }
+                firstRequestId = request.headers().value(REQUEST_ID_HEADER);
+                if (firstRequestId == null) {
+                    Assert.fail();
+                }
+                return Mono.just(mockResponse);
+            }
+        },
+        new RequestIdPolicy(),
+        new RetryPolicy(1, Duration.of(0, ChronoUnit.SECONDS)));
+
+        pipeline.send(new HttpRequest(HttpMethod.GET, new URL("http://localhost/"))).block();
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/http/policy/RetryPolicyTests.java b/common/azure-common/src/test/java/com/azure/common/http/policy/RetryPolicyTests.java
new file mode 100644
index 0000000000000..6f7aa54365904
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/http/policy/RetryPolicyTests.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.http.policy;
+
+import com.azure.common.http.*;
+import org.junit.Assert;
+import org.junit.Test;
+
+import reactor.core.publisher.Mono;
+
+import java.net.URL;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+public class RetryPolicyTests {
+    @Test
+    public void exponentialRetryEndOn501() throws Exception {
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient() {
+           // Send 408, 500, 502, all retried, with a 501 ending
+           private final int[] codes = new int[]{408, 500, 502, 501};
+           private int count = 0;
+
+           @Override
+           public Mono<HttpResponse> send(HttpRequest request) {
+               return Mono.<HttpResponse>just(new MockHttpResponse(request, codes[count++]));
+           }
+       },
+       new RetryPolicy(3, Duration.of(0, ChronoUnit.MILLIS)));
+
+        HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET,
+                        new URL("http://localhost/"))).block();
+
+        Assert.assertEquals(501, response.statusCode());
+    }
+
+    @Test
+    public void exponentialRetryMax() throws Exception {
+        final int maxRetries = 5;
+        final HttpPipeline pipeline = new HttpPipeline(new MockHttpClient() {
+            int count = -1;
+
+            @Override
+            public Mono<HttpResponse> send(HttpRequest request) {
+                Assert.assertTrue(count++ < maxRetries);
+                return Mono.<HttpResponse>just(new MockHttpResponse(request, 500));
+            }
+        },
+        new RetryPolicy(maxRetries, Duration.of(0, ChronoUnit.MILLIS)));
+
+
+        HttpResponse response = pipeline.send(new HttpRequest(HttpMethod.GET,
+                        new URL("http://localhost/"))).block();
+
+        Assert.assertEquals(500, response.statusCode());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/Base64UrlTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/Base64UrlTests.java
new file mode 100644
index 0000000000000..58c8737d24732
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/Base64UrlTests.java
@@ -0,0 +1,111 @@
+package com.azure.common.implementation;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Base64UrlTests {
+    @Test
+    public void constructorWithNullBytes() {
+        final Base64Url base64Url = new Base64Url((byte[])null);
+        assertNull(base64Url.encodedBytes());
+        assertNull(base64Url.decodedBytes());
+        assertNull(base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithEmptyBytes() {
+        final Base64Url base64Url = new Base64Url(new byte[0]);
+        assertArrayEquals(new byte[0], base64Url.encodedBytes());
+        assertArrayEquals(new byte[0], base64Url.decodedBytes());
+        assertEquals("", base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithNonEmptyBytes() {
+        final Base64Url base64Url = new Base64Url(new byte[] { 65, 65, 69, 67, 65, 119, 81, 70, 66, 103, 99, 73, 67, 81 });
+        assertArrayEquals(new byte[] { 65, 65, 69, 67, 65, 119, 81, 70, 66, 103, 99, 73, 67, 81 }, base64Url.encodedBytes());
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, base64Url.decodedBytes());
+        assertEquals("AAECAwQFBgcICQ", base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithNullString() {
+        final Base64Url base64Url = new Base64Url((String)null);
+        assertNull(base64Url.encodedBytes());
+        assertNull(base64Url.decodedBytes());
+        assertNull(base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithEmptyString() {
+        final Base64Url base64Url = new Base64Url("");
+        assertArrayEquals(new byte[0], base64Url.encodedBytes());
+        assertArrayEquals(new byte[0], base64Url.decodedBytes());
+        assertEquals("", base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithEmptyDoubleQuotedString() {
+        final Base64Url base64Url = new Base64Url("\"\"");
+        assertArrayEquals(new byte[0], base64Url.encodedBytes());
+        assertArrayEquals(new byte[0], base64Url.decodedBytes());
+        assertEquals("", base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithEmptySingleQuotedString() {
+        final Base64Url base64Url = new Base64Url("\'\'");
+        assertArrayEquals(new byte[0], base64Url.encodedBytes());
+        assertArrayEquals(new byte[0], base64Url.decodedBytes());
+        assertEquals("", base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithNonEmptyString() {
+        final Base64Url base64Url = new Base64Url("AAECAwQFBgcICQ");
+        assertArrayEquals(new byte[] { 65, 65, 69, 67, 65, 119, 81, 70, 66, 103, 99, 73, 67, 81 }, base64Url.encodedBytes());
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, base64Url.decodedBytes());
+        assertEquals("AAECAwQFBgcICQ", base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithNonEmptyDoubleQuotedString() {
+        final Base64Url base64Url = new Base64Url("\"AAECAwQFBgcICQ\"");
+        assertArrayEquals(new byte[] { 65, 65, 69, 67, 65, 119, 81, 70, 66, 103, 99, 73, 67, 81 }, base64Url.encodedBytes());
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, base64Url.decodedBytes());
+        assertEquals("AAECAwQFBgcICQ", base64Url.toString());
+    }
+
+    @Test
+    public void constructorWithNonEmptySingleQuotedString() {
+        final Base64Url base64Url = new Base64Url("\'AAECAwQFBgcICQ\'");
+        assertArrayEquals(new byte[] { 65, 65, 69, 67, 65, 119, 81, 70, 66, 103, 99, 73, 67, 81 }, base64Url.encodedBytes());
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, base64Url.decodedBytes());
+        assertEquals("AAECAwQFBgcICQ", base64Url.toString());
+    }
+
+    @Test
+    public void encodeWithNullBytes() {
+        final Base64Url base64Url = Base64Url.encode(null);
+        assertNull(base64Url.encodedBytes());
+        assertNull(base64Url.decodedBytes());
+        assertNull(base64Url.toString());
+    }
+
+    @Test
+    public void encodeWithEmptyBytes() {
+        final Base64Url base64Url = Base64Url.encode(new byte[0]);
+        assertArrayEquals(new byte[0], base64Url.encodedBytes());
+        assertArrayEquals(new byte[0], base64Url.decodedBytes());
+        assertEquals("", base64Url.toString());
+    }
+
+    @Test
+    public void encodeWithNonEmptyBytes() {
+        final Base64Url base64Url = Base64Url.encode(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
+        assertArrayEquals(new byte[] { 65, 65, 69, 67, 65, 119, 81, 70, 66, 103, 99, 73, 67, 81 }, base64Url.encodedBytes());
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, base64Url.decodedBytes());
+        assertEquals("AAECAwQFBgcICQ", base64Url.toString());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/EncodedParameterTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/EncodedParameterTests.java
new file mode 100644
index 0000000000000..ef626abf010cd
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/EncodedParameterTests.java
@@ -0,0 +1,14 @@
+package com.azure.common.implementation;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class EncodedParameterTests {
+    @Test
+    public void constructor() {
+        final EncodedParameter ep = new EncodedParameter("ABC", "123");
+        assertEquals("ABC", ep.name());
+        assertEquals("123", ep.encodedValue());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyStressTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyStressTests.java
new file mode 100644
index 0000000000000..d358bdcc63000
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyStressTests.java
@@ -0,0 +1,537 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.azure.common.MockServer;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.annotations.BodyParam;
+import com.azure.common.annotations.DELETE;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.HeaderParam;
+import com.azure.common.annotations.Host;
+import com.azure.common.annotations.PUT;
+import com.azure.common.annotations.PathParam;
+import com.azure.common.implementation.http.ContentType;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpPipelineCallContext;
+import com.azure.common.http.HttpPipelineNextPolicy;
+import com.azure.common.http.policy.HttpLoggingPolicy;
+import com.azure.common.http.policy.HttpPipelinePolicy;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.policy.AddDatePolicy;
+import com.azure.common.http.policy.AddHeadersPolicy;
+import com.azure.common.http.policy.HostPolicy;
+import com.azure.common.http.policy.HttpLogDetailLevel;
+import com.azure.common.http.rest.RestStreamResponse;
+import com.azure.common.http.rest.RestVoidResponse;
+import com.azure.common.implementation.util.FlowableUtils;
+import com.azure.common.implementation.util.FluxUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.ResourceLeakDetector;
+import io.reactivex.Completable;
+import io.reactivex.CompletableSource;
+import io.reactivex.Flowable;
+import io.reactivex.functions.Function;
+
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+import reactor.core.Disposable;
+import reactor.core.Exceptions;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.ProcessBuilder.Redirect;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.FileChannel;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class RestProxyStressTests {
+    private static IOService service;
+    private static Process testServer;
+    // By default will spawn a test server running on the default port.
+    // If JAVA_SDK_TEST_PORT is specified in the environment, we assume
+    // the server is already running on that port.
+    private static int port = 8080;
+
+    @BeforeClass
+    public static void beforeClass() throws IOException {
+        Assume.assumeTrue(
+                "Set the environment variable JAVA_SDK_STRESS_TESTS to \"true\" to run stress tests",
+                Boolean.parseBoolean(System.getenv("JAVA_SDK_STRESS_TESTS")));
+
+        ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID);
+        LoggerFactory.getLogger(RestProxyStressTests.class).info("ResourceLeakDetector level: " + ResourceLeakDetector.getLevel());
+
+        String tempFolderPath = System.getenv("JAVA_STRESS_TEST_TEMP_PATH");
+        if (tempFolderPath == null || tempFolderPath.isEmpty()) {
+            tempFolderPath = "temp";
+        }
+
+        HttpHeaders headers = new HttpHeaders()
+                .set("x-ms-version", "2017-04-17");
+        // Order in which policies applied will be the order in which they added to builder
+        List<HttpPipelinePolicy> polices = new ArrayList<HttpPipelinePolicy>();
+        polices.add(new AddDatePolicy());
+        polices.add(new AddHeadersPolicy(headers));
+        polices.add(new ThrottlingRetryPolicy());
+        //
+        String liveStressTests = System.getenv("JAVA_SDK_TEST_SAS");
+        if (liveStressTests == null || liveStressTests.isEmpty()) {
+            launchTestServer();
+            polices.add(new HostPolicy("http://localhost:" + port));
+        }
+        //
+        polices.add(new HttpLoggingPolicy(HttpLogDetailLevel.BASIC, false));
+        //
+        service = RestProxy.create(IOService.class,
+                new HttpPipeline(polices.toArray(new HttpPipelinePolicy[polices.size()])));
+
+        TEMP_FOLDER_PATH = Paths.get(tempFolderPath);
+        create100MFiles(false);
+    }
+
+    private static void launchTestServer() throws IOException {
+        String portString = System.getenv("JAVA_SDK_TEST_PORT");
+        // TODO: figure out why test server hangs only when spawned as a subprocess
+        Assume.assumeTrue("JAVA_SDK_TEST_PORT must specify the port of a running local server", portString != null);
+        if (portString != null) {
+            port = Integer.parseInt(portString, 10);
+            LoggerFactory.getLogger(RestProxyStressTests.class).warn("Attempting to connect to already-running test server on port {}", port);
+        } else {
+            String javaHome = System.getProperty("java.home");
+            String javaExecutable = javaHome + File.separator + "bin" + File.separator + "java";
+            String classpath = System.getProperty("java.class.path");
+            String className = MockServer.class.getCanonicalName();
+
+            ProcessBuilder builder = new ProcessBuilder(
+                    javaExecutable, "-cp", classpath, className).redirectErrorStream(true).redirectOutput(Redirect.INHERIT);
+            testServer = builder.start();
+        }
+    }
+
+    @AfterClass
+    public static void afterClass() throws Exception {
+        if (testServer != null) {
+            testServer.destroy();
+        }
+    }
+
+    private static final class ThrottlingRetryPolicy implements HttpPipelinePolicy {
+        @Override
+        public Mono<HttpResponse> process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) {
+            return process(1 + ThreadLocalRandom.current().nextInt(5), context, next);
+        }
+
+        Mono<HttpResponse> process(final int waitTimeSeconds, final HttpPipelineCallContext context, final HttpPipelineNextPolicy nextPolicy) {
+            return nextPolicy.clone().process().flatMap(httpResponse -> {
+                if (httpResponse.statusCode() != 503 && httpResponse.statusCode() != 500) {
+                    return Mono.just(httpResponse);
+                } else {
+                    LoggerFactory.getLogger(getClass()).warn("Received " + httpResponse.statusCode() + " for request. Waiting " + waitTimeSeconds + " seconds before retry.");
+                    final int nextWaitTime = 5 + ThreadLocalRandom.current().nextInt(10);
+                    httpResponse.body().subscribe().dispose(); // TODO: Anu re-evaluate this
+                    return Mono.delay(Duration.of(waitTimeSeconds, ChronoUnit.SECONDS))
+                            .then(process(nextWaitTime, context, nextPolicy));
+                }
+            }).onErrorResume(throwable -> {
+                if (throwable instanceof IOException) {
+                    LoggerFactory.getLogger(getClass()).warn("I/O exception occurred: " + throwable.getMessage());
+                    return process(context, nextPolicy).delaySubscription(Duration.of(waitTimeSeconds, ChronoUnit.SECONDS));
+                }
+                LoggerFactory.getLogger(getClass()).warn("Unrecoverable exception occurred: " + throwable.getMessage());
+                return Mono.error(throwable);
+            });
+        }
+    }
+
+    @Host("https://javasdktest.blob.core.windows.net")
+    interface IOService {
+        @ExpectedResponses({201})
+        @PUT("/javasdktest/upload/100m-{id}.dat?{sas}")
+        Mono<RestVoidResponse> upload100MB(@PathParam("id") String id, @PathParam(value = "sas", encoded = true) String sas, @HeaderParam("x-ms-blob-type") String blobType, @BodyParam(ContentType.APPLICATION_OCTET_STREAM) Flux<ByteBuf> stream, @HeaderParam("content-length") long contentLength);
+
+        @GET("/javasdktest/upload/100m-{id}.dat?{sas}")
+        Mono<RestStreamResponse> download100M(@PathParam("id") String id, @PathParam(value = "sas", encoded = true) String sas);
+
+        @ExpectedResponses({201})
+        @PUT("/testcontainer{id}?restype=container&{sas}")
+        Mono<RestVoidResponse> createContainer(@PathParam("id") String id, @PathParam(value = "sas", encoded = true) String sas);
+
+        @ExpectedResponses({202})
+        @DELETE("/testcontainer{id}?restype=container&{sas}")
+        Mono<RestVoidResponse> deleteContainer(@PathParam("id") String id, @PathParam(value = "sas", encoded = true) String sas);
+    }
+
+    private static Path TEMP_FOLDER_PATH;
+    private static final int NUM_FILES = 100;
+    private static final int FILE_SIZE = 1024 * 1024 * 100;
+    private static final int CHUNK_SIZE = 8192;
+    private static final int CHUNKS_PER_FILE = FILE_SIZE / CHUNK_SIZE;
+
+    private static void deleteRecursive(Path tempFolderPath) throws IOException {
+        try {
+            Files.walkFileTree(tempFolderPath, new SimpleFileVisitor<Path>() {
+                @Override
+                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+                    Files.delete(file);
+                    return FileVisitResult.CONTINUE;
+                }
+
+                @Override
+                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+                    if (exc != null) {
+                        throw exc;
+                    }
+
+                    Files.delete(dir);
+                    return FileVisitResult.CONTINUE;
+                }
+            });
+        } catch (NoSuchFileException ignored) {
+        }
+    }
+
+    private static void create100MFiles(boolean recreate) throws IOException {
+        final Flowable<java.nio.ByteBuffer> contentGenerator = Flowable.generate(Random::new, (random, emitter) -> {
+            java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(CHUNK_SIZE);
+            random.nextBytes(buf.array());
+            emitter.onNext(buf);
+        });
+
+        if (recreate) {
+            deleteRecursive(TEMP_FOLDER_PATH);
+        }
+
+        if (Files.exists(TEMP_FOLDER_PATH)) {
+            LoggerFactory.getLogger(RestProxyStressTests.class).info("Temp files directory already exists: " + TEMP_FOLDER_PATH.toAbsolutePath());
+        } else {
+            LoggerFactory.getLogger(RestProxyStressTests.class).info("Generating temp files in directory: " + TEMP_FOLDER_PATH.toAbsolutePath());
+            Files.createDirectory(TEMP_FOLDER_PATH);
+            Flowable.range(0, NUM_FILES).flatMapCompletable(new Function<Integer, Completable>() {
+                @Override
+                public Completable apply(Integer integer) throws Exception {
+                    final int i = integer;
+                    final Path filePath = TEMP_FOLDER_PATH.resolve("100m-" + i + ".dat");
+
+                    Files.deleteIfExists(filePath);
+                    Files.createFile(filePath);
+                    final AsynchronousFileChannel file = AsynchronousFileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.WRITE);
+                    final MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+
+                    Flowable<java.nio.ByteBuffer> fileContent = contentGenerator
+                            .take(CHUNKS_PER_FILE)
+                            .doOnNext(buf -> messageDigest.update(buf.array()));
+
+                    return FlowableUtils.writeFile(fileContent, file).andThen(Completable.defer(new Callable<CompletableSource>() {
+                        @Override
+                        public CompletableSource call() throws Exception {
+                            file.close();
+                            Files.write(TEMP_FOLDER_PATH.resolve("100m-" + i + "-md5.dat"), messageDigest.digest());
+                            LoggerFactory.getLogger(getClass()).info("Finished writing file " + i);
+                            return Completable.complete();
+                        }
+                    }));
+                }
+            }).blockingAwait();
+        }
+    }
+
+    @Test
+    @Ignore("Should only be run manually")
+    public void prepare100MFiles() throws Exception {
+        create100MFiles(true);
+    }
+
+    @Test
+    public void upload100MParallelTest() {
+        final String sas = System.getenv("JAVA_SDK_TEST_SAS") == null ? "" : System.getenv("JAVA_SDK_TEST_SAS");
+
+        Flux<byte[]> md5s = Flux.range(0, NUM_FILES)
+        .map(integer -> {
+            final Path filePath = TEMP_FOLDER_PATH.resolve("100m-" + integer + "-md5.dat");
+            try {
+                return Files.readAllBytes(filePath);
+            } catch (IOException ioe) {
+                throw Exceptions.propagate(ioe);
+            }
+        });
+        //
+        Instant uploadStart = Instant.now();
+        //
+        Flux.range(0, NUM_FILES)
+                .zipWith(md5s, (id, md5) -> {
+                    AsynchronousFileChannel fileStream = null;
+                    try {
+                        fileStream = AsynchronousFileChannel.open(TEMP_FOLDER_PATH.resolve("100m-" + id + ".dat"));
+                    } catch (IOException ioe) {
+                        Exceptions.propagate(ioe);
+                    }
+                    return service.upload100MB(String.valueOf(id), sas, "BlockBlob", FluxUtil.byteBufStreamFromFile(fileStream), FILE_SIZE).map(response -> {
+                        String base64MD5 = response.headers().value("Content-MD5");
+                        byte[] receivedMD5 = Base64.getDecoder().decode(base64MD5);
+                        Assert.assertArrayEquals(md5, receivedMD5);
+                        return response;
+                    });
+                })
+                .flatMapDelayError(m -> m, 15, 1)
+                .blockLast();
+        //
+        long durationMilliseconds = Duration.between(uploadStart, Instant.now()).toMillis();
+        LoggerFactory.getLogger(getClass()).info("Upload took " + durationMilliseconds + " milliseconds.");
+    }
+
+    @Test
+    public void uploadMemoryMappedTest() {
+        final String sas = System.getenv("JAVA_SDK_TEST_SAS") == null ? "" : System.getenv("JAVA_SDK_TEST_SAS");
+
+        Flux<byte[]> md5s = Flux.range(0, NUM_FILES)
+                .map(integer -> {
+                    final Path filePath = TEMP_FOLDER_PATH.resolve("100m-" + integer + "-md5.dat");
+                    try {
+                        return Files.readAllBytes(filePath);
+                    } catch (IOException ioe) {
+                        throw Exceptions.propagate(ioe);
+                    }
+                });
+
+        Instant uploadStart = Instant.now();
+        //
+        Flux.range(0, NUM_FILES)
+                .zipWith(md5s, (id, md5) -> {
+                    FileChannel fileStream = null;
+                    try {
+                        fileStream = FileChannel.open(TEMP_FOLDER_PATH.resolve("100m-" + id + ".dat"), StandardOpenOption.READ);
+                    } catch (IOException ioe) {
+                        Exceptions.propagate(ioe);
+                    }
+                    //
+                    ByteBuf mappedByteBufFile = null;
+                    Flux<ByteBuf> stream = null;
+                    try {
+                        MappedByteBuffer mappedByteBufferFile = fileStream.map(FileChannel.MapMode.READ_ONLY, 0, fileStream.size());
+                        mappedByteBufFile = Unpooled.wrappedBuffer(mappedByteBufferFile);
+                        stream = FluxUtil.split(mappedByteBufFile, CHUNK_SIZE);
+                    } catch (IOException ioe) {
+                        mappedByteBufFile.release();
+                        Exceptions.propagate(ioe);
+                    }
+                    //
+                    return service.upload100MB(String.valueOf(id), sas, "BlockBlob", stream, FILE_SIZE).map(response -> {
+                        String base64MD5 = response.headers().value("Content-MD5");
+                        byte[] receivedMD5 = Base64.getDecoder().decode(base64MD5);
+                        Assert.assertArrayEquals(md5, receivedMD5);
+                        return response;
+                    });
+                })
+                .flatMapDelayError(m -> m, 15, 1)
+                .blockLast();
+        //
+        long durationMilliseconds = Duration.between(uploadStart, Instant.now()).toMillis();
+        LoggerFactory.getLogger(getClass()).info("Upload took " + durationMilliseconds + " milliseconds.");
+    }
+
+
+    /**
+     * Run after running one of the corresponding upload tests.
+     */
+    @Test
+    public void download100MParallelTest() {
+        final String sas = System.getenv("JAVA_SDK_TEST_SAS") == null ? "" : System.getenv("JAVA_SDK_TEST_SAS");
+
+        Flux<byte[]> md5s = Flux.range(0, NUM_FILES)
+                .map(integer -> {
+                    final Path filePath = TEMP_FOLDER_PATH.resolve("100m-" + integer + "-md5.dat");
+                    try {
+                        return Files.readAllBytes(filePath);
+                    } catch (IOException ioe) {
+                        throw Exceptions.propagate(ioe);
+                    }
+                });
+        //
+        Instant downloadStart = Instant.now();
+        //
+        Flux.range(0, NUM_FILES)
+                .zipWith(md5s, (id, md5) -> {
+                    return service.download100M(String.valueOf(id), sas).flatMap(response -> {
+                        Flux<ByteBuf> content;
+                        try {
+                            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+                            content = response.body()
+                                    .doOnNext(buf -> messageDigest.update(buf.slice().nioBuffer()));
+
+                            return content.last().doOnSuccess(b -> {
+                                assertArrayEquals(md5, messageDigest.digest());
+                                LoggerFactory.getLogger(getClass()).info("Finished downloading and MD5 validated for " + id);
+
+                            });
+
+                        } catch (NoSuchAlgorithmException nsae) {
+                            throw Exceptions.propagate(nsae);
+                        }
+                    });
+                })
+                .flatMapDelayError(m -> m, 15, 1)
+                .blockLast();
+        //
+        long durationMilliseconds = Duration.between(downloadStart, Instant.now()).toMillis();
+        LoggerFactory.getLogger(getClass()).info("Download took " + durationMilliseconds + " milliseconds.");
+    }
+
+    @Test
+    public void downloadUploadStreamingTest() {
+        final String sas = System.getenv("JAVA_SDK_TEST_SAS") == null ? "" : System.getenv("JAVA_SDK_TEST_SAS");
+
+        Flux<byte[]> md5s = Flux.range(0, NUM_FILES)
+                .map(integer -> {
+                    final Path filePath = TEMP_FOLDER_PATH.resolve("100m-" + integer + "-md5.dat");
+                    try {
+                        return Files.readAllBytes(filePath);
+                    } catch (IOException ioe) {
+                        throw Exceptions.propagate(ioe);
+                    }
+                });
+        //
+        Instant downloadStart = Instant.now();
+        //
+        Flux.range(0, NUM_FILES)
+                .zipWith(md5s, (integer, md5) -> {
+                    final int id = integer;
+                    Flux<ByteBuf> downloadContent = service.download100M(String.valueOf(id), sas)
+                            // Ideally we would intercept this content to load an MD5 to check consistency between download and upload directly,
+                            // but it's sufficient to demonstrate that no corruption occurred between preparation->upload->download->upload.
+                            .flatMapMany(RestStreamResponse::body)
+                            .map(reactorNettybb -> {
+                                //
+                                // This test 'downloadUploadStreamingTest' exercises piping scenario.
+                                //
+                                //   A. Receive ByteBufFlux from reactor-netty from NettyInbound.receive() [via service.download100M].
+                                //   B. Directly pass this ByteBufFlux to Outbound.send() [via service.upload100MB]
+                                //
+                                //   NettyOutbound.send(NettyInbound.receive())
+                                //
+                                // A property of ByteBufFlux publisher is - The chunks in the stream gets released automatically once 'onNext' returns.
+                                //
+                                // The Outbound.send method subscribe to ByteBufFlux
+                                //     1. on each onNext call, the received ByteBuf chunk gets 'scheduled' to write through Netty.write()
+                                //     2. onNext returns.
+                                //     3. repeat 1 & 2 until stream completes or errored.
+                                //
+                                // The scheduling & immediate return from onNext [1 & 2] can result in the a chunk of ByteBufFlux to be released
+                                // before the scheduled Netty.write() completes.
+                                //
+                                // This can cause following issues:
+                                //   a. Write of content of released chunks, which is bad.
+                                //   b. Netty.write() calls release on the ByteBuf after write is done. We have double release problem here.
+                                //
+                                // Solution is to aware of ByteBufFlux auto-release property and retain each chunk before passing to Netty.write().
+                                //
+                                return reactorNettybb.retain();
+                            });
+                    //
+                    return service.upload100MB("copy-" + integer, sas, "BlockBlob", downloadContent, FILE_SIZE)
+                            .flatMap(uploadResponse -> {
+                                String base64MD5 = uploadResponse.headers().value("Content-MD5");
+                                byte[] uploadMD5 = Base64.getDecoder().decode(base64MD5);
+                                assertArrayEquals(md5, uploadMD5);
+                                LoggerFactory.getLogger(getClass()).info("Finished upload and validation for id " + id);
+                                return Mono.just(uploadResponse);
+                            });
+                })
+                .flatMapDelayError(m -> m, 30, 1)
+                .blockLast();
+        //
+        long durationMilliseconds = Duration.between(downloadStart, Instant.now()).toMillis();
+        LoggerFactory.getLogger(getClass()).info("Download/Upload took " + durationMilliseconds + " milliseconds.");
+    }
+
+    @Test
+    public void cancellationTest() throws Exception {
+        final String sas = System.getenv("JAVA_SDK_TEST_SAS") == null ? "" : System.getenv("JAVA_SDK_TEST_SAS");
+        final Disposable d = Flux.range(0, NUM_FILES)
+                .flatMap(integer ->
+                        service.download100M(String.valueOf(integer), sas)
+                                .flatMapMany(RestStreamResponse::body))
+                .subscribe();
+
+        Mono.delay(Duration.ofSeconds(10)).then(Mono.defer(() -> {
+            d.dispose();
+            return Mono.empty();
+        })).block();
+        // Wait to see if any leak reports come up
+        Thread.sleep(10000);
+    }
+
+    @Test
+    public void testHighParallelism() {
+        final String sas = System.getenv("JAVA_SDK_TEST_SAS") == null ? "" : System.getenv("JAVA_SDK_TEST_SAS");
+
+        HttpHeaders headers = new HttpHeaders()
+                .set("x-ms-version", "2017-04-17");
+        // Order in which policies applied will be the order in which they added to builder
+        //
+        List<HttpPipelinePolicy> policies = new ArrayList<>();
+        policies.add(new AddDatePolicy());
+        policies.add(new AddHeadersPolicy(headers));
+        policies.add(new ThrottlingRetryPolicy());
+
+        if (sas == null || sas.isEmpty()) {
+            policies.add(new HostPolicy("http://localhost:" + port));
+        }
+
+        final IOService innerService = RestProxy.create(IOService.class,
+                new HttpPipeline(policies.toArray(new HttpPipelinePolicy[policies.size()])));
+
+        // When running with MockServer, connections sometimes get dropped,
+        // but this doesn't seem to result in any bad behavior as long as we retry.
+
+        Flux.range(0, 10000)
+                .flatMap(integer ->
+                        innerService.createContainer(integer.toString(), sas)
+                                .onErrorResume(throwable -> {
+                                    if (throwable instanceof RestException) {
+                                        RestException restException = (RestException) throwable;
+                                        if ((restException.response().statusCode() == 409 || restException.response().statusCode() == 404)) {
+                                            return Mono.empty();
+                                        }
+                                    }
+                                    return Mono.error(throwable);
+                                })
+                                .then(innerService.deleteContainer(integer.toString(), sas)))
+                                .blockLast();
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyTests.java
new file mode 100644
index 0000000000000..cee0c9886c947
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyTests.java
@@ -0,0 +1,1488 @@
+package com.azure.common.implementation;
+
+import com.azure.common.MyRestException;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.annotations.BodyParam;
+import com.azure.common.annotations.DELETE;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.HEAD;
+import com.azure.common.annotations.HeaderParam;
+import com.azure.common.annotations.Headers;
+import com.azure.common.annotations.Host;
+import com.azure.common.annotations.HostParam;
+import com.azure.common.annotations.PATCH;
+import com.azure.common.annotations.POST;
+import com.azure.common.annotations.PUT;
+import com.azure.common.annotations.PathParam;
+import com.azure.common.annotations.QueryParam;
+import com.azure.common.annotations.UnexpectedResponseExceptionType;
+import com.azure.common.entities.HttpBinHeaders;
+import com.azure.common.entities.HttpBinJSON;
+import com.azure.common.implementation.http.ContentType;
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.policy.HttpLogDetailLevel;
+import com.azure.common.http.policy.HttpLoggingPolicy;
+import com.azure.common.http.rest.RestResponse;
+import com.azure.common.http.rest.RestResponseBase;
+import com.azure.common.http.rest.RestStreamResponse;
+import com.azure.common.http.rest.RestVoidResponse;
+import com.azure.common.implementation.serializer.SerializerAdapter;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import com.azure.common.implementation.util.FluxUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.util.ReferenceCountUtil;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+public abstract class RestProxyTests {
+
+    /**
+     * Get the HTTP client that will be used for each test. This will be called once per test.
+     * @return The HTTP client to use for each test.
+     */
+    protected abstract HttpClient createHttpClient();
+
+    @Host("http://httpbin.org")
+    private interface Service1 {
+        @GET("bytes/100")
+        @ExpectedResponses({200})
+        byte[] getByteArray();
+
+        @GET("bytes/100")
+        @ExpectedResponses({200})
+        Mono<byte[]> getByteArrayAsync();
+
+        @GET("bytes/100")
+        Mono<byte[]> getByteArrayAsyncWithNoExpectedResponses();
+    }
+
+    @Test
+    public void SyncRequestWithByteArrayReturnType() {
+        final byte[] result = createService(Service1.class)
+                .getByteArray();
+        assertNotNull(result);
+        assertEquals(100, result.length);
+    }
+
+    @Test
+    public void AsyncRequestWithByteArrayReturnType() {
+        final byte[] result = createService(Service1.class)
+                .getByteArrayAsync()
+                .block();
+        assertNotNull(result);
+        assertEquals(100, result.length);
+    }
+
+    @Test
+    public void getByteArrayAsyncWithNoExpectedResponses() {
+        final byte[] result = createService(Service1.class)
+                .getByteArrayAsyncWithNoExpectedResponses()
+                .block();
+        assertNotNull(result);
+        assertEquals(result.length, 100);
+    }
+
+    @Host("http://{hostName}.org")
+    private interface Service2 {
+        @GET("bytes/{numberOfBytes}")
+        @ExpectedResponses({200})
+        byte[] getByteArray(@HostParam("hostName") String host, @PathParam("numberOfBytes") int numberOfBytes);
+
+        @GET("bytes/{numberOfBytes}")
+        @ExpectedResponses({200})
+        Mono<byte[]> getByteArrayAsync(@HostParam("hostName") String host, @PathParam("numberOfBytes") int numberOfBytes);
+    }
+
+    @Test
+    public void SyncRequestWithByteArrayReturnTypeAndParameterizedHostAndPath() {
+        final byte[] result = createService(Service2.class)
+                .getByteArray("httpbin", 50);
+        assertNotNull(result);
+        assertEquals(result.length, 50);
+    }
+
+    @Test
+    public void AsyncRequestWithByteArrayReturnTypeAndParameterizedHostAndPath() {
+        final byte[] result = createService(Service2.class)
+                .getByteArrayAsync("httpbin", 50)
+                .block();
+        assertNotNull(result);
+        assertEquals(result.length, 50);
+    }
+
+    @Test
+    public void SyncRequestWithEmptyByteArrayReturnTypeAndParameterizedHostAndPath() {
+        final byte[] result = createService(Service2.class)
+                .getByteArray("httpbin", 0);
+        // If no body then for async returns Mono.empty() for sync return null.
+        assertNull(result);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service3 {
+        @GET("bytes/2")
+        @ExpectedResponses({200})
+        void getNothing();
+
+        @GET("bytes/2")
+        @ExpectedResponses({200})
+        Mono<Void> getNothingAsync();
+    }
+
+    @Test
+    public void SyncGetRequestWithNoReturn() {
+        createService(Service3.class).getNothing();
+    }
+
+    @Test
+    public void AsyncGetRequestWithNoReturn() {
+        createService(Service3.class)
+                .getNothingAsync()
+                .block();
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service5 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnything();
+
+        @GET("anything/with+plus")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithPlus();
+
+        @GET("anything/{path}")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithPathParam(@PathParam("path") String pathParam);
+
+        @GET("anything/{path}")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithEncodedPathParam(@PathParam(value="path", encoded=true) String pathParam);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAnythingAsync();
+    }
+
+    @Test
+    public void SyncGetRequestWithAnything() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnything();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPlus() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPlus();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with+plus", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPathParam() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPathParam("withpathparam");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/withpathparam", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPathParamWithSpace() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPathParam("with path param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with path param", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithPathParamWithPlus() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithPathParam("with+path+param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with+path+param", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithEncodedPathParam() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithEncodedPathParam("withpathparam");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/withpathparam", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithEncodedPathParamWithPercent20() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithEncodedPathParam("with%20path%20param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with path param", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithAnythingWithEncodedPathParamWithPlus() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingWithEncodedPathParam("with+path+param");
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything/with+path+param", json.url);
+    }
+
+    @Test
+    public void AsyncGetRequestWithAnything() {
+        final HttpBinJSON json = createService(Service5.class)
+                .getAnythingAsync()
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service6 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnything(@QueryParam("a") String a, @QueryParam("b") int b);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnythingWithEncoded(@QueryParam(value="a", encoded=true) String a, @QueryParam("b") int b);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAnythingAsync(@QueryParam("a") String a, @QueryParam("b") int b);
+    }
+
+    @Test
+    public void SyncGetRequestWithQueryParametersAndAnything() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnything("A", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=A&b=15", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithQueryParametersAndAnythingWithPercent20() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnything("A%20Z", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=A%2520Z&b=15", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithQueryParametersAndAnythingWithEncodedWithPercent20() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnythingWithEncoded("x%20y", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=x y&b=15", json.url);
+    }
+
+    @Test
+    public void AsyncGetRequestWithQueryParametersAndAnything() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnythingAsync("A", 15)
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?a=A&b=15", json.url);
+    }
+
+    @Test
+    public void SyncGetRequestWithNullQueryParameter() {
+        final HttpBinJSON json = createService(Service6.class)
+                .getAnything(null, 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything?b=15", json.url);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service7 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        HttpBinJSON getAnything(@HeaderParam("a") String a, @HeaderParam("b") int b);
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAnythingAsync(@HeaderParam("a") String a, @HeaderParam("b") int b);
+    }
+
+    @Test
+    public void SyncGetRequestWithHeaderParametersAndAnythingReturn() {
+        final HttpBinJSON json = createService(Service7.class)
+                .getAnything("A", 15);
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("A", headers.value("A"));
+        assertArrayEquals(new String[]{"A"}, headers.values("A"));
+        assertEquals("15", headers.value("B"));
+        assertArrayEquals(new String[]{"15"}, headers.values("B"));
+    }
+
+    @Test
+    public void AsyncGetRequestWithHeaderParametersAndAnything() {
+        final HttpBinJSON json = createService(Service7.class)
+                .getAnythingAsync("A", 15)
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("A", headers.value("A"));
+        assertArrayEquals(new String[]{"A"}, headers.values("A"));
+        assertEquals("15", headers.value("B"));
+        assertArrayEquals(new String[]{"15"}, headers.values("B"));
+    }
+
+    @Test
+    public void SyncGetRequestWithNullHeader() {
+        final HttpBinJSON json = createService(Service7.class)
+                .getAnything(null, 15);
+
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+
+        assertEquals(null, headers.value("A"));
+        assertArrayEquals(null, headers.values("A"));
+        assertEquals("15", headers.value("B"));
+        assertArrayEquals(new String[]{"15"}, headers.values("B"));
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service8 {
+        @POST("post")
+        @ExpectedResponses({200})
+        HttpBinJSON post(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String postBody);
+
+        @POST("post")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> postAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String postBody);
+    }
+
+    @Test
+    public void SyncPostRequestWithStringBody() {
+        final HttpBinJSON json = createService(Service8.class)
+                .post("I'm a post body!");
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("I'm a post body!", (String)json.data);
+    }
+
+    @Test
+    public void AsyncPostRequestWithStringBody() {
+        final HttpBinJSON json = createService(Service8.class)
+                .postAsync("I'm a post body!")
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("I'm a post body!", (String)json.data);
+    }
+
+    @Test
+    public void SyncPostRequestWithNullBody() {
+        final HttpBinJSON result = createService(Service8.class).post(null);
+        assertEquals("", result.data);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service9 {
+        @PUT("put")
+        @ExpectedResponses({200})
+        HttpBinJSON put(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) int putBody);
+
+        @PUT("put")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> putAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) int putBody);
+
+        @PUT("put")
+        @ExpectedResponses({201})
+        HttpBinJSON putWithUnexpectedResponse(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String putBody);
+
+        @PUT("put")
+        @ExpectedResponses({201})
+        Mono<HttpBinJSON> putWithUnexpectedResponseAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String putBody);
+
+        @PUT("put")
+        @ExpectedResponses({201})
+        @UnexpectedResponseExceptionType(MyRestException.class)
+        HttpBinJSON putWithUnexpectedResponseAndExceptionType(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String putBody);
+
+        @PUT("put")
+        @ExpectedResponses({201})
+        @UnexpectedResponseExceptionType(MyRestException.class)
+        Mono<HttpBinJSON> putWithUnexpectedResponseAndExceptionTypeAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String putBody);
+    }
+
+    @Test
+    public void SyncPutRequestWithIntBody() {
+        final HttpBinJSON json = createService(Service9.class)
+                .put(42);
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("42", (String)json.data);
+    }
+
+    @Test
+    public void AsyncPutRequestWithIntBody() {
+        final HttpBinJSON json = createService(Service9.class)
+                .putAsync(42)
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("42", (String)json.data);
+    }
+
+    @Test
+    public void SyncPutRequestWithUnexpectedResponse() {
+        try {
+            createService(Service9.class)
+                    .putWithUnexpectedResponse("I'm the body!");
+            fail("Expected RestException would be thrown.");
+        } catch (RestException e) {
+            assertNotNull(e.body());
+            assertTrue(e.body() instanceof LinkedHashMap);
+
+            final LinkedHashMap<String,String> expectedBody = (LinkedHashMap<String, String>)e.body();
+            assertEquals("I'm the body!", expectedBody.get("data"));
+        }
+    }
+
+    @Test
+    public void AsyncPutRequestWithUnexpectedResponse() {
+        try {
+            createService(Service9.class)
+                    .putWithUnexpectedResponseAsync("I'm the body!")
+                    .block();
+            fail("Expected RestException would be thrown.");
+        } catch (RestException e) {
+            assertNotNull(e.body());
+            assertTrue(e.body() instanceof LinkedHashMap);
+
+            final LinkedHashMap<String,String> expectedBody = (LinkedHashMap<String, String>)e.body();
+            assertEquals("I'm the body!", expectedBody.get("data"));
+        }
+    }
+
+    @Test
+    public void SyncPutRequestWithUnexpectedResponseAndExceptionType() {
+        try {
+            createService(Service9.class)
+                    .putWithUnexpectedResponseAndExceptionType("I'm the body!");
+            fail("Expected RestException would be thrown.");
+        } catch (MyRestException e) {
+            assertNotNull(e.body());
+            Assert.assertEquals("I'm the body!", e.body().data);
+        } catch (Throwable e) {
+            fail("Expected MyRestException would be thrown. Instead got " + e.getClass().getSimpleName());
+        }
+    }
+
+    @Test
+    public void AsyncPutRequestWithUnexpectedResponseAndExceptionType() {
+        try {
+            createService(Service9.class)
+                    .putWithUnexpectedResponseAndExceptionTypeAsync("I'm the body!")
+                    .block();
+            fail("Expected RestException would be thrown.");
+        } catch (MyRestException e) {
+            assertNotNull(e.body());
+            Assert.assertEquals("I'm the body!", e.body().data);
+        } catch (Throwable e) {
+            fail("Expected MyRestException would be thrown. Instead got " + e.getClass().getSimpleName());
+        }
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service10 {
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        RestVoidResponse head();
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        boolean headBoolean();
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        void voidHead();
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        Mono<RestVoidResponse> headAsync();
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        Mono<Boolean> headBooleanAsync();
+
+        @HEAD("anything")
+        @ExpectedResponses({200})
+        Mono<Void> completableHeadAsync();
+    }
+
+    @Test
+    public void SyncHeadRequest() {
+        final Void body = createService(Service10.class)
+                .head()
+                .body();
+        assertNull(body);
+    }
+
+    @Test
+    public void SyncHeadBooleanRequest() {
+        final boolean result = createService(Service10.class).headBoolean();
+        assertTrue(result);
+    }
+
+    @Test
+    public void SyncVoidHeadRequest() {
+        createService(Service10.class)
+                .voidHead();
+    }
+
+    @Test
+    public void AsyncHeadRequest() {
+        final Void body = createService(Service10.class)
+                .headAsync()
+                .block()
+                .body();
+
+        assertNull(body);
+    }
+
+    @Test
+    public void AsyncHeadBooleanRequest() {
+        final boolean result = createService(Service10.class).headBooleanAsync().block();
+        assertTrue(result);
+    }
+
+    @Test
+    public void AsyncCompletableHeadRequest() {
+        createService(Service10.class)
+                .completableHeadAsync()
+                .block();
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service11 {
+        @DELETE("delete")
+        @ExpectedResponses({200})
+        HttpBinJSON delete(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) boolean bodyBoolean);
+
+        @DELETE("delete")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> deleteAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) boolean bodyBoolean);
+    }
+
+    @Test
+    public void SyncDeleteRequest() {
+        final HttpBinJSON json = createService(Service11.class)
+                .delete(false);
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("false", (String)json.data);
+    }
+
+    @Test
+    public void AsyncDeleteRequest() {
+        final HttpBinJSON json = createService(Service11.class)
+                .deleteAsync(false)
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("false", (String)json.data);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service12 {
+        @PATCH("patch")
+        @ExpectedResponses({200})
+        HttpBinJSON patch(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String bodyString);
+
+        @PATCH("patch")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> patchAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String bodyString);
+    }
+
+    @Test
+    public void SyncPatchRequest() {
+        final HttpBinJSON json = createService(Service12.class)
+                .patch("body-contents");
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("body-contents", (String)json.data);
+    }
+
+    @Test
+    public void AsyncPatchRequest() {
+        final HttpBinJSON json = createService(Service12.class)
+                .patchAsync("body-contents")
+                .block();
+        assertEquals(String.class, json.data.getClass());
+        assertEquals("body-contents", (String)json.data);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service13 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue", "MyOtherHeader:My,Header,Value" })
+        HttpBinJSON get();
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue", "MyOtherHeader:My,Header,Value" })
+        Mono<HttpBinJSON> getAsync();
+    }
+
+    @Test
+    public void SyncHeadersRequest() {
+        final HttpBinJSON json = createService(Service13.class)
+                .get();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("MyHeaderValue", headers.value("MyHeader"));
+        assertArrayEquals(new String[]{"MyHeaderValue"}, headers.values("MyHeader"));
+        assertEquals("My,Header,Value", headers.value("MyOtherHeader"));
+        assertArrayEquals(new String[]{"My", "Header", "Value"}, headers.values("MyOtherHeader"));
+    }
+
+    @Test
+    public void AsyncHeadersRequest() {
+        final HttpBinJSON json = createService(Service13.class)
+                .getAsync()
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("MyHeaderValue", headers.value("MyHeader"));
+        assertArrayEquals(new String[]{"MyHeaderValue"}, headers.values("MyHeader"));
+    }
+
+    @Host("https://httpbin.org")
+    private interface Service14 {
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue" })
+        HttpBinJSON get();
+
+        @GET("anything")
+        @ExpectedResponses({200})
+        @Headers({ "MyHeader:MyHeaderValue" })
+        Mono<HttpBinJSON> getAsync();
+    }
+
+    @Test
+    public void AsyncHttpsHeadersRequest() {
+        final HttpBinJSON json = createService(Service14.class)
+                .getAsync()
+                .block();
+        assertNotNull(json);
+        assertMatchWithHttpOrHttps("httpbin.org/anything", json.url);
+        assertNotNull(json.headers);
+        final HttpHeaders headers = new HttpHeaders(json.headers);
+        assertEquals("MyHeaderValue", headers.value("MyHeader"));
+    }
+
+    @Host("https://httpbin.org")
+    private interface Service16 {
+        @PUT("put")
+        @ExpectedResponses({200})
+        HttpBinJSON putByteArray(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) byte[] bytes);
+
+        @PUT("put")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> putByteArrayAsync(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) byte[] bytes);
+    }
+
+    @Test
+    public void service16Put() throws Exception {
+        final Service16 service16 = createService(Service16.class);
+        final byte[] expectedBytes = new byte[] { 1, 2, 3, 4 };
+        final HttpBinJSON httpBinJSON = service16.putByteArray(expectedBytes);
+
+        // httpbin sends the data back as a string like "\u0001\u0002\u0003\u0004"
+        assertTrue(httpBinJSON.data instanceof String);
+
+        final String base64String = (String) httpBinJSON.data;
+        final byte[] actualBytes = base64String.getBytes();
+        assertArrayEquals(expectedBytes, actualBytes);
+    }
+
+    @Test
+    public void service16PutAsync() throws Exception {
+        final Service16 service16 = createService(Service16.class);
+        final byte[] expectedBytes = new byte[] { 1, 2, 3, 4 };
+        final HttpBinJSON httpBinJSON = service16.putByteArrayAsync(expectedBytes)
+                .block();
+        assertTrue(httpBinJSON.data instanceof String);
+
+        final String base64String = (String) httpBinJSON.data;
+        final byte[] actualBytes = base64String.getBytes();
+        assertArrayEquals(expectedBytes, actualBytes);
+    }
+
+    @Host("http://{hostPart1}{hostPart2}.org")
+    private interface Service17 {
+        @GET("get")
+        @ExpectedResponses({200})
+        HttpBinJSON get(@HostParam("hostPart1") String hostPart1, @HostParam("hostPart2") String hostPart2);
+
+        @GET("get")
+        @ExpectedResponses({200})
+        Mono<HttpBinJSON> getAsync(@HostParam("hostPart1") String hostPart1, @HostParam("hostPart2") String hostPart2);
+    }
+
+    @Test
+    public void SyncRequestWithMultipleHostParams() {
+        final Service17 service17 = createService(Service17.class);
+        final HttpBinJSON result = service17.get("http", "bin");
+        assertNotNull(result);
+        assertMatchWithHttpOrHttps("httpbin.org/get", result.url);
+    }
+
+    @Test
+    public void AsyncRequestWithMultipleHostParams() {
+        final Service17 service17 = createService(Service17.class);
+        final HttpBinJSON result = service17.getAsync("http", "bin").block();
+        assertNotNull(result);
+        assertMatchWithHttpOrHttps("httpbin.org/get", result.url);
+    }
+
+    @Host("https://httpbin.org")
+    private interface Service18 {
+        @GET("status/200")
+        void getStatus200();
+
+        @GET("status/200")
+        @ExpectedResponses({200})
+        void getStatus200WithExpectedResponse200();
+
+        @GET("status/300")
+        void getStatus300();
+
+        @GET("status/300")
+        @ExpectedResponses({300})
+        void getStatus300WithExpectedResponse300();
+
+        @GET("status/400")
+        void getStatus400();
+
+        @GET("status/400")
+        @ExpectedResponses({400})
+        void getStatus400WithExpectedResponse400();
+
+        @GET("status/500")
+        void getStatus500();
+
+        @GET("status/500")
+        @ExpectedResponses({500})
+        void getStatus500WithExpectedResponse500();
+    }
+
+    @Test
+    public void service18GetStatus200() {
+        createService(Service18.class)
+                .getStatus200();
+    }
+
+    @Test
+    public void service18GetStatus200WithExpectedResponse200() {
+        createService(Service18.class)
+                .getStatus200WithExpectedResponse200();
+    }
+
+    @Test
+    public void service18GetStatus300() {
+        createService(Service18.class)
+                .getStatus300();
+    }
+
+    @Test
+    public void service18GetStatus300WithExpectedResponse300() {
+        createService(Service18.class)
+                .getStatus300WithExpectedResponse300();
+    }
+
+    @Test(expected = RestException.class)
+    public void service18GetStatus400() {
+        createService(Service18.class)
+                .getStatus400();
+    }
+
+    @Test
+    public void service18GetStatus400WithExpectedResponse400() {
+        createService(Service18.class)
+                .getStatus400WithExpectedResponse400();
+    }
+
+    @Test(expected = RestException.class)
+    public void service18GetStatus500() {
+        createService(Service18.class)
+                .getStatus500();
+    }
+
+    @Test
+    public void service18GetStatus500WithExpectedResponse500() {
+        createService(Service18.class)
+                .getStatus500WithExpectedResponse500();
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service19 {
+        @PUT("put")
+        HttpBinJSON putWithNoContentTypeAndStringBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);
+
+        @PUT("put")
+        HttpBinJSON putWithNoContentTypeAndByteArrayBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) byte[] body);
+
+        @PUT("put")
+        HttpBinJSON putWithHeaderApplicationJsonContentTypeAndStringBody(@BodyParam(ContentType.APPLICATION_JSON) String body);
+
+        @PUT("put")
+        @Headers({ "Content-Type: application/json" })
+        HttpBinJSON putWithHeaderApplicationJsonContentTypeAndByteArrayBody(@BodyParam(ContentType.APPLICATION_JSON) byte[] body);
+
+        @PUT("put")
+        @Headers({ "Content-Type: application/json; charset=utf-8" })
+        HttpBinJSON putWithHeaderApplicationJsonContentTypeAndCharsetAndStringBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);
+
+        @PUT("put")
+        @Headers({ "Content-Type: application/octet-stream" })
+        HttpBinJSON putWithHeaderApplicationOctetStreamContentTypeAndStringBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);
+
+        @PUT("put")
+        @Headers({ "Content-Type: application/octet-stream" })
+        HttpBinJSON putWithHeaderApplicationOctetStreamContentTypeAndByteArrayBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) byte[] body);
+
+        @PUT("put")
+        HttpBinJSON putWithBodyParamApplicationJsonContentTypeAndStringBody(@BodyParam(ContentType.APPLICATION_JSON) String body);
+
+        @PUT("put")
+        HttpBinJSON putWithBodyParamApplicationJsonContentTypeAndCharsetAndStringBody(@BodyParam(ContentType.APPLICATION_JSON + "; charset=utf-8") String body);
+
+        @PUT("put")
+        HttpBinJSON putWithBodyParamApplicationJsonContentTypeAndByteArrayBody(@BodyParam(ContentType.APPLICATION_JSON) byte[] body);
+
+        @PUT("put")
+        HttpBinJSON putWithBodyParamApplicationOctetStreamContentTypeAndStringBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);
+
+        @PUT("put")
+        HttpBinJSON putWithBodyParamApplicationOctetStreamContentTypeAndByteArrayBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) byte[] body);
+    }
+
+    @Test
+    public void service19PutWithNoContentTypeAndStringBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithNoContentTypeAndStringBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithNoContentTypeAndStringBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithNoContentTypeAndStringBody("");
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithNoContentTypeAndStringBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithNoContentTypeAndStringBody("hello");
+        assertEquals("hello", result.data);
+    }
+
+    @Test
+    public void service19PutWithNoContentTypeAndByteArrayBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithNoContentTypeAndByteArrayBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithNoContentTypeAndByteArrayBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithNoContentTypeAndByteArrayBody(new byte[0]);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithNoContentTypeAndByteArrayBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithNoContentTypeAndByteArrayBody(new byte[] { 0, 1, 2, 3, 4 });
+        assertEquals(new String(new byte[] { 0, 1, 2, 3, 4 }), result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndStringBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndStringBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndStringBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndStringBody("");
+        assertEquals("\"\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndStringBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndStringBody("soups and stuff");
+        assertEquals("\"soups and stuff\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndByteArrayBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndByteArrayBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndByteArrayBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndByteArrayBody(new byte[0]);
+        assertEquals("\"\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndByteArrayBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndByteArrayBody(new byte[] { 0, 1, 2, 3, 4 });
+        assertEquals("\"AAECAwQ=\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndCharsetAndStringBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndCharsetAndStringBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndCharsetAndStringBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndCharsetAndStringBody("");
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationJsonContentTypeAndCharsetAndStringBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationJsonContentTypeAndCharsetAndStringBody("soups and stuff");
+        assertEquals("soups and stuff", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationOctetStreamContentTypeAndStringBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationOctetStreamContentTypeAndStringBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationOctetStreamContentTypeAndStringBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationOctetStreamContentTypeAndStringBody("");
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationOctetStreamContentTypeAndStringBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationOctetStreamContentTypeAndStringBody("penguins");
+        assertEquals("penguins", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationOctetStreamContentTypeAndByteArrayBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationOctetStreamContentTypeAndByteArrayBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationOctetStreamContentTypeAndByteArrayBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationOctetStreamContentTypeAndByteArrayBody(new byte[0]);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithHeaderApplicationOctetStreamContentTypeAndByteArrayBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithHeaderApplicationOctetStreamContentTypeAndByteArrayBody(new byte[] { 0, 1, 2, 3, 4 });
+        assertEquals(new String(new byte[] { 0, 1, 2, 3, 4 }), result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndStringBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndStringBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndStringBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndStringBody("");
+        assertEquals("\"\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndStringBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndStringBody("soups and stuff");
+        assertEquals("\"soups and stuff\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndCharsetAndStringBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndCharsetAndStringBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndCharsetAndStringBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndCharsetAndStringBody("");
+        assertEquals("\"\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndCharsetAndStringBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndCharsetAndStringBody("soups and stuff");
+        assertEquals("\"soups and stuff\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndByteArrayBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndByteArrayBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndByteArrayBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndByteArrayBody(new byte[0]);
+        assertEquals("\"\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationJsonContentTypeAndByteArrayBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationJsonContentTypeAndByteArrayBody(new byte[] { 0, 1, 2, 3, 4 });
+        assertEquals("\"AAECAwQ=\"", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationOctetStreamContentTypeAndStringBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationOctetStreamContentTypeAndStringBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationOctetStreamContentTypeAndStringBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationOctetStreamContentTypeAndStringBody("");
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationOctetStreamContentTypeAndStringBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationOctetStreamContentTypeAndStringBody("penguins");
+        assertEquals("penguins", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationOctetStreamContentTypeAndByteArrayBodyWithNullBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationOctetStreamContentTypeAndByteArrayBody(null);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationOctetStreamContentTypeAndByteArrayBodyWithEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationOctetStreamContentTypeAndByteArrayBody(new byte[0]);
+        assertEquals("", result.data);
+    }
+
+    @Test
+    public void service19PutWithBodyParamApplicationOctetStreamContentTypeAndByteArrayBodyWithNonEmptyBody() {
+        final HttpBinJSON result = createService(Service19.class)
+                .putWithBodyParamApplicationOctetStreamContentTypeAndByteArrayBody(new byte[] { 0, 1, 2, 3, 4 });
+        assertEquals(new String(new byte[] { 0, 1, 2, 3, 4 }), result.data);
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service20 {
+        @GET("bytes/100")
+        RestResponseBase<HttpBinHeaders,Void> getBytes100OnlyHeaders();
+
+        @GET("bytes/100")
+        RestResponseBase<HttpHeaders,Void> getBytes100OnlyRawHeaders();
+
+        @GET("bytes/100")
+        RestResponseBase<HttpBinHeaders,byte[]> getBytes100BodyAndHeaders();
+
+        @PUT("put")
+        RestResponseBase<HttpBinHeaders,Void> putOnlyHeaders(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);
+
+        @PUT("put")
+        RestResponseBase<HttpBinHeaders,HttpBinJSON> putBodyAndHeaders(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);
+
+        @GET("bytes/100")
+        RestResponseBase<Void, Void> getBytesOnlyStatus();
+
+        @GET("bytes/100")
+        RestVoidResponse getVoidRestResponse();
+
+        @PUT("put")
+        RestResponse<HttpBinJSON> putBody(@BodyParam(ContentType.APPLICATION_OCTET_STREAM) String body);
+    }
+
+    @Test
+    public void service20GetBytes100OnlyHeaders() {
+        final RestResponseBase<HttpBinHeaders,Void> response = createService(Service20.class)
+                .getBytes100OnlyHeaders();
+        assertNotNull(response);
+
+        assertEquals(200, response.statusCode());
+
+        final HttpBinHeaders headers = response.deserializedHeaders();
+        assertNotNull(headers);
+        assertEquals(true, headers.accessControlAllowCredentials);
+        assertEquals("keep-alive", headers.connection.toLowerCase());
+        assertNotNull(headers.date);
+        // assertEquals("1.1 vegur", headers.via);
+        assertNotEquals(0, headers.xProcessedTime);
+    }
+
+    @Test
+    public void service20GetBytes100BodyAndHeaders() {
+        final RestResponseBase<HttpBinHeaders,byte[]> response = createService(Service20.class)
+                .getBytes100BodyAndHeaders();
+        assertNotNull(response);
+
+        assertEquals(200, response.statusCode());
+
+        final byte[] body = response.body();
+        assertNotNull(body);
+        assertEquals(100, body.length);
+
+        final HttpBinHeaders headers = response.deserializedHeaders();
+        assertNotNull(headers);
+        assertEquals(true, headers.accessControlAllowCredentials);
+        assertNotNull(headers.date);
+        // assertEquals("1.1 vegur", headers.via);
+        assertNotEquals(0, headers.xProcessedTime);
+    }
+
+    @Test
+    public void service20GetBytesOnlyStatus() {
+        final RestResponse<Void> response = createService(Service20.class)
+                .getBytesOnlyStatus();
+        assertNotNull(response);
+        assertEquals(200, response.statusCode());
+    }
+
+    @Test
+    public void service20GetBytesOnlyHeaders() {
+        final RestResponse<Void> response = createService(Service20.class)
+                .getBytes100OnlyRawHeaders();
+
+        assertNotNull(response);
+        assertEquals(200, response.statusCode());
+        assertNotNull(response.headers());
+        assertNotEquals(0, response.headers().size());
+    }
+
+    @Test
+    public void service20PutOnlyHeaders() {
+        final RestResponseBase<HttpBinHeaders,Void> response = createService(Service20.class)
+                .putOnlyHeaders("body string");
+        assertNotNull(response);
+
+        assertEquals(200, response.statusCode());
+
+        final HttpBinHeaders headers = response.deserializedHeaders();
+        assertNotNull(headers);
+        assertEquals(true, headers.accessControlAllowCredentials);
+        assertEquals("keep-alive", headers.connection.toLowerCase());
+        assertNotNull(headers.date);
+        // assertEquals("1.1 vegur", headers.via);
+        assertNotEquals(0, headers.xProcessedTime);
+    }
+
+    @Test
+    public void service20PutBodyAndHeaders() {
+        final RestResponseBase<HttpBinHeaders,HttpBinJSON> response = createService(Service20.class)
+                .putBodyAndHeaders("body string");
+        assertNotNull(response);
+
+        assertEquals(200, response.statusCode());
+
+        final HttpBinJSON body = response.body();
+        assertNotNull(body);
+        assertMatchWithHttpOrHttps("httpbin.org/put", body.url);
+        assertEquals("body string", body.data);
+
+        final HttpBinHeaders headers = response.deserializedHeaders();
+        assertNotNull(headers);
+        assertEquals(true, headers.accessControlAllowCredentials);
+        assertEquals("keep-alive", headers.connection.toLowerCase());
+        assertNotNull(headers.date);
+        // assertEquals("1.1 vegur", headers.via);
+        assertNotEquals(0, headers.xProcessedTime);
+    }
+
+    @Test
+    public void service20GetVoidRestResponse() {
+        final RestVoidResponse response = createService(Service20.class).getVoidRestResponse();
+        assertNotNull(response);
+        assertEquals(200, response.statusCode());
+    }
+
+    @Test
+    public void service20GetRestResponseBody() {
+        final RestResponse<HttpBinJSON> response = createService(Service20.class).putBody("body string");
+        assertNotNull(response);
+        assertEquals(200, response.statusCode());
+
+        final HttpBinJSON body = response.body();
+        assertNotNull(body);
+        assertMatchWithHttpOrHttps("httpbin.org/put", body.url);
+        assertEquals("body string", body.data);
+
+        final HttpHeaders headers = response.headers();
+        assertNotNull(headers);
+    }
+
+    @Host("http://httpbin.org")
+    interface UnexpectedOKService {
+        @GET("/bytes/1024")
+        @ExpectedResponses({400})
+        RestStreamResponse getBytes();
+    }
+
+    @Test
+    public void UnexpectedHTTPOK() {
+        try {
+            createService(UnexpectedOKService.class).getBytes();
+            fail();
+        } catch (RestException e) {
+            assertEquals("Status code 200, (1024-byte body)", e.getMessage());
+        }
+    }
+
+    @Host("https://www.example.com")
+    private interface Service21 {
+        @GET("http://httpbin.org/bytes/100")
+        @ExpectedResponses({200})
+        byte[] getBytes100();
+    }
+
+    @Test
+    public void service21GetBytes100() {
+        final byte[] bytes = createService(Service21.class)
+            .getBytes100();
+        assertNotNull(bytes);
+        assertEquals(100, bytes.length);
+    }
+
+    @Host("http://httpbin.org")
+    interface DownloadService {
+        @GET("/bytes/30720")
+        RestStreamResponse getBytes();
+
+        @GET("/bytes/30720")
+        Flux<ByteBuf> getBytesFlowable();
+    }
+
+    @Test
+    public void SimpleDownloadTest() {
+        try (RestStreamResponse response = createService(DownloadService.class).getBytes()) {
+            int count = 0;
+            for (ByteBuf byteBuf : response.body().doOnNext(b -> b.retain()).toIterable()) {
+                // assertEquals(1, byteBuf.refCnt());
+                count += byteBuf.readableBytes();
+                ReferenceCountUtil.refCnt(byteBuf);
+            }
+            assertEquals(30720, count);
+        }
+    }
+
+    @Test
+    public void RawFlowableDownloadTest() {
+        Flux<ByteBuf> response = createService(DownloadService.class).getBytesFlowable();
+        int count = 0;
+        for (ByteBuf byteBuf : response.doOnNext(b -> b.retain()).toIterable()) {
+            count += byteBuf.readableBytes();
+            ReferenceCountUtil.refCnt(byteBuf);
+        }
+        assertEquals(30720, count);
+    }
+
+    @Host("https://httpbin.org")
+    interface FlowableUploadService {
+        @PUT("/put")
+        RestResponse<HttpBinJSON> put(@BodyParam("text/plain") Flux<ByteBuf> content, @HeaderParam("Content-Length") long contentLength);
+    }
+
+    @Test
+    public void FlowableUploadTest() throws Exception {
+        Path filePath = Paths.get(getClass().getClassLoader().getResource("upload.txt").toURI());
+        Flux<ByteBuf> stream = FluxUtil.byteBufStreamFromFile(AsynchronousFileChannel.open(filePath));
+
+        final HttpClient httpClient = createHttpClient();
+        // Scenario: Log the body so that body buffering/replay behavior is exercised.
+        //
+        // Order in which policies applied will be the order in which they added to builder
+        //
+        final HttpPipeline httpPipeline = new HttpPipeline(httpClient,
+                new HttpLoggingPolicy(HttpLogDetailLevel.BODY_AND_HEADERS, true));
+        //
+        RestResponse<HttpBinJSON> response = RestProxy.create(FlowableUploadService.class, httpPipeline, serializer).put(stream, Files.size(filePath));
+
+        assertEquals("The quick brown fox jumps over the lazy dog", response.body().data);
+    }
+
+    @Test
+    public void SegmentUploadTest() throws Exception {
+        Path filePath = Paths.get(getClass().getClassLoader().getResource("upload.txt").toURI());
+        AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(filePath, StandardOpenOption.READ);
+        RestResponse<HttpBinJSON> response = createService(FlowableUploadService.class)
+                .put(FluxUtil.byteBufStreamFromFile(fileChannel, 4, 15), 15);
+
+        assertEquals("quick brown fox", response.body().data);
+    }
+
+    @Host("{url}")
+    interface Service22 {
+        @GET("{container}/{blob}")
+        byte[] getBytes(@HostParam("url") String url);
+    }
+
+    @Test
+    public void service22GetBytes() {
+        final byte[] bytes = createService(Service22.class).getBytes("http://httpbin.org/bytes/27");
+        assertNotNull(bytes);
+        assertEquals(27, bytes.length);
+    }
+
+    @Host("http://httpbin.org/")
+    interface Service23 {
+        @GET("bytes/28")
+        byte[] getBytes();
+    }
+
+    @Test
+    public void service23GetBytes() {
+        final byte[] bytes = createService(Service23.class)
+            .getBytes();
+        assertNotNull(bytes);
+        assertEquals(28, bytes.length);
+    }
+
+    @Host("http://httpbin.org/")
+    interface Service24 {
+        @PUT("put")
+        HttpBinJSON put(@HeaderParam("ABC") Map<String,String> headerCollection);
+    }
+
+    @Test
+    public void service24Put() {
+        final Map<String,String> headerCollection = new HashMap<>();
+        headerCollection.put("DEF", "GHIJ");
+        headerCollection.put("123", "45");
+        final HttpBinJSON result = createService(Service24.class)
+            .put(headerCollection);
+        assertNotNull(result.headers);
+        final HttpHeaders resultHeaders = new HttpHeaders(result.headers);
+        assertEquals("GHIJ", resultHeaders.value("ABCDEF"));
+        assertEquals("45", resultHeaders.value("ABC123"));
+    }
+
+    @Host("http://httpbin.org")
+    interface Service25 {
+        @GET("anything")
+        HttpBinJSON get();
+
+        @GET("anything")
+        Mono<HttpBinJSON> getAsync();
+
+        @GET("anything")
+        Mono<RestResponse<HttpBinJSON>> getBodyResponseAsync();
+    }
+
+    @Test(expected = RestException.class)
+    @Ignore("Decoding is not a policy anymore")
+    public void testMissingDecodingPolicyCausesException() {
+        Service25 service = RestProxy.create(Service25.class, new HttpPipeline());
+        service.get();
+    }
+
+    @Test(expected = RestException.class)
+    @Ignore("Decoding is not a policy anymore")
+    public void testSingleMissingDecodingPolicyCausesException() {
+        Service25 service = RestProxy.create(Service25.class, new HttpPipeline());
+        service.getAsync().block();
+        service.getBodyResponseAsync().block();
+    }
+
+    @Test(expected = RestException.class)
+    @Ignore("Decoding is not a policy anymore")
+    public void testSingleBodyResponseMissingDecodingPolicyCausesException() {
+        Service25 service = RestProxy.create(Service25.class, new HttpPipeline());
+        service.getBodyResponseAsync().block();
+    }
+
+    // Helpers
+    protected <T> T createService(Class<T> serviceClass) {
+        final HttpClient httpClient = createHttpClient();
+        return createService(serviceClass, httpClient);
+    }
+
+    protected <T> T createService(Class<T> serviceClass, HttpClient httpClient) {
+        final HttpPipeline httpPipeline = new HttpPipeline(httpClient);
+
+        return RestProxy.create(serviceClass, httpPipeline, serializer);
+    }
+
+    private static void assertContains(String value, String expectedSubstring) {
+        assertTrue("Expected \"" + value + "\" to contain \"" + expectedSubstring + "\".", value.contains(expectedSubstring));
+    }
+
+    private static void assertMatchWithHttpOrHttps(String url1, String url2) {
+        final String s1 = "http://" + url1;
+        if (s1.equalsIgnoreCase(url2)) {
+            return;
+        }
+        final String s2 = "https://" + url1;
+        if (s2.equalsIgnoreCase(url2)) {
+            return;
+        }
+        Assert.assertTrue("'" + url2 + "' does not match with '" + s1 + "' or '" + s2 + "'." , false);
+    }
+
+    private static final SerializerAdapter serializer = new JacksonAdapter();
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithHttpProxyNettyTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithHttpProxyNettyTests.java
new file mode 100644
index 0000000000000..ccea70d65aacb
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithHttpProxyNettyTests.java
@@ -0,0 +1,18 @@
+package com.azure.common.implementation;
+
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.ProxyOptions;
+import com.azure.common.http.ProxyOptions.Type;
+import org.junit.Ignore;
+
+import java.net.InetSocketAddress;
+
+@Ignore("Should only be run manually when a local proxy server (e.g. Fiddler) is running")
+public class RestProxyWithHttpProxyNettyTests extends RestProxyTests {
+
+    @Override
+    protected HttpClient createHttpClient() {
+        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
+        return HttpClient.createDefault().proxy(() -> new ProxyOptions(Type.HTTP, address));
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithMockTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithMockTests.java
new file mode 100644
index 0000000000000..9db96e8060883
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithMockTests.java
@@ -0,0 +1,416 @@
+package com.azure.common.implementation;
+
+import com.azure.common.http.rest.RestException;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.HeaderCollection;
+import com.azure.common.annotations.Host;
+import com.azure.common.annotations.ReturnValueWireType;
+import com.azure.common.entities.HttpBinJSON;
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.MockHttpClient;
+import com.azure.common.http.MockHttpResponse;
+import com.azure.common.http.rest.RestResponse;
+import com.azure.common.http.rest.RestResponseBase;
+import com.azure.common.http.ProxyOptions;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class RestProxyWithMockTests extends RestProxyTests {
+    @Override
+    protected HttpClient createHttpClient() {
+        return new MockHttpClient();
+    }
+
+    @Host("http://httpbin.org")
+    private interface Service1 {
+        @GET("Base64UrlBytes/10")
+        @ReturnValueWireType(Base64Url.class)
+        byte[] getBase64UrlBytes10();
+
+        @GET("Base64UrlListOfBytes")
+        @ReturnValueWireType(Base64Url.class)
+        List<byte[]> getBase64UrlListOfBytes();
+
+        @GET("Base64UrlListOfListOfBytes")
+        @ReturnValueWireType(Base64Url.class)
+        List<List<byte[]>> getBase64UrlListOfListOfBytes();
+
+        @GET("Base64UrlMapOfBytes")
+        @ReturnValueWireType(Base64Url.class)
+        Map<String,byte[]> getBase64UrlMapOfBytes();
+
+        @GET("DateTimeRfc1123")
+        @ReturnValueWireType(DateTimeRfc1123.class)
+        OffsetDateTime getDateTimeRfc1123();
+
+        @GET("UnixTime")
+        @ReturnValueWireType(UnixTime.class)
+        OffsetDateTime getDateTimeUnix();
+    }
+
+    @Test
+    public void service1GetBase64UrlBytes10() {
+        final byte[] bytes = createService(Service1.class)
+                .getBase64UrlBytes10();
+        assertNotNull(bytes);
+        assertEquals(10, bytes.length);
+        for (int i = 0; i < 10; ++i) {
+            assertEquals((byte)i, bytes[i]);
+        }
+    }
+
+    @Test
+    public void service1GetBase64UrlListOfBytes() {
+        final List<byte[]> bytesList = createService(Service1.class)
+                .getBase64UrlListOfBytes();
+        assertNotNull(bytesList);
+        assertEquals(3, bytesList.size());
+
+        for (int i = 0; i < bytesList.size(); ++i) {
+            final byte[] bytes = bytesList.get(i);
+            assertNotNull(bytes);
+            assertEquals((i + 1) * 10, bytes.length);
+            for (int j = 0; j < bytes.length; ++j) {
+                assertEquals((byte)j, bytes[j]);
+            }
+        }
+    }
+
+    @Test
+    public void service1GetBase64UrlListOfListOfBytes() {
+        final List<List<byte[]>> bytesList = createService(Service1.class)
+                .getBase64UrlListOfListOfBytes();
+        assertNotNull(bytesList);
+        assertEquals(2, bytesList.size());
+
+        for (int i = 0; i < bytesList.size(); ++i) {
+            final List<byte[]> innerList = bytesList.get(i);
+            assertEquals((i + 1) * 2, innerList.size());
+
+            for (int j = 0; j < innerList.size(); ++j) {
+                final byte[] bytes = innerList.get(j);
+                assertNotNull(bytes);
+                assertEquals((j + 1) * 5, bytes.length);
+                for (int k = 0; k < bytes.length; ++k) {
+                    assertEquals(k, bytes[k]);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void service1GetBase64UrlMapOfBytes() {
+        final Map<String,byte[]> bytesMap = createService(Service1.class)
+                .getBase64UrlMapOfBytes();
+        assertNotNull(bytesMap);
+        assertEquals(2, bytesMap.size());
+
+        for (int i = 0; i < bytesMap.size(); ++i) {
+            final byte[] bytes = bytesMap.get(Integer.toString(i));
+
+            final int expectedArrayLength = (i + 1) * 10;
+            assertEquals(expectedArrayLength, bytes.length);
+            for (int j = 0; j < expectedArrayLength; ++j) {
+                assertEquals((byte)j, bytes[j]);
+            }
+        }
+    }
+
+    @Test
+    public void service1GetDateTimeRfc1123() {
+        final OffsetDateTime dateTime = createService(Service1.class)
+                .getDateTimeRfc1123();
+        assertNotNull(dateTime);
+        assertEquals(OffsetDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneOffset.UTC), dateTime);
+    }
+
+    @Test
+    public void service1GetDateTimeUnix() {
+        final OffsetDateTime dateTime = createService(Service1.class)
+                .getDateTimeUnix();
+        assertNotNull(dateTime);
+        assertEquals(OffsetDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneOffset.UTC), dateTime);
+    }
+
+
+    @Host("http://httpbin.org")
+    interface ServiceErrorWithCharsetService {
+        @GET("/get")
+        @ExpectedResponses({400})
+        HttpBinJSON get();
+    }
+
+    @Test
+    public void ServiceErrorWithResponseContentType() {
+        ServiceErrorWithCharsetService service = RestProxy.create(
+                ServiceErrorWithCharsetService.class,
+                new HttpPipeline(new SimpleMockHttpClient() {
+                    @Override
+                    public Mono<HttpResponse> send(HttpRequest request) {
+                        HttpHeaders headers = new HttpHeaders();
+                        headers.set("Content-Type", "application/json");
+
+                        HttpResponse response = new MockHttpResponse(request, 200, headers,
+                                "{ \"error\": \"Something went wrong, but at least this JSON is valid.\"}".getBytes(StandardCharsets.UTF_8));
+                        return Mono.just(response);
+                    }
+                }));
+
+        try {
+            service.get();
+            fail();
+        } catch (RuntimeException ex) {
+            assertEquals(ex.getMessage(), "Status code 200, \"{ \"error\": \"Something went wrong, but at least this JSON is valid.\"}\"");
+        }
+    }
+
+    @Test
+    public void ServiceErrorWithResponseContentTypeBadJSON() {
+        ServiceErrorWithCharsetService service = RestProxy.create(
+                ServiceErrorWithCharsetService.class,
+                new HttpPipeline(new SimpleMockHttpClient() {
+                    @Override
+                    public Mono<HttpResponse> send(HttpRequest request) {
+                        HttpHeaders headers = new HttpHeaders();
+                        headers.set("Content-Type", "application/json");
+
+                        HttpResponse response = new MockHttpResponse(request, 200, headers, "BAD JSON".getBytes(StandardCharsets.UTF_8));
+                        return Mono.just(response);
+                    }
+                }));
+
+        try {
+            service.get();
+            fail();
+        } catch (RestException ex) {
+            assertContains(ex.getMessage(), "Status code 200");
+            assertContains(ex.getMessage(), "\"BAD JSON\"");
+        }
+    }
+
+    @Test
+    public void ServiceErrorWithResponseContentTypeCharset() {
+        ServiceErrorWithCharsetService service = RestProxy.create(
+                ServiceErrorWithCharsetService.class,
+                new HttpPipeline(new SimpleMockHttpClient() {
+                    @Override
+                    public Mono<HttpResponse> send(HttpRequest request) {
+                        HttpHeaders headers = new HttpHeaders();
+                        headers.set("Content-Type", "application/json; charset=UTF-8");
+
+                        HttpResponse response = new MockHttpResponse(request, 200, headers,
+                                "{ \"error\": \"Something went wrong, but at least this JSON is valid.\"}".getBytes(StandardCharsets.UTF_8));
+                        return Mono.just(response);
+                    }
+                }));
+
+        try {
+            service.get();
+            fail();
+        } catch (RuntimeException ex) {
+            assertEquals(ex.getMessage(), "Status code 200, \"{ \"error\": \"Something went wrong, but at least this JSON is valid.\"}\"");
+        }
+    }
+
+    @Test
+    public void ServiceErrorWithResponseContentTypeCharsetBadJSON() {
+        ServiceErrorWithCharsetService service = RestProxy.create(
+                ServiceErrorWithCharsetService.class,
+                new HttpPipeline(new SimpleMockHttpClient() {
+                    @Override
+                    public Mono<HttpResponse> send(HttpRequest request) {
+                        HttpHeaders headers = new HttpHeaders();
+                        headers.set("Content-Type", "application/json; charset=UTF-8");
+
+                        HttpResponse response = new MockHttpResponse(request, 200, headers, "BAD JSON".getBytes(StandardCharsets.UTF_8));
+                        return Mono.just(response);
+                    }
+                }));
+
+        try {
+            service.get();
+            fail();
+        } catch (RestException ex) {
+            assertContains(ex.getMessage(), "Status code 200");
+            assertContains(ex.getMessage(), "\"BAD JSON\"");
+        }
+    }
+
+    private static class HeaderCollectionTypePublicFields {
+        public String name;
+
+        @HeaderCollection("header-collection-prefix-")
+        public Map<String,String> headerCollection;
+    }
+
+    private static class HeaderCollectionTypeProtectedFields {
+        protected String name;
+
+        @HeaderCollection("header-collection-prefix-")
+        protected Map<String,String> headerCollection;
+    }
+
+    private static class HeaderCollectionTypePrivateFields {
+        private String name;
+
+        @HeaderCollection("header-collection-prefix-")
+        private Map<String,String> headerCollection;
+    }
+
+    private static class HeaderCollectionTypePackagePrivateFields {
+        String name;
+
+        @HeaderCollection("header-collection-prefix-")
+        Map<String,String> headerCollection;
+    }
+
+    @Host("https://www.example.com")
+    interface ServiceHeaderCollections {
+        @GET("url/path")
+        RestResponseBase<HeaderCollectionTypePublicFields,Void> publicFields();
+
+        @GET("url/path")
+        RestResponseBase<HeaderCollectionTypeProtectedFields,Void> protectedFields();
+
+        @GET("url/path")
+        RestResponseBase<HeaderCollectionTypePrivateFields,Void> privateFields();
+
+        @GET("url/path")
+        RestResponseBase<HeaderCollectionTypePackagePrivateFields,Void> packagePrivateFields();
+    }
+
+    private static final HttpClient headerCollectionHttpClient = new MockHttpClient() {
+        @Override
+        public Mono<HttpResponse> send(HttpRequest request) {
+            final HttpHeaders headers = new HttpHeaders();
+            headers.set("name", "Phillip");
+            headers.set("header-collection-prefix-one", "1");
+            headers.set("header-collection-prefix-two", "2");
+            headers.set("header-collection-prefix-three", "3");
+            final MockHttpResponse response = new MockHttpResponse(request, 200, headers);
+            return Mono.<HttpResponse>just(response);
+        }
+    };
+
+    private ServiceHeaderCollections createHeaderCollectionsService() {
+        return createService(ServiceHeaderCollections.class, headerCollectionHttpClient);
+    }
+
+    private static void assertHeaderCollectionsRawHeaders(RestResponse<Void> response) {
+        final HttpHeaders responseRawHeaders = response.headers();
+        assertEquals("Phillip", responseRawHeaders.value("name"));
+        assertEquals("1", responseRawHeaders.value("header-collection-prefix-one"));
+        assertEquals("2", responseRawHeaders.value("header-collection-prefix-two"));
+        assertEquals("3", responseRawHeaders.value("header-collection-prefix-three"));
+        assertEquals(4, responseRawHeaders.size());
+    }
+
+    private static void assertHeaderCollections(Map<String,String> headerCollections) {
+        final Map<String,String> expectedHeaderCollections = new HashMap<>();
+        expectedHeaderCollections.put("one", "1");
+        expectedHeaderCollections.put("two", "2");
+        expectedHeaderCollections.put("three", "3");
+
+        for (final String key : headerCollections.keySet()) {
+            assertEquals(expectedHeaderCollections.get(key), headerCollections.get(key));
+        }
+        assertEquals(expectedHeaderCollections.size(), headerCollections.size());
+    }
+
+    @Test
+    public void serviceHeaderCollectionPublicFields() {
+        final RestResponseBase<HeaderCollectionTypePublicFields,Void> response = createHeaderCollectionsService()
+            .publicFields();
+        assertNotNull(response);
+        assertHeaderCollectionsRawHeaders(response);
+
+        final HeaderCollectionTypePublicFields responseHeaders = response.deserializedHeaders();
+        assertNotNull(responseHeaders);
+        assertEquals("Phillip", responseHeaders.name);
+        assertHeaderCollections(responseHeaders.headerCollection);
+    }
+
+    @Test
+    public void serviceHeaderCollectionProtectedFields() {
+        final RestResponseBase<HeaderCollectionTypeProtectedFields,Void> response = createHeaderCollectionsService()
+            .protectedFields();
+        assertNotNull(response);
+        assertHeaderCollectionsRawHeaders(response);
+
+        final HeaderCollectionTypeProtectedFields responseHeaders = response.deserializedHeaders();
+        assertNotNull(responseHeaders);
+        assertEquals("Phillip", responseHeaders.name);
+        assertHeaderCollections(responseHeaders.headerCollection);
+    }
+
+    @Test
+    public void serviceHeaderCollectionPrivateFields() {
+        final RestResponseBase<HeaderCollectionTypePrivateFields,Void> response = createHeaderCollectionsService()
+            .privateFields();
+        assertNotNull(response);
+        assertHeaderCollectionsRawHeaders(response);
+
+        final HeaderCollectionTypePrivateFields responseHeaders = response.deserializedHeaders();
+        assertNotNull(responseHeaders);
+        assertEquals("Phillip", responseHeaders.name);
+        assertHeaderCollections(responseHeaders.headerCollection);
+    }
+
+    @Test
+    public void serviceHeaderCollectionPackagePrivateFields() {
+        final RestResponseBase<HeaderCollectionTypePackagePrivateFields,Void> response = createHeaderCollectionsService()
+            .packagePrivateFields();
+        assertNotNull(response);
+        assertHeaderCollectionsRawHeaders(response);
+
+        final HeaderCollectionTypePackagePrivateFields responseHeaders = response.deserializedHeaders();
+        assertNotNull(responseHeaders);
+        assertEquals("Phillip", responseHeaders.name);
+        assertHeaderCollections(responseHeaders.headerCollection);
+    }
+
+    private static void assertContains(String value, String expectedSubstring) {
+        assertTrue("Expected \"" + value + "\" to contain \"" + expectedSubstring + "\".", value.contains(expectedSubstring));
+    }
+
+
+    private static abstract class SimpleMockHttpClient implements HttpClient {
+
+        @Override
+        public abstract Mono<HttpResponse> send(HttpRequest request);
+
+        @Override
+        public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+            throw new IllegalStateException("MockHttpClient.proxy not implemented.");
+        }
+
+        @Override
+        public HttpClient wiretap(boolean enableWiretap) {
+            throw new IllegalStateException("MockHttpClient.wiretap not implemented.");
+        }
+
+        @Override
+        public HttpClient port(int port) {
+            throw new IllegalStateException("MockHttpClient.port not implemented.");
+        }
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithNettyTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithNettyTests.java
new file mode 100644
index 0000000000000..92c0fad41bd0e
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyWithNettyTests.java
@@ -0,0 +1,11 @@
+package com.azure.common.implementation;
+
+import com.azure.common.http.HttpClient;
+
+public class RestProxyWithNettyTests extends RestProxyTests {
+
+    @Override
+    protected HttpClient createHttpClient() {
+        return HttpClient.createDefault();
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyXMLTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyXMLTests.java
new file mode 100644
index 0000000000000..43b69112e2782
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/RestProxyXMLTests.java
@@ -0,0 +1,227 @@
+/**
+ *
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *
+ */
+
+package com.azure.common.implementation;
+
+import com.azure.common.annotations.BodyParam;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.Host;
+import com.azure.common.annotations.PUT;
+import com.azure.common.entities.AccessPolicy;
+import com.azure.common.entities.SignedIdentifierInner;
+import com.azure.common.entities.SignedIdentifiersWrapper;
+import com.azure.common.entities.Slideshow;
+import com.azure.common.http.HttpClient;
+import com.azure.common.http.HttpHeaders;
+import com.azure.common.http.HttpMethod;
+import com.azure.common.http.HttpPipeline;
+import com.azure.common.http.HttpRequest;
+import com.azure.common.http.HttpResponse;
+import com.azure.common.http.MockHttpResponse;
+import com.azure.common.http.ProxyOptions;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.jackson.JacksonAdapter;
+import com.azure.common.implementation.util.FluxUtil;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.OffsetDateTime;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static org.junit.Assert.*;
+
+
+public class RestProxyXMLTests {
+    static class MockXMLHTTPClient implements HttpClient {
+        private HttpResponse response(HttpRequest request, String resource) throws IOException, URISyntaxException {
+            URL url = getClass().getClassLoader().getResource(resource);
+            byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
+            HttpHeaders headers = new HttpHeaders();
+            headers.set("Content-Type", "application/xml");
+            HttpResponse res = new MockHttpResponse(request, 200, headers, bytes);
+            return res;
+        }
+        @Override
+        public Mono<HttpResponse> send(HttpRequest request) {
+            try {
+                if (request.url().toString().endsWith("GetContainerACLs")) {
+                    return Mono.just(response(request, "GetContainerACLs.xml"));
+                } else if (request.url().toString().endsWith("GetXMLWithAttributes")) {
+                    return Mono.just(response(request, "GetXMLWithAttributes.xml"));
+                } else {
+                    return Mono.<HttpResponse>just(new MockHttpResponse(request, 404));
+                }
+            } catch (IOException | URISyntaxException e) {
+                return Mono.error(e);
+            }
+        }
+
+        @Override
+        public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+            throw new IllegalStateException("MockHttpClient.proxy");
+        }
+
+        @Override
+        public HttpClient wiretap(boolean enableWiretap) {
+            throw new IllegalStateException("MockHttpClient.wiretap");
+        }
+
+        @Override
+        public HttpClient port(int port) {
+            throw new IllegalStateException("MockHttpClient.port");
+        }
+    }
+
+    @Host("http://unused")
+    interface MyXMLService {
+        @GET("GetContainerACLs")
+        SignedIdentifiersWrapper getContainerACLs();
+
+        @PUT("SetContainerACLs")
+        void setContainerACLs(@BodyParam("application/xml") SignedIdentifiersWrapper signedIdentifiers);
+    }
+
+    @Test
+    public void canReadXMLResponse() throws Exception {
+        //
+        final HttpPipeline pipeline = new HttpPipeline(new MockXMLHTTPClient());
+
+        //
+        MyXMLService myXMLService = RestProxy.create(MyXMLService.class,
+                pipeline,
+                new JacksonAdapter());
+        List<SignedIdentifierInner> identifiers = myXMLService.getContainerACLs().signedIdentifiers();
+        assertNotNull(identifiers);
+        assertNotEquals(0, identifiers.size());
+    }
+
+    static class MockXMLReceiverClient implements HttpClient {
+        byte[] receivedBytes = null;
+
+        @Override
+        public Mono<HttpResponse> send(HttpRequest request) {
+            if (request.url().toString().endsWith("SetContainerACLs")) {
+                return FluxUtil.collectBytesInByteBufStream(request.body(), false)
+                        .map(bytes -> {
+                            receivedBytes = bytes;
+                            return new MockHttpResponse(request, 200);
+                        });
+            } else {
+                return Mono.<HttpResponse>just(new MockHttpResponse(request, 404));
+            }
+        }
+
+        @Override
+        public HttpClient proxy(Supplier<ProxyOptions> proxyOptions) {
+            throw new IllegalStateException("MockHttpClient.proxy");
+        }
+
+        @Override
+        public HttpClient wiretap(boolean enableWiretap) {
+            throw new IllegalStateException("MockHttpClient.wiretap");
+        }
+
+        @Override
+        public HttpClient port(int port) {
+            throw new IllegalStateException("MockHttpClient.port");
+        }
+    }
+
+    @Test
+    public void canWriteXMLRequest() throws Exception {
+        URL url = getClass().getClassLoader().getResource("GetContainerACLs.xml");
+        byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
+        HttpRequest request = new HttpRequest(HttpMethod.PUT, new URL("http://unused/SetContainerACLs"));
+        request.withBody(bytes);
+
+        SignedIdentifierInner si = new SignedIdentifierInner();
+        si.withId("MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=");
+
+        AccessPolicy ap = new AccessPolicy();
+        ap.withStart(OffsetDateTime.parse("2009-09-28T08:49:37.0000000Z"));
+        ap.withExpiry(OffsetDateTime.parse("2009-09-29T08:49:37.0000000Z"));
+        ap.withPermission("rwd");
+
+        si.withAccessPolicy(ap);
+        List<SignedIdentifierInner> expectedAcls = Collections.singletonList(si);
+
+        JacksonAdapter serializer = new JacksonAdapter();
+        MockXMLReceiverClient httpClient = new MockXMLReceiverClient();
+        //
+        final HttpPipeline pipeline = new HttpPipeline(httpClient);
+        //
+        MyXMLService myXMLService = RestProxy.create(MyXMLService.class,
+                pipeline,
+                serializer);
+        SignedIdentifiersWrapper wrapper = new SignedIdentifiersWrapper(expectedAcls);
+        myXMLService.setContainerACLs(wrapper);
+
+        SignedIdentifiersWrapper actualAclsWrapped = serializer.deserialize(
+                new String(httpClient.receivedBytes, StandardCharsets.UTF_8),
+                SignedIdentifiersWrapper.class,
+                SerializerEncoding.XML);
+
+        List<SignedIdentifierInner> actualAcls = actualAclsWrapped.signedIdentifiers();
+
+        // Ideally we'd just check for "things that matter" about the XML-- e.g. the tag names, structure, and attributes needs to be the same,
+        // but it doesn't matter if one document has a trailing newline or has UTF-8 in the header instead of utf-8, or if comments are missing.
+        assertEquals(expectedAcls.size(), actualAcls.size());
+        assertEquals(expectedAcls.get(0).id(), actualAcls.get(0).id());
+        assertEquals(expectedAcls.get(0).accessPolicy().expiry(), actualAcls.get(0).accessPolicy().expiry());
+        assertEquals(expectedAcls.get(0).accessPolicy().start(), actualAcls.get(0).accessPolicy().start());
+        assertEquals(expectedAcls.get(0).accessPolicy().permission(), actualAcls.get(0).accessPolicy().permission());
+    }
+
+    @Host("http://unused")
+    public interface MyXMLServiceWithAttributes {
+        @GET("GetXMLWithAttributes")
+        Slideshow getSlideshow();
+    }
+
+    @Test
+    public void canDeserializeXMLWithAttributes() throws Exception {
+        JacksonAdapter serializer = new JacksonAdapter();
+        //
+        final HttpPipeline pipeline = new HttpPipeline(new MockXMLHTTPClient());
+
+        //
+        MyXMLServiceWithAttributes myXMLService = RestProxy.create(
+                MyXMLServiceWithAttributes.class,
+                pipeline,
+                serializer);
+
+        Slideshow slideshow = myXMLService.getSlideshow();
+        assertEquals("Sample Slide Show", slideshow.title);
+        assertEquals("Date of publication", slideshow.date);
+        assertEquals("Yours Truly", slideshow.author);
+        assertEquals(2, slideshow.slides.length);
+
+        assertEquals("all", slideshow.slides[0].type);
+        assertEquals("Wake up to WonderWidgets!", slideshow.slides[0].title);
+        assertNull(slideshow.slides[0].items);
+
+        assertEquals("all", slideshow.slides[1].type);
+        assertEquals("Overview", slideshow.slides[1].title);
+        assertEquals(3, slideshow.slides[1].items.length);
+        assertEquals("Why WonderWidgets are great", slideshow.slides[1].items[0]);
+        assertEquals("", slideshow.slides[1].items[1]);
+        assertEquals("Who buys WonderWidgets", slideshow.slides[1].items[2]);
+
+        String xml = serializer.serialize(slideshow, SerializerEncoding.XML);
+        Slideshow newSlideshow = serializer.deserialize(xml, Slideshow.class, SerializerEncoding.XML);
+        String newXML = serializer.serialize(newSlideshow, SerializerEncoding.XML);
+        assertEquals(xml, newXML);
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/SubstitutionTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/SubstitutionTests.java
new file mode 100644
index 0000000000000..a51a534d50837
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/SubstitutionTests.java
@@ -0,0 +1,15 @@
+package com.azure.common.implementation;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class SubstitutionTests {
+    @Test
+    public void constructor() {
+        final Substitution s = new Substitution("A", 2, true);
+        assertEquals("A", s.urlParameterName());
+        assertEquals(2, s.methodParameterIndex());
+        assertEquals(true, s.shouldEncode());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/SwaggerInterfaceParserTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/SwaggerInterfaceParserTests.java
new file mode 100644
index 0000000000000..4c4f87a2c4455
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/SwaggerInterfaceParserTests.java
@@ -0,0 +1,54 @@
+package com.azure.common.implementation;
+
+import com.azure.common.implementation.exception.MissingRequiredAnnotationException;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.GET;
+import com.azure.common.annotations.Host;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+import static org.junit.Assert.*;
+
+public class SwaggerInterfaceParserTests {
+
+    interface TestInterface1 {
+        String testMethod1();
+    }
+
+    @Host("https://management.azure.com")
+    interface TestInterface2 {
+    }
+
+    @Test(expected = MissingRequiredAnnotationException.class)
+    public void hostWithNoHostAnnotation() {
+        new SwaggerInterfaceParser(TestInterface1.class, null);
+    }
+
+    @Test
+    public void hostWithHostAnnotation() {
+        final SwaggerInterfaceParser interfaceParser = new SwaggerInterfaceParser(TestInterface2.class, null);
+        assertEquals("https://management.azure.com", interfaceParser.host());
+    }
+
+    @Host("https://azure.com")
+    interface TestInterface3 {
+        @GET("my/url/path")
+        @ExpectedResponses({200})
+        void testMethod3();
+    }
+
+    @Test
+    public void methodParser() {
+        final SwaggerInterfaceParser interfaceParser = new SwaggerInterfaceParser(TestInterface3.class, null);
+        final Method testMethod3 = TestInterface3.class.getDeclaredMethods()[0];
+        assertEquals("testMethod3", testMethod3.getName());
+
+        final SwaggerMethodParser methodParser = interfaceParser.methodParser(testMethod3);
+        assertNotNull(methodParser);
+        assertEquals("com.azure.common.implementation.SwaggerInterfaceParserTests$TestInterface3.testMethod3", methodParser.fullyQualifiedMethodName());
+
+        final SwaggerMethodParser methodDetails2 = interfaceParser.methodParser(testMethod3);
+        assertSame(methodParser, methodDetails2);
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/SwaggerMethodParserTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/SwaggerMethodParserTests.java
new file mode 100644
index 0000000000000..1168e187ddaf2
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/SwaggerMethodParserTests.java
@@ -0,0 +1,76 @@
+package com.azure.common.implementation;
+
+import com.azure.common.implementation.exception.MissingRequiredAnnotationException;
+import com.azure.common.MyRestException;
+import com.azure.common.http.rest.RestException;
+import com.azure.common.annotations.ExpectedResponses;
+import com.azure.common.annotations.PATCH;
+import com.azure.common.annotations.UnexpectedResponseExceptionType;
+import com.azure.common.entities.HttpBinJSON;
+import com.azure.common.http.HttpMethod;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import static org.junit.Assert.*;
+
+public class SwaggerMethodParserTests {
+
+    interface TestInterface1 {
+        void testMethod1();
+    }
+
+    @Test(expected = MissingRequiredAnnotationException.class)
+    public void withNoAnnotations() {
+        final Method testMethod1 = TestInterface1.class.getDeclaredMethods()[0];
+        assertEquals("testMethod1", testMethod1.getName());
+
+        new SwaggerMethodParser(testMethod1, RestProxy.createDefaultSerializer(), "https://raw.host.com");
+    }
+
+    interface TestInterface2 {
+        @PATCH("my/rest/api/path")
+        @ExpectedResponses({200})
+        void testMethod2();
+    }
+
+    @Test
+    public void withOnlyExpectedResponse() throws IOException {
+        final Method testMethod2 = TestInterface2.class.getDeclaredMethods()[0];
+        assertEquals("testMethod2", testMethod2.getName());
+
+        final SwaggerMethodParser methodParser = new SwaggerMethodParser(testMethod2, RestProxy.createDefaultSerializer(), "https://raw.host.com");
+        assertEquals("com.azure.common.implementation.SwaggerMethodParserTests$TestInterface2.testMethod2", methodParser.fullyQualifiedMethodName());
+        assertEquals(HttpMethod.PATCH, methodParser.httpMethod());
+        assertArrayEquals(new int[] { 200 }, methodParser.expectedStatusCodes());
+        assertEquals(RestException.class, methodParser.exceptionType());
+        assertEquals(Object.class, methodParser.exceptionBodyType());
+        assertEquals(false, methodParser.headers(null).iterator().hasNext());
+        assertEquals("https", methodParser.scheme(null));
+        assertEquals("raw.host.com", methodParser.host(null));
+    }
+
+    interface TestInterface3 {
+        @PATCH("my/rest/api/path")
+        @ExpectedResponses({200})
+        @UnexpectedResponseExceptionType(MyRestException.class)
+        void testMethod3();
+    }
+
+    @Test
+    public void withExpectedResponseAndUnexpectedResponseExceptionType() throws IOException {
+        final Method testMethod3 = TestInterface3.class.getDeclaredMethods()[0];
+        assertEquals("testMethod3", testMethod3.getName());
+
+        final SwaggerMethodParser methodParser = new SwaggerMethodParser(testMethod3, RestProxy.createDefaultSerializer(), "https://raw.host.com");
+        assertEquals("com.azure.common.implementation.SwaggerMethodParserTests$TestInterface3.testMethod3", methodParser.fullyQualifiedMethodName());
+        assertEquals(HttpMethod.PATCH, methodParser.httpMethod());
+        assertArrayEquals(new int[] { 200 }, methodParser.expectedStatusCodes());
+        assertEquals(MyRestException.class, methodParser.exceptionType());
+        assertEquals(HttpBinJSON.class, methodParser.exceptionBodyType());
+        assertEquals(false, methodParser.headers(null).iterator().hasNext());
+        assertEquals("https", methodParser.scheme(null));
+        assertEquals("raw.host.com", methodParser.host(null));
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/UrlEscaperTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/UrlEscaperTests.java
new file mode 100644
index 0000000000000..ab481f898c4e3
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/UrlEscaperTests.java
@@ -0,0 +1,67 @@
+package com.azure.common.implementation;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class UrlEscaperTests {
+    private static String simple = "abcABC-123";
+    private static String genDelim = "abc[456#78";
+    private static String safeForPath = "abc:456@78";
+    private static String safeForQuery = "abc/456?78";
+
+    @Test
+    public void canEscapePathSimple() {
+        PercentEscaper escaper = UrlEscapers.PATH_ESCAPER;
+        String actual = escaper.escape(simple);
+        Assert.assertEquals(simple, actual);
+    }
+
+    @Test
+    public void canEscapeQuerySimple() {
+        PercentEscaper escaper = UrlEscapers.QUERY_ESCAPER;
+        String actual = escaper.escape(simple);
+        Assert.assertEquals(simple, actual);
+    }
+
+    @Test
+    public void canEscapePathWithGenDelim() {
+        PercentEscaper escaper = UrlEscapers.PATH_ESCAPER;
+        String actual = escaper.escape(genDelim);
+        Assert.assertEquals("abc%5b456%2378", actual);
+    }
+
+    @Test
+    public void canEscapeQueryWithGenDelim() {
+        PercentEscaper escaper = UrlEscapers.QUERY_ESCAPER;
+        String actual = escaper.escape(genDelim);
+        Assert.assertEquals("abc%5b456%2378", actual);
+    }
+
+    @Test
+    public void canEscapePathWithSafeForPath() {
+        PercentEscaper escaper = UrlEscapers.PATH_ESCAPER;
+        String actual = escaper.escape(safeForPath);
+        Assert.assertEquals(safeForPath, actual);
+    }
+
+    @Test
+    public void canEscapeQueryWithSafeForPath() {
+        PercentEscaper escaper = UrlEscapers.QUERY_ESCAPER;
+        String actual = escaper.escape(safeForPath);
+        Assert.assertEquals("abc%3a456%4078", actual);
+    }
+
+    @Test
+    public void canEscapePathWithSafeForQuery() {
+        PercentEscaper escaper = UrlEscapers.PATH_ESCAPER;
+        String actual = escaper.escape(safeForQuery);
+        Assert.assertEquals("abc%2f456%3f78", actual);
+    }
+
+    @Test
+    public void canEscapeQueryWithSafeForQuery() {
+        PercentEscaper escaper = UrlEscapers.QUERY_ESCAPER;
+        String actual = escaper.escape(safeForQuery);
+        Assert.assertEquals(safeForQuery, actual);
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/ValidatorTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/ValidatorTests.java
new file mode 100644
index 0000000000000..a4f40f7d51ca7
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/ValidatorTests.java
@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.node.TextNode;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.fail;
+
+public class ValidatorTests {
+    @Test
+    public void validateInt() throws Exception {
+        IntWrapper body = new IntWrapper();
+        body.value = 2;
+        body.nullable = null;
+        Validator.validate(body); // pass
+    }
+
+    @Test
+    public void validateInteger() throws Exception {
+        IntegerWrapper body = new IntegerWrapper();
+        body.value = 3;
+        Validator.validate(body); // pass
+        try {
+            body.value = null;
+            Validator.validate(body); // fail
+            fail();
+        } catch (IllegalArgumentException ex) {
+            Assert.assertTrue(ex.getMessage().contains("value is required"));
+        }
+    }
+
+    @Test
+    public void validateString() throws Exception {
+        StringWrapper body = new StringWrapper();
+        body.value = "";
+        Validator.validate(body); // pass
+        try {
+            body.value = null;
+            Validator.validate(body); // fail
+            fail();
+        } catch (IllegalArgumentException ex) {
+            Assert.assertTrue(ex.getMessage().contains("value is required"));
+        }
+    }
+
+    @Test
+    public void validateLocalDate() throws Exception {
+        LocalDateWrapper body = new LocalDateWrapper();
+        body.value = LocalDate.of(1, 2, 3);
+        Validator.validate(body); // pass
+        try {
+            body.value = null;
+            Validator.validate(body); // fail
+            fail();
+        } catch (IllegalArgumentException ex) {
+            Assert.assertTrue(ex.getMessage().contains("value is required"));
+        }
+    }
+
+    @Test
+    public void validateList() throws Exception {
+        ListWrapper body = new ListWrapper();
+        try {
+            body.list = null;
+            Validator.validate(body); // fail
+            fail();
+        } catch (IllegalArgumentException ex) {
+            Assert.assertTrue(ex.getMessage().contains("list is required"));
+        }
+        body.list = new ArrayList<StringWrapper>();
+        Validator.validate(body); // pass
+        StringWrapper wrapper = new StringWrapper();
+        wrapper.value = "valid";
+        body.list.add(wrapper);
+        Validator.validate(body); // pass
+        body.list.add(null);
+        Validator.validate(body); // pass
+        body.list.add(new StringWrapper());
+        try {
+            Validator.validate(body); // fail
+            fail();
+        } catch (IllegalArgumentException ex) {
+            Assert.assertTrue(ex.getMessage().contains("list.value is required"));
+        }
+    }
+
+    @Test
+    public void validateMap() throws Exception {
+        MapWrapper body = new MapWrapper();
+        try {
+            body.map = null;
+            Validator.validate(body); // fail
+            fail();
+        } catch (IllegalArgumentException ex) {
+            Assert.assertTrue(ex.getMessage().contains("map is required"));
+        }
+        body.map = new HashMap<LocalDate, StringWrapper>();
+        Validator.validate(body); // pass
+        StringWrapper wrapper = new StringWrapper();
+        wrapper.value = "valid";
+        body.map.put(LocalDate.of(1, 2, 3), wrapper);
+        Validator.validate(body); // pass
+        body.map.put(LocalDate.of(1, 2, 3), null);
+        Validator.validate(body); // pass
+        body.map.put(LocalDate.of(1, 2, 3), new StringWrapper());
+        try {
+            Validator.validate(body); // fail
+            fail();
+        } catch (IllegalArgumentException ex) {
+            Assert.assertTrue(ex.getMessage().contains("map.value is required"));
+        }
+    }
+
+    @Test
+    public void validateObject() throws Exception {
+        Product product = new Product();
+        Validator.validate(product);
+    }
+
+    @Test
+    public void validateRecursive() throws Exception {
+        TextNode textNode = new TextNode("\"\"");
+        Validator.validate(textNode);
+    }
+
+    public final class IntWrapper {
+        @JsonProperty(required = true)
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 2 LINES
+        public int value;
+        public Object nullable;
+    }
+
+    public final class IntegerWrapper {
+        @JsonProperty(required = true)
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 1 LINE
+        public Integer value;
+    }
+
+    public final class StringWrapper {
+        @JsonProperty(required = true)
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 1 LINE
+        public String value;
+    }
+
+    public final class LocalDateWrapper {
+        @JsonProperty(required = true)
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 1 LINE
+        public LocalDate value;
+    }
+
+    public final class ListWrapper {
+        @JsonProperty(required = true)
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 1 LINE
+        public List<StringWrapper> list;
+    }
+
+    public final class MapWrapper {
+        @JsonProperty(required = true)
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 1 LINE
+        public Map<LocalDate, StringWrapper> map;
+    }
+
+    public enum Color {
+        RED,
+        GREEN,
+        Blue
+    }
+
+    public final class EnumWrapper {
+        @JsonProperty(required = true)
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 1 LINE
+        public Color color;
+    }
+
+    public final class Product {
+        // CHECKSTYLE IGNORE VisibilityModifier FOR NEXT 2 LINES
+        public String id;
+        public String tag;
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/http/UrlBuilderTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/http/UrlBuilderTests.java
new file mode 100644
index 0000000000000..4922618150c9f
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/http/UrlBuilderTests.java
@@ -0,0 +1,687 @@
+package com.azure.common.implementation.http;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import static org.junit.Assert.*;
+
+public class UrlBuilderTests {
+    @Test
+    public void withScheme() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http");
+        assertEquals("http://", builder.toString());
+    }
+
+    @Test
+    public void withSchemeWhenSchemeIsNull() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withScheme("http");
+        builder.withScheme(null);
+        assertNull(builder.scheme());
+    }
+
+    @Test
+    public void withSchemeWhenSchemeIsEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withScheme("http");
+        builder.withScheme("");
+        assertNull(builder.scheme());
+    }
+
+    @Test
+    public void withSchemeWhenSchemeIsNotEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withScheme("http");
+        builder.withScheme("https");
+        assertEquals("https", builder.scheme());
+    }
+
+    @Test
+    public void withSchemeWhenSchemeContainsTerminator() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http://");
+        assertEquals("http", builder.scheme());
+        assertNull(builder.host());
+        assertEquals("http://", builder.toString());
+    }
+
+    @Test
+    public void withSchemeWhenSchemeContainsHost() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http://www.example.com");
+        assertEquals("http", builder.scheme());
+        assertEquals("www.example.com", builder.host());
+        assertEquals("http://www.example.com", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHost() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com");
+        assertEquals("http://www.example.com", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHostWhenHostHasWhitespace() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.exa mple.com");
+        assertEquals("http://www.exa mple.com", builder.toString());
+    }
+
+    @Test
+    public void withHost() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com");
+        assertEquals("www.example.com", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostIsNull() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withHost("www.example.com");
+        builder.withHost(null);
+        assertNull(builder.host());
+    }
+
+    @Test
+    public void withHostWhenHostIsEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withHost("www.example.com");
+        builder.withHost("");
+        assertNull(builder.host());
+    }
+
+    @Test
+    public void withHostWhenHostIsNotEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withHost("www.example.com");
+        builder.withHost("www.bing.com");
+        assertEquals("www.bing.com", builder.host());
+    }
+
+    @Test
+    public void withHostWhenHostContainsSchemeTerminator() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("://www.example.com");
+        assertNull(builder.scheme());
+        assertEquals("www.example.com", builder.host());
+        assertEquals("www.example.com", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostContainsScheme() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("https://www.example.com");
+        assertEquals("https", builder.scheme());
+        assertEquals("www.example.com", builder.host());
+        assertEquals("https://www.example.com", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostContainsColonButNoPort() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com:");
+        assertEquals("www.example.com", builder.host());
+        assertNull(builder.port());
+        assertEquals("www.example.com", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostContainsPort() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com:1234");
+        assertEquals("www.example.com", builder.host());
+        assertEquals(1234, builder.port().intValue());
+        assertEquals("www.example.com:1234", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostContainsForwardSlashButNoPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com/");
+        assertEquals("www.example.com", builder.host());
+        assertEquals("/", builder.path());
+        assertEquals("www.example.com/", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostContainsPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com/index.html");
+        assertEquals("www.example.com", builder.host());
+        assertEquals("/index.html", builder.path());
+        assertEquals("www.example.com/index.html", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostContainsQuestionMarkButNoQuery() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com?");
+        assertEquals("www.example.com", builder.host());
+        assertEquals(0, builder.query().size());
+        assertEquals("www.example.com", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostContainsQuery() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com?a=b");
+        assertEquals("www.example.com", builder.host());
+        assertThat(builder.toString(), CoreMatchers.containsString("a=b"));
+        assertEquals("www.example.com?a=b", builder.toString());
+    }
+
+    @Test
+    public void withHostWhenHostHasWhitespace() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.exampl e.com");
+        assertEquals("www.exampl e.com", builder.toString());
+    }
+
+    @Test
+    public void withHostAndPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com")
+                .withPath("my/path");
+        assertEquals("www.example.com/my/path", builder.toString());
+    }
+
+    @Test
+    public void withHostAndPathWithSlashAfterHost() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com/")
+                .withPath("my/path");
+        assertEquals("www.example.com/my/path", builder.toString());
+    }
+
+    @Test
+    public void withHostAndPathWithSlashBeforePath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com")
+                .withPath("/my/path");
+        assertEquals("www.example.com/my/path", builder.toString());
+    }
+
+    @Test
+    public void withHostAndPathWithSlashAfterHostAndBeforePath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com/")
+                .withPath("/my/path");
+        assertEquals("www.example.com/my/path", builder.toString());
+    }
+
+    @Test
+    public void withHostAndPathWithWhitespaceInPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com")
+                .withPath("my path");
+        assertEquals("www.example.com/my path", builder.toString());
+    }
+
+    @Test
+    public void withHostAndPathWithPlusInPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com")
+                .withPath("my+path");
+        assertEquals("www.example.com/my+path", builder.toString());
+    }
+
+    @Test
+    public void withHostAndPathWithPercent20InPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withHost("www.example.com")
+                .withPath("my%20path");
+        assertEquals("www.example.com/my%20path", builder.toString());
+    }
+
+    @Test
+    public void withPortInt() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort(50);
+        assertEquals(50, builder.port().intValue());
+        assertEquals(":50", builder.toString());
+    }
+
+    @Test
+    public void withPortStringWithNull() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort(null);
+        assertNull(builder.port());
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void withPortStringWithEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort("");
+        assertNull(builder.port());
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void withPortString() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort("50");
+        assertEquals(50, builder.port().intValue());
+        assertEquals(":50", builder.toString());
+    }
+
+    @Test
+    public void withPortStringWithForwardSlashButNoPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort("50/");
+        assertEquals(50, builder.port().intValue());
+        assertEquals("/", builder.path());
+        assertEquals(":50/", builder.toString());
+    }
+
+    @Test
+    public void withPortStringWithPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort("50/index.html");
+        assertEquals(50, builder.port().intValue());
+        assertEquals("/index.html", builder.path());
+        assertEquals(":50/index.html", builder.toString());
+    }
+
+    @Test
+    public void withPortStringWithQuestionMarkButNoQuery() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort("50?");
+        assertEquals(50, builder.port().intValue());
+        assertEquals(0, builder.query().size());
+        assertEquals(":50", builder.toString());
+    }
+
+    @Test
+    public void withPortStringWithQuery() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withPort("50?a=b&c=d");
+        assertEquals(50, builder.port().intValue());
+        assertThat(builder.toString(), CoreMatchers.containsString("?a=b&c=d"));
+        assertEquals(":50?a=b&c=d", builder.toString());
+    }
+
+    @Test
+    public void withPortStringWhenPortIsNull() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withPort(8080);
+        builder.withPort(null);
+        assertNull(builder.port());
+    }
+
+    @Test
+    public void withPortStringWhenPortIsEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withPort(8080);
+        builder.withPort("");
+        assertNull(builder.port());
+    }
+
+    @Test
+    public void withPortStringWhenPortIsNotEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withPort(8080);
+        builder.withPort("123");
+        assertEquals(123, builder.port().intValue());
+    }
+
+    @Test
+    public void withSchemeAndHostAndOneQueryParameter() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .setQueryParameter("A", "B");
+        assertEquals("http://www.example.com?A=B", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHostAndOneQueryParameterWhenQueryParameterNameHasWhitespace() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .setQueryParameter("App les", "B");
+        assertEquals("http://www.example.com?App les=B", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHostAndOneQueryParameterWhenQueryParameterNameHasPercent20() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .setQueryParameter("App%20les", "B");
+        assertEquals("http://www.example.com?App%20les=B", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHostAndOneQueryParameterWhenQueryParameterValueHasWhitespace() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .setQueryParameter("Apples", "Go od");
+        assertEquals("http://www.example.com?Apples=Go od", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHostAndOneQueryParameterWhenQueryParameterValueHasPercent20() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .setQueryParameter("Apples", "Go%20od");
+        assertEquals("http://www.example.com?Apples=Go%20od", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHostAndTwoQueryParameters() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .setQueryParameter("A", "B")
+                .setQueryParameter("C", "D");
+        assertEquals("http://www.example.com?A=B&C=D", builder.toString());
+    }
+
+    @Test
+    public void withSchemeAndHostAndPathAndTwoQueryParameters() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .setQueryParameter("A", "B")
+                .setQueryParameter("C", "D")
+                .withPath("index.html");
+        assertEquals("http://www.example.com/index.html?A=B&C=D", builder.toString());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsNullAndPathIsNull() {
+        final UrlBuilder builder = new UrlBuilder();
+        builder.withPath(null);
+        assertNull(builder.path());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsNullAndPathIsEmptyString() {
+        final UrlBuilder builder = new UrlBuilder();
+        builder.withPath("");
+        assertNull(builder.path());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsNullAndPathIsForwardSlash() {
+        final UrlBuilder builder = new UrlBuilder();
+        builder.withPath("/");
+        assertEquals("/", builder.path());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsNullAndPath() {
+        final UrlBuilder builder = new UrlBuilder();
+        builder.withPath("test/path.html");
+        assertEquals("test/path.html", builder.path());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsForwardSlashAndPathIsNull() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withPath("/");
+        builder.withPath(null);
+        assertNull(builder.path());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsForwardSlashAndPathIsEmptyString() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withPath("/");
+        builder.withPath("");
+        assertNull(builder.path());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsForwardSlashAndPathIsForwardSlash() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withPath("/");
+        builder.withPath("/");
+        assertEquals("/", builder.path());
+    }
+
+    @Test
+    public void withPathWhenBuilderPathIsForwardSlashAndPath() {
+        final UrlBuilder builder = new UrlBuilder()
+            .withPath("/");
+        builder.withPath("test/path.html");
+        assertEquals("test/path.html", builder.path());
+    }
+
+    @Test
+    public void withAbsolutePath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .withPath("http://www.othersite.com");
+        assertEquals("http://www.othersite.com", builder.toString());
+    }
+
+    @Test
+    public void withQueryInPath() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .withPath("mypath?thing=stuff")
+                .setQueryParameter("otherthing", "otherstuff");
+        assertEquals("http://www.example.com/mypath?thing=stuff&otherthing=otherstuff", builder.toString());
+    }
+
+    @Test
+    public void withAbsolutePathAndQuery() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withScheme("http")
+                .withHost("www.example.com")
+                .withPath("http://www.othersite.com/mypath?thing=stuff")
+                .setQueryParameter("otherthing", "otherstuff");
+        assertEquals("http://www.othersite.com/mypath?thing=stuff&otherthing=otherstuff", builder.toString());
+    }
+
+    @Test
+    public void withQueryWithNull() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withQuery(null);
+        assertEquals(0, builder.query().size());
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void withQueryWithEmpty() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withQuery("");
+        assertEquals(0, builder.query().size());
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void withQueryWithQuestionMark() {
+        final UrlBuilder builder = new UrlBuilder()
+                .withQuery("?");
+        assertEquals(0, builder.query().size());
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void parseWithNullString() {
+        final UrlBuilder builder = UrlBuilder.parse((String)null);
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void parseWithEmpty() {
+        final UrlBuilder builder = UrlBuilder.parse("");
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void parseWithHost() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com");
+        assertEquals("www.bing.com", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHost() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com");
+        assertEquals("https://www.bing.com", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPort() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com:8080");
+        assertEquals("www.bing.com:8080", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPort() {
+        final UrlBuilder builder = UrlBuilder.parse("ftp://www.bing.com:8080");
+        assertEquals("ftp://www.bing.com:8080", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPath() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com/my/path");
+        assertEquals("www.bing.com/my/path", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPath() {
+        final UrlBuilder builder = UrlBuilder.parse("ftp://www.bing.com/my/path");
+        assertEquals("ftp://www.bing.com/my/path", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPortAndPath() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com:1234/my/path");
+        assertEquals("www.bing.com:1234/my/path", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPortAndPath() {
+        final UrlBuilder builder = UrlBuilder.parse("ftp://www.bing.com:2345/my/path");
+        assertEquals("ftp://www.bing.com:2345/my/path", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com?a=1");
+        assertEquals("www.bing.com?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com?a=1");
+        assertEquals("https://www.bing.com?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPortAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com:123?a=1");
+        assertEquals("www.bing.com:123?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPortAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com:987?a=1");
+        assertEquals("https://www.bing.com:987?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPathAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com/folder/index.html?a=1");
+        assertEquals("www.bing.com/folder/index.html?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPathAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com/image.gif?a=1");
+        assertEquals("https://www.bing.com/image.gif?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPortAndPathAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com:123/index.html?a=1");
+        assertEquals("www.bing.com:123/index.html?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPortAndPathAndOneQueryParameter() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com:987/my/path/again?a=1");
+        assertEquals("https://www.bing.com:987/my/path/again?a=1", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com?a=1&b=2");
+        assertEquals("www.bing.com?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com?a=1&b=2");
+        assertEquals("https://www.bing.com?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPortAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com:123?a=1&b=2");
+        assertEquals("www.bing.com:123?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPortAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com:987?a=1&b=2");
+        assertEquals("https://www.bing.com:987?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPathAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com/folder/index.html?a=1&b=2");
+        assertEquals("www.bing.com/folder/index.html?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPathAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com/image.gif?a=1&b=2");
+        assertEquals("https://www.bing.com/image.gif?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithHostAndPortAndPathAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("www.bing.com:123/index.html?a=1&b=2");
+        assertEquals("www.bing.com:123/index.html?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithProtocolAndHostAndPortAndPathAndTwoQueryParameters() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com:987/my/path/again?a=1&b=2");
+        assertEquals("https://www.bing.com:987/my/path/again?a=1&b=2", builder.toString());
+    }
+
+    @Test
+    public void parseWithColonInPath() {
+        final UrlBuilder builder = UrlBuilder.parse("https://www.bing.com/my:/path");
+        assertEquals("https://www.bing.com/my:/path", builder.toString());
+    }
+
+    @Test
+    public void parseURLWithNull() {
+        final UrlBuilder builder = UrlBuilder.parse((URL)null);
+        assertEquals("", builder.toString());
+    }
+
+    @Test
+    public void parseURLWithSchemeAndHost() throws MalformedURLException {
+        final UrlBuilder builder = UrlBuilder.parse(new URL("http://www.bing.com"));
+        assertEquals("http://www.bing.com", builder.toString());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/http/UrlTokenizerTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/http/UrlTokenizerTests.java
new file mode 100644
index 0000000000000..38269d80e7e73
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/http/UrlTokenizerTests.java
@@ -0,0 +1,208 @@
+package com.azure.common.implementation.http;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class UrlTokenizerTests {
+    @Test
+    public void constructor() {
+        final UrlTokenizer tokenizer = new UrlTokenizer("http://www.bing.com");
+        assertNull(tokenizer.current());
+    }
+
+    @Test
+    public void nextWithNullText() {
+        final UrlTokenizer tokenizer = new UrlTokenizer(null);
+        assertFalse(tokenizer.next());
+        assertNull(tokenizer.current());
+    }
+
+    @Test
+    public void nextWithEmptyText() {
+        final UrlTokenizer tokenizer = new UrlTokenizer("");
+        assertFalse(tokenizer.next());
+        assertNull(tokenizer.current());
+    }
+
+    @Test
+    public void nextWithSchemeButNoSeparator() {
+        nextTest("http", UrlToken.host("http"));
+    }
+
+    @Test
+    public void nextWithSchemeAndColon() {
+        nextTest("http:",
+            UrlToken.host("http"),
+            UrlToken.port(""));
+    }
+
+    @Test
+    public void nextWithSchemeAndColonAndForwardSlash() {
+        nextTest("http:/",
+            UrlToken.host("http"),
+            UrlToken.port(""),
+            UrlToken.path("/"));
+    }
+
+    @Test
+    public void nextWithSchemeAndColonAndTwoForwardSlashes() {
+        nextTest("http://",
+            UrlToken.scheme("http"),
+            UrlToken.host(""));
+    }
+
+    @Test
+    public void nextWithSchemeAndHost() {
+        nextTest("https://www.example.com",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndColon() {
+        nextTest("https://www.example.com:",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.port(""));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPort() {
+        nextTest("https://www.example.com:8080",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.port("8080"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPortAndForwardSlash() {
+        nextTest("ftp://www.bing.com:123/",
+            UrlToken.scheme("ftp"),
+            UrlToken.host("www.bing.com"),
+            UrlToken.port("123"),
+            UrlToken.path("/"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPortAndPath() {
+        nextTest("ftp://www.bing.com:123/a/b/c.txt",
+            UrlToken.scheme("ftp"),
+            UrlToken.host("www.bing.com"),
+            UrlToken.port("123"),
+            UrlToken.path("/a/b/c.txt"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPortAndQuestionMark() {
+        nextTest("ftp://www.bing.com:123?",
+            UrlToken.scheme("ftp"),
+            UrlToken.host("www.bing.com"),
+            UrlToken.port("123"),
+            UrlToken.query(""));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPortAndQuery() {
+        nextTest("ftp://www.bing.com:123?a=b&c=d",
+            UrlToken.scheme("ftp"),
+            UrlToken.host("www.bing.com"),
+            UrlToken.port("123"),
+            UrlToken.query("a=b&c=d"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndForwardSlash() {
+        nextTest("https://www.example.com/",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.path("/"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPath() {
+        nextTest("https://www.example.com/index.html",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.path("/index.html"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPathAndQuestionMark() {
+        nextTest("https://www.example.com/index.html?",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.path("/index.html"),
+            UrlToken.query(""));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndPathAndQuery() {
+        nextTest("https://www.example.com/index.html?alpha=beta",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.path("/index.html"),
+            UrlToken.query("alpha=beta"));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndQuestionMark() {
+        nextTest("https://www.example.com?",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.query(""));
+    }
+
+    @Test
+    public void nextWithSchemeAndHostAndQuery() {
+        nextTest("https://www.example.com?a=b",
+            UrlToken.scheme("https"),
+            UrlToken.host("www.example.com"),
+            UrlToken.query("a=b"));
+    }
+
+    @Test
+    public void nextWithHostAndForwardSlash() {
+        nextTest("www.test.com/",
+            UrlToken.host("www.test.com"),
+            UrlToken.path("/"));
+    }
+
+    @Test
+    public void nextWithHostAndQuestionMark() {
+        nextTest("www.test.com?",
+            UrlToken.host("www.test.com"),
+            UrlToken.query(""));
+    }
+
+    @Test
+    public void nextWithPath() {
+        nextTest("folder/index.html",
+            UrlToken.host("folder"),
+            UrlToken.path("/index.html"));
+    }
+
+    @Test
+    public void nextWithForwardSlashAndPath() {
+        nextTest("/folder/index.html",
+            UrlToken.host(""),
+            UrlToken.path("/folder/index.html"));
+    }
+
+    private static void nextTest(String text, UrlToken... expectedTokens) {
+        final UrlTokenizer tokenizer = new UrlTokenizer(text);
+        final List<UrlToken> tokenList = new ArrayList<>();
+        while (tokenizer.next()) {
+            tokenList.add(tokenizer.current());
+        }
+        final UrlToken[] tokenArray = new UrlToken[tokenList.size()];
+        tokenList.toArray(tokenArray);
+        assertArrayEquals(expectedTokens, tokenArray);
+
+        assertFalse(tokenizer.next());
+        assertNull(tokenizer.current());
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesSerializerTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesSerializerTests.java
new file mode 100644
index 0000000000000..749e5ced663f4
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/AdditionalPropertiesSerializerTests.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.util.Foo;
+import com.azure.common.implementation.util.FooChild;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class AdditionalPropertiesSerializerTests {
+    @Test
+    public void canSerializeAdditionalProperties() throws Exception {
+        Foo foo = new Foo();
+        foo.bar = "hello.world";
+        foo.baz = new ArrayList<>();
+        foo.baz.add("hello");
+        foo.baz.add("hello.world");
+        foo.qux = new HashMap<>();
+        foo.qux.put("hello", "world");
+        foo.qux.put("a.b", "c.d");
+        foo.qux.put("bar.a", "ttyy");
+        foo.qux.put("bar.b", "uuzz");
+        foo.additionalProperties = new HashMap<>();
+        foo.additionalProperties.put("bar", "baz");
+        foo.additionalProperties.put("a.b", "c.d");
+        foo.additionalProperties.put("properties.bar", "barbar");
+
+        String serialized = new JacksonAdapter().serialize(foo, SerializerEncoding.JSON);
+        Assert.assertEquals("{\"$type\":\"foo\",\"properties\":{\"bar\":\"hello.world\",\"props\":{\"baz\":[\"hello\",\"hello.world\"],\"q\":{\"qux\":{\"hello\":\"world\",\"a.b\":\"c.d\",\"bar.b\":\"uuzz\",\"bar.a\":\"ttyy\"}}}},\"bar\":\"baz\",\"a.b\":\"c.d\",\"properties.bar\":\"barbar\"}", serialized);
+    }
+
+    @Test
+    public void canDeserializeAdditionalProperties() throws Exception {
+        String wireValue = "{\"$type\":\"foo\",\"properties\":{\"bar\":\"hello.world\",\"props\":{\"baz\":[\"hello\",\"hello.world\"],\"q\":{\"qux\":{\"hello\":\"world\",\"a.b\":\"c.d\",\"bar.b\":\"uuzz\",\"bar.a\":\"ttyy\"}}}},\"bar\":\"baz\",\"a.b\":\"c.d\",\"properties.bar\":\"barbar\"}";
+        Foo deserialized = new JacksonAdapter().deserialize(wireValue, Foo.class, SerializerEncoding.JSON);
+        Assert.assertNotNull(deserialized.additionalProperties);
+        Assert.assertEquals("baz", deserialized.additionalProperties.get("bar"));
+        Assert.assertEquals("c.d", deserialized.additionalProperties.get("a.b"));
+        Assert.assertEquals("barbar", deserialized.additionalProperties.get("properties.bar"));
+    }
+
+    @Test
+    public void canSerializeAdditionalPropertiesThroughInheritance() throws Exception {
+        Foo foo = new FooChild();
+        foo.bar = "hello.world";
+        foo.baz = new ArrayList<>();
+        foo.baz.add("hello");
+        foo.baz.add("hello.world");
+        foo.qux = new HashMap<>();
+        foo.qux.put("hello", "world");
+        foo.qux.put("a.b", "c.d");
+        foo.qux.put("bar.a", "ttyy");
+        foo.qux.put("bar.b", "uuzz");
+        foo.additionalProperties = new HashMap<>();
+        foo.additionalProperties.put("bar", "baz");
+        foo.additionalProperties.put("a.b", "c.d");
+        foo.additionalProperties.put("properties.bar", "barbar");
+
+        String serialized = new JacksonAdapter().serialize(foo, SerializerEncoding.JSON);
+        Assert.assertEquals("{\"$type\":\"foochild\",\"properties\":{\"bar\":\"hello.world\",\"props\":{\"baz\":[\"hello\",\"hello.world\"],\"q\":{\"qux\":{\"hello\":\"world\",\"a.b\":\"c.d\",\"bar.b\":\"uuzz\",\"bar.a\":\"ttyy\"}}}},\"bar\":\"baz\",\"a.b\":\"c.d\",\"properties.bar\":\"barbar\"}", serialized);
+    }
+
+    @Test
+    public void canDeserializeAdditionalPropertiesThroughInheritance() throws Exception {
+        String wireValue = "{\"$type\":\"foochild\",\"properties\":{\"bar\":\"hello.world\",\"props\":{\"baz\":[\"hello\",\"hello.world\"],\"q\":{\"qux\":{\"hello\":\"world\",\"a.b\":\"c.d\",\"bar.b\":\"uuzz\",\"bar.a\":\"ttyy\"}}}},\"bar\":\"baz\",\"a.b\":\"c.d\",\"properties.bar\":\"barbar\"}";
+        Foo deserialized = new JacksonAdapter().deserialize(wireValue, Foo.class, SerializerEncoding.JSON);
+        Assert.assertNotNull(deserialized.additionalProperties);
+        Assert.assertEquals("baz", deserialized.additionalProperties.get("bar"));
+        Assert.assertEquals("c.d", deserialized.additionalProperties.get("a.b"));
+        Assert.assertEquals("barbar", deserialized.additionalProperties.get("properties.bar"));
+        Assert.assertTrue(deserialized instanceof FooChild);
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/DateTimeSerializerTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/DateTimeSerializerTests.java
new file mode 100644
index 0000000000000..047e6d8a059c6
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/DateTimeSerializerTests.java
@@ -0,0 +1,25 @@
+package com.azure.common.implementation.serializer.jackson;
+
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+
+import static org.junit.Assert.*;
+
+public class DateTimeSerializerTests {
+    @Test
+    public void toStringWithNull() {
+        assertNull(DateTimeSerializer.toString(null));
+    }
+
+    @Test
+    public void toStringOffsetDateTime() {
+        assertEquals("0001-01-01T14:00:00Z", DateTimeSerializer.toString(OffsetDateTime.of(1, 1, 1, 0, 0, 0, 0, ZoneOffset.ofHours(-14))));
+        assertEquals("10000-01-01T13:59:59.999Z", DateTimeSerializer.toString(OffsetDateTime.of(LocalDate.of(10000, 1, 1), LocalTime.parse("13:59:59.999"), ZoneOffset.UTC)));
+        assertEquals("2010-01-01T12:34:56Z", DateTimeSerializer.toString(OffsetDateTime.of(2010, 1, 1, 12, 34, 56, 0, ZoneOffset.UTC)));
+        assertEquals("0001-01-01T00:00:00Z", DateTimeSerializer.toString(OffsetDateTime.of(1, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC)));
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/DurationSerializerTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/DurationSerializerTests.java
new file mode 100644
index 0000000000000..c4bccde1448f3
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/DurationSerializerTests.java
@@ -0,0 +1,204 @@
+package com.azure.common.implementation.serializer.jackson;
+
+import org.junit.Test;
+
+import java.time.Duration;
+
+import static org.junit.Assert.*;
+
+public class DurationSerializerTests {
+    @Test
+    public void toStringWithNull() {
+        assertNull(DurationSerializer.toString(null));
+    }
+
+    @Test
+    public void toStringWith0Milliseconds() {
+        assertEquals("PT0S", DurationSerializer.toString(Duration.ofMillis(0)));
+    }
+
+    @Test
+    public void toStringWith1Milliseconds() {
+        assertEquals("PT0.001S", DurationSerializer.toString(Duration.ofMillis(1)));
+    }
+
+    @Test
+    public void toStringWith9Milliseconds() {
+        assertEquals("PT0.009S", DurationSerializer.toString(Duration.ofMillis(9)));
+    }
+
+    @Test
+    public void toStringWith10Milliseconds() {
+        assertEquals("PT0.01S", DurationSerializer.toString(Duration.ofMillis(10)));
+    }
+
+    @Test
+    public void toStringWith11Milliseconds() {
+        assertEquals("PT0.011S", DurationSerializer.toString(Duration.ofMillis(11)));
+    }
+
+    @Test
+    public void toStringWith99Milliseconds() {
+        assertEquals("PT0.099S", DurationSerializer.toString(Duration.ofMillis(99)));
+    }
+
+    @Test
+    public void toStringWith100Milliseconds() {
+        assertEquals("PT0.1S", DurationSerializer.toString(Duration.ofMillis(100)));
+    }
+
+    @Test
+    public void toStringWith101Milliseconds() {
+        assertEquals("PT0.101S", DurationSerializer.toString(Duration.ofMillis(101)));
+    }
+
+    @Test
+    public void toStringWith999Milliseconds() {
+        assertEquals("PT0.999S", DurationSerializer.toString(Duration.ofMillis(999)));
+    }
+
+    @Test
+    public void toStringWith10illiseconds() {
+        assertEquals("PT1S", DurationSerializer.toString(Duration.ofMillis(1000)));
+    }
+
+    @Test
+    public void toStringWith1Second() {
+        assertEquals("PT1S", DurationSerializer.toString(Duration.ofSeconds(1)));
+    }
+
+    @Test
+    public void toStringWith9Seconds() {
+        assertEquals("PT9S", DurationSerializer.toString(Duration.ofSeconds(9)));
+    }
+
+    @Test
+    public void toStringWith10Seconds() {
+        assertEquals("PT10S", DurationSerializer.toString(Duration.ofSeconds(10)));
+    }
+
+    @Test
+    public void toStringWith11Seconds() {
+        assertEquals("PT11S", DurationSerializer.toString(Duration.ofSeconds(11)));
+    }
+
+    @Test
+    public void toStringWith59Seconds() {
+        assertEquals("PT59S", DurationSerializer.toString(Duration.ofSeconds(59)));
+    }
+
+    @Test
+    public void toStringWith60Seconds() {
+        assertEquals("PT1M", DurationSerializer.toString(Duration.ofSeconds(60)));
+    }
+
+    @Test
+    public void toStringWith61Seconds() {
+        assertEquals("PT1M1S", DurationSerializer.toString(Duration.ofSeconds(61)));
+    }
+
+    @Test
+    public void toStringWith1Minute() {
+        assertEquals("PT1M", DurationSerializer.toString(Duration.ofMinutes(1)));
+    }
+
+    @Test
+    public void toStringWith9Minutes() {
+        assertEquals("PT9M", DurationSerializer.toString(Duration.ofMinutes(9)));
+    }
+
+    @Test
+    public void toStringWith10Minutes() {
+        assertEquals("PT10M", DurationSerializer.toString(Duration.ofMinutes(10)));
+    }
+
+    @Test
+    public void toStringWith11Minutes() {
+        assertEquals("PT11M", DurationSerializer.toString(Duration.ofMinutes(11)));
+    }
+
+    @Test
+    public void toStringWith59Minutes() {
+        assertEquals("PT59M", DurationSerializer.toString(Duration.ofMinutes(59)));
+    }
+
+    @Test
+    public void toStringWith60Minutes() {
+        assertEquals("PT1H", DurationSerializer.toString(Duration.ofMinutes(60)));
+    }
+
+    @Test
+    public void toStringWith61Minutes() {
+        assertEquals("PT1H1M", DurationSerializer.toString(Duration.ofMinutes(61)));
+    }
+
+    @Test
+    public void toStringWith1Hour() {
+        assertEquals("PT1H", DurationSerializer.toString(Duration.ofHours(1)));
+    }
+
+    @Test
+    public void toStringWith9Hours() {
+        assertEquals("PT9H", DurationSerializer.toString(Duration.ofHours(9)));
+    }
+
+    @Test
+    public void toStringWith10Hours() {
+        assertEquals("PT10H", DurationSerializer.toString(Duration.ofHours(10)));
+    }
+
+    @Test
+    public void toStringWith11Hours() {
+        assertEquals("PT11H", DurationSerializer.toString(Duration.ofHours(11)));
+    }
+
+    @Test
+    public void toStringWith23Hours() {
+        assertEquals("PT23H", DurationSerializer.toString(Duration.ofHours(23)));
+    }
+
+    @Test
+    public void toStringWith24Hours() {
+        assertEquals("P1D", DurationSerializer.toString(Duration.ofHours(24)));
+    }
+
+    @Test
+    public void toStringWith25Hours() {
+        assertEquals("P1DT1H", DurationSerializer.toString(Duration.ofHours(25)));
+    }
+
+    @Test
+    public void toStringWith1Day() {
+        assertEquals("P1D", DurationSerializer.toString(Duration.ofDays(1)));
+    }
+
+    @Test
+    public void toStringWith9Days() {
+        assertEquals("P9D", DurationSerializer.toString(Duration.ofDays(9)));
+    }
+
+    @Test
+    public void toStringWith10Days() {
+        assertEquals("P10D", DurationSerializer.toString(Duration.ofDays(10)));
+    }
+
+    @Test
+    public void toStringWith11Days() {
+        assertEquals("P11D", DurationSerializer.toString(Duration.ofDays(11)));
+    }
+
+    @Test
+    public void toStringWith99Days() {
+        assertEquals("P99D", DurationSerializer.toString(Duration.ofDays(99)));
+    }
+
+    @Test
+    public void toStringWith100Days() {
+        assertEquals("P100D", DurationSerializer.toString(Duration.ofDays(100)));
+    }
+
+    @Test
+    public void toStringWith101Days() {
+        assertEquals("P101D", DurationSerializer.toString(Duration.ofDays(101)));
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/FlatteningSerializerTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/FlatteningSerializerTests.java
new file mode 100644
index 0000000000000..b3528cf066f8b
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/FlatteningSerializerTests.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import com.azure.common.implementation.serializer.JsonFlatten;
+import com.azure.common.implementation.util.Foo;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FlatteningSerializerTests {
+    @Test
+    public void canFlatten() throws Exception {
+        Foo foo = new Foo();
+        foo.bar = "hello.world";
+        foo.baz = new ArrayList<>();
+        foo.baz.add("hello");
+        foo.baz.add("hello.world");
+        foo.qux = new HashMap<>();
+        foo.qux.put("hello", "world");
+        foo.qux.put("a.b", "c.d");
+        foo.qux.put("bar.a", "ttyy");
+        foo.qux.put("bar.b", "uuzz");
+        foo.moreProps = "hello";
+
+        JacksonAdapter adapter = new JacksonAdapter();
+
+        // serialization
+        String serialized = adapter.serialize(foo, SerializerEncoding.JSON);
+        Assert.assertEquals("{\"$type\":\"foo\",\"properties\":{\"bar\":\"hello.world\",\"props\":{\"baz\":[\"hello\",\"hello.world\"],\"q\":{\"qux\":{\"hello\":\"world\",\"a.b\":\"c.d\",\"bar.b\":\"uuzz\",\"bar.a\":\"ttyy\"}}},\"more.props\":\"hello\"}}", serialized);
+
+        // deserialization
+        Foo deserialized = adapter.deserialize(serialized, Foo.class, SerializerEncoding.JSON);
+        Assert.assertEquals("hello.world", deserialized.bar);
+        Assert.assertArrayEquals(new String[]{"hello", "hello.world"}, deserialized.baz.toArray());
+        Assert.assertNotNull(deserialized.qux);
+        Assert.assertEquals("world", deserialized.qux.get("hello"));
+        Assert.assertEquals("c.d", deserialized.qux.get("a.b"));
+        Assert.assertEquals("ttyy", deserialized.qux.get("bar.a"));
+        Assert.assertEquals("uuzz", deserialized.qux.get("bar.b"));
+        Assert.assertEquals("hello", deserialized.moreProps);
+    }
+
+    @Test
+    public void canSerializeMapKeysWithDotAndSlash() throws Exception {
+        String serialized = new JacksonAdapter().serialize(prepareSchoolModel(), SerializerEncoding.JSON);
+        Assert.assertEquals("{\"teacher\":{\"students\":{\"af.B/D\":{},\"af.B/C\":{}}},\"tags\":{\"foo.aa\":\"bar\",\"x.y\":\"zz\"},\"properties\":{\"name\":\"school1\"}}", serialized);
+    }
+
+    @JsonFlatten
+    private class School {
+        @JsonProperty(value = "teacher")
+        private Teacher teacher;
+
+        @JsonProperty(value = "properties.name")
+        private String name;
+
+        @JsonProperty(value = "tags")
+        private Map<String, String> tags;
+
+        public School withTeacher(Teacher teacher) {
+            this.teacher = teacher;
+            return this;
+        }
+
+        public School withName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public School withTags(Map<String, String> tags) {
+            this.tags = tags;
+            return this;
+        }
+    }
+
+    private class Student {
+    }
+
+    private class Teacher {
+        @JsonProperty(value = "students")
+        private Map<String, Student> students;
+
+        public Teacher withStudents(Map<String, Student> students) {
+            this.students = students;
+            return this;
+        }
+    }
+
+    private School prepareSchoolModel() {
+        Teacher teacher = new Teacher();
+
+        Map<String, Student> students = new HashMap<String, Student>();
+        students.put("af.B/C", new Student());
+        students.put("af.B/D", new Student());
+
+        teacher.withStudents(students);
+
+        School school = new School().withName("school1");
+        school.withTeacher(teacher);
+
+        Map<String, String> schoolTags = new HashMap<String, String>();
+        schoolTags.put("foo.aa", "bar");
+        schoolTags.put("x.y", "zz");
+
+        school.withTags(schoolTags);
+
+        return school;
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/JacksonAdapterTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/JacksonAdapterTests.java
new file mode 100644
index 0000000000000..4210c974a183c
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/serializer/jackson/JacksonAdapterTests.java
@@ -0,0 +1,60 @@
+package com.azure.common.implementation.serializer.jackson;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.azure.common.implementation.serializer.SerializerEncoding;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+public class JacksonAdapterTests {
+    @Test
+    public void emptyMap() throws IOException {
+        final Map<String,String> map = new HashMap<>();
+        final JacksonAdapter serializer = new JacksonAdapter();
+        assertEquals("{}", serializer.serialize(map, SerializerEncoding.JSON));
+    }
+
+    @Test
+    public void mapWithNullKey() throws IOException {
+        final Map<String,String> map = new HashMap<>();
+        map.put(null, null);
+        final JacksonAdapter serializer = new JacksonAdapter();
+        assertEquals("{}", serializer.serialize(map, SerializerEncoding.JSON));
+    }
+
+    @Test
+    public void mapWithEmptyKeyAndNullValue() throws IOException {
+        final MapHolder mapHolder = new MapHolder();
+        mapHolder.map = new HashMap<>();
+        mapHolder.map.put("", null);
+
+        final JacksonAdapter serializer = new JacksonAdapter();
+        assertEquals("{\"map\":{\"\":null}}", serializer.serialize(mapHolder, SerializerEncoding.JSON));
+    }
+
+    @Test
+    public void mapWithEmptyKeyAndEmptyValue() throws IOException {
+        final MapHolder mapHolder = new MapHolder();
+        mapHolder.map = new HashMap<>();
+        mapHolder.map.put("", "");
+        final JacksonAdapter serializer = new JacksonAdapter();
+        assertEquals("{\"map\":{\"\":\"\"}}", serializer.serialize(mapHolder, SerializerEncoding.JSON));
+    }
+
+    @Test
+    public void mapWithEmptyKeyAndNonEmptyValue() throws IOException {
+        final Map<String,String> map = new HashMap<>();
+        map.put("", "test");
+        final JacksonAdapter serializer = new JacksonAdapter();
+        assertEquals("{\"\":\"test\"}", serializer.serialize(map, SerializerEncoding.JSON));
+    }
+
+    private static class MapHolder {
+        @JsonInclude(content = JsonInclude.Include.ALWAYS)
+        public Map<String,String> map = new HashMap<>();
+    }
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/util/FlowableUtils.java b/common/azure-common/src/test/java/com/azure/common/implementation/util/FlowableUtils.java
new file mode 100644
index 0000000000000..21947c53cea1b
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/util/FlowableUtils.java
@@ -0,0 +1,97 @@
+package com.azure.common.implementation.util;
+
+import org.reactivestreams.Subscription;
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.FlowableSubscriber;
+import java.nio.ByteBuffer;
+
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.CompletionHandler;
+
+/**
+ * The methods exposed by this type is based on rx-java publishers.
+ * We are unable to implement similar utility using reactor.core.publisher.Flux (instead of io.reactivex.Flowable), its seems a bug in Reactor
+ * TODO: anuchan open a bug in the reactor github repo. Repro can be found here https://github.com/anuchandy/flux-asyncfilechannel
+ */
+public class FlowableUtils {
+    //region Utility methods to write Flowable<ByteBuffer> to AsynchronousFileChannel.
+    /**
+     * Writes the bytes emitted by a Flowable to an AsynchronousFileChannel.
+     *
+     * @param content the Flowable content
+     * @param outFile the file channel
+     * @return a Completable which performs the write operation when subscribed
+     */
+    public static Completable writeFile(Flowable<ByteBuffer> content, AsynchronousFileChannel outFile) {
+        return writeFile(content, outFile, 0);
+    }
+
+    /**
+     * Writes the bytes emitted by a Flowable to an AsynchronousFileChannel
+     * starting at the given position in the file.
+     *
+     * @param content the Flowable content
+     * @param outFile the file channel
+     * @param position the position in the file to begin writing
+     * @return a Completable which performs the write operation when subscribed
+     */
+    public static Completable writeFile(Flowable<ByteBuffer> content, AsynchronousFileChannel outFile, long position) {
+        return Completable.create(emitter -> content.subscribe(new FlowableSubscriber<ByteBuffer>() {
+            // volatile ensures that writes to these fields by one thread will be immediately visible to other threads.
+            // An I/O pool thread will write to isWriting and read isCompleted,
+            // while another thread may read isWriting and write to isCompleted.
+            volatile boolean isWriting = false;
+            volatile boolean isCompleted = false;
+            volatile Subscription subscription;
+            volatile long pos = position;
+
+            @Override
+            public void onSubscribe(Subscription s) {
+                subscription = s;
+                s.request(1);
+            }
+
+            @Override
+            public void onNext(ByteBuffer bytes) {
+                isWriting = true;
+                outFile.write(bytes, pos, null, onWriteCompleted);
+            }
+
+
+            CompletionHandler<Integer, Object> onWriteCompleted = new CompletionHandler<Integer, Object>() {
+                @Override
+                public void completed(Integer bytesWritten, Object attachment) {
+                    isWriting = false;
+                    if (isCompleted) {
+                        emitter.onComplete();
+                    }
+                    //noinspection NonAtomicOperationOnVolatileField
+                    pos += bytesWritten;
+                    subscription.request(1);
+                }
+
+                @Override
+                public void failed(Throwable exc, Object attachment) {
+                    subscription.cancel();
+                    emitter.onError(exc);
+                }
+            };
+
+            @Override
+            public void onError(Throwable throwable) {
+                subscription.cancel();
+                emitter.onError(throwable);
+            }
+
+            @Override
+            public void onComplete() {
+                isCompleted = true;
+                if (!isWriting) {
+                    emitter.onComplete();
+                }
+            }
+        }));
+    }
+    //endregion
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/util/FluxUtilTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/util/FluxUtilTests.java
new file mode 100644
index 0000000000000..6c30105ca3fe9
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/util/FluxUtilTests.java
@@ -0,0 +1,254 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.util;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.ReferenceCountUtil;
+import org.junit.Ignore;
+import org.junit.Test;
+import reactor.core.Exceptions;
+import reactor.test.StepVerifier;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import static org.junit.Assert.*;
+
+public class FluxUtilTests {
+
+    @Test
+    public void testCanReadSlice() throws IOException {
+        File file = new File("target/test1");
+        FileOutputStream stream = new FileOutputStream(file);
+        stream.write("hello there".getBytes(StandardCharsets.UTF_8));
+        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ)) {
+            byte[] bytes = FluxUtil.byteBufStreamFromFile(channel, 1, 3)
+                    .map(bb -> {
+                        byte[] bt = toBytes(bb);
+                        ReferenceCountUtil.release(bb);
+                        return bt;
+                    })
+                    .collect(() -> new ByteArrayOutputStream(),
+                        (bos, b) -> {
+                            try {
+                                bos.write(b);
+                            } catch (IOException ioe) {
+                                throw Exceptions.propagate(ioe);
+                            }
+                        })
+                    .block()
+                    .toByteArray();
+            assertEquals("ell", new String(bytes, StandardCharsets.UTF_8));
+        } catch (IOException ioe) {
+
+        }
+
+    }
+
+    @Test
+    public void testCanReadEmptyFile() throws IOException {
+        File file = new File("target/test2");
+        file.createNewFile();
+        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ)) {
+            byte[] bytes = FluxUtil.byteBufStreamFromFile(channel, 1, 3)
+                    .map(bb -> {
+                        byte[] bt = toBytes(bb);
+                        ReferenceCountUtil.release(bb);
+                        return bt;
+                    })
+                    .collect(() -> new ByteArrayOutputStream(),
+                            (bos, b) -> {
+                                try {
+                                    bos.write(b);
+                                } catch (IOException ioe) {
+                                    throw Exceptions.propagate(ioe);
+                                }
+                            })
+                    .block().toByteArray();
+            assertEquals(0, bytes.length);
+        }
+        assertTrue(file.delete());
+    }
+
+    @Test
+    public void testAsynchronyShortInput() throws IOException {
+        File file = new File("target/test3");
+        FileOutputStream stream = new FileOutputStream(file);
+        stream.write("hello there".getBytes(StandardCharsets.UTF_8));
+        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ)) {
+            byte[] bytes = FluxUtil.byteBufStreamFromFile(channel)
+                    .map(bb -> {
+                        byte[] bt = toBytes(bb);
+                        ReferenceCountUtil.release(bb);
+                        return bt;
+                    })
+                    .limitRequest(1)
+                    .subscribeOn(reactor.core.scheduler.Schedulers.newElastic("io", 30))
+                    .publishOn(reactor.core.scheduler.Schedulers.newElastic("io", 30))
+                    .collect(() -> new ByteArrayOutputStream(),
+                            (bos, b) -> {
+                                try {
+                                    bos.write(b);
+                                } catch (IOException ioe) {
+                                    throw Exceptions.propagate(ioe);
+                                }
+                            })
+                    .block()
+                    .toByteArray();
+            assertEquals("hello there", new String(bytes, StandardCharsets.UTF_8));
+        }
+        assertTrue(file.delete());
+    }
+
+    private static final int NUM_CHUNKS_IN_LONG_INPUT = 10_000_000;
+
+    @Test
+    public void testAsynchronyLongInput() throws IOException, NoSuchAlgorithmException {
+        File file = new File("target/test4");
+        byte[] array = "1234567690".getBytes(StandardCharsets.UTF_8);
+        MessageDigest digest = MessageDigest.getInstance("MD5");
+        try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
+            for (int i = 0; i < NUM_CHUNKS_IN_LONG_INPUT; i++) {
+                out.write(array);
+                digest.update(array);
+            }
+        }
+        System.out.println("long input file size="+ file.length()/(1024*1024) + "MB");
+        byte[] expected = digest.digest();
+        digest.reset();
+        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ)) {
+            FluxUtil.byteBufStreamFromFile(channel)
+                    .subscribeOn(reactor.core.scheduler.Schedulers.newElastic("io", 30))
+                    .publishOn(reactor.core.scheduler.Schedulers.newElastic("io", 30))
+                    .toIterable().forEach(bb -> {
+                        digest.update(bb.nioBuffer());
+                        ReferenceCountUtil.release(bb);
+                    });
+
+            assertArrayEquals(expected, digest.digest());
+        }
+        assertTrue(file.delete());
+    }
+
+      @Test
+      @Ignore("Need to sync with smaldini to find equivalent for rx.test.awaitDone")
+      public void testBackpressureLongInput() throws IOException, NoSuchAlgorithmException {
+//        File file = new File("target/test4");
+//        byte[] array = "1234567690".getBytes(StandardCharsets.UTF_8);
+//        MessageDigest digest = MessageDigest.getInstance("MD5");
+//        try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
+//            for (int i = 0; i < NUM_CHUNKS_IN_LONG_INPUT; i++) {
+//                out.write(array);
+//                digest.update(array);
+//            }
+//        }
+//        byte[] expected = digest.digest();
+//        digest.reset();
+//
+//        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ)) {
+//            FluxUtil1.byteBufferStreamFromFile(channel)
+//                    .rebatchRequests(1)
+//                    .subscribeOn(Schedulers.io())
+//                    .observeOn(Schedulers.io())
+//                    .doOnNext(bb -> digest.update(bb))
+//                    .test(0)
+//                    .assertNoValues()
+//                    .requestMore(1)
+//                    .awaitCount(1)
+//                    .assertValueCount(1)
+//                    .requestMore(1)
+//                    .awaitCount(2)
+//                    .assertValueCount(2)
+//                    .requestMore(Long.MAX_VALUE)
+//                    .awaitDone(20, TimeUnit.SECONDS)
+//                    .assertComplete();
+//        }
+//
+//        assertArrayEquals(expected, digest.digest());
+//        assertTrue(file.delete());
+      }
+    
+    @Test
+    public void testSplitForMultipleSplitSizesFromOneTo16() throws NoSuchAlgorithmException {
+        ByteBuf bb = null;
+        try {
+            bb = Unpooled.directBuffer(1000);
+            byte [] oneByte = new byte[1];
+            for (int i = 0; i < 1000; i++) {
+                oneByte[0] = (byte) i;
+                bb.writeBytes(oneByte);
+            }
+            MessageDigest digest = MessageDigest.getInstance("MD5");
+            digest.update(bb.nioBuffer());
+            byte[] expected = digest.digest();
+            for (int size = 1; size < 16; size++) {
+                System.out.println("size=" + size);
+                digest.reset();
+                bb.readerIndex(0);
+                //
+                FluxUtil.split(bb, 3).doOnNext(b -> digest.update(b.nioBuffer()))
+                        .subscribe();
+//
+//            StepVerifier.create(FluxUtil1.split(bb, 3).doOnNext(b -> digest.update(b)))
+//                    .expectNextCount(?) // TODO: ? is Unknown. Check with smaldini - what is the Verifier way to ignore all next calls and simply check stream completes?
+//                    .verifyComplete();
+//
+                assertArrayEquals(expected, digest.digest());
+            }
+        } finally {
+            if (bb != null) {
+                bb.release();
+            }
+        }
+    }
+    
+    @Test
+    public void testSplitOnEmptyContent() {
+        ByteBuf bb = null;
+        try {
+            bb = Unpooled.directBuffer(16);
+            StepVerifier.create(FluxUtil.split(bb, 3))
+                    .expectNextCount(0)
+                    .expectComplete()
+                    .verify();
+        } finally {
+            if (bb != null) {
+                bb.release();
+            }
+        }
+    }
+
+    @Test
+    public void toByteArrayWithEmptyByteBuffer() {
+        assertArrayEquals(new byte[0], FluxUtil.byteBufToArray(Unpooled.wrappedBuffer(new byte[0])));
+    }
+
+    @Test
+    public void toByteArrayWithNonEmptyByteBuffer() {
+        final ByteBuf byteBuffer = Unpooled.wrappedBuffer(new byte[] { 0, 1, 2, 3, 4 });
+        assertEquals(5, byteBuffer.readableBytes());
+        final byte[] byteArray = FluxUtil.byteBufToArray(byteBuffer);
+        assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, byteArray);
+        assertEquals(5, byteBuffer.readableBytes());
+    }
+//
+    private static byte[] toBytes(ByteBuf bb) {
+        byte[] bytes = new byte[bb.readableBytes()];
+        bb.readBytes(bytes);
+        return bytes;
+    }
+
+}
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/util/Foo.java b/common/azure-common/src/test/java/com/azure/common/implementation/util/Foo.java
new file mode 100644
index 0000000000000..10c0453c05820
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/util/Foo.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.util;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.azure.common.implementation.serializer.JsonFlatten;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for testing serialization.
+ */
+@JsonFlatten
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "$type")
+@JsonTypeName("foo")
+@JsonSubTypes({
+        @JsonSubTypes.Type(name = "foochild", value = FooChild.class)
+})
+public class Foo {
+    @JsonProperty(value = "properties.bar")
+    public String bar;
+    @JsonProperty(value = "properties.props.baz")
+    public List<String> baz;
+    @JsonProperty(value = "properties.props.q.qux")
+    public Map<String, String> qux;
+    @JsonProperty(value = "properties.more\\.props")
+    public String moreProps;
+    @JsonProperty(value = "props.empty")
+    public Integer empty;
+    @JsonProperty(value = "")
+    public Map<String, Object> additionalProperties;
+}
\ No newline at end of file
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/util/FooChild.java b/common/azure-common/src/test/java/com/azure/common/implementation/util/FooChild.java
new file mode 100644
index 0000000000000..5e463ad7310db
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/util/FooChild.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for
+ * license information.
+ */
+
+package com.azure.common.implementation.util;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import com.azure.common.implementation.serializer.JsonFlatten;
+
+/**
+ * Class for testing serialization.
+ */
+@JsonFlatten
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "$type")
+@JsonTypeName("foochild")
+public class FooChild extends Foo {
+}
\ No newline at end of file
diff --git a/common/azure-common/src/test/java/com/azure/common/implementation/util/TypeUtilTests.java b/common/azure-common/src/test/java/com/azure/common/implementation/util/TypeUtilTests.java
new file mode 100644
index 0000000000000..6717a1d93a16b
--- /dev/null
+++ b/common/azure-common/src/test/java/com/azure/common/implementation/util/TypeUtilTests.java
@@ -0,0 +1,110 @@
+package com.azure.common.implementation.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+public class TypeUtilTests {
+
+    @Test
+    public void testGetClasses() {
+        Puppy puppy = new Puppy();
+        List<Class<?>> classes = TypeUtil.getAllClasses(puppy.getClass());
+        Assert.assertEquals(4, classes.size());
+        Assert.assertTrue(classes.contains(Puppy.class));
+        Assert.assertTrue(classes.contains(Dog.class));
+        Assert.assertTrue(classes.contains(Pet.class));
+        Assert.assertTrue(classes.contains(Object.class));
+    }
+
+    @Test
+    public void testGetTypeArguments() {
+        Type[] puppyArgs = TypeUtil.getTypeArguments(Puppy.class);
+        Type[] dogArgs = TypeUtil.getTypeArguments(Puppy.class.getGenericSuperclass());
+        Type[] petArgs = TypeUtil.getTypeArguments(Dog.class.getGenericSuperclass());
+
+        Assert.assertEquals(0, puppyArgs.length);
+        Assert.assertEquals(1, dogArgs.length);
+        Assert.assertEquals(2, petArgs.length);
+    }
+
+    @Test
+    public void testGetTypeArgument() {
+        Type dogArgs = TypeUtil.getTypeArgument(Puppy.class.getGenericSuperclass());
+        Assert.assertEquals(Kid.class, dogArgs);
+    }
+
+    @Test
+    public void testGetRawClass() {
+        Type petType = Puppy.class.getSuperclass().getGenericSuperclass();
+        Assert.assertEquals(Pet.class, TypeUtil.getRawClass(petType));
+    }
+
+    @Test
+    public void testGetSuperType() {
+        Type dogType = TypeUtil.getSuperType(Puppy.class);
+        Type petType = TypeUtil.getSuperType(dogType);
+
+        Type[] arguments = TypeUtil.getTypeArguments(petType);
+        Assert.assertEquals(2, arguments.length);
+        Assert.assertEquals(Kid.class, arguments[0]);
+        Assert.assertEquals(String.class, arguments[1]);
+    }
+
+    @Test
+    public void testGetTopSuperType() {
+        Type petType = TypeUtil.getSuperType(Puppy.class, Pet.class);
+
+        Type[] arguments = TypeUtil.getTypeArguments(petType);
+        Assert.assertEquals(2, arguments.length);
+        Assert.assertEquals(Kid.class, arguments[0]);
+        Assert.assertEquals(String.class, arguments[1]);
+    }
+
+    @Test
+    public void testIsTypeOrSubTypeOf() {
+        Type dogType = TypeUtil.getSuperType(Puppy.class);
+        Type petType = TypeUtil.getSuperType(dogType);
+
+        Assert.assertTrue(TypeUtil.isTypeOrSubTypeOf(Puppy.class, dogType));
+        Assert.assertTrue(TypeUtil.isTypeOrSubTypeOf(Puppy.class, Puppy.class));
+        Assert.assertTrue(TypeUtil.isTypeOrSubTypeOf(Puppy.class, petType));
+        Assert.assertTrue(TypeUtil.isTypeOrSubTypeOf(dogType, petType));
+        Assert.assertTrue(TypeUtil.isTypeOrSubTypeOf(dogType, dogType));
+        Assert.assertTrue(TypeUtil.isTypeOrSubTypeOf(petType, petType));
+    }
+
+    @Test
+    public void testCreateParameterizedType() {
+        Type dogType = TypeUtil.getSuperType(Puppy.class);
+        Type petType = TypeUtil.getSuperType(dogType);
+
+        Type createdType = TypeUtil.createParameterizedType(Pet.class, Kid.class, String.class);
+        Assert.assertEquals(TypeUtil.getRawClass(petType), TypeUtil.getRawClass(createdType));
+        Assert.assertArrayEquals(TypeUtil.getTypeArguments(petType), TypeUtil.getTypeArguments(createdType));
+    }
+
+    private static abstract class Pet<T extends Human, V> {
+        abstract T owner();
+    }
+
+    private static class Human {
+    }
+
+    private static class Kid extends Human {
+    }
+
+    private static class Dog<T extends Human> extends Pet<T, String> {
+        private T owner;
+
+        @Override
+        public T owner() {
+            return owner;
+        }
+    }
+
+    private static class Puppy extends Dog<Kid> {
+    }
+}
diff --git a/common/azure-common/src/test/resources/GetContainerACLs.xml b/common/azure-common/src/test/resources/GetContainerACLs.xml
new file mode 100644
index 0000000000000..9d33b4a791449
--- /dev/null
+++ b/common/azure-common/src/test/resources/GetContainerACLs.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<SignedIdentifiers>
+    <SignedIdentifier>
+        <Id>MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=</Id>
+        <AccessPolicy>
+            <Start>2009-09-28T08:49:37.0000000Z</Start>
+            <Expiry>2009-09-29T08:49:37.0000000Z</Expiry>
+            <Permission>rwd</Permission>
+        </AccessPolicy>
+    </SignedIdentifier>
+</SignedIdentifiers>
\ No newline at end of file
diff --git a/common/azure-common/src/test/resources/GetXMLWithAttributes.xml b/common/azure-common/src/test/resources/GetXMLWithAttributes.xml
new file mode 100644
index 0000000000000..db2867ef813ca
--- /dev/null
+++ b/common/azure-common/src/test/resources/GetXMLWithAttributes.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<slideshow
+        title="Sample Slide Show"
+        date="Date of publication"
+        author="Yours Truly">
+    <slide type="all">
+        <title>Wake up to WonderWidgets!</title>
+    </slide>
+
+    <slide type="all">
+        <title>Overview</title>
+        <item>Why WonderWidgets are great</item>
+        <item/>
+        <item>Who buys WonderWidgets</item>
+    </slide>
+</slideshow>
diff --git a/common/azure-common/src/test/resources/upload.txt b/common/azure-common/src/test/resources/upload.txt
new file mode 100644
index 0000000000000..ff3bb63948b4b
--- /dev/null
+++ b/common/azure-common/src/test/resources/upload.txt
@@ -0,0 +1 @@
+The quick brown fox jumps over the lazy dog
\ No newline at end of file
diff --git a/common/build-tools/pom.xml b/common/build-tools/pom.xml
new file mode 100644
index 0000000000000..2eba61f473287
--- /dev/null
+++ b/common/build-tools/pom.xml
@@ -0,0 +1,48 @@
+<!--
+ Copyright (c) Microsoft Corporation. All rights reserved.
+ Licensed under the MIT License. See License.txt in the project root for
+ license information.
+-->
+<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">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.azure</groupId>
+    <artifactId>azure-common-parent</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>azure-common-build-tools</artifactId>
+  <version>1.0.0-SNAPSHOT</version>
+  <packaging>jar</packaging>
+
+  <name>Build tools for Azure common Java libraries</name>
+  <description>This package contains the build tools for Azure Java client common libraries.</description>
+  <url>https://github.com/Azure/autorest-clientruntime-for-java</url>
+
+  <licenses>
+    <license>
+      <name>The MIT License (MIT)</name>
+      <url>http://opensource.org/licenses/MIT</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <scm>
+    <url>scm:git:https://github.com/Azure/autorest-clientruntime-for-java</url>
+    <connection>scm:git:git@github.com:Azure/autorest-clientruntime-for-java.git</connection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <legal><![CDATA[[INFO] Any downloads listed may be third party software.  Microsoft grants you no rights for third party software.]]></legal>
+  </properties>
+
+  <developers>
+    <developer>
+      <id>microsoft</id>
+      <name>Microsoft</name>
+    </developer>
+  </developers>
+</project>
diff --git a/common/build-tools/src/main/resources/checkstyle.xml b/common/build-tools/src/main/resources/checkstyle.xml
new file mode 100644
index 0000000000000..ab36c681e1d56
--- /dev/null
+++ b/common/build-tools/src/main/resources/checkstyle.xml
@@ -0,0 +1,262 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+
+  Checkstyle configuration that checks the sun coding conventions from:
+
+    - the Java Language Specification at
+      http://java.sun.com/docs/books/jls/second_edition/html/index.html
+
+    - the Sun Code Conventions at http://java.sun.com/docs/codeconv/
+
+    - the Javadoc guidelines at
+      http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
+
+    - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
+
+    - some best practices
+
+  Checkstyle is very configurable. Be sure to read the documentation at
+  http://checkstyle.sf.net (or in your downloaded distribution).
+
+  Most Checks are configurable, be sure to consult the documentation.
+
+  To completely disable a check, just comment it out or delete it from the file.
+
+  Finally, it is worth reading the documentation.
+
+-->
+
+<module name="Checker">
+    <module name="SuppressWithNearbyCommentFilter">
+        <property name="commentFormat" value="CHECKSTYLE IGNORE (\w+) FOR NEXT (\d+) LINE"/>
+        <property name="checkFormat" value="$1"/>
+        <property name="influenceFormat" value="$2"/>
+    </module>
+    <module name="SuppressionFilter">
+        <property name="file" value="${samedir}/suppressions.xml"/>
+        <property name="optional" value="true"/>
+    </module>
+    <module name="Header">
+        <property name="header" value="/**\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the MIT License. See License.txt in the project root for\n * license information.\n"/>
+        <property name="fileExtensions" value="java"/>
+    </module>
+    
+    <!--
+        If you set the basedir property below, then all reported file
+        names will be relative to the specified directory. See
+        http://checkstyle.sourceforge.net/5.x/config.html#Checker
+
+        <property name="basedir" value="${basedir}"/>
+    -->
+
+    <!-- Checks that each Java package has a Javadoc file used for commenting. -->
+    <!-- See http://checkstyle.sf.net/config_javadoc.html#JavadocPackage       -->
+    <module name="JavadocPackage">
+        <property name="allowLegacy" value="true"/>
+    </module>
+
+    <!-- Checks whether files end with a new line.                        -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
+
+    <!-- Removed on 11/02/2012
+         Justification: The rule incorrectly flags all files as violating
+         this rule, regardless of what they end with.
+    <module name="NewlineAtEndOfFile"/>
+    -->
+
+    <!-- Checks that property files contain the same keys.         -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
+    <module name="Translation"/>
+
+    <!-- Removed on 20/01/2014
+        Justification: generated code.
+        <module name="FileLength"/> -->
+
+    <!-- Following interprets the header file as regular expressions. -->
+    <!-- <module name="RegexpHeader"/>                                -->
+
+    <module name="FileTabCharacter">
+        <property name="eachLine" value="true"/>
+    </module>
+
+    <module name="TreeWalker">
+        <!-- Checks for Javadoc comments.                     -->
+        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+
+        <module name="JavadocMethod">
+            <property name="scope" value="public"/>
+        </module>
+        <module name="JavadocType">
+            <property name="scope" value="public"/>
+        </module>
+        <module name="JavadocVariable">
+            <property name="scope" value="public"/>
+        </module>
+        <module name="JavadocStyle"/>
+
+
+        <!-- Checks for Naming Conventions.                  -->
+        <!-- See http://checkstyle.sf.net/config_naming.html -->
+        <module name="ConstantName"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="MethodName"/>
+        <module name="PackageName"/>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+
+
+        <!-- Checks for Headers                                -->
+        <!-- See http://checkstyle.sf.net/config_header.html   -->
+        <!-- <module name="Header">                            -->
+            <!-- The follow property value demonstrates the ability     -->
+            <!-- to have access to ANT properties. In this case it uses -->
+            <!-- the ${basedir} property to allow Checkstyle to be run  -->
+            <!-- from any directory within a project. See property      -->
+            <!-- expansion,                                             -->
+            <!-- http://checkstyle.sf.net/config.html#properties        -->
+            <!-- <property                                              -->
+            <!--     name="headerFile"                                  -->
+            <!--     value="${basedir}/java.header"/>                   -->
+        <!-- </module> -->
+
+
+        <!-- Checks for imports                              -->
+        <!-- See http://checkstyle.sf.net/config_import.html -->
+        <module name="AvoidStarImport"/>
+        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+
+
+        <!-- Checks for Size Violations.                    -->
+        <!-- See http://checkstyle.sf.net/config_sizes.html -->
+
+        <!-- Removed on 11/02/2012:
+             Justification: The project properties automatically breaks
+             lines longer than 120 characters. Any lines that are longer
+             cannot be broken.
+        <module name="LineLength">
+          <property name="max" value="160" />
+        </module>
+        -->
+        <!-- Removed on 01/20/2014
+            Justification: a lot of the code is generated.
+            As such, method length is not a true representation
+            of complexity / etc issues.
+            <module name="MethodLength"/> -->
+        <!-- Remove on 05/19/2016
+            Justification: generated code
+        <module name="ParameterNumber"/> -->
+
+
+        <!-- Checks for whitespace                               -->
+        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+        <module name="EmptyForIteratorPad"/>
+        <module name="MethodParamPad"/>
+        <module name="NoWhitespaceAfter"/>
+        <module name="NoWhitespaceBefore"/>
+        <module name="OperatorWrap"/>
+        <module name="ParenPad"/>
+        <module name="TypecastParenPad"/>
+        <module name="WhitespaceAfter"/>
+        <module name="WhitespaceAround"/>
+
+
+        <!-- Modifier Checks                                    -->
+        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+        <module name="ModifierOrder"/>
+        <module name="RedundantModifier"/>
+        <module name="FileContentsHolder"/>
+
+
+        <!-- Checks for blocks. You know, those {}'s         -->
+        <!-- See http://checkstyle.sf.net/config_blocks.html -->
+        <module name="AvoidNestedBlocks"/>
+        <!-- Removed on 12/04/2014:
+            Justification: 1.x.x candidate. Not necessary for now.
+            <module name="EmptyBlock"/>
+        -->
+        <module name="LeftCurly" />
+        <module name="NeedBraces"/>
+
+        <!-- Removed on 11/02/2012:
+             Justification: The Azure SDK coding guidelines
+             are to have the right curly on a separate line.
+        <module name="RightCurly"/>
+        -->
+
+
+        <!-- Checks for common coding problems               -->
+        <!-- See http://checkstyle.sf.net/config_coding.html -->
+        <!-- Removed on 14/1/2014:
+                Justification: The Azure SDK coding guidelines
+                are that inline conditionals can actually make the
+                code more readable.
+            <module name="AvoidInlineConditionals"/> -->
+
+        <module name="EmptyStatement"/>
+        <module name="EqualsHashCode"/>
+         <!-- Removed on 11/02/2012:
+               Justification: The Azure SDK coding guidelines
+               are that the setter and constructor parameter names match
+               the names of the fields that are being set.
+        <module name="HiddenField"/>
+        -->
+
+        <module name="IllegalInstantiation"/>
+        <module name="InnerAssignment"/>
+
+        <!-- Updated on 03/17/2014: -->
+        <!-- Added ignore. Code is generated so magic numbers are not a largish issue. -->
+        <!-- <module name="MagicNumber" /> -->
+        <module name="MissingSwitchDefault"/>
+        <module name="SimplifyBooleanExpression"/>
+        <module name="SimplifyBooleanReturn"/>
+
+        <!-- Checks for class design                         -->
+        <!-- See http://checkstyle.sf.net/config_design.html -->
+
+        <!-- Removed on 11/02/2012:
+             Justification: None of the SDK classes are intended
+             to be subclassed by end-users, but at the same time we don't
+             want to mark them final and prevent derivation for scenarios
+             like mocking, etc.
+        <module name="DesignForExtension"/>
+        -->
+
+        <module name="FinalClass"/>
+        <module name="InterfaceIsType"/>
+        <module name="VisibilityModifier">
+            <property name="protectedAllowed" value="true"/>
+            <property name="packageAllowed" value="true"/>
+        </module>
+
+
+        <!-- Miscellaneous other checks.                   -->
+        <!-- See http://checkstyle.sf.net/config_misc.html -->
+        <module name="ArrayTypeStyle"/>
+
+        <!-- Removed on 11/02/2012:
+             Justification: The cost to implement and verify the changes
+             outweighs the benefit here.
+        <module name="FinalParameters"/>
+        -->
+
+        <module name="UpperEll"/>
+
+        <module name="Regexp">
+            <property name="format" value="Charset\.defaultCharset()" />
+            <property name="illegalPattern" value="true" />
+            <property name="ignoreComments" value="true" />
+            <property name="message" value="Call to platform-dependent method Charset.defaultCharset()" />
+        </module>
+    </module>
+
+</module>
diff --git a/common/build-tools/src/main/resources/suppressions.xml b/common/build-tools/src/main/resources/suppressions.xml
new file mode 100644
index 0000000000000..16a6bd2e73a98
--- /dev/null
+++ b/common/build-tools/src/main/resources/suppressions.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!DOCTYPE suppressions PUBLIC
+        "-//Puppy Crawl//DTD Suppressions 1.1//EN"
+        "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
+
+<!--
+
+  Checkstyle configuration that checks the sun coding conventions from:
+
+    - the Java Language Specification at
+      http://java.sun.com/docs/books/jls/second_edition/html/index.html
+
+    - the Sun Code Conventions at http://java.sun.com/docs/codeconv/
+
+    - the Javadoc guidelines at
+      http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
+
+    - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
+
+    - some best practices
+
+  Checkstyle is very configurable. Be sure to read the documentation at
+  http://checkstyle.sf.net (or in your downloaded distribution).
+
+  Most Checks are configurable, be sure to consult the documentation.
+
+  To completely disable a check, just comment it out or delete it from the file.
+
+  Finally, it is worth reading the documentation.
+
+-->
+
+<suppressions>
+    <suppress checks="Javadoc" files="Test[s]?.*\.java"/>
+    <suppress checks="Javadoc" files=".*Test[s]?\.java"/>
+    <suppress checks="Javadoc" files=".*Test[s]?Base\.java"/>
+    <suppress checks="Javadoc" files=".*CoverageReporter\.java"/>
+    <suppress checks="Header" files=".*package-info.java"/>
+</suppressions>
\ No newline at end of file
diff --git a/common/pom.xml b/common/pom.xml
new file mode 100644
index 0000000000000..8e1fcb14fd789
--- /dev/null
+++ b/common/pom.xml
@@ -0,0 +1,206 @@
+<!--
+ Copyright (c) Microsoft Corporation. All rights reserved.
+ Licensed under the MIT License. See License.txt in the project root for
+ license information.
+-->
+<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">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>com.azure</groupId>
+  <artifactId>azure-common-parent</artifactId>
+  <version>1.0.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <name>Azure Common Libraries for Java</name>
+  <description>This package contains the common libraries Java clients.</description>
+  <url>https://github.com/Azure/autorest-clientruntime-for-java</url>
+
+  <licenses>
+    <license>
+      <name>The MIT License (MIT)</name>
+      <url>http://opensource.org/licenses/MIT</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <scm>
+    <url>scm:git:https://github.com/Azure/autorest-clientruntime-for-java</url>
+    <connection>scm:git:git@github.com:Azure/autorest-clientruntime-for-java.git</connection>
+    <tag>HEAD</tag>
+  </scm>
+
+  <properties>
+    <netty.version>4.1.33.Final</netty.version>
+    <jackson.version>2.9.4</jackson.version>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <legal><![CDATA[[INFO] Any downloads listed may be third party software.  Microsoft grants you no rights for third party software.]]></legal>
+  </properties>
+
+  <developers>
+    <developer>
+      <id>microsoft</id>
+      <name>Microsoft</name>
+    </developer>
+  </developers>
+
+  <distributionManagement>
+    <snapshotRepository>
+      <id>ossrh</id>
+      <name>Sonatype Snapshots</name>
+      <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
+      <uniqueVersion>true</uniqueVersion>
+      <layout>default</layout>
+    </snapshotRepository>
+  </distributionManagement>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>com.fasterxml.jackson.datatype</groupId>
+        <artifactId>jackson-datatype-jsr310</artifactId>
+        <version>${jackson.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.fasterxml.jackson.dataformat</groupId>
+        <artifactId>jackson-dataformat-xml</artifactId>
+        <version>${jackson.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>io.reactivex.rxjava2</groupId>
+        <artifactId>rxjava</artifactId>
+        <version>2.2.0</version>
+      </dependency>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>1.7.22</version>
+      </dependency>
+      <dependency>
+        <groupId>io.netty</groupId>
+        <artifactId>netty-buffer</artifactId>
+        <version>${netty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>io.netty</groupId>
+        <artifactId>netty-handler</artifactId>
+        <version>${netty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>io.netty</groupId>
+        <artifactId>netty-handler-proxy</artifactId>
+        <version>${netty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>io.netty</groupId>
+        <artifactId>netty-codec-http</artifactId>
+        <version>${netty.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>com.microsoft.azure</groupId>
+        <artifactId>adal4j</artifactId>
+        <version>1.6.1</version>
+      </dependency>
+
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-simple</artifactId>
+        <version>1.7.22</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.12</version>
+        <scope>test</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.17</version>
+        <dependencies>
+          <dependency>
+            <groupId>com.azure</groupId>
+            <artifactId>azure-common-build-tools</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+          </dependency>
+          <dependency>
+            <groupId>com.puppycrawl.tools</groupId>
+            <artifactId>checkstyle</artifactId>
+            <version>6.18</version>
+          </dependency>
+        </dependencies>
+        <configuration>
+          <configLocation>checkstyle.xml</configLocation>
+          <propertyExpansion>samedir=build-tools/src/main/resources</propertyExpansion>
+          <suppressionsLocation>suppressions.xml</suppressionsLocation>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.1</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+          <showWarnings>true</showWarnings>
+          <showDeprecation>true</showDeprecation>
+          <compilerArgument>-Xlint:unchecked</compilerArgument>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>3.0.0</version>
+        <configuration>
+          <excludePackageNames>*.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization;*.blob.core.storage</excludePackageNames>
+          <bottom><![CDATA[<code>/**
+<br />* Copyright (c) Microsoft Corporation. All rights reserved.
+<br />* Licensed under the MIT License. See License.txt in the project root for
+<br />* license information.
+<br />*/</code>]]></bottom>
+        </configuration>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-resources-plugin</artifactId>
+          <version>2.4.3</version>
+        </plugin>
+
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <version>2.18.1</version>
+          <configuration>
+            <includes>
+              <include>**/Test*.java</include>
+              <include>**/*Test.java</include>
+              <include>**/*Tests.java</include>
+              <include>**/*TestCase.java</include>
+            </includes>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-release-plugin</artifactId>
+          <version>2.5.2</version>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+  <modules>
+    <module>build-tools</module>
+    <module>azure-common</module>
+    <module>azure-common-mgmt</module>
+    <module>azure-common-auth</module>
+  </modules>
+</project>
diff --git a/common/versionConfig.csv b/common/versionConfig.csv
new file mode 100644
index 0000000000000..3193fca87ba2b
--- /dev/null
+++ b/common/versionConfig.csv
@@ -0,0 +1,5 @@
+com.microsoft.azure.v3,autorest-clientruntime-for-java,2.0.0,2.0.1-SNAPSHOT
+com.microsoft.rest.v3,client-runtime,2.0.0,2.0.1-SNAPSHOT
+com.microsoft.azure.v3,azure-client-runtime,2.0.0-beta5,2.0.1-SNAPSHOT
+com.microsoft.azure.v3,azure-client-authentication,2.0.0-beta5,2.0.1-SNAPSHOT
+com.microsoft.azure.v3,autorest-build-tools,2.0.0-beta5,2.0.1-SNAPSHOT
diff --git a/eng/.docsettings.yml b/eng/.docsettings.yml
index ae5d8f24b05bb..131ee6e821ce8 100644
--- a/eng/.docsettings.yml
+++ b/eng/.docsettings.yml
@@ -40,6 +40,10 @@ known_presence_issues:
   - ['keyvault/data-plane/azure-keyvault-extensions', '#2847']
   - ['keyvault/data-plane/azure-keyvault-webkey', '#2847']
   - ['mediaservices/data-plane', '#2847']
+  - ['common/build-tools', '#2847']
+  - ['common/azure-common', '#2847']
+  - ['common/azure-common-auth', '#2847']
+  - ['common/azure-common-mgmt', '#2847']
 known_content_issues:
   - ['sdk/template/azure-sdk-template/README.md','has other required sections']
   - ['README.md', '#3113']
@@ -53,3 +57,4 @@ known_content_issues:
   - ['loganalytics/data-plane/README.md', '#3113']
   - ['storage/data-plane/README.md', '#3113']
   - ['storage/data-plane/swagger/README.md', '#3113']
+  - ['common/README.md', '#3113']