diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 0b26b8467bc228aff3891c6049cc88a7ee82da1f..75434383023f37f3aadf2093fe601be77710cd06 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -10,6 +10,14 @@ sure your code compiles by running `mvn clean verify`. Checkstyle failures
 during compilation indicate errors in your style and can be viewed in the
 `checkstyle-result.xml` file.
 
+Some general advice
+
+- Don’t change public API lightly, avoid if possible, and include your reasoning in the PR if essential.  It causes pain for developers who use OkHttp and sometimes runtime errors.
+- Favour a working external library if appropriate.  There are many examples of OkHttp libraries that can sit on top or hook in via existing APIs.
+- Get working code on a personal branch with tests before you submit a PR.
+- OkHttp is a small and light dependency.  Don't introduce new dependencies or major new functionality.
+- OkHttp targets the intersection of RFC correct *and* widely implemented.  Incorrect implementations that are very widely implemented e.g. a bug in Apache, Nginx, Google, Firefox should also be handled.
+
 Before your code can be accepted into the project you must also sign the
 [Individual Contributor License Agreement (CLA)][1].
 
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index e65d371715a6a17a565f7a98b4ea24a503368804..0000000000000000000000000000000000000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,12 +0,0 @@
-What kind of issue is this?
-
- - [ ] Question. This issue tracker is not the place for questions. If you want to ask how to do
-       something, or to understand why something isn't working the way you expect it to, use Stack
-       Overflow. https://stackoverflow.com/questions/tagged/okhttp
-
- - [ ] Bug report. If you’ve found a bug, spend the time to write a failing test. Bugs with tests
-       get fixed. Here’s an example: https://gist.github.com/swankjesse/981fcae102f513eb13ed
-
- - [ ] Feature Request. Start by telling us what problem you’re trying to solve. Often a solution
-       already exists! Don’t send pull requests to implement new features without first getting our
-       support. Sometimes we leave features out on purpose to keep the project small.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000000000000000000000000000000000..8b276c7deab072018ad02e6e49d09de692005cd0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,13 @@
+---
+name: Bug report
+about: A reproducible problem
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+Good bug reports include a failing test! Writing a test helps you to isolate and describe the problem, and it helps us to fix it fast. Bug reports without a failing test or reproduction steps are likely to be closed.
+
+Here’s an example test to get you started.
+https://gist.github.com/swankjesse/981fcae102f513eb13ed
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000000000000000000000000000000000..e320e37f4a8e44e03c53d3c2ec8590f065b3d9fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,12 @@
+---
+name: Feature request
+about: Suggest an idea
+title: ''
+labels: enhancement
+assignees: ''
+
+---
+
+Start by telling us what problem you’re trying to solve. Often a solution already exists!
+
+Don’t send pull requests to implement new features without first getting our support. Sometimes we leave features out on purpose to keep the project small.
diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md
new file mode 100644
index 0000000000000000000000000000000000000000..73f2b5c1af2a3c01ecfc451a7ae23d6610171e80
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/question.md
@@ -0,0 +1,16 @@
+---
+name: Question
+about: Use Stack Overflow instead
+title: "\U0001F649"
+labels: ''
+assignees: ''
+
+---
+
+🛑 𝙎𝙏𝙊𝙋
+
+This issue tracker is not the place for questions!
+
+If you want to ask how to do something, or to understand why something isn't working the way you expect it to, use Stack Overflow. https://stackoverflow.com/questions/tagged/okhttp
+
+We close all questions without reading them.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f480f2c40760ecbd4701863542461af1f1f92cb..ac4c0bd4f13a29a3b8caf22fd989faf278b30fe7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,58 @@
 Change Log
 ==========
 
+## Version 3.13.0
+
+_2019-02-04_
+
+ *  **This release bumps our minimum requirements to Java 8+ or Android 5+.** Cutting off old
+    devices is a serious change and we don't do it lightly! [This post][require_android_5] explains
+    why we're doing this and how to upgrade.
+
+    The OkHttp 3.12.x branch will be our long-term branch for Android 2.3+ (API level 9+) and Java
+    7+. These platforms lack support for TLS 1.2 and should not be used. But because upgrading is
+    difficult we will backport critical fixes to the 3.12.x branch through December 31, 2020.
+
+ *  **TLSv1 and TLSv1.1 are no longer enabled by default.** Major web browsers are working towards
+    removing these versions altogether in early 2020. If your servers aren't ready yet you can
+    configure OkHttp 3.13 to allow TLSv1 and TLSv1.1 connections:
+
+    ```
+    OkHttpClient client = new OkHttpClient.Builder()
+        .connectionSpecs(Arrays.asList(ConnectionSpec.COMPATIBLE_TLS))
+        .build();
+    ```
+
+ *  New: You can now access HTTP trailers with `Response.trailers()`. This method may only be called
+    after the entire HTTP response body has been read.
+
+ *  New: Upgrade to Okio 1.17.3. If you're on Kotlin-friendly Okio 2.x this release requires 2.2.2
+    or newer.
+
+    ```kotlin
+    implementation("com.squareup.okio:okio:1.17.3")
+    ```
+
+ *  Fix: Don't miss cancels when sending HTTP/2 request headers.
+ *  Fix: Don't miss whole operation timeouts when calls redirect.
+ *  Fix: Don't leak connections if web sockets have malformed responses or if `onOpen()` throws.
+ *  Fix: Don't retry when request bodies fail due to `FileNotFoundException`.
+ *  Fix: Don't crash when URLs have IPv4-mapped IPv6 addresses.
+ *  Fix: Don't crash when building `HandshakeCertificates` on Android API 28.
+ *  Fix: Permit multipart file names to contain non-ASCII characters.
+ *  New: API to get MockWebServer's dispatcher.
+ *  New: API to access headers as `java.time.Instant`.
+ *  New: Fail fast if a `SSLSocketFactory` is used as a `SocketFactory`.
+ *  New: Log the TLS handshake in `LoggingEventListener`.
+
+
+## Version 3.12.1
+
+_2018-12-23_
+
+ *  Fix: Remove overlapping `package-info.java`. This caused issues with some build tools.
+
+
 ## Version 3.12.0
 
 _2018-11-16_
@@ -1599,4 +1651,5 @@ Initial release.
  [remove_cbc_ecdsa]: https://developers.google.com/web/updates/2016/12/chrome-56-deprecations#remove_cbc-mode_ecdsa_ciphers_in_tls
  [conscrypt]: https://github.com/google/conscrypt/
  [conscrypt_dependency]: https://github.com/google/conscrypt/#download
- [https_server_sample]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/HttpsServer.java
\ No newline at end of file
+ [https_server_sample]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/HttpsServer.java
+ [require_android_5]: https://medium.com/square-corner-blog/okhttp-3-13-requires-android-5-XXXXXXXXXXXX
diff --git a/README.md b/README.md
index fcecace9525d238b4a11783f4fac523ae71763bb..5a90766d80a196668cbe49a30a8bc096ead98916 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,42 @@
 OkHttp
 ======
 
-An HTTP & HTTP/2 client for Android and Java applications. For more information see [the website][1] and [the wiki][2].
+An HTTP & HTTP/2 client for Android and Java applications. For more information see [the
+website][website] and [the wiki][wiki].
+
+
+Requirements
+------------
+
+OkHttp works on Android 5.0+ (API level 21+) and on Java 8+.
+
+OkHttp has one library dependency on [Okio][okio], a small library for high-performance I/O. It
+works with either Okio 1.x (implemented in Java) or Okio 2.x (upgraded to Kotlin).
+
+We highly recommend you keep OkHttp up-to-date. As with auto-updating web browsers, staying current
+with HTTPS clients is an important defense against potential security problems. [We
+track][tls_history] the dynamic TLS ecosystem and adjust OkHttp to improve connectivity and
+security.
+
+OkHttp uses your platform's built-in TLS implementation. On Java platforms OkHttp also supports
+[Conscrypt][conscrypt], which integrates BoringSSL with Java. OkHttp will use Conscrypt if it is
+the first security provider:
+
+```java
+Security.insertProviderAt(Conscrypt.newProvider(), 1);
+```
+
+The OkHttp 3.12.x branch supports Android 2.3+ (API level 9+) and Java 7+. These platforms lack
+support for TLS 1.2 and should not be used. But because upgrading is difficult we will backport
+critical fixes to the [3.12.x branch][okhttp_312x] through December 31, 2020.
 
 Download
 --------
 
-Download [the latest JAR][3] or configure this dependency:
+Download [the latest JAR][okhttp_latest_jar] or configure this dependency:
 
 ```kotlin
-implementation("com.squareup.okhttp3:okhttp:3.12.0")
+implementation("com.squareup.okhttp3:okhttp:3.13.0")
 ```
 
 Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
@@ -24,16 +51,15 @@ MockWebServer coupling with OkHttp is essential for proper testing of HTTP/2 so
 
 ### Download
 
-Download [the latest JAR][4] or configure this dependency:
+Download [the latest JAR][mockwebserver_latest_jar] or configure this dependency:
 ```xml
-testImplementation("com.squareup.okhttp3:mockwebserver:3.12.0")
+testImplementation("com.squareup.okhttp3:mockwebserver:3.13.0")
 ```
 
 R8 / ProGuard
 -------------
 
-If you are using R8 or ProGuard add the options from
-[this file](https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro).
+If you are using R8 or ProGuard add the options from [`okhttp3.pro`][okhttp3_pro].
 
 You might also need rules for Okio which is a dependency of this library.
 
@@ -54,8 +80,13 @@ License
     limitations under the License.
 
 
- [1]: https://square.github.io/okhttp
- [2]: https://github.com/square/okhttp/wiki
- [3]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=okhttp&v=LATEST
- [4]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=mockwebserver&v=LATEST
+ [conscrypt]: https://github.com/google/conscrypt/
+ [mockwebserver_latest_jar]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=mockwebserver&v=LATEST
+ [okhttp_312x]: https://github.com/square/okhttp/tree/okhttp_3.12.x
+ [okhttp_latest_jar]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=okhttp&v=LATEST
+ [okio]: https://github.com/square/okio/
  [snap]: https://oss.sonatype.org/content/repositories/snapshots/
+ [tls_history]: https://github.com/square/okhttp/wiki/TLS-Configuration-History
+ [website]: https://square.github.io/okhttp
+ [wiki]: https://github.com/square/okhttp/wiki
+ [okhttp3_pro]: https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
diff --git a/benchmarks/README.md b/benchmarks/README.md
deleted file mode 100644
index ee9be3ff477e818f9f9fa25e43b5c4265b0e6376..0000000000000000000000000000000000000000
--- a/benchmarks/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-OkHttp Benchmarks
-=======================================
-
-This module allows you to test the performance of HTTP clients.
-
-### Running
-  1. If you made modifications to `Benchmark` run `mvn compile`.
-  2. Run `mvn exec:exec` to launch a new JVM, which will execute the benchmark.
diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml
deleted file mode 100644
index 82cdc0df17a10c3173cb17df71b273c1b5e80b25..0000000000000000000000000000000000000000
--- a/benchmarks/pom.xml
+++ /dev/null
@@ -1,145 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.squareup.okhttp3</groupId>
-    <artifactId>parent</artifactId>
-    <version>3.12.1</version>
-  </parent>
-
-  <artifactId>benchmarks</artifactId>
-  <name>Benchmarks</name>
-
-  <dependencies>
-    <dependency>
-      <groupId>com.google.caliper</groupId>
-      <artifactId>caliper</artifactId>
-      <version>1.0-beta-1</version>
-    </dependency>
-    <!-- caliper needs to be updated to be compatible with guava 16 -->
-    <dependency>
-      <groupId>com.google.guava</groupId>
-      <artifactId>guava</artifactId>
-      <version>14.0.1</version>
-    </dependency>
-    <dependency>
-      <groupId>${project.groupId}</groupId>
-      <artifactId>okhttp</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>${project.groupId}</groupId>
-      <artifactId>okhttp-urlconnection</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>${project.groupId}</groupId>
-      <artifactId>okhttp-tls</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>${project.groupId}</groupId>
-      <artifactId>mockwebserver</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.httpcomponents</groupId>
-      <artifactId>httpclient</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>io.netty</groupId>
-      <artifactId>netty-transport</artifactId>
-      <version>4.0.15.Final</version>
-    </dependency>
-    <dependency>
-      <groupId>io.netty</groupId>
-      <artifactId>netty-handler</artifactId>
-      <version>4.0.15.Final</version>
-    </dependency>
-    <dependency>
-      <groupId>io.netty</groupId>
-      <artifactId>netty-codec-http</artifactId>
-      <version>4.0.15.Final</version>
-    </dependency>
-    <!-- Netty needs this if gzip is enabled. -->
-    <dependency>
-      <groupId>com.jcraft</groupId>
-      <artifactId>jzlib</artifactId>
-      <version>1.1.2</version>
-    </dependency>
-  </dependencies>
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>exec-maven-plugin</artifactId>
-        <version>1.5.0</version>
-        <executions>
-          <execution>
-            <goals>
-              <goal>java</goal>
-            </goals>
-          </execution>
-        </executions>
-        <configuration>
-          <executable>java</executable>
-          <arguments>
-            <argument>-Xms512m</argument>
-            <argument>-Xmx512m</argument>
-            <commandlineArgs>-Xbootclasspath/p:${bootclasspath}</commandlineArgs>
-            <argument>-classpath</argument>
-            <classpath />
-            <argument>okhttp3.benchmarks.Benchmark</argument>
-          </arguments>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-  <profiles>
-    <profile>
-      <id>alpn-when-jdk7</id>
-      <activation>
-        <jdk>1.7</jdk>
-      </activation>
-      <dependencies>
-        <dependency>
-          <groupId>org.mortbay.jetty.alpn</groupId>
-          <artifactId>alpn-boot</artifactId>
-          <version>${alpn.jdk7.version}</version>
-          <scope>provided</scope>
-        </dependency>
-      </dependencies>
-    </profile>
-    <profile>
-      <id>alpn-when-jdk8</id>
-      <activation>
-        <jdk>1.8</jdk>
-      </activation>
-      <dependencies>
-        <dependency>
-          <groupId>org.mortbay.jetty.alpn</groupId>
-          <artifactId>alpn-boot</artifactId>
-          <version>${alpn.jdk8.version}</version>
-          <scope>provided</scope>
-        </dependency>
-      </dependencies>
-      <build>
-        <plugins>
-          <plugin>
-            <!-- Fails on caliper's ASM on OpenJDK 8. -->
-            <groupId>org.codehaus.mojo</groupId>
-            <artifactId>animal-sniffer-maven-plugin</artifactId>
-            <version>1.15</version>
-            <executions>
-              <execution>
-                <phase>none</phase>
-              </execution>
-            </executions>
-          </plugin>
-        </plugins>
-      </build>
-    </profile>
-  </profiles>
-</project>
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/ApacheHttpClient.java b/benchmarks/src/main/java/okhttp3/benchmarks/ApacheHttpClient.java
deleted file mode 100644
index e3dbd3084cb4be4e6aab8371277b4fc92630cc46..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/ApacheHttpClient.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.GZIPInputStream;
-import okhttp3.HttpUrl;
-import okhttp3.tls.HandshakeCertificates;
-import org.apache.http.Header;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.conn.scheme.Scheme;
-import org.apache.http.conn.ssl.SSLSocketFactory;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.PoolingClientConnectionManager;
-
-import static okhttp3.tls.internal.TlsUtil.localhost;
-
-/** Benchmark Apache HTTP client. */
-class ApacheHttpClient extends SynchronousHttpClient {
-  private static final boolean VERBOSE = false;
-
-  private HttpClient client;
-
-  @Override public void prepare(Benchmark benchmark) {
-    super.prepare(benchmark);
-    ClientConnectionManager connectionManager = new PoolingClientConnectionManager();
-    if (benchmark.tls) {
-      HandshakeCertificates handshakeCertificates = localhost();
-      connectionManager.getSchemeRegistry().register(
-          new Scheme("https", 443, new SSLSocketFactory(handshakeCertificates.sslContext())));
-    }
-    client = new DefaultHttpClient(connectionManager);
-  }
-
-  @Override public Runnable request(HttpUrl url) {
-    return new ApacheHttpClientRequest(url);
-  }
-
-  class ApacheHttpClientRequest implements Runnable {
-    private final HttpUrl url;
-
-    ApacheHttpClientRequest(HttpUrl url) {
-      this.url = url;
-    }
-
-    public void run() {
-      long start = System.nanoTime();
-      try {
-        HttpResponse response = client.execute(new HttpGet(url.toString()));
-        InputStream in = response.getEntity().getContent();
-        Header contentEncoding = response.getFirstHeader("Content-Encoding");
-        if (contentEncoding != null && contentEncoding.getValue().equals("gzip")) {
-          in = new GZIPInputStream(in);
-        }
-
-        long total = readAllAndClose(in);
-        long finish = System.nanoTime();
-
-        if (VERBOSE) {
-          System.out.println(String.format("Transferred % 8d bytes in %4d ms",
-              total, TimeUnit.NANOSECONDS.toMillis(finish - start)));
-        }
-      } catch (IOException e) {
-        System.out.println("Failed: " + e);
-      }
-    }
-  }
-}
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/Benchmark.java b/benchmarks/src/main/java/okhttp3/benchmarks/Benchmark.java
deleted file mode 100644
index 081fcfccb9f09f1885f94b59b863fbd0a027fb40..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/Benchmark.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-import com.google.caliper.Param;
-import com.google.caliper.model.ArbitraryMeasurement;
-import com.google.caliper.runner.CaliperMain;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import okhttp3.HttpUrl;
-import okhttp3.Protocol;
-import okhttp3.mockwebserver.Dispatcher;
-import okhttp3.mockwebserver.MockResponse;
-import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.mockwebserver.RecordedRequest;
-import okhttp3.tls.HandshakeCertificates;
-import okio.Buffer;
-import okio.GzipSink;
-
-import static okhttp3.tls.internal.TlsUtil.localhost;
-
-/**
- * This benchmark is fake, but may be useful for certain relative comparisons. It uses a local
- * connection to a MockWebServer to measure how many identical requests per second can be carried
- * over a fixed number of threads.
- */
-public class Benchmark extends com.google.caliper.Benchmark {
-  private static final int NUM_REPORTS = 10;
-  private static final boolean VERBOSE = false;
-
-  private final Random random = new Random(0);
-
-  /** Which client to run. */
-  @Param
-  Client client;
-
-  /** How many concurrent requests to execute. */
-  @Param({"1", "10"})
-  int concurrencyLevel;
-
-  /** How many requests to enqueue to await threads to execute them. */
-  @Param({"10"})
-  int targetBacklog;
-
-  /** True to use TLS. */
-  // TODO: compare different ciphers?
-  @Param
-  boolean tls;
-
-  /** True to use gzip content-encoding for the response body. */
-  @Param
-  boolean gzip;
-
-  /** Don't combine chunked with HTTP_2; that's not allowed. */
-  @Param
-  boolean chunked;
-
-  /** The size of the HTTP response body, in uncompressed bytes. */
-  @Param({"128", "1048576"})
-  int bodyByteCount;
-
-  /** How many additional headers were included, beyond the built-in ones. */
-  @Param({"0", "20"})
-  int headerCount;
-
-  /** Which ALPN protocols are in use. Only useful with TLS. */
-  List<Protocol> protocols = Arrays.asList(Protocol.HTTP_1_1);
-
-  public static void main(String[] args) {
-    List<String> allArgs = new ArrayList<>();
-    allArgs.add("--instrument");
-    allArgs.add("arbitrary");
-    allArgs.addAll(Arrays.asList(args));
-
-    CaliperMain.main(Benchmark.class, allArgs.toArray(new String[allArgs.size()]));
-  }
-
-  @ArbitraryMeasurement(description = "requests per second")
-  public double run() throws Exception {
-    if (VERBOSE) System.out.println(toString());
-    HttpClient httpClient = client.create();
-
-    // Prepare the client & server
-    httpClient.prepare(this);
-    MockWebServer server = startServer();
-    HttpUrl url = server.url("/");
-
-    int requestCount = 0;
-    long reportStart = System.nanoTime();
-    long reportPeriod = TimeUnit.SECONDS.toNanos(1);
-    int reports = 0;
-    double best = 0.0;
-
-    // Run until we've printed enough reports.
-    while (reports < NUM_REPORTS) {
-      // Print a report if we haven't recently.
-      long now = System.nanoTime();
-      double reportDuration = now - reportStart;
-      if (reportDuration > reportPeriod) {
-        double requestsPerSecond = requestCount / reportDuration * TimeUnit.SECONDS.toNanos(1);
-        if (VERBOSE) {
-          System.out.println(String.format("Requests per second: %.1f", requestsPerSecond));
-        }
-        best = Math.max(best, requestsPerSecond);
-        requestCount = 0;
-        reportStart = now;
-        reports++;
-      }
-
-      // Fill the job queue with work.
-      while (httpClient.acceptingJobs()) {
-        httpClient.enqueue(url);
-        requestCount++;
-      }
-
-      // The job queue is full. Take a break.
-      sleep(1);
-    }
-
-    return best;
-  }
-
-  @Override public String toString() {
-    List<Object> modifiers = new ArrayList<>();
-    if (tls) modifiers.add("tls");
-    if (gzip) modifiers.add("gzip");
-    if (chunked) modifiers.add("chunked");
-    modifiers.addAll(protocols);
-
-    return String.format("%s %s\nbodyByteCount=%s headerCount=%s concurrencyLevel=%s",
-        client, modifiers, bodyByteCount, headerCount, concurrencyLevel);
-  }
-
-  private void sleep(int millis) {
-    try {
-      Thread.sleep(millis);
-    } catch (InterruptedException ignored) {
-    }
-  }
-
-  private MockWebServer startServer() throws IOException {
-    Logger.getLogger(MockWebServer.class.getName()).setLevel(Level.WARNING);
-    MockWebServer server = new MockWebServer();
-
-    if (tls) {
-      HandshakeCertificates handshakeCertificates = localhost();
-      server.useHttps(handshakeCertificates.sslSocketFactory(), false);
-      server.setProtocols(protocols);
-    }
-
-    final MockResponse response = newResponse();
-    server.setDispatcher(new Dispatcher() {
-      @Override public MockResponse dispatch(RecordedRequest request) {
-        return response;
-      }
-    });
-
-    server.start();
-    return server;
-  }
-
-  private MockResponse newResponse() throws IOException {
-    byte[] bytes = new byte[bodyByteCount];
-    random.nextBytes(bytes);
-    Buffer body = new Buffer().write(bytes);
-
-    MockResponse result = new MockResponse();
-
-    if (gzip) {
-      Buffer gzipBody = new Buffer();
-      GzipSink gzipSink = new GzipSink(gzipBody);
-      gzipSink.write(body, body.size());
-      gzipSink.close();
-      body = gzipBody;
-      result.addHeader("Content-Encoding: gzip");
-    }
-
-    if (chunked) {
-      result.setChunkedBody(body, 1024);
-    } else {
-      result.setBody(body);
-    }
-
-    for (int i = 0; i < headerCount; i++) {
-      result.addHeader(randomString(12), randomString(20));
-    }
-
-    return result;
-  }
-
-  private String randomString(int length) {
-    String alphabet = "-abcdefghijklmnopqrstuvwxyz";
-    char[] result = new char[length];
-    for (int i = 0; i < length; i++) {
-      result[i] = alphabet.charAt(random.nextInt(alphabet.length()));
-    }
-    return new String(result);
-  }
-}
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/Client.java b/benchmarks/src/main/java/okhttp3/benchmarks/Client.java
deleted file mode 100644
index b7c6b3f3b9ba8892980b7e816908d6f7e98f9a7d..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/Client.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-enum Client {
-  OkHttp {
-    @Override HttpClient create() {
-      return new OkHttp();
-    }
-  },
-
-  OkHttpAsync {
-    @Override HttpClient create() {
-      return new OkHttpAsync();
-    }
-  },
-
-  Apache {
-    @Override HttpClient create() {
-      return new ApacheHttpClient();
-    }
-  },
-
-  UrlConnection {
-    @Override HttpClient create() {
-      return new UrlConnection();
-    }
-  },
-
-  Netty {
-    @Override HttpClient create() {
-      return new NettyHttpClient();
-    }
-  };
-
-  abstract HttpClient create();
-}
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/NettyHttpClient.java b/benchmarks/src/main/java/okhttp3/benchmarks/NettyHttpClient.java
deleted file mode 100644
index 35731fa3b1cc82127455ee4c0c158341109beaac..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/NettyHttpClient.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-import io.netty.bootstrap.Bootstrap;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.PooledByteBufAllocator;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInitializer;
-import io.netty.channel.ChannelOption;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.channel.socket.SocketChannel;
-import io.netty.channel.socket.nio.NioSocketChannel;
-import io.netty.handler.codec.http.DefaultFullHttpRequest;
-import io.netty.handler.codec.http.HttpClientCodec;
-import io.netty.handler.codec.http.HttpContent;
-import io.netty.handler.codec.http.HttpContentDecompressor;
-import io.netty.handler.codec.http.HttpHeaders;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpObject;
-import io.netty.handler.codec.http.HttpRequest;
-import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.codec.http.LastHttpContent;
-import io.netty.handler.ssl.SslHandler;
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.concurrent.TimeUnit;
-import javax.net.ssl.SSLEngine;
-import okhttp3.HttpUrl;
-import okhttp3.tls.HandshakeCertificates;
-
-import static okhttp3.tls.internal.TlsUtil.localhost;
-
-/** Netty isn't an HTTP client, but it's almost one. */
-class NettyHttpClient implements HttpClient {
-  private static final boolean VERBOSE = false;
-
-  // Guarded by this. Real apps need more capable connection management.
-  private final Deque<HttpChannel> freeChannels = new ArrayDeque<>();
-  private final Deque<HttpUrl> backlog = new ArrayDeque<>();
-
-  private int totalChannels = 0;
-  private int concurrencyLevel;
-  private int targetBacklog;
-  private Bootstrap bootstrap;
-
-  @Override public void prepare(final Benchmark benchmark) {
-    this.concurrencyLevel = benchmark.concurrencyLevel;
-    this.targetBacklog = benchmark.targetBacklog;
-
-    ChannelInitializer<SocketChannel> channelInitializer = new ChannelInitializer<SocketChannel>() {
-      @Override public void initChannel(SocketChannel channel) {
-        ChannelPipeline pipeline = channel.pipeline();
-
-        if (benchmark.tls) {
-          HandshakeCertificates handshakeCertificates = localhost();
-          SSLEngine engine = handshakeCertificates.sslContext().createSSLEngine();
-          engine.setUseClientMode(true);
-          pipeline.addLast("ssl", new SslHandler(engine));
-        }
-
-        pipeline.addLast("codec", new HttpClientCodec());
-        pipeline.addLast("inflater", new HttpContentDecompressor());
-        pipeline.addLast("handler", new HttpChannel(channel));
-      }
-    };
-
-    bootstrap = new Bootstrap();
-    bootstrap.group(new NioEventLoopGroup(concurrencyLevel))
-        .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
-        .channel(NioSocketChannel.class)
-        .handler(channelInitializer);
-  }
-
-  @Override public void enqueue(HttpUrl url) throws Exception {
-    HttpChannel httpChannel = null;
-    synchronized (this) {
-      if (!freeChannels.isEmpty()) {
-        httpChannel = freeChannels.pop();
-      } else if (totalChannels < concurrencyLevel) {
-        totalChannels++; // Create a new channel. (outside of the synchronized block).
-      } else {
-        backlog.add(url); // Enqueue this for later, to be picked up when another request completes.
-        return;
-      }
-    }
-    if (httpChannel == null) {
-      Channel channel = bootstrap.connect(url.host(), url.port())
-          .sync().channel();
-      httpChannel = (HttpChannel) channel.pipeline().last();
-    }
-    httpChannel.sendRequest(url);
-  }
-
-  @Override public synchronized boolean acceptingJobs() {
-    return backlog.size() < targetBacklog || hasFreeChannels();
-  }
-
-  private boolean hasFreeChannels() {
-    int activeChannels = totalChannels - freeChannels.size();
-    return activeChannels < concurrencyLevel;
-  }
-
-  private void release(HttpChannel httpChannel) {
-    HttpUrl url;
-    synchronized (this) {
-      url = backlog.pop();
-      if (url == null) {
-        // There were no URLs in the backlog. Pool this channel for later.
-        freeChannels.push(httpChannel);
-        return;
-      }
-    }
-
-    // We removed a URL from the backlog. Schedule it right away.
-    httpChannel.sendRequest(url);
-  }
-
-  class HttpChannel extends SimpleChannelInboundHandler<HttpObject> {
-    private final SocketChannel channel;
-    byte[] buffer = new byte[1024];
-    int total;
-    long start;
-
-    HttpChannel(SocketChannel channel) {
-      this.channel = channel;
-    }
-
-    private void sendRequest(HttpUrl url) {
-      start = System.nanoTime();
-      total = 0;
-      HttpRequest request = new DefaultFullHttpRequest(
-          HttpVersion.HTTP_1_1, HttpMethod.GET, url.encodedPath());
-      request.headers().set(HttpHeaders.Names.HOST, url.host());
-      request.headers().set(HttpHeaders.Names.ACCEPT_ENCODING, HttpHeaders.Values.GZIP);
-      channel.writeAndFlush(request);
-    }
-
-    @Override protected void channelRead0(
-        ChannelHandlerContext context, HttpObject message) {
-      if (message instanceof HttpResponse) {
-        receive((HttpResponse) message);
-      }
-      if (message instanceof HttpContent) {
-        receive((HttpContent) message);
-        if (message instanceof LastHttpContent) {
-          release(this);
-        }
-      }
-    }
-
-    @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception {
-      super.channelInactive(ctx);
-    }
-
-    void receive(HttpResponse response) {
-      // Don't do anything with headers.
-    }
-
-    void receive(HttpContent content) {
-      // Consume the response body.
-      ByteBuf byteBuf = content.content();
-      for (int toRead; (toRead = byteBuf.readableBytes()) > 0; ) {
-        byteBuf.readBytes(buffer, 0, Math.min(buffer.length, toRead));
-        total += toRead;
-      }
-
-      if (VERBOSE && content instanceof LastHttpContent) {
-        long finish = System.nanoTime();
-        System.out.println(String.format("Transferred % 8d bytes in %4d ms",
-            total, TimeUnit.NANOSECONDS.toMillis(finish - start)));
-      }
-    }
-
-    @Override public void exceptionCaught(ChannelHandlerContext context, Throwable cause) {
-      System.out.println("Failed: " + cause);
-    }
-  }
-}
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttp.java b/benchmarks/src/main/java/okhttp3/benchmarks/OkHttp.java
deleted file mode 100644
index 86b44523a21b63b9eb7a2e916d25ceebe76d6208..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttp.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocketFactory;
-import okhttp3.Call;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.ResponseBody;
-import okhttp3.tls.HandshakeCertificates;
-
-import static okhttp3.tls.internal.TlsUtil.localhost;
-
-class OkHttp extends SynchronousHttpClient {
-  private static final boolean VERBOSE = false;
-
-  private OkHttpClient client;
-
-  @Override public void prepare(Benchmark benchmark) {
-    super.prepare(benchmark);
-    client = new OkHttpClient.Builder()
-        .protocols(benchmark.protocols)
-        .build();
-
-    if (benchmark.tls) {
-      HandshakeCertificates handshakeCertificates = localhost();
-      SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory();
-      HostnameVerifier hostnameVerifier = new HostnameVerifier() {
-        @Override public boolean verify(String s, SSLSession session) {
-          return true;
-        }
-      };
-      client = new OkHttpClient.Builder()
-          .sslSocketFactory(socketFactory, handshakeCertificates.trustManager())
-          .hostnameVerifier(hostnameVerifier)
-          .build();
-    }
-  }
-
-  @Override public Runnable request(HttpUrl url) {
-    Call call = client.newCall(new Request.Builder().url(url).build());
-    return new OkHttpRequest(call);
-  }
-
-  class OkHttpRequest implements Runnable {
-    private final Call call;
-
-    OkHttpRequest(Call call) {
-      this.call = call;
-    }
-
-    public void run() {
-      long start = System.nanoTime();
-      try {
-        ResponseBody body = call.execute().body();
-        long total = readAllAndClose(body.byteStream());
-        long finish = System.nanoTime();
-
-        if (VERBOSE) {
-          System.out.println(String.format("Transferred % 8d bytes in %4d ms",
-              total, TimeUnit.NANOSECONDS.toMillis(finish - start)));
-        }
-      } catch (IOException e) {
-        System.out.println("Failed: " + e);
-      }
-    }
-  }
-}
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttpAsync.java b/benchmarks/src/main/java/okhttp3/benchmarks/OkHttpAsync.java
deleted file mode 100644
index d6d0bc5cb4fc90be9081690815bec2a9fe999139..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/OkHttpAsync.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-import java.io.IOException;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocketFactory;
-import okhttp3.Call;
-import okhttp3.Callback;
-import okhttp3.Dispatcher;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.ResponseBody;
-import okhttp3.tls.HandshakeCertificates;
-
-import static okhttp3.tls.internal.TlsUtil.localhost;
-
-class OkHttpAsync implements HttpClient {
-  private static final boolean VERBOSE = false;
-
-  private final AtomicInteger requestsInFlight = new AtomicInteger();
-
-  private OkHttpClient client;
-  private Callback callback;
-  private int concurrencyLevel;
-  private int targetBacklog;
-
-  @Override public void prepare(final Benchmark benchmark) {
-    concurrencyLevel = benchmark.concurrencyLevel;
-    targetBacklog = benchmark.targetBacklog;
-
-    client = new OkHttpClient.Builder()
-        .protocols(benchmark.protocols)
-        .dispatcher(new Dispatcher(new ThreadPoolExecutor(benchmark.concurrencyLevel,
-            benchmark.concurrencyLevel, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())))
-        .build();
-
-    if (benchmark.tls) {
-      HandshakeCertificates handshakeCertificates = localhost();
-      SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory();
-      HostnameVerifier hostnameVerifier = new HostnameVerifier() {
-        @Override public boolean verify(String s, SSLSession session) {
-          return true;
-        }
-      };
-      client = client.newBuilder()
-          .sslSocketFactory(socketFactory, handshakeCertificates.trustManager())
-          .hostnameVerifier(hostnameVerifier)
-          .build();
-    }
-
-    callback = new Callback() {
-      @Override public void onFailure(Call call, IOException e) {
-        System.out.println("Failed: " + e);
-      }
-
-      @Override public void onResponse(Call call, Response response) throws IOException {
-        ResponseBody body = response.body();
-        long total = SynchronousHttpClient.readAllAndClose(body.byteStream());
-        long finish = System.nanoTime();
-        if (VERBOSE) {
-          long start = (Long) response.request().tag();
-          System.out.printf("Transferred % 8d bytes in %4d ms%n",
-              total, TimeUnit.NANOSECONDS.toMillis(finish - start));
-        }
-        requestsInFlight.decrementAndGet();
-      }
-    };
-  }
-
-  @Override public void enqueue(HttpUrl url) throws Exception {
-    requestsInFlight.incrementAndGet();
-    client.newCall(new Request.Builder().tag(System.nanoTime()).url(url).build()).enqueue(callback);
-  }
-
-  @Override public synchronized boolean acceptingJobs() {
-    return requestsInFlight.get() < (concurrencyLevel + targetBacklog);
-  }
-}
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/SynchronousHttpClient.java b/benchmarks/src/main/java/okhttp3/benchmarks/SynchronousHttpClient.java
deleted file mode 100644
index 4a17b8693de9989f969370ea8f8192d91904c60e..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/SynchronousHttpClient.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import okhttp3.HttpUrl;
-
-/** Any HTTP client with a blocking API. */
-abstract class SynchronousHttpClient implements HttpClient {
-  ThreadPoolExecutor executor;
-  int targetBacklog;
-
-  @Override public void prepare(Benchmark benchmark) {
-    this.targetBacklog = benchmark.targetBacklog;
-    executor = new ThreadPoolExecutor(benchmark.concurrencyLevel, benchmark.concurrencyLevel,
-        1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
-  }
-
-  @Override public void enqueue(HttpUrl url) {
-    executor.execute(request(url));
-  }
-
-  @Override public boolean acceptingJobs() {
-    return executor.getQueue().size() < targetBacklog;
-  }
-
-  static long readAllAndClose(InputStream in) throws IOException {
-    byte[] buffer = new byte[1024];
-    long total = 0;
-    for (int count; (count = in.read(buffer)) != -1; ) {
-      total += count;
-    }
-    in.close();
-    return total;
-  }
-
-  abstract Runnable request(HttpUrl url);
-}
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/UrlConnection.java b/benchmarks/src/main/java/okhttp3/benchmarks/UrlConnection.java
deleted file mode 100644
index 96eb7422cd75c080e8dc677caaee99c18bfe7593..0000000000000000000000000000000000000000
--- a/benchmarks/src/main/java/okhttp3/benchmarks/UrlConnection.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package okhttp3.benchmarks;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.GZIPInputStream;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocketFactory;
-import okhttp3.HttpUrl;
-import okhttp3.tls.HandshakeCertificates;
-
-import static okhttp3.tls.internal.TlsUtil.localhost;
-
-class UrlConnection extends SynchronousHttpClient {
-  private static final boolean VERBOSE = false;
-
-  @Override public void prepare(Benchmark benchmark) {
-    super.prepare(benchmark);
-    if (benchmark.tls) {
-      HandshakeCertificates handshakeCertificates = localhost();
-      SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory();
-      HostnameVerifier hostnameVerifier = new HostnameVerifier() {
-        @Override public boolean verify(String s, SSLSession session) {
-          return true;
-        }
-      };
-      HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
-      HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
-    }
-  }
-
-  @Override public Runnable request(HttpUrl url) {
-    return new UrlConnectionRequest(url);
-  }
-
-  static class UrlConnectionRequest implements Runnable {
-    private final HttpUrl url;
-
-    UrlConnectionRequest(HttpUrl url) {
-      this.url = url;
-    }
-
-    public void run() {
-      long start = System.nanoTime();
-      try {
-        HttpURLConnection urlConnection = (HttpURLConnection) url.url().openConnection();
-        InputStream in = urlConnection.getInputStream();
-        if ("gzip".equals(urlConnection.getHeaderField("Content-Encoding"))) {
-          in = new GZIPInputStream(in);
-        }
-
-        long total = readAllAndClose(in);
-        long finish = System.nanoTime();
-
-        if (VERBOSE) {
-          System.out.println(String.format("Transferred % 8d bytes in %4d ms",
-              total, TimeUnit.NANOSECONDS.toMillis(finish - start)));
-        }
-      } catch (IOException e) {
-        System.out.println("Failed: " + e);
-      }
-    }
-  }
-}
diff --git a/bom/pom.xml b/bom/pom.xml
deleted file mode 100644
index dac7c59a2472165bb5623d23d8f2680bea816b53..0000000000000000000000000000000000000000
--- a/bom/pom.xml
+++ /dev/null
@@ -1,81 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.squareup.okhttp3</groupId>
-    <artifactId>parent</artifactId>
-    <version>3.12.1</version>
-  </parent>
-
-  <artifactId>okhttp-bom</artifactId>
-  <packaging>pom</packaging>
-  <name>OkHttp (Bill of Materials)</name>
-
-  <dependencyManagement>
-    <dependencies>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-tests</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-android-support</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-apache</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-sse</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-testing-support</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-tls</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-urlconnection</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-logging-interceptor</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okhttp-dnsoverhttps</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>okcurl</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <groupId>${project.groupId}</groupId>
-        <artifactId>mockwebserver</artifactId>
-        <version>${project.version}</version>
-      </dependency>
-    </dependencies>
-  </dependencyManagement>
-
-</project>
diff --git a/mockwebserver/pom.xml b/mockwebserver/pom.xml
index 11f66cf42aa35bd4f27767c8a47a90d8fda806a3..da4c7bd07f45328cf16bad15d938f42b5ce0212d 100644
--- a/mockwebserver/pom.xml
+++ b/mockwebserver/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>mockwebserver</artifactId>
@@ -41,7 +41,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.4</version>
+        <version>3.0.1</version>
         <configuration>
           <links>
             <link>http://square.github.io/okhttp/javadoc/</link>
@@ -52,7 +52,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-assembly-plugin</artifactId>
-        <version>3.0.0</version>
+        <version>3.1.0</version>
         <configuration>
           <descriptorRefs>
             <descriptorRef>jar-with-dependencies</descriptorRef>
@@ -70,7 +70,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/mockwebserver/src/main/java/okhttp3/internal/duplex/MwsDuplexAccess.java b/mockwebserver/src/main/java/okhttp3/internal/duplex/MwsDuplexAccess.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1eea003978b104b58ba799b1a282f845080dca7
--- /dev/null
+++ b/mockwebserver/src/main/java/okhttp3/internal/duplex/MwsDuplexAccess.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okhttp3.internal.duplex;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.internal.duplex.DuplexResponseBody;
+
+/**
+ * Internal access to MockWebServer APIs. Don't use this, don't use internal, these APIs are not
+ * stable.
+ */
+public abstract class MwsDuplexAccess {
+  public static MwsDuplexAccess instance;
+
+  public abstract void setBody(MockResponse mockResponse, DuplexResponseBody duplexResponseBody);
+}
diff --git a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java
index 36457229ed6fef8d2592af669797f9b3666bd90e..2c9ad33b49fa3972e23442f86fae4ce406e239ce 100644
--- a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java
+++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockResponse.java
@@ -22,6 +22,7 @@ import okhttp3.Headers;
 import okhttp3.WebSocketListener;
 import okhttp3.internal.Internal;
 import okhttp3.internal.http2.Settings;
+import okhttp3.mockwebserver.internal.duplex.DuplexResponseBody;
 import okio.Buffer;
 
 /** A scripted response to be replayed by the mock web server. */
@@ -30,6 +31,7 @@ public final class MockResponse implements Cloneable {
 
   private String status;
   private Headers.Builder headers = new Headers.Builder();
+  private Headers.Builder trailers = new Headers.Builder();
 
   private Buffer body;
 
@@ -49,6 +51,7 @@ public final class MockResponse implements Cloneable {
   private List<PushPromise> promises = new ArrayList<>();
   private Settings settings;
   private WebSocketListener webSocketListener;
+  private DuplexResponseBody duplexResponseBody;
 
   /** Creates a new mock response with an empty body. */
   public MockResponse() {
@@ -98,6 +101,10 @@ public final class MockResponse implements Cloneable {
     return headers.build();
   }
 
+  public Headers getTrailers() {
+    return trailers.build();
+  }
+
   /**
    * Removes all HTTP headers including any "Content-Length" and "Transfer-encoding" headers that
    * were added by default.
@@ -143,18 +150,32 @@ public final class MockResponse implements Cloneable {
     return addHeader(name, value);
   }
 
-  /** Replaces all headers with those specified in {@code headers}. */
+  /** Replaces all headers with those specified. */
   public MockResponse setHeaders(Headers headers) {
     this.headers = headers.newBuilder();
     return this;
   }
 
+  /** Replaces all trailers with those specified. */
+  public MockResponse setTrailers(Headers trailers) {
+    this.trailers = trailers.newBuilder();
+    return this;
+  }
+
   /** Removes all headers named {@code name}. */
   public MockResponse removeHeader(String name) {
     headers.removeAll(name);
     return this;
   }
 
+  boolean isDuplex() {
+    return duplexResponseBody != null;
+  }
+
+  DuplexResponseBody getDuplexResponseBody() {
+    return duplexResponseBody;
+  }
+
   /** Returns a copy of the raw HTTP payload. */
   public Buffer getBody() {
     return body != null ? body.clone() : null;
@@ -171,6 +192,11 @@ public final class MockResponse implements Cloneable {
     return setBody(new Buffer().writeUtf8(body));
   }
 
+  MockResponse setBody(DuplexResponseBody duplexResponseBody) {
+    this.duplexResponseBody = duplexResponseBody;
+    return this;
+  }
+
   /**
    * Sets the response body to {@code body}, chunked every {@code maxChunkSize} bytes.
    */
@@ -186,7 +212,7 @@ public final class MockResponse implements Cloneable {
       bytesOut.write(body, chunkSize);
       bytesOut.writeUtf8("\r\n");
     }
-    bytesOut.writeUtf8("0\r\n\r\n"); // Last chunk + empty trailer + CRLF.
+    bytesOut.writeUtf8("0\r\n"); // Last chunk. Trailers follow!
 
     this.body = bytesOut;
     return this;
diff --git a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java
index d2a532dd6f7f578510bf874c47783fbd8a7c6d0b..abdbda7148428f027f76c3639453b66a39ca7c70 100644
--- a/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java
+++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/MockWebServer.java
@@ -59,6 +59,7 @@ import okhttp3.Response;
 import okhttp3.internal.Internal;
 import okhttp3.internal.NamedRunnable;
 import okhttp3.internal.Util;
+import okhttp3.internal.duplex.MwsDuplexAccess;
 import okhttp3.internal.http.HttpMethod;
 import okhttp3.internal.http2.ErrorCode;
 import okhttp3.internal.http2.Header;
@@ -68,6 +69,7 @@ import okhttp3.internal.http2.Settings;
 import okhttp3.internal.platform.Platform;
 import okhttp3.internal.ws.RealWebSocket;
 import okhttp3.internal.ws.WebSocketProtocol;
+import okhttp3.mockwebserver.internal.duplex.DuplexResponseBody;
 import okio.Buffer;
 import okio.BufferedSink;
 import okio.BufferedSource;
@@ -101,6 +103,12 @@ import static okhttp3.mockwebserver.SocketPolicy.UPGRADE_TO_SSL_AT_END;
 public final class MockWebServer extends ExternalResource implements Closeable {
   static {
     Internal.initializeInstanceForTests();
+    MwsDuplexAccess.instance = new MwsDuplexAccess() {
+      @Override public void setBody(
+          MockResponse mockResponse, DuplexResponseBody duplexResponseBody) {
+        mockResponse.setBody(duplexResponseBody);
+      }
+    };
   }
 
   private static final int CLIENT_AUTH_NONE = 0;
@@ -127,9 +135,9 @@ public final class MockWebServer extends ExternalResource implements Closeable {
   private final BlockingQueue<RecordedRequest> requestQueue = new LinkedBlockingQueue<>();
 
   private final Set<Socket> openClientSockets =
-      Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
+      Collections.newSetFromMap(new ConcurrentHashMap<>());
   private final Set<Http2Connection> openConnections =
-      Collections.newSetFromMap(new ConcurrentHashMap<Http2Connection, Boolean>());
+      Collections.newSetFromMap(new ConcurrentHashMap<>());
   private final AtomicInteger requestCount = new AtomicInteger();
   private long bodyLimit = Long.MAX_VALUE;
   private ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
@@ -751,7 +759,19 @@ public final class MockWebServer extends ExternalResource implements Closeable {
     sink.writeUtf8(response.getStatus());
     sink.writeUtf8("\r\n");
 
-    Headers headers = response.getHeaders();
+    writeHeaders(sink, response.getHeaders());
+
+    Buffer body = response.getBody();
+    if (body == null) return;
+    sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS));
+    throttledTransfer(response, socket, body, sink, body.size(), false);
+
+    if ("chunked".equalsIgnoreCase(response.getHeaders().get("Transfer-Encoding"))) {
+      writeHeaders(sink, response.getTrailers());
+    }
+  }
+
+  private void writeHeaders(BufferedSink sink, Headers headers) throws IOException {
     for (int i = 0, size = headers.size(); i < size; i++) {
       sink.writeUtf8(headers.name(i));
       sink.writeUtf8(": ");
@@ -760,11 +780,6 @@ public final class MockWebServer extends ExternalResource implements Closeable {
     }
     sink.writeUtf8("\r\n");
     sink.flush();
-
-    Buffer body = response.getBody();
-    if (body == null) return;
-    sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS));
-    throttledTransfer(response, socket, body, sink, body.size(), false);
   }
 
   private void sleepIfDelayed(long delayMs) {
@@ -835,6 +850,14 @@ public final class MockWebServer extends ExternalResource implements Closeable {
     if (line.length() != 0) throw new IllegalStateException("Expected empty but was: " + line);
   }
 
+  /**
+   * Returns the dispatcher used to respond to HTTP requests. The default dispatcher is a {@link
+   * QueueDispatcher} but other dispatchers can be configured.
+   */
+  public Dispatcher getDispatcher() {
+    return dispatcher;
+  }
+
   /**
    * Sets the dispatcher used to match incoming requests to mock responses. The default dispatcher
    * simply serves a fixed sequence of responses from a {@link #enqueue(MockResponse) queue}; custom
@@ -924,7 +947,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
         socket.close();
         return;
       }
-      writeResponse(stream, response);
+      writeResponse(stream, request, response);
       if (logger.isLoggable(Level.INFO)) {
         logger.info(MockWebServer.this + " received request: " + request
             + " and responded: " + response + " protocol is " + protocol.toString());
@@ -962,9 +985,12 @@ public final class MockWebServer extends ExternalResource implements Closeable {
       Headers headers = httpHeaders.build();
 
       MockResponse peek = dispatcher.peek();
-      if (!readBody && peek.getSocketPolicy() == EXPECT_CONTINUE) {
-        stream.writeHeaders(Collections.singletonList(
-            new Header(Header.RESPONSE_STATUS, ByteString.encodeUtf8("100 Continue"))), true);
+      if (peek.isDuplex()) {
+        readBody = false;
+      } else if (!readBody && peek.getSocketPolicy() == EXPECT_CONTINUE) {
+        List<Header> continueHeaders = Collections.singletonList(
+            new Header(Header.RESPONSE_STATUS, ByteString.encodeUtf8("100 Continue")));
+        stream.writeHeaders(continueHeaders, false, true);
         stream.getConnection().flush();
         readBody = true;
       }
@@ -984,7 +1010,8 @@ public final class MockWebServer extends ExternalResource implements Closeable {
           sequenceNumber.getAndIncrement(), socket);
     }
 
-    private void writeResponse(Http2Stream stream, MockResponse response) throws IOException {
+    private void writeResponse(final Http2Stream stream,
+        final RecordedRequest request, final MockResponse response) throws IOException {
       Settings settings = response.getSettings();
       if (settings != null) {
         stream.getConnection().setSettings(settings);
@@ -1004,24 +1031,41 @@ public final class MockWebServer extends ExternalResource implements Closeable {
       for (int i = 0, size = headers.size(); i < size; i++) {
         http2Headers.add(new Header(headers.name(i), headers.value(i)));
       }
+      Headers trailers = response.getTrailers();
 
       sleepIfDelayed(response.getHeadersDelay(TimeUnit.MILLISECONDS));
 
       Buffer body = response.getBody();
-      boolean closeStreamAfterHeaders = body != null || !response.getPushPromises().isEmpty();
-      stream.writeHeaders(http2Headers, closeStreamAfterHeaders);
-      pushPromises(stream, response.getPushPromises());
+      boolean outFinished = body == null
+          && response.getPushPromises().isEmpty()
+          && !response.isDuplex();
+      boolean flushHeaders = body == null;
+      if (outFinished && trailers.size() > 0) {
+        throw new IllegalStateException("unsupported: no body and non-empty trailers " + trailers);
+      }
+      stream.writeHeaders(http2Headers, outFinished, flushHeaders);
+      if (trailers.size() > 0) {
+        stream.enqueueTrailers(trailers);
+      }
+      pushPromises(stream, request, response.getPushPromises());
       if (body != null) {
-        BufferedSink sink = Okio.buffer(stream.getSink());
-        sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS));
-        throttledTransfer(response, socket, body, sink, body.size(), false);
-        sink.close();
-      } else if (closeStreamAfterHeaders) {
+        try (BufferedSink sink = Okio.buffer(stream.getSink())) {
+          sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS));
+          throttledTransfer(response, socket, body, sink, body.size(), false);
+        }
+      } else if (response.isDuplex()) {
+        try (BufferedSink sink = Okio.buffer(stream.getSink());
+             BufferedSource source = Okio.buffer(stream.getSource())) {
+          DuplexResponseBody duplexResponseBody = response.getDuplexResponseBody();
+          duplexResponseBody.onRequest(request, source, sink);
+        }
+      } else if (!outFinished) {
         stream.close(ErrorCode.NO_ERROR);
       }
     }
 
-    private void pushPromises(Http2Stream stream, List<PushPromise> promises) throws IOException {
+    private void pushPromises(Http2Stream stream, RecordedRequest request,
+        List<PushPromise> promises) throws IOException {
       for (PushPromise pushPromise : promises) {
         List<Header> pushedHeaders = new ArrayList<>();
         pushedHeaders.add(new Header(Header.TARGET_AUTHORITY, url(pushPromise.path()).host()));
@@ -1038,7 +1082,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
         boolean hasBody = pushPromise.response().getBody() != null;
         Http2Stream pushedStream =
             stream.getConnection().pushStream(stream.getId(), pushedHeaders, hasBody);
-        writeResponse(pushedStream, pushPromise.response());
+        writeResponse(pushedStream, request, pushPromise.response());
       }
     }
   }
diff --git a/benchmarks/src/main/java/okhttp3/benchmarks/HttpClient.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/DuplexResponseBody.java
similarity index 60%
rename from benchmarks/src/main/java/okhttp3/benchmarks/HttpClient.java
rename to mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/DuplexResponseBody.java
index 0c0986ffeb2e454931ecf54534561c04b946cabe..79a8245e8fd4163c5d0cdcee22f22bbbf437d8f6 100644
--- a/benchmarks/src/main/java/okhttp3/benchmarks/HttpClient.java
+++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/DuplexResponseBody.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 Square, Inc.
+ * Copyright (C) 2018 Square, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,15 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package okhttp3.benchmarks;
+package okhttp3.mockwebserver.internal.duplex;
 
-import okhttp3.HttpUrl;
+import java.io.IOException;
+import okhttp3.mockwebserver.RecordedRequest;
+import okio.BufferedSink;
+import okio.BufferedSource;
 
-/** An HTTP client to benchmark. */
-interface HttpClient {
-  void prepare(Benchmark benchmark);
-
-  void enqueue(HttpUrl url) throws Exception;
-
-  boolean acceptingJobs();
+public interface DuplexResponseBody {
+  void onRequest(RecordedRequest request, BufferedSource requestBody, BufferedSink responseBody)
+      throws IOException;
 }
diff --git a/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/MockDuplexResponseBody.java b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/MockDuplexResponseBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..7be8b934b20e231570b567af3ceaf187f2e70e5d
--- /dev/null
+++ b/mockwebserver/src/main/java/okhttp3/mockwebserver/internal/duplex/MockDuplexResponseBody.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okhttp3.mockwebserver.internal.duplex;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import okhttp3.mockwebserver.RecordedRequest;
+import okio.BufferedSink;
+import okio.BufferedSource;
+import okio.Utf8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * A scriptable request/response conversation. Create the script by calling methods like {@link
+ * #receiveRequest} in the sequence they are run.
+ */
+public final class MockDuplexResponseBody implements DuplexResponseBody {
+  private final BlockingQueue<Action> actions = new LinkedBlockingQueue<>();
+  private final BlockingQueue<FutureTask<Void>> results = new LinkedBlockingQueue<>();
+
+  public MockDuplexResponseBody receiveRequest(String expected) {
+    actions.add((request, requestBody, responseBody) -> {
+      assertEquals(expected, requestBody.readUtf8(Utf8.size(expected)));
+    });
+    return this;
+  }
+
+  public MockDuplexResponseBody exhaustRequest() {
+    actions.add((request, requestBody, responseBody) -> {
+      assertTrue(requestBody.exhausted());
+    });
+    return this;
+  }
+
+  public MockDuplexResponseBody sendResponse(String s) {
+    actions.add((request, requestBody, responseBody) -> {
+      responseBody.writeUtf8(s);
+      responseBody.flush();
+    });
+    return this;
+  }
+
+  public MockDuplexResponseBody exhaustResponse() {
+    actions.add((request, requestBody, responseBody) -> {
+      responseBody.close();
+    });
+    return this;
+  }
+
+  @Override public void onRequest(RecordedRequest request, BufferedSource requestBody,
+      BufferedSink responseBody) {
+    FutureTask<Void> futureTask = new FutureTask<>(() -> {
+      for (Action action; (action = actions.poll()) != null; ) {
+        action.execute(request, requestBody, responseBody);
+      }
+      return null; // Success!
+    });
+    results.add(futureTask);
+    futureTask.run();
+  }
+
+  /** Returns once the duplex conversation completes successfully. */
+  public void awaitSuccess() throws Exception {
+    FutureTask<Void> futureTask = results.poll(5, TimeUnit.SECONDS);
+    if (futureTask == null) throw new AssertionError("no onRequest call received");
+    futureTask.get(5, TimeUnit.SECONDS);
+  }
+
+  private interface Action {
+    void execute(RecordedRequest request, BufferedSource requestBody, BufferedSink responseBody)
+        throws IOException;
+  }
+}
+
diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java
index ec8317e95e9b9e729727fafe47014bc694af441d..0a43dd9165fb002df8809fa453d49a73275f2dc4 100644
--- a/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java
+++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/CustomDispatcherTest.java
@@ -83,16 +83,14 @@ public class CustomDispatcherTest {
     assertEquals(200, secondResponseCode.get()); // (Still done).
   }
 
-  private Thread buildRequestThread(final String path, final AtomicInteger responseCode) {
-    return new Thread(new Runnable() {
-      @Override public void run() {
-        final URL url = mockWebServer.url(path).url();
-        final HttpURLConnection conn;
-        try {
-          conn = (HttpURLConnection) url.openConnection();
-          responseCode.set(conn.getResponseCode()); // Force the connection to hit the "server".
-        } catch (IOException e) {
-        }
+  private Thread buildRequestThread(String path, AtomicInteger responseCode) {
+    return new Thread(() -> {
+      URL url = mockWebServer.url(path).url();
+      HttpURLConnection conn;
+      try {
+        conn = (HttpURLConnection) url.openConnection();
+        responseCode.set(conn.getResponseCode()); // Force the connection to hit the "server".
+      } catch (IOException ignored) {
       }
     });
   }
diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java
index eb41f4fbcaed2993cf054b2d0e4c4e164cd2887e..67f417e73f01bff29c13e8f470ea0f7916ee012a 100644
--- a/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java
+++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/MockWebServerTest.java
@@ -27,7 +27,6 @@ import java.net.ProtocolException;
 import java.net.SocketTimeoutException;
 import java.net.URL;
 import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -40,14 +39,15 @@ import okhttp3.HttpUrl;
 import okhttp3.Protocol;
 import okhttp3.RecordingHostnameVerifier;
 import okhttp3.internal.Util;
-import okhttp3.tls.HeldCertificate;
 import okhttp3.tls.HandshakeCertificates;
+import okhttp3.tls.HeldCertificate;
 import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static okhttp3.tls.internal.TlsUtil.localhost;
@@ -165,15 +165,13 @@ public final class MockWebServerTest {
    * response is ready.
    */
   @Test public void dispatchBlocksWaitingForEnqueue() throws Exception {
-    new Thread() {
-      @Override public void run() {
-        try {
-          Thread.sleep(1000);
-        } catch (InterruptedException ignored) {
-        }
-        server.enqueue(new MockResponse().setBody("enqueued in the background"));
+    new Thread(() -> {
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException ignored) {
       }
-    }.start();
+      server.enqueue(new MockResponse().setBody("enqueued in the background"));
+    }).start();
 
     URLConnection connection = server.url("/").url().openConnection();
     InputStream in = connection.getInputStream();
@@ -248,7 +246,7 @@ public final class MockWebServerTest {
     long startNanos = System.nanoTime();
     URLConnection connection = server.url("/").url().openConnection();
     connection.setDoOutput(true);
-    connection.getOutputStream().write("ABCDEF".getBytes("UTF-8"));
+    connection.getOutputStream().write("ABCDEF".getBytes(UTF_8));
     InputStream in = connection.getInputStream();
     assertEquals(-1, in.read());
     long elapsedNanos = System.nanoTime() - startNanos;
@@ -477,7 +475,7 @@ public final class MockWebServerTest {
     HttpURLConnection connection = (HttpURLConnection) url.openConnection();
     connection.setDoOutput(true);
     connection.setRequestProperty("Expect", "100-Continue");
-    connection.getOutputStream().write("request".getBytes(StandardCharsets.UTF_8));
+    connection.getOutputStream().write("request".getBytes(UTF_8));
 
     InputStream in = connection.getInputStream();
     BufferedReader reader = new BufferedReader(new InputStreamReader(in));
diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java
index e74e3c527545bca4930eb4f86b837508718ff2ef..acd37905f9cc0e39eb4c40bb5b1de869b592bc07 100644
--- a/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java
+++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/RecordedRequestTest.java
@@ -18,20 +18,18 @@ package okhttp3.mockwebserver;
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.UnknownHostException;
 import java.util.Collections;
-import java.util.Random;
 import okhttp3.Headers;
-import okhttp3.HttpUrl;
+import okhttp3.internal.Util;
 import okio.Buffer;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 
 public class RecordedRequestTest {
-  Headers headers = new Headers.Builder().build();
+  Headers headers = Util.EMPTY_HEADERS;
 
   private class FakeSocket extends Socket {
     private final InetAddress localAddress;
@@ -75,20 +73,18 @@ public class RecordedRequestTest {
     Socket socket =
         new FakeSocket(InetAddress.getByAddress("127.0.0.1", new byte[] { 127, 0, 0, 1 }), 80);
 
-    RecordedRequest request =
-        new RecordedRequest("GET / HTTP/1.1", headers, Collections.<Integer>emptyList(), 0,
-            new Buffer(), 0, socket);
+    RecordedRequest request = new RecordedRequest("GET / HTTP/1.1", headers,
+        Collections.emptyList(), 0, new Buffer(), 0, socket);
 
     assertEquals("http://127.0.0.1/", request.getRequestUrl().toString());
   }
 
-  @Test public void testIPv6() throws UnknownHostException {
+  @Test public void testIpv6() throws UnknownHostException {
     Socket socket = new FakeSocket(InetAddress.getByAddress("::1",
         new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }), 80);
 
-    RecordedRequest request =
-        new RecordedRequest("GET / HTTP/1.1", headers, Collections.<Integer>emptyList(), 0,
-            new Buffer(), 0, socket);
+    RecordedRequest request = new RecordedRequest("GET / HTTP/1.1", headers,
+        Collections.emptyList(), 0, new Buffer(), 0, socket);
 
     assertEquals("http://[::1]/", request.getRequestUrl().toString());
   }
@@ -97,9 +93,8 @@ public class RecordedRequestTest {
     Socket socket =
         new FakeSocket(InetAddress.getByAddress("127.0.0.1", new byte[] { 127, 0, 0, 1 }), 80);
 
-    RecordedRequest request =
-        new RecordedRequest("GET / HTTP/1.1", headers, Collections.<Integer>emptyList(), 0,
-            new Buffer(), 0, socket);
+    RecordedRequest request = new RecordedRequest("GET / HTTP/1.1", headers,
+        Collections.emptyList(), 0, new Buffer(), 0, socket);
 
     assertEquals("http://127.0.0.1/", request.getRequestUrl().toString());
   }
diff --git a/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java b/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java
index 7d3b09a2cbc9697a6c6db9642d6b7b260b7dd3f5..b7ca4b248872546d79e75493964e3ce5ea3119d5 100644
--- a/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java
+++ b/mockwebserver/src/test/java/okhttp3/mockwebserver/internal/http2/Http2Server.java
@@ -130,7 +130,7 @@ public final class Http2Server extends Http2Connection.Listener {
         new Header(":version", "HTTP/1.1"),
         new Header("content-type", "text/plain")
     );
-    stream.writeHeaders(responseHeaders, true);
+    stream.writeHeaders(responseHeaders, false, false);
     BufferedSink out = Okio.buffer(stream.getSink());
     out.writeUtf8("Not found: " + path);
     out.close();
@@ -142,7 +142,7 @@ public final class Http2Server extends Http2Connection.Listener {
         new Header(":version", "HTTP/1.1"),
         new Header("content-type", "text/html; charset=UTF-8")
     );
-    stream.writeHeaders(responseHeaders, true);
+    stream.writeHeaders(responseHeaders, false, false);
     BufferedSink out = Okio.buffer(stream.getSink());
     for (File file : files) {
       String target = file.isDirectory() ? (file.getName() + "/") : file.getName();
@@ -157,14 +157,9 @@ public final class Http2Server extends Http2Connection.Listener {
         new Header(":version", "HTTP/1.1"),
         new Header("content-type", contentType(file))
     );
-    stream.writeHeaders(responseHeaders, true);
-    Source source = Okio.source(file);
-    try {
-      BufferedSink out = Okio.buffer(stream.getSink());
-      out.writeAll(source);
-      out.close();
-    } finally {
-      Util.closeQuietly(source);
+    stream.writeHeaders(responseHeaders, false, false);
+    try (Source source = Okio.source(file); BufferedSink sink = Okio.buffer(stream.getSink())) {
+      sink.writeAll(source);
     }
   }
 
diff --git a/okcurl/pom.xml b/okcurl/pom.xml
index 1048a3b25ccf4595e0bdf792e4df784789ca8b7b..54b92c6aa99002453ec5d321e1da6356f230090a 100644
--- a/okcurl/pom.xml
+++ b/okcurl/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okcurl</artifactId>
@@ -57,7 +57,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-assembly-plugin</artifactId>
-        <version>3.0.0</version>
+        <version>3.1.0</version>
         <configuration>
           <descriptorRefs>
             <descriptorRef>jar-with-dependencies</descriptorRef>
@@ -80,7 +80,7 @@
       <plugin>
         <groupId>org.skife.maven</groupId>
         <artifactId>really-executable-jar-maven-plugin</artifactId>
-        <version>1.1.0</version>
+        <version>1.5.0</version>
         <executions>
           <execution>
             <phase>package</phase>
@@ -96,7 +96,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okcurl/src/main/java/okhttp3/curl/Main.java b/okcurl/src/main/java/okhttp3/curl/Main.java
index 4b1fd210871e1a21cbcff32f46b04a39eb9aa771..22c8b5c45ca9314df14d8261edd91ddf155075e0 100644
--- a/okcurl/src/main/java/okhttp3/curl/Main.java
+++ b/okcurl/src/main/java/okhttp3/curl/Main.java
@@ -33,7 +33,6 @@ import java.util.logging.Logger;
 import java.util.logging.SimpleFormatter;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;
@@ -104,6 +103,11 @@ public class Main extends HelpOption implements Runnable {
   @Option(name = "--read-timeout", description = "Maximum time allowed for reading data (seconds)")
   public int readTimeout = DEFAULT_TIMEOUT;
 
+  @Option(
+      name = "--call-timeout",
+      description = "Maximum time allowed for the entire call (seconds)")
+  public int callTimeout = DEFAULT_TIMEOUT;
+
   @Option(name = {"-L", "--location"}, description = "Follow redirects")
   public boolean followRedirects;
 
@@ -164,7 +168,7 @@ public class Main extends HelpOption implements Runnable {
       Sink out = Okio.sink(System.out);
       BufferedSource source = response.body().source();
       while (!source.exhausted()) {
-        out.write(source.buffer(), source.buffer().size());
+        out.write(source.getBuffer(), source.getBuffer().size());
         out.flush();
       }
 
@@ -185,6 +189,9 @@ public class Main extends HelpOption implements Runnable {
     if (readTimeout != DEFAULT_TIMEOUT) {
       builder.readTimeout(readTimeout, SECONDS);
     }
+    if (callTimeout != DEFAULT_TIMEOUT) {
+      builder.callTimeout(callTimeout, SECONDS);
+    }
     if (allowInsecure) {
       X509TrustManager trustManager = createInsecureTrustManager();
       SSLSocketFactory sslSocketFactory = createInsecureSslSocketFactory(trustManager);
@@ -192,13 +199,7 @@ public class Main extends HelpOption implements Runnable {
       builder.hostnameVerifier(createInsecureHostnameVerifier());
     }
     if (verbose) {
-      HttpLoggingInterceptor.Logger logger =
-          new HttpLoggingInterceptor.Logger() {
-            @Override
-            public void log(String message) {
-              System.out.println(message);
-            }
-          };
+      HttpLoggingInterceptor.Logger logger = System.out::println;
       builder.eventListenerFactory(new LoggingEventListener.Factory(logger));
     }
     return builder.build();
@@ -284,11 +285,7 @@ public class Main extends HelpOption implements Runnable {
   }
 
   private static HostnameVerifier createInsecureHostnameVerifier() {
-    return new HostnameVerifier() {
-      @Override public boolean verify(String s, SSLSession sslSession) {
-        return true;
-      }
-    };
+    return (name, session) -> true;
   }
 
   private static void enableHttp2FrameLogging() {
diff --git a/okhttp-android-support/pom.xml b/okhttp-android-support/pom.xml
index 28128731b7b8d810644f8e7dd3ac0decf4ed6d6e..47dce8502d9946109ab17e2dd4ce23be9326a558 100644
--- a/okhttp-android-support/pom.xml
+++ b/okhttp-android-support/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-android-support</artifactId>
@@ -57,7 +57,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.4</version>
+        <version>3.0.1</version>
         <configuration>
           <excludePackageNames>okhttp3.internal.*</excludePackageNames>
           <links>
@@ -68,7 +68,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java b/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java
index c46a0cb9fd771b29f613f4473beb952f2b72fdc9..a0590b2c4b578c2444b86eeacf0c101f79a34015 100644
--- a/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java
+++ b/okhttp-android-support/src/main/java/okhttp3/AndroidShimResponseCache.java
@@ -24,6 +24,7 @@ import java.net.URI;
 import java.net.URLConnection;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
 import okhttp3.internal.huc.JavaApiConverter;
 
 /**
@@ -64,7 +65,8 @@ public class AndroidShimResponseCache extends ResponseCache {
     return JavaApiConverter.createJavaCacheResponse(okResponse);
   }
 
-  @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
+  @Override public @Nullable CacheRequest put(
+      URI uri, URLConnection urlConnection) throws IOException {
     Response okResponse = JavaApiConverter.createOkResponseForCachePut(uri, urlConnection);
     if (okResponse == null) {
       // The URLConnection is not cacheable or could not be converted. Stop.
diff --git a/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java b/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java
index 1dec85cdc0d562669a4cb9bfd57015ddd3fc5334..fa0236a3591975b4b9ae197cee8bae86ad271bde 100644
--- a/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java
+++ b/okhttp-android-support/src/main/java/okhttp3/internal/huc/JavaApiConverter.java
@@ -167,7 +167,7 @@ public final class JavaApiConverter {
     }
     Set<String> varyFields = HttpHeaders.varyFields(responseHeaders);
     if (varyFields.isEmpty()) {
-      return new Headers.Builder().build();
+      return Util.EMPTY_HEADERS;
     }
 
     // This probably indicates another HTTP stack is trying to use the shared ResponseCache.
@@ -213,7 +213,7 @@ public final class JavaApiConverter {
     Headers varyHeaders;
     if (HttpHeaders.hasVaryAll(responseHeaders)) {
       // "*" means that this will be treated as uncacheable anyway.
-      varyHeaders = new Headers.Builder().build();
+      varyHeaders = Util.EMPTY_HEADERS;
     } else {
       varyHeaders = HttpHeaders.varyHeaders(request.headers(), responseHeaders);
     }
@@ -878,7 +878,7 @@ public final class JavaApiConverter {
   }
 
   private static <T> List<T> nullSafeImmutableList(T[] elements) {
-    return elements == null ? Collections.<T>emptyList() : Util.immutableList(elements);
+    return elements == null ? Collections.emptyList() : Util.immutableList(elements);
   }
 
   private static long stringToLong(String s) {
diff --git a/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java b/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java
index d755487c92af0a145ca12b815829129f92172217..e5ce90dd34f8fcb8a35b5467b53530046950465a 100644
--- a/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java
+++ b/okhttp-android-support/src/test/java/okhttp3/AbstractResponseCache.java
@@ -25,14 +25,16 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
 
 public class AbstractResponseCache extends ResponseCache {
-  @Override public CacheResponse get(URI uri, String requestMethod,
+  @Override public @Nullable CacheResponse get(URI uri, String requestMethod,
       Map<String, List<String>> requestHeaders) throws IOException {
     return null;
   }
 
-  @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException {
+  @Override public @Nullable CacheRequest put(
+      URI uri, URLConnection connection) throws IOException {
     return null;
   }
 
diff --git a/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java b/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java
index 09a438807f77cca41c0ac198d2d398007f2d5e06..c1b4a6f4d2813fb67520e6a54db574bd8e5ed963 100644
--- a/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java
+++ b/okhttp-android-support/src/test/java/okhttp3/internal/huc/CacheAdapterTest.java
@@ -23,7 +23,6 @@ import java.net.ResponseCache;
 import java.net.URI;
 import java.net.URL;
 import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -43,6 +42,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static okhttp3.tls.internal.TlsUtil.localhost;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -137,7 +137,7 @@ public class CacheAdapterTest {
 
   @Test public void put_httpGet() throws Exception {
     final String statusLine = "HTTP/1.1 200 Fantastic";
-    final byte[] response = "ResponseString".getBytes(StandardCharsets.UTF_8);
+    final byte[] response = "ResponseString".getBytes(UTF_8);
     final URL serverUrl = configureServer(
         new MockResponse()
             .setStatus(statusLine)
diff --git a/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java b/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java
index ad5d3fdfd351e0bdf7d3eab498dfb37c8484695f..8acbe31519add6f1a451b5f2e3ce96ae9a1a470a 100644
--- a/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java
+++ b/okhttp-android-support/src/test/java/okhttp3/internal/huc/JavaApiConverterTest.java
@@ -23,7 +23,6 @@ import java.net.CacheResponse;
 import java.net.HttpURLConnection;
 import java.net.SecureCacheResponse;
 import java.net.URI;
-import java.nio.charset.StandardCharsets;
 import java.security.Principal;
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
@@ -49,7 +48,6 @@ import okhttp3.Response;
 import okhttp3.ResponseBody;
 import okhttp3.TlsVersion;
 import okhttp3.internal.Internal;
-import okhttp3.internal.Util;
 import okhttp3.mockwebserver.MockWebServer;
 import okio.Buffer;
 import okio.BufferedSource;
@@ -57,6 +55,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -114,7 +113,7 @@ public class JavaApiConverterTest {
       }
 
       @Override public InputStream getBody() throws IOException {
-        return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8));
+        return new ByteArrayInputStream("HelloWorld".getBytes(UTF_8));
       }
     };
 
@@ -160,9 +159,9 @@ public class JavaApiConverterTest {
   @Test public void createOkResponseForCacheGet_secure() throws Exception {
     final String statusLine = "HTTP/1.1 200 Fantastic";
     final Principal localPrincipal = LOCAL_CERT.getSubjectX500Principal();
-    final List<Certificate> localCertificates = Arrays.<Certificate>asList(LOCAL_CERT);
+    final List<Certificate> localCertificates = Arrays.asList(LOCAL_CERT);
     final Principal serverPrincipal = SERVER_CERT.getSubjectX500Principal();
-    final List<Certificate> serverCertificates = Arrays.<Certificate>asList(SERVER_CERT);
+    final List<Certificate> serverCertificates = Arrays.asList(SERVER_CERT);
     URI uri = new URI("https://foo/bar");
     Request request = new Request.Builder().url(uri.toURL()).build();
     SecureCacheResponse cacheResponse = new SecureCacheResponse() {
@@ -174,7 +173,7 @@ public class JavaApiConverterTest {
       }
 
       @Override public InputStream getBody() throws IOException {
-        return new ByteArrayInputStream("HelloWorld".getBytes(StandardCharsets.UTF_8));
+        return new ByteArrayInputStream("HelloWorld".getBytes(UTF_8));
       }
 
       @Override public String getCipherSuite() {
@@ -400,7 +399,7 @@ public class JavaApiConverterTest {
     }
 
     // Check retrieval of headers by index.
-    assertEquals(null, httpUrlConnection.getHeaderFieldKey(0));
+    assertNull(httpUrlConnection.getHeaderFieldKey(0));
     assertEquals("HTTP/1.1 200 Fantastic", httpUrlConnection.getHeaderField(0));
     // After header zero there may be additional entries provided at the beginning or end by the
     // implementation. It's probably important that the relative ordering of the headers is
@@ -466,7 +465,7 @@ public class JavaApiConverterTest {
         .url("https://secure/request")
         .build();
     Handshake handshake = Handshake.get(TlsVersion.SSL_3_0, CipherSuite.TLS_RSA_WITH_NULL_MD5,
-        Arrays.<Certificate>asList(SERVER_CERT), Arrays.<Certificate>asList(LOCAL_CERT));
+        Arrays.asList(SERVER_CERT), Arrays.asList(LOCAL_CERT));
     Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
         .handshake(handshake)
         .build();
@@ -556,7 +555,7 @@ public class JavaApiConverterTest {
             .build();
     ResponseBody responseBody = createResponseBody("ResponseBody");
     Handshake handshake = Handshake.get(TlsVersion.SSL_3_0, CipherSuite.TLS_RSA_WITH_NULL_MD5,
-        Arrays.<Certificate>asList(SERVER_CERT), Arrays.<Certificate>asList(LOCAL_CERT));
+        Arrays.asList(SERVER_CERT), Arrays.asList(LOCAL_CERT));
     Response okResponse = createArbitraryOkResponse(okRequest).newBuilder()
         .protocol(Protocol.HTTP_1_1)
         .code(200)
@@ -612,7 +611,7 @@ public class JavaApiConverterTest {
     assertEquals("StatusLine", JavaApiConverter.extractStatusLine(javaResponseHeaders));
 
     try {
-      JavaApiConverter.extractStatusLine(Collections.<String, List<String>>emptyMap());
+      JavaApiConverter.extractStatusLine(Collections.emptyMap());
       fail();
     } catch (IOException expected) {
     }
@@ -626,7 +625,7 @@ public class JavaApiConverterTest {
   private static X509Certificate certificate(String certificate) {
     try {
       return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
-          new ByteArrayInputStream(certificate.getBytes(Util.UTF_8)));
+          new ByteArrayInputStream(certificate.getBytes(UTF_8)));
     } catch (CertificateException e) {
       fail();
       return null;
diff --git a/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java b/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java
index eb95e3ea4ecb07a19f07f8901e2ef2c08363d244..94c875d814e55ac366b10b4cac5c2c727d14a67d 100644
--- a/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java
+++ b/okhttp-android-support/src/test/java/okhttp3/internal/huc/ResponseCacheTest.java
@@ -16,12 +16,10 @@
 
 package okhttp3.internal.huc;
 
-import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.CacheRequest;
 import java.net.CacheResponse;
@@ -67,6 +65,7 @@ import okhttp3.mockwebserver.SocketPolicy;
 import okhttp3.tls.HandshakeCertificates;
 import okio.Buffer;
 import okio.BufferedSink;
+import okio.BufferedSource;
 import okio.GzipSink;
 import okio.Okio;
 import org.junit.After;
@@ -493,15 +492,15 @@ public final class ResponseCacheTest {
     server.enqueue(new MockResponse()
         .setBody("Request #2"));
 
-    BufferedReader reader = new BufferedReader(
-        new InputStreamReader(openConnection(server.url("/").url()).getInputStream()));
-    assertEquals("ABCDE", reader.readLine());
+    BufferedSource bodySource = Okio.buffer(Okio.source(
+        openConnection(server.url("/").url()).getInputStream()));
+    assertEquals("ABCDE\n", bodySource.readUtf8(6));
     try {
-      reader.readLine();
+      bodySource.readUtf8(21);
       fail("This implementation silently ignored a truncated HTTP body.");
     } catch (IOException expected) {
     } finally {
-      reader.close();
+      bodySource.close();
     }
 
     URLConnection connection = openConnection(server.url("/").url());
@@ -1543,7 +1542,7 @@ public final class ResponseCacheTest {
 
     URLConnection connection2 = openConnection(server.url("/").url());
     assertEquals("A", readAscii(connection2));
-    assertEquals(null, connection2.getHeaderField("Warning"));
+    assertNull(connection2.getHeaderField("Warning"));
   }
 
   @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception {
@@ -1600,7 +1599,7 @@ public final class ResponseCacheTest {
     // still valid
     HttpURLConnection connection1 = openConnection(server.url("/a").url());
     assertEquals("A", readAscii(connection1));
-    assertEquals(null, connection1.getHeaderField("Allow"));
+    assertNull(connection1.getHeaderField("Allow"));
 
     // conditional cache hit; The cached data should be returned, but the cache is not updated.
     HttpURLConnection connection2 = openConnection(server.url("/a").url());
@@ -1761,17 +1760,17 @@ public final class ResponseCacheTest {
   }
 
   enum TransferKind {
-    CHUNKED() {
+    CHUNKED {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setChunkedBody(content, chunkSize);
       }
     },
-    FIXED_LENGTH() {
+    FIXED_LENGTH {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setBody(content);
       }
     },
-    END_OF_STREAM() {
+    END_OF_STREAM {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setBody(content);
         response.setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
diff --git a/okhttp-apache/pom.xml b/okhttp-apache/pom.xml
index 2c2eeddd02bdcf198d17e4eea05abf3da12f4ed1..53424378cf62861b207aa613e0c442894d88cf3b 100644
--- a/okhttp-apache/pom.xml
+++ b/okhttp-apache/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-apache</artifactId>
@@ -48,7 +48,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.4</version>
+        <version>3.0.1</version>
         <configuration>
           <links>
             <link>http://square.github.io/okhttp/javadoc/</link>
@@ -60,7 +60,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java b/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java
index 77493a25409e6086e73c97c41c18dbad3554bf2c..f8482564ab93e1da6856c6bd7569e4cc3564c7ef 100644
--- a/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java
+++ b/okhttp-apache/src/test/java/okhttp3/apache/OkApacheClientTest.java
@@ -25,7 +25,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static okhttp3.internal.Util.UTF_8;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
diff --git a/okhttp-dnsoverhttps/pom.xml b/okhttp-dnsoverhttps/pom.xml
index 1c0a561d9ac1f810c55b291e2d6a727c76f9fd5c..6a6cccb2054e8e844a20a5118ec62d7b39ff3f6b 100644
--- a/okhttp-dnsoverhttps/pom.xml
+++ b/okhttp-dnsoverhttps/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-dnsoverhttps</artifactId>
@@ -53,7 +53,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-hpacktests/pom.xml b/okhttp-hpacktests/pom.xml
index 12bc4e4b4959bae5335876d82168f278ebc16727..a2a22ae3656e78a887032527ccb1d4c21b3f8a2e 100644
--- a/okhttp-hpacktests/pom.xml
+++ b/okhttp-hpacktests/pom.xml
@@ -8,7 +8,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.5.0-SNAPSHOT</version>
+    <version>3.13.0-SNAPSHOT</version>
   </parent>
 
   <artifactId>okhttp-hpacktests</artifactId>
diff --git a/okhttp-logging-interceptor/pom.xml b/okhttp-logging-interceptor/pom.xml
index 18d5722adb78e0b75f647f9f234b91d1c60b0645..173604128b9a70bcb535946e288515ff96b9db80 100644
--- a/okhttp-logging-interceptor/pom.xml
+++ b/okhttp-logging-interceptor/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>logging-interceptor</artifactId>
@@ -54,7 +54,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java
index c6acbccf1ebca6bc6c2b4e49535531882733e15e..1c960b74bd5a76071e8d382fbf06deee9e0f9b7b 100644
--- a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java
+++ b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java
@@ -109,11 +109,7 @@ public final class HttpLoggingInterceptor implements Interceptor {
     void log(String message);
 
     /** A {@link Logger} defaults output appropriate for the current platform. */
-    Logger DEFAULT = new Logger() {
-      @Override public void log(String message) {
-        Platform.get().log(INFO, message, null);
-      }
-    };
+    Logger DEFAULT = message -> Platform.get().log(INFO, message, null);
   }
 
   public HttpLoggingInterceptor() {
@@ -251,20 +247,14 @@ public final class HttpLoggingInterceptor implements Interceptor {
       } else {
         BufferedSource source = responseBody.source();
         source.request(Long.MAX_VALUE); // Buffer the entire body.
-        Buffer buffer = source.buffer();
+        Buffer buffer = source.getBuffer();
 
         Long gzippedLength = null;
         if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
           gzippedLength = buffer.size();
-          GzipSource gzippedResponseBody = null;
-          try {
-            gzippedResponseBody = new GzipSource(buffer.clone());
+          try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) {
             buffer = new Buffer();
             buffer.writeAll(gzippedResponseBody);
-          } finally {
-            if (gzippedResponseBody != null) {
-              gzippedResponseBody.close();
-            }
           }
         }
 
diff --git a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java
index cc5adde48e9a69466557bcfe9c475b0ed11eb336..da38261bfb07071926d20550457f432625ded95c 100644
--- a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java
+++ b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/LoggingEventListener.java
@@ -75,7 +75,7 @@ public final class LoggingEventListener extends EventListener {
 
   @Override
   public void secureConnectEnd(Call call, @Nullable Handshake handshake) {
-    logWithTime("secureConnectEnd");
+    logWithTime("secureConnectEnd: " + handshake);
   }
 
   @Override
diff --git a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java
index 6fd53f7753ee6f79a056c9b15c3fcdafb48a26d2..bd4b8f79bcdd99b43b449b803fa06ef2c6aabb56 100644
--- a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java
+++ b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java
@@ -16,14 +16,11 @@
 package okhttp3.logging;
 
 import java.io.IOException;
-import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Pattern;
 import javax.net.ssl.HostnameVerifier;
-import okhttp3.Dns;
 import okhttp3.HttpUrl;
 import okhttp3.MediaType;
 import okhttp3.OkHttpClient;
@@ -705,11 +702,7 @@ public final class HttpLoggingInterceptorTest {
   @Test public void connectFail() throws IOException {
     setLevel(Level.BASIC);
     client = new OkHttpClient.Builder()
-        .dns(new Dns() {
-          @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
-            throw new UnknownHostException("reason");
-          }
-        })
+        .dns(hostname -> { throw new UnknownHostException("reason"); })
         .addInterceptor(applicationInterceptor)
         .build();
 
@@ -827,7 +820,7 @@ public final class HttpLoggingInterceptorTest {
     }
 
     void assertNoMoreLogs() {
-      assertTrue("More messages remain: " + logs.subList(index, logs.size()), index == logs.size());
+      assertEquals("More messages remain: " + logs.subList(index, logs.size()), index, logs.size());
     }
 
     @Override public void log(String message) {
diff --git a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java
index 0f0ea2bd715e4db3c0c52d508584e74cd9052d1a..84aecb571c24f5bb1c9fe1f343a49a64b294ca99 100644
--- a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java
+++ b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/LoggingEventListenerTest.java
@@ -16,10 +16,7 @@
 package okhttp3.logging;
 
 import java.io.IOException;
-import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.util.List;
-import okhttp3.Dns;
 import okhttp3.HttpUrl;
 import okhttp3.MediaType;
 import okhttp3.OkHttpClient;
@@ -145,7 +142,11 @@ public final class LoggingEventListenerTest {
         .assertLogMatch("dnsEnd: \\[.+\\]")
         .assertLogMatch("connectStart: " + url.host() + "/.+ DIRECT")
         .assertLogMatch("secureConnectStart")
-        .assertLogMatch("secureConnectEnd")
+        .assertLogMatch("secureConnectEnd: Handshake\\{"
+            + "tlsVersion=TLS_1_2 "
+            + "cipherSuite=TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 "
+            + "peerCertificates=\\[CN=localhost\\] "
+            + "localCertificates=\\[\\]}")
         .assertLogMatch("connectEnd: h2")
         .assertLogMatch(
             "connectionAcquired: Connection\\{"
@@ -167,17 +168,10 @@ public final class LoggingEventListenerTest {
 
   @Test
   public void dnsFail() throws IOException {
-    client =
-        new OkHttpClient.Builder()
-            .dns(
-                new Dns() {
-                  @Override
-                  public List<InetAddress> lookup(String hostname) throws UnknownHostException {
-                    throw new UnknownHostException("reason");
-                  }
-                })
-            .eventListenerFactory(loggingEventListenerFactory)
-            .build();
+    client = new OkHttpClient.Builder()
+        .dns(hostname -> { throw new UnknownHostException("reason"); })
+        .eventListenerFactory(loggingEventListenerFactory)
+        .build();
 
     try {
       client.newCall(request().build()).execute();
diff --git a/okhttp-sse/pom.xml b/okhttp-sse/pom.xml
index e2e64ca1f3285f4bad618f0342952a6343a2a200..82563b88d3639e740ac47267848bdbc603002956 100644
--- a/okhttp-sse/pom.xml
+++ b/okhttp-sse/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-sse</artifactId>
@@ -18,11 +18,6 @@
       <artifactId>okhttp</artifactId>
       <version>${project.version}</version>
     </dependency>
-    <dependency>
-      <groupId>${project.groupId}</groupId>
-      <artifactId>okhttp-testing-support</artifactId>
-      <version>${project.version}</version>
-    </dependency>
     <dependency>
       <groupId>com.google.code.findbugs</groupId>
       <artifactId>jsr305</artifactId>
@@ -53,8 +48,9 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.4</version>
+        <version>3.0.1</version>
         <configuration>
+          <excludePackageNames>okhttp3.internal:okhttp3.internal.*</excludePackageNames>
           <links>
             <link>http://square.github.io/okhttp/javadoc/</link>
           </links>
@@ -63,7 +59,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java b/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java
index 0e0e775fb646c07abb9a1cead255be0814201d4c..ae19d3745e4ab6bf96d3e7d06573d7c0efcf76a1 100644
--- a/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java
+++ b/okhttp-sse/src/main/java/okhttp3/internal/sse/ServerSentEventReader.java
@@ -63,7 +63,7 @@ public final class ServerSentEventReader {
         return false;
       }
 
-      switch (source.buffer().getByte(0)) {
+      switch (source.getBuffer().getByte(0)) {
         case '\r':
         case '\n':
           completeEvent(id, type, data);
@@ -162,7 +162,7 @@ public final class ServerSentEventReader {
    */
   private boolean isKey(ByteString key) throws IOException {
     if (source.rangeEquals(0, key)) {
-      byte nextByte = source.buffer().getByte(key.size());
+      byte nextByte = source.getBuffer().getByte(key.size());
       return nextByte == ':'
           || nextByte == '\r'
           || nextByte == '\n';
@@ -174,7 +174,7 @@ public final class ServerSentEventReader {
   private void skipCrAndOrLf() throws IOException {
     if ((source.readByte() & 0xff) == '\r'
         && source.request(1)
-        && source.buffer().getByte(0) == '\n') {
+        && source.getBuffer().getByte(0) == '\n') {
       source.skip(1);
     }
   }
@@ -186,11 +186,11 @@ public final class ServerSentEventReader {
   private long skipNameAndDivider(long length) throws IOException {
     source.skip(length);
 
-    if (source.buffer().getByte(0) == ':') {
+    if (source.getBuffer().getByte(0) == ':') {
       source.skip(1L);
       length++;
 
-      if (source.buffer().getByte(0) == ' ') {
+      if (source.getBuffer().getByte(0) == ' ') {
         source.skip(1);
         length++;
       }
diff --git a/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java b/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java
index 77f596c5f2657e9fc4d610aef1a6ee66f69dc69e..db0c818d54e78f9efa9d8b1e6538218d968580d9 100644
--- a/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java
+++ b/okhttp-sse/src/main/java/okhttp3/sse/EventSources.java
@@ -16,18 +16,15 @@
 package okhttp3.sse;
 
 import okhttp3.OkHttpClient;
-import okhttp3.Request;
 import okhttp3.Response;
 import okhttp3.internal.sse.RealEventSource;
 
 public final class EventSources {
   public static EventSource.Factory createFactory(final OkHttpClient client) {
-    return new EventSource.Factory() {
-      @Override public EventSource newEventSource(Request request, EventSourceListener listener) {
-        RealEventSource eventSource = new RealEventSource(request, listener);
-        eventSource.connect(client);
-        return eventSource;
-      }
+    return (request, listener) -> {
+      RealEventSource eventSource = new RealEventSource(request, listener);
+      eventSource.connect(client);
+      return eventSource;
     };
   }
 
diff --git a/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java b/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java
index 149be0014ce36413b017529047a3c14d93347ea5..fade2b978a2ffc609ecffdea69df5751f7b47216 100644
--- a/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java
+++ b/okhttp-sse/src/test/java/okhttp3/internal/sse/Event.java
@@ -15,6 +15,7 @@
  */
 package okhttp3.internal.sse;
 
+import java.util.Objects;
 import javax.annotation.Nullable;
 
 final class Event {
@@ -37,14 +38,14 @@ final class Event {
     if (this == o) return true;
     if (!(o instanceof Event)) return false;
     Event other = (Event) o;
-    return (id != null ? id.equals(other.id) : other.id == null)
-        && (type != null ? type.equals(other.type) : other.type == null)
+    return Objects.equals(id, other.id)
+        && Objects.equals(type, other.type)
         && data.equals(other.data);
   }
 
   @Override public int hashCode() {
-    int result = (id != null ? id.hashCode() : 0);
-    result = 31 * result + (type != null ? type.hashCode() : 0);
+    int result = Objects.hashCode(id);
+    result = 31 * result + Objects.hashCode(type);
     result = 31 * result + data.hashCode();
     return result;
   }
diff --git a/okhttp-testing-support/pom.xml b/okhttp-testing-support/pom.xml
index 4ef7c3af17ae6bd5d5eb00612bfae0b4ad09df6b..763b92b42b9ecb9aa7013a068aa1188efb6aeae1 100644
--- a/okhttp-testing-support/pom.xml
+++ b/okhttp-testing-support/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-testing-support</artifactId>
@@ -35,7 +35,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java b/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java
index 2a85617dc18d27e745963fe726173a0635f832e6..81b47ddf55502edd4a547ec154d590f9054d6e34 100644
--- a/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java
+++ b/okhttp-testing-support/src/main/java/okhttp3/TestUtil.java
@@ -17,7 +17,6 @@ package okhttp3;
 
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -32,11 +31,9 @@ public final class TestUtil {
    * A network that resolves only one IP address per host. Use this when testing route selection
    * fallbacks to prevent the host machine's various IP addresses from interfering.
    */
-  private static final Dns SINGLE_INET_ADDRESS_DNS = new Dns() {
-    @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
-      List<InetAddress> addresses = Dns.SYSTEM.lookup(hostname);
-      return Collections.singletonList(addresses.get(0));
-    }
+  private static final Dns SINGLE_INET_ADDRESS_DNS = hostname -> {
+    List<InetAddress> addresses = Dns.SYSTEM.lookup(hostname);
+    return Collections.singletonList(addresses.get(0));
   };
 
   private TestUtil() {
diff --git a/okhttp-testing-support/src/main/java/okhttp3/internal/duplex/AsyncRequestBody.java b/okhttp-testing-support/src/main/java/okhttp3/internal/duplex/AsyncRequestBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..1310423e6f45c7fce32c65bc1b933c1ec4c1a3f1
--- /dev/null
+++ b/okhttp-testing-support/src/main/java/okhttp3/internal/duplex/AsyncRequestBody.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okhttp3.internal.duplex;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import okhttp3.MediaType;
+import okhttp3.RequestBody;
+import okio.BufferedSink;
+
+import static junit.framework.TestCase.assertTrue;
+
+/** A duplex request body that keeps the provided sinks so they can be written to later. */
+public final class AsyncRequestBody extends RequestBody implements DuplexRequestBody {
+  private final BlockingQueue<BufferedSink> requestBodySinks = new LinkedBlockingQueue<>();
+
+  @Override public @Nullable MediaType contentType() {
+    return null;
+  }
+
+  @Override public void writeTo(BufferedSink sink) {
+    requestBodySinks.add(sink);
+  }
+
+  public BufferedSink takeSink() throws InterruptedException {
+    BufferedSink result = requestBodySinks.poll(5, TimeUnit.SECONDS);
+    if (result == null) throw new AssertionError("no sink to take");
+    return result;
+  }
+
+  public void assertNoMoreSinks() {
+    assertTrue(requestBodySinks.isEmpty());
+  }
+}
diff --git a/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java b/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java
index ae034cd9a6013a06cf566b49b9d3b09b05718d64..c1768dcd4ba2624fa6aea314b07edcb5c8fd9e8b 100644
--- a/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java
+++ b/okhttp-testing-support/src/main/java/okhttp3/testing/InstallUncaughtExceptionHandlerListener.java
@@ -38,24 +38,22 @@ public class InstallUncaughtExceptionHandlerListener extends RunListener {
   @Override public void testRunStarted(Description description) {
     System.err.println("Installing aggressive uncaught exception handler");
     oldDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
-    Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
-      @Override public void uncaughtException(Thread thread, Throwable throwable) {
-        StringWriter errorText = new StringWriter(256);
-        errorText.append("Uncaught exception in OkHttp thread \"");
-        errorText.append(thread.getName());
-        errorText.append("\"\n");
-        throwable.printStackTrace(new PrintWriter(errorText));
+    Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
+      StringWriter errorText = new StringWriter(256);
+      errorText.append("Uncaught exception in OkHttp thread \"");
+      errorText.append(thread.getName());
+      errorText.append("\"\n");
+      throwable.printStackTrace(new PrintWriter(errorText));
+      errorText.append("\n");
+      if (lastTestStarted != null) {
+        errorText.append("Last test to start was: ");
+        errorText.append(lastTestStarted.getDisplayName());
         errorText.append("\n");
-        if (lastTestStarted != null) {
-          errorText.append("Last test to start was: ");
-          errorText.append(lastTestStarted.getDisplayName());
-          errorText.append("\n");
-        }
-        System.err.print(errorText.toString());
+      }
+      System.err.print(errorText.toString());
 
-        synchronized (exceptions) {
-          exceptions.put(throwable, lastTestStarted.getDisplayName());
-        }
+      synchronized (exceptions) {
+        exceptions.put(throwable, lastTestStarted.getDisplayName());
       }
     });
   }
diff --git a/okhttp-tests/pom.xml b/okhttp-tests/pom.xml
index 06a589d22b43185dc7fc68991f2ee4ebcf06f5a1..daf88f42440388c5d96eb71ad7a707cc7bb14b8a 100644
--- a/okhttp-tests/pom.xml
+++ b/okhttp-tests/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-tests</artifactId>
@@ -97,7 +97,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-deploy-plugin</artifactId>
-        <version>2.7</version>
+        <version>2.8.2</version>
         <configuration>
           <skip>true</skip>
         </configuration>
diff --git a/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java b/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java
index dfe52799850aee9ce38b4060a6d285f144563b40..9e954b95fe99d3ac58b2e41c661df4e225d14d69 100644
--- a/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java
+++ b/okhttp-tests/src/main/java/okhttp3/TestTls13Request.java
@@ -26,7 +26,6 @@ public class TestTls13Request {
   private static final ConnectionSpec TLS_13 = new ConnectionSpec.Builder(true)
       .cipherSuites(TLS13_CIPHER_SUITES)
       .tlsVersions(TlsVersion.TLS_1_3)
-      .supportsTlsExtensions(true)
       .build();
 
 
@@ -87,10 +86,7 @@ public class TestTls13Request {
 
     Request request = new Request.Builder().url(url).build();
 
-    Response response = null;
-    try {
-      response = client.newCall(request).execute();
-
+    try (Response response = client.newCall(request).execute()) {
       Handshake handshake = response.handshake();
       System.out.println(handshake.tlsVersion()
           + " "
@@ -104,10 +100,6 @@ public class TestTls13Request {
           + "b");
     } catch (IOException ioe) {
       System.out.println(ioe.toString());
-    } finally {
-      if (response != null) {
-        response.close();
-      }
     }
   }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/AddressTest.java b/okhttp-tests/src/test/java/okhttp3/AddressTest.java
index ad07b7824160c0404db324851bc8e73e77ca4e12..7a1b7f1bd104ac3a3955060230fe2be3547a6fb1 100644
--- a/okhttp-tests/src/test/java/okhttp3/AddressTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/AddressTest.java
@@ -23,7 +23,7 @@ import okhttp3.internal.http.RecordingProxySelector;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 
 public final class AddressTest {
   private Dns dns = Dns.SYSTEM;
@@ -47,7 +47,7 @@ public final class AddressTest {
         authenticator, null, protocols, connectionSpecs, new RecordingProxySelector());
     Address b = new Address("square.com", 80, dns, socketFactory, null, null, null,
         authenticator, null, protocols, connectionSpecs, new RecordingProxySelector());
-    assertFalse(a.equals(b));
+    assertNotEquals(a, b);
   }
 
   @Test public void addressToString() throws Exception {
diff --git a/okhttp-tests/src/test/java/okhttp3/CacheTest.java b/okhttp-tests/src/test/java/okhttp3/CacheTest.java
index 0d9ca9fa75f2cecb5ee06c4cabb572f7889f21aa..c75d5b4163da04f54b9c1cf594f9f149b5b088bb 100644
--- a/okhttp-tests/src/test/java/okhttp3/CacheTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/CacheTest.java
@@ -34,7 +34,6 @@ import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLSession;
 import okhttp3.internal.Internal;
 import okhttp3.internal.io.InMemoryFileSystem;
 import okhttp3.internal.platform.Platform;
@@ -63,11 +62,7 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 public final class CacheTest {
-  private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() {
-    @Override public boolean verify(String s, SSLSession sslSession) {
-      return true;
-    }
-  };
+  private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = (name, session) -> true;
 
   @Rule public MockWebServer server = new MockWebServer();
   @Rule public MockWebServer server2 = new MockWebServer();
@@ -524,7 +519,7 @@ public final class CacheTest {
     BufferedSource bodySource = get(server.url("/")).body().source();
     assertEquals("ABCDE", bodySource.readUtf8Line());
     try {
-      bodySource.readUtf8Line();
+      bodySource.readUtf8(21);
       fail("This implementation silently ignored a truncated HTTP body.");
     } catch (IOException expected) {
     } finally {
@@ -1865,7 +1860,7 @@ public final class CacheTest {
 
     Response response2 = get(server.url("/"));
     assertEquals("A", response2.body().string());
-    assertEquals(null, response2.header("Warning"));
+    assertNull(response2.header("Warning"));
   }
 
   @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception {
@@ -1910,7 +1905,7 @@ public final class CacheTest {
     long t0 = System.currentTimeMillis();
     Response response1 = get(server.url("/a"));
     assertEquals("A", response1.body().string());
-    assertEquals(null, response1.header("Allow"));
+    assertNull(response1.header("Allow"));
     assertEquals(0, response1.receivedResponseAtMillis() - t0, 250.0);
 
     // A conditional cache hit updates the cache.
@@ -2220,12 +2215,11 @@ public final class CacheTest {
 
     final AtomicReference<String> ifNoneMatch = new AtomicReference<>();
     client = client.newBuilder()
-        .addNetworkInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            ifNoneMatch.compareAndSet(null, chain.request().header("If-None-Match"));
-            return chain.proceed(chain.request());
-          }
-        }).build();
+        .addNetworkInterceptor(chain -> {
+          ifNoneMatch.compareAndSet(null, chain.request().header("If-None-Match"));
+          return chain.proceed(chain.request());
+        })
+        .build();
 
     // Confirm the value is cached and intercepted.
     assertEquals("A", get(url).body().string());
@@ -2243,11 +2237,8 @@ public final class CacheTest {
 
     // Confirm the interceptor isn't exercised.
     client = client.newBuilder()
-        .addNetworkInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            throw new AssertionError();
-          }
-        }).build();
+        .addNetworkInterceptor(chain -> { throw new AssertionError(); })
+        .build();
     assertEquals("A", get(url).body().string());
   }
 
@@ -2397,7 +2388,7 @@ public final class CacheTest {
     assertEquals("B", get(url).body().string());
     assertEquals("B", get(url).body().string());
 
-    assertEquals(null, server.takeRequest().getHeader("If-None-Match"));
+    assertNull(server.takeRequest().getHeader("If-None-Match"));
     assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
     assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
     assertEquals("v2", server.takeRequest().getHeader("If-None-Match"));
@@ -2443,7 +2434,7 @@ public final class CacheTest {
     Response response2 = get(server.url("/"));
     assertEquals("abcd", response2.body().string());
 
-    assertEquals(null, server.takeRequest().getHeader("If-None-Match"));
+    assertNull(server.takeRequest().getHeader("If-None-Match"));
     assertEquals("α", server.takeRequest().getHeader("If-None-Match"));
   }
 
@@ -2586,18 +2577,17 @@ public final class CacheTest {
   }
 
   enum TransferKind {
-    CHUNKED() {
-      @Override void setBody(MockResponse response, Buffer content, int chunkSize)
-          throws IOException {
+    CHUNKED {
+      @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setChunkedBody(content, chunkSize);
       }
     },
-    FIXED_LENGTH() {
+    FIXED_LENGTH {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setBody(content);
       }
     },
-    END_OF_STREAM() {
+    END_OF_STREAM {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setBody(content);
         response.setSocketPolicy(DISCONNECT_AT_END);
diff --git a/okhttp-tests/src/test/java/okhttp3/CallTest.java b/okhttp-tests/src/test/java/okhttp3/CallTest.java
index a58bc879c27b07503416b48192662c5caf151e48..88135fab441801d50bf2ddd9e1eff1f882f82fbf 100644
--- a/okhttp-tests/src/test/java/okhttp3/CallTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/CallTest.java
@@ -37,7 +37,6 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -84,6 +83,7 @@ import org.junit.rules.TestRule;
 import org.junit.rules.Timeout;
 
 import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
+import static okhttp3.CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256;
 import static okhttp3.TestUtil.awaitGarbageCollection;
 import static okhttp3.TestUtil.defaultClient;
 import static okhttp3.tls.internal.TlsUtil.localhost;
@@ -116,6 +116,10 @@ public final class CallTest {
   @After public void tearDown() throws Exception {
     cache.delete();
     logger.removeHandler(logHandler);
+
+    // Ensure the test has released all connections.
+    client.connectionPool().evictAll();
+    assertEquals(0, client.connectionPool().connectionCount());
   }
 
   @Test public void get() throws Exception {
@@ -344,7 +348,7 @@ public final class CallTest {
     assertEquals("POST", recordedRequest.getMethod());
     assertEquals(0, recordedRequest.getBody().size());
     assertEquals("0", recordedRequest.getHeader("Content-Length"));
-    assertEquals(null, recordedRequest.getHeader("Content-Type"));
+    assertNull(recordedRequest.getHeader("Content-Type"));
   }
 
   @Test public void postZerolength_HTTPS() throws Exception {
@@ -487,7 +491,7 @@ public final class CallTest {
     assertEquals("DELETE", recordedRequest.getMethod());
     assertEquals(0, recordedRequest.getBody().size());
     assertEquals("0", recordedRequest.getHeader("Content-Length"));
-    assertEquals(null, recordedRequest.getHeader("Content-Type"));
+    assertNull(recordedRequest.getHeader("Content-Type"));
   }
 
   @Test public void delete_HTTPS() throws Exception {
@@ -605,7 +609,7 @@ public final class CallTest {
     executeSynchronously(request).assertCode(200);
 
     RecordedRequest recordedRequest = server.takeRequest();
-    assertEquals(null, recordedRequest.getHeader("Content-Type"));
+    assertNull(recordedRequest.getHeader("Content-Type"));
     assertEquals("3", recordedRequest.getHeader("Content-Length"));
     assertEquals("abc", recordedRequest.getBody().readUtf8());
   }
@@ -669,6 +673,8 @@ public final class CallTest {
     }
 
     assertEquals("SyncApiTest", server.takeRequest().getHeader("User-Agent"));
+
+    callback.await(request.url()).assertSuccessful();
   }
 
   @Test public void legalToExecuteTwiceCloning() throws Exception {
@@ -922,11 +928,7 @@ public final class CallTest {
   /** https://github.com/square/okhttp/issues/1801 */
   @Test public void asyncCallEngineInitialized() throws Exception {
     OkHttpClient c = defaultClient().newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            throw new IOException();
-          }
-        })
+        .addInterceptor(chain -> { throw new IOException(); })
         .build();
     Request request = new Request.Builder().url(server.url("/")).build();
     c.newCall(request).enqueue(callback);
@@ -1180,6 +1182,96 @@ public final class CallTest {
     }
   }
 
+  /**
+   * When the server doesn't present any certificates we fail the TLS handshake. This test requires
+   * that the client and server are each configured with a cipher suite that permits the server to
+   * be unauthenticated.
+   */
+  @Test public void tlsSuccessWithNoPeerCertificates() throws Exception {
+    server.enqueue(new MockResponse()
+        .setBody("abc"));
+
+    // The _anon_ cipher suites don't require server certificates.
+    CipherSuite cipherSuite = TLS_DH_anon_WITH_AES_128_GCM_SHA256;
+
+    HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
+        .build();
+    client = client.newBuilder()
+        .sslSocketFactory(
+            socketFactoryWithCipherSuite(clientCertificates.sslSocketFactory(), cipherSuite),
+            clientCertificates.trustManager())
+        .connectionSpecs(Arrays.asList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+            .cipherSuites(cipherSuite)
+            .build()))
+        .hostnameVerifier(new RecordingHostnameVerifier())
+        .build();
+
+    HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+        .build();
+    server.useHttps(socketFactoryWithCipherSuite(
+        serverCertificates.sslSocketFactory(), cipherSuite), false);
+
+    Call call = client.newCall(new Request.Builder()
+        .url(server.url("/"))
+        .build());
+    Response response = call.execute();
+    assertEquals("abc", response.body().string());
+    assertNull(response.handshake().peerPrincipal());
+    assertEquals(Collections.emptyList(), response.handshake().peerCertificates());
+    assertEquals(cipherSuite, response.handshake().cipherSuite());
+  }
+
+  @Test public void tlsHostnameVerificationFailure() throws Exception {
+    server.enqueue(new MockResponse());
+
+    HeldCertificate serverCertificate = new HeldCertificate.Builder()
+        .commonName("localhost") // Unusued for hostname verification.
+        .addSubjectAlternativeName("wronghostname")
+        .build();
+
+    HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+        .heldCertificate(serverCertificate)
+        .build();
+
+    HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
+        .addTrustedCertificate(serverCertificate.certificate())
+        .build();
+
+    client = client.newBuilder()
+        .sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
+        .build();
+    server.useHttps(serverCertificates.sslSocketFactory(), false);
+
+    executeSynchronously("/")
+        .assertFailureMatches("(?s)Hostname localhost not verified.*");
+  }
+
+  @Test public void tlsHostnameVerificationFailureNoPeerCertificates() throws Exception {
+    server.enqueue(new MockResponse());
+
+    // The _anon_ cipher suites don't require server certificates.
+    CipherSuite cipherSuite = TLS_DH_anon_WITH_AES_128_GCM_SHA256;
+
+    HandshakeCertificates clientCertificates = new HandshakeCertificates.Builder()
+        .build();
+    client = client.newBuilder()
+        .sslSocketFactory(
+            socketFactoryWithCipherSuite(clientCertificates.sslSocketFactory(), cipherSuite),
+            clientCertificates.trustManager())
+        .connectionSpecs(Arrays.asList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+            .cipherSuites(cipherSuite)
+            .build()))
+        .build();
+
+    HandshakeCertificates serverCertificates = new HandshakeCertificates.Builder()
+        .build();
+    server.useHttps(socketFactoryWithCipherSuite(
+        serverCertificates.sslSocketFactory(), cipherSuite), false);
+
+    executeSynchronously("/")
+        .assertFailure("Hostname localhost not verified (no certificates)");
+  }
+
   @Test public void cleartextCallsFailWhenCleartextIsDisabled() throws Exception {
     // Configure the client with only TLS configurations. No cleartext!
     client = client.newBuilder()
@@ -1580,7 +1672,6 @@ public final class CallTest {
         .assertBody("A");
 
     // Attempt conditional cache validation and a DNS miss.
-    client.connectionPool().evictAll();
     client = client.newBuilder()
         .dns(new FakeDns())
         .build();
@@ -2062,15 +2153,13 @@ public final class CallTest {
     server.enqueue(new MockResponse());
     final CountDownLatch latch = new CountDownLatch(1);
     client = client.newBuilder()
-        .addNetworkInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            try {
-              latch.await();
-            } catch (InterruptedException e) {
-              throw new AssertionError(e);
-            }
-            return chain.proceed(chain.request());
+        .addNetworkInterceptor(chain -> {
+          try {
+            latch.await();
+          } catch (InterruptedException e) {
+            throw new AssertionError(e);
           }
+          return chain.proceed(chain.request());
         })
         .build();
 
@@ -2093,16 +2182,44 @@ public final class CallTest {
     callback.await(server.url("/")).assertFailure("Canceled", "Socket closed");
   }
 
+  @Test
+  public void cancelWhileRequestHeadersAreSent() throws Exception {
+    server.enqueue(new MockResponse().setBody("A"));
+
+    EventListener listener =
+        new EventListener() {
+          @Override
+          public void requestHeadersStart(Call call) {
+            try {
+              // Cancel call from another thread to avoid reentrance.
+              cancelLater(call, 0).join();
+            } catch (InterruptedException e) {
+              throw new AssertionError();
+            }
+          }
+        };
+    client = client.newBuilder().eventListener(listener).build();
+
+    Call call = client.newCall(new Request.Builder().url(server.url("/a")).build());
+    try {
+      call.execute();
+      fail();
+    } catch (IOException expected) {
+    }
+  }
+
+  @Test
+  public void cancelWhileRequestHeadersAreSent_HTTP_2() throws Exception {
+    enableProtocol(Protocol.HTTP_2);
+    cancelWhileRequestHeadersAreSent();
+  }
+
   @Test public void cancelBeforeBodyIsRead() throws Exception {
     server.enqueue(new MockResponse().setBody("def").throttleBody(1, 750, TimeUnit.MILLISECONDS));
 
     final Call call = client.newCall(new Request.Builder().url(server.url("/a")).build());
     ExecutorService executor = Executors.newSingleThreadExecutor();
-    Future<Response> result = executor.submit(new Callable<Response>() {
-      @Override public Response call() throws Exception {
-        return call.execute();
-      }
-    });
+    Future<Response> result = executor.submit(call::execute);
 
     Thread.sleep(100); // wait for it to go in flight.
 
@@ -2266,12 +2383,11 @@ public final class CallTest {
 
   @Test public void cancelWithInterceptor() throws Exception {
     client = client.newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            chain.proceed(chain.request());
-            throw new AssertionError(); // We expect an exception.
-          }
-        }).build();
+        .addInterceptor(chain -> {
+          chain.proceed(chain.request());
+          throw new AssertionError(); // We expect an exception.
+        })
+        .build();
 
     Call call = client.newCall(new Request.Builder().url(server.url("/a")).build());
     call.cancel();
@@ -2890,19 +3006,17 @@ public final class CallTest {
             handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
         .proxy(server.toProxyAddress())
         .hostnameVerifier(new RecordingHostnameVerifier())
-        .proxyAuthenticator(new Authenticator() {
-          @Override public Request authenticate(Route route, Response response) {
-            assertEquals("CONNECT", response.request().method());
-            assertEquals(HttpURLConnection.HTTP_PROXY_AUTH, response.code());
-            assertEquals("android.com", response.request().url().host());
-
-            List<Challenge> challenges = response.challenges();
-            assertEquals("OkHttp-Preemptive", challenges.get(0).scheme());
-
-            return response.request().newBuilder()
-                .header("Proxy-Authorization", credential)
-                .build();
-          }
+        .proxyAuthenticator((route, response) -> {
+          assertEquals("CONNECT", response.request().method());
+          assertEquals(HttpURLConnection.HTTP_PROXY_AUTH, response.code());
+          assertEquals("android.com", response.request().url().host());
+
+          List<Challenge> challenges = response.challenges();
+          assertEquals("OkHttp-Preemptive", challenges.get(0).scheme());
+
+          return response.request().newBuilder()
+              .header("Proxy-Authorization", credential)
+              .build();
         })
         .build();
 
@@ -2942,14 +3056,12 @@ public final class CallTest {
             handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
         .proxy(server.toProxyAddress())
         .hostnameVerifier(new RecordingHostnameVerifier())
-        .proxyAuthenticator(new Authenticator() {
-          @Override public Request authenticate(Route route, Response response) {
-            List<Challenge> challenges = response.challenges();
-            challengeSchemes.add(challenges.get(0).scheme());
-            return response.request().newBuilder()
-                .header("Proxy-Authorization", credential)
-                .build();
-          }
+        .proxyAuthenticator((route, response) -> {
+          List<Challenge> challenges = response.challenges();
+          challengeSchemes.add(challenges.get(0).scheme());
+          return response.request().newBuilder()
+              .header("Proxy-Authorization", credential)
+              .build();
         })
         .build();
 
@@ -2975,11 +3087,9 @@ public final class CallTest {
 
     // Capture the protocol as it is observed by the interceptor.
     final AtomicReference<Protocol> protocolRef = new AtomicReference<>();
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        protocolRef.set(chain.connection().protocol());
-        return chain.proceed(chain.request());
-      }
+    Interceptor interceptor = chain -> {
+      protocolRef.set(chain.connection().protocol());
+      return chain.proceed(chain.request());
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -3276,13 +3386,8 @@ public final class CallTest {
     server.enqueue(new MockResponse()
         .setResponseCode(401));
 
-    client.connectionPool().evictAll();
     client = client.newBuilder()
-        .authenticator(new Authenticator() {
-          @Override public Request authenticate(Route route, Response response) throws IOException {
-            throw new IOException("IOException!");
-          }
-        })
+        .authenticator((route, response) -> { throw new IOException("IOException!"); })
         .build();
 
     Request request = new Request.Builder()
@@ -3299,13 +3404,8 @@ public final class CallTest {
     server.enqueue(new MockResponse()
         .setResponseCode(407));
 
-    client.connectionPool().evictAll();
     client = client.newBuilder()
-        .proxyAuthenticator(new Authenticator() {
-          @Override public Request authenticate(Route route, Response response) throws IOException {
-            throw new IOException("IOException!");
-          }
-        })
+        .proxyAuthenticator((route, response) -> { throw new IOException("IOException!"); })
         .build();
 
     Request request = new Request.Builder()
@@ -3375,12 +3475,70 @@ public final class CallTest {
         .post(body)
         .build();
 
+    client = client.newBuilder()
+        .dns(new DoubleInetAddressDns())
+        .build();
+
     executeSynchronously(request)
         .assertFailure(FileNotFoundException.class);
 
     assertEquals(1L, called.get());
   }
 
+  @Test public void clientReadsHeadersDataTrailersHttp1ChunkedTransferEncoding() throws Exception {
+    MockResponse mockResponse = new MockResponse()
+        .clearHeaders()
+        .addHeader("h1", "v1")
+        .addHeader("h2", "v2")
+        .setChunkedBody("HelloBonjour", 1024)
+        .setTrailers(Headers.of("trailers", "boom"));
+    server.enqueue(mockResponse);
+
+    Call call = client.newCall(new Request.Builder()
+        .url(server.url("/"))
+        .build());
+
+    Response response = call.execute();
+    BufferedSource source = response.body().source();
+
+    assertEquals("v1", response.header("h1"));
+    assertEquals("v2", response.header("h2"));
+
+    assertEquals("Hello", source.readUtf8(5));
+    assertEquals("Bonjour", source.readUtf8(7));
+
+    assertTrue(source.exhausted());
+    assertEquals(Headers.of("trailers", "boom"), response.trailers());
+  }
+
+  @Test public void clientReadsHeadersDataTrailersHttp2() throws IOException {
+    MockResponse mockResponse = new MockResponse()
+        .clearHeaders()
+        .addHeader("h1", "v1")
+        .addHeader("h2", "v2")
+        .setBody("HelloBonjour")
+        .setTrailers(Headers.of("trailers", "boom"));
+    server.enqueue(mockResponse);
+    enableProtocol(Protocol.HTTP_2);
+
+    Call call = client.newCall(new Request.Builder()
+        .url(server.url("/"))
+        .build());
+
+    try (Response response = call.execute()) {
+      BufferedSource source = response.body().source();
+
+      assertEquals("v1", response.header("h1"));
+      assertEquals("v2", response.header("h2"));
+
+      assertEquals("Hello", source.readUtf8(5));
+      assertEquals("Bonjour", source.readUtf8(7));
+
+      assertTrue(source.exhausted());
+      assertEquals(Headers.of("trailers", "boom"), response.trailers());
+    }
+  }
+
   private void makeFailingCall() {
     RequestBody requestBody = new RequestBody() {
       @Override public MediaType contentType() {
@@ -3459,8 +3617,8 @@ public final class CallTest {
     return result;
   }
 
-  private void cancelLater(final Call call, final long delay) {
-    new Thread("canceler") {
+  private Thread cancelLater(final Call call, final long delay) {
+    Thread thread = new Thread("canceler") {
       @Override public void run() {
         try {
           Thread.sleep(delay);
@@ -3469,7 +3627,19 @@ public final class CallTest {
         }
         call.cancel();
       }
-    }.start();
+    };
+    thread.start();
+    return thread;
+  }
+
+  private SSLSocketFactory socketFactoryWithCipherSuite(
+      final SSLSocketFactory sslSocketFactory, final CipherSuite cipherSuite) {
+    return new DelegatingSSLSocketFactory(sslSocketFactory) {
+      @Override protected SSLSocket configureSocket(SSLSocket sslSocket) throws IOException {
+        sslSocket.setEnabledCipherSuites(new String[] { cipherSuite.javaName() });
+        return super.configureSocket(sslSocket);
+      }
+    };
   }
 
   private static class RecordingSSLSocketFactory extends DelegatingSSLSocketFactory {
diff --git a/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java b/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java
index 28e2af84230cbc3c4746e34893cecb8ed956c358..a0e03cdcc6cadd19a45c5dad46c715c5955c64d8 100644
--- a/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/CertificatePinnerTest.java
@@ -24,8 +24,7 @@ import okhttp3.tls.HeldCertificate;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
 public final class CertificatePinnerTest {
@@ -87,9 +86,9 @@ public final class CertificatePinnerTest {
         .build();
     String keypairBCertificate2Pin = CertificatePinner.pin(heldCertificateB2.certificate());
 
-    assertTrue(certA1Sha256Pin.equals(keypairACertificate2Pin));
-    assertTrue(certB1Sha256Pin.equals(keypairBCertificate2Pin));
-    assertFalse(certA1Sha256Pin.equals(certB1Sha256Pin));
+    assertEquals(certA1Sha256Pin, keypairACertificate2Pin);
+    assertEquals(certB1Sha256Pin, keypairBCertificate2Pin);
+    assertNotEquals(certA1Sha256Pin, certB1Sha256Pin);
   }
 
   @Test public void successfulCheck() throws Exception {
diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java
index be741a0b53a02637868cabbe6503d4681579f62a..c792d75ba09b9837ef57be9ed6e233dd4424929b 100644
--- a/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/ConnectionCoalescingTest.java
@@ -25,11 +25,10 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLSession;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.tls.HeldCertificate;
 import okhttp3.tls.HandshakeCertificates;
+import okhttp3.tls.HeldCertificate;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
@@ -71,7 +70,7 @@ public final class ConnectionCoalescingTest {
     dns.set("san.com", serverIps);
     dns.set("nonsan.com", serverIps);
     dns.set("www.wildcard.com", serverIps);
-    dns.set("differentdns.com", Collections.<InetAddress>emptyList());
+    dns.set("differentdns.com", Collections.emptyList());
 
     HandshakeCertificates handshakeCertificates = new HandshakeCertificates.Builder()
         .addTrustedCertificate(rootCa.certificate())
@@ -127,13 +126,11 @@ public final class ConnectionCoalescingTest {
     server.enqueue(new MockResponse().setResponseCode(200));
     server.enqueue(new MockResponse().setResponseCode(200));
 
-    final AtomicReference<Connection> connection = new AtomicReference<>();
+    AtomicReference<Connection> connection = new AtomicReference<>();
     client = client.newBuilder()
-        .addNetworkInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            connection.set(chain.connection());
-            return chain.proceed(chain.request());
-          }
+        .addNetworkInterceptor(chain -> {
+          connection.set(chain.connection());
+          return chain.proceed(chain.request());
         })
         .build();
     dns.set("san.com", Dns.SYSTEM.lookup(server.getHostName()).subList(0, 1));
@@ -222,11 +219,7 @@ public final class ConnectionCoalescingTest {
    * verification is a black box.
    */
   @Test public void skipsWhenHostnameVerifierUsed() throws Exception {
-    HostnameVerifier verifier = new HostnameVerifier() {
-      @Override public boolean verify(String s, SSLSession sslSession) {
-        return true;
-      }
-    };
+    HostnameVerifier verifier = (name, session) -> true;
     client = client.newBuilder().hostnameVerifier(verifier).build();
 
     server.enqueue(new MockResponse().setResponseCode(200));
@@ -249,7 +242,7 @@ public final class ConnectionCoalescingTest {
     server.enqueue(new MockResponse().setResponseCode(200));
     server.enqueue(new MockResponse().setResponseCode(200));
 
-    final AtomicInteger connectCount = new AtomicInteger();
+    AtomicInteger connectCount = new AtomicInteger();
     EventListener listener = new EventListener() {
       @Override public void connectStart(Call call, InetSocketAddress inetSocketAddress,
           Proxy proxy) {
@@ -288,11 +281,9 @@ public final class ConnectionCoalescingTest {
 
   /** Network interceptors check for changes to target. */
   @Test public void worksWithNetworkInterceptors() throws Exception {
-    client = client.newBuilder().addNetworkInterceptor(new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        return chain.proceed(chain.request());
-      }
-    }).build();
+    client = client.newBuilder()
+        .addNetworkInterceptor(chain -> chain.proceed(chain.request()))
+        .build();
 
     server.enqueue(new MockResponse().setResponseCode(200));
     server.enqueue(new MockResponse().setResponseCode(200));
diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java
index d2c778ce655bee908b6e27be1f99811d113545dc..b95f8521a5f0954ffb9712454d8a3eeddc1a9a6e 100644
--- a/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/ConnectionPoolTest.java
@@ -193,9 +193,8 @@ public final class ConnectionPoolTest {
 
   private Address newAddress(String name) {
     return new Address(name, 1, Dns.SYSTEM, SocketFactory.getDefault(), null, null, null,
-        new RecordingOkAuthenticator("password", null), null, Collections.<Protocol>emptyList(),
-        Collections.<ConnectionSpec>emptyList(),
-        ProxySelector.getDefault());
+        new RecordingOkAuthenticator("password", null), null, Collections.emptyList(),
+        Collections.emptyList(), ProxySelector.getDefault());
   }
 
   private Route newRoute(Address address) {
diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java
index f5c8e75c8566c82eed844a9fa7d5b0f9e638b5ce..f8c4991120c7679fa9f52170eabc571e30053503 100644
--- a/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/ConnectionReuseTest.java
@@ -15,7 +15,6 @@
  */
 package okhttp3;
 
-import java.io.IOException;
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 import javax.net.ssl.SSLException;
@@ -301,14 +300,18 @@ public final class ConnectionReuseTest {
    * https://github.com/square/okhttp/issues/2409
    */
   @Test public void connectionsAreNotReusedIfNetworkInterceptorInterferes() throws Exception {
-    client = client.newBuilder().addNetworkInterceptor(new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Response response = chain.proceed(chain.request());
-        return response.newBuilder()
-            .body(ResponseBody.create(null, "unrelated response body!"))
-            .build();
-      }
-    }).build();
+    client = client.newBuilder()
+        // Since this test knowingly leaks a connection, avoid using the default shared connection
+        // pool, which should remain clean for subsequent tests.
+        .connectionPool(new ConnectionPool())
+        .addNetworkInterceptor(chain -> {
+          Response response = chain.proceed(chain.request());
+          return response
+              .newBuilder()
+              .body(ResponseBody.create(null, "unrelated response body!"))
+              .build();
+        })
+        .build();
 
     server.enqueue(new MockResponse()
         .setResponseCode(301)
diff --git a/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java b/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java
index c74a6b314c371f5b72320ae4851606ca49652116..13b2824f060082565b4ca598f2ddcf6f8e26e53c 100644
--- a/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/ConnectionSpecTest.java
@@ -297,6 +297,7 @@ public final class ConnectionSpecTest {
         + "supportsTlsExtensions=true)", connectionSpec.toString());
   }
 
+  @SafeVarargs
   private static <T> Set<T> set(T... values) {
     return new LinkedHashSet<>(Arrays.asList(values));
   }
diff --git a/okhttp-tests/src/test/java/okhttp3/CookieTest.java b/okhttp-tests/src/test/java/okhttp3/CookieTest.java
index b38dd3dd9a1c1b0a04debbf97d697651f3bda4e9..3304fa588a44f5323aefcc9ad678c5c49ae0b609 100644
--- a/okhttp-tests/src/test/java/okhttp3/CookieTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/CookieTest.java
@@ -27,6 +27,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -79,20 +80,20 @@ public final class CookieTest {
   }
 
   @Test public void invalidCharacters() throws Exception {
-    assertEquals(null, Cookie.parse(url, "a\u0000b=cd"));
-    assertEquals(null, Cookie.parse(url, "ab=c\u0000d"));
-    assertEquals(null, Cookie.parse(url, "a\u0001b=cd"));
-    assertEquals(null, Cookie.parse(url, "ab=c\u0001d"));
-    assertEquals(null, Cookie.parse(url, "a\u0009b=cd"));
-    assertEquals(null, Cookie.parse(url, "ab=c\u0009d"));
-    assertEquals(null, Cookie.parse(url, "a\u001fb=cd"));
-    assertEquals(null, Cookie.parse(url, "ab=c\u001fd"));
-    assertEquals(null, Cookie.parse(url, "a\u007fb=cd"));
-    assertEquals(null, Cookie.parse(url, "ab=c\u007fd"));
-    assertEquals(null, Cookie.parse(url, "a\u0080b=cd"));
-    assertEquals(null, Cookie.parse(url, "ab=c\u0080d"));
-    assertEquals(null, Cookie.parse(url, "a\u00ffb=cd"));
-    assertEquals(null, Cookie.parse(url, "ab=c\u00ffd"));
+    assertNull(Cookie.parse(url, "a\u0000b=cd"));
+    assertNull(Cookie.parse(url, "ab=c\u0000d"));
+    assertNull(Cookie.parse(url, "a\u0001b=cd"));
+    assertNull(Cookie.parse(url, "ab=c\u0001d"));
+    assertNull(Cookie.parse(url, "a\u0009b=cd"));
+    assertNull(Cookie.parse(url, "ab=c\u0009d"));
+    assertNull(Cookie.parse(url, "a\u001fb=cd"));
+    assertNull(Cookie.parse(url, "ab=c\u001fd"));
+    assertNull(Cookie.parse(url, "a\u007fb=cd"));
+    assertNull(Cookie.parse(url, "ab=c\u007fd"));
+    assertNull(Cookie.parse(url, "a\u0080b=cd"));
+    assertNull(Cookie.parse(url, "ab=c\u0080d"));
+    assertNull(Cookie.parse(url, "a\u00ffb=cd"));
+    assertNull(Cookie.parse(url, "ab=c\u00ffd"));
   }
 
   @Test public void maxAge() throws Exception {
@@ -523,7 +524,7 @@ public final class CookieTest {
         .hostOnlyDomain("example.com")
         .secure()
         .build();
-    assertEquals(true, cookie.secure());
+    assertTrue(cookie.secure());
   }
 
   @Test public void builderHttpOnly() throws Exception {
@@ -533,7 +534,7 @@ public final class CookieTest {
         .hostOnlyDomain("example.com")
         .httpOnly()
         .build();
-    assertEquals(true, cookie.httpOnly());
+    assertTrue(cookie.httpOnly());
   }
 
   @Test public void builderIpv6() throws Exception {
@@ -563,11 +564,11 @@ public final class CookieTest {
           assertEquals(cookieA.hashCode(), cookieB.hashCode());
           assertEquals(cookieA, cookieB);
         } else {
-          assertFalse(cookieA.hashCode() == cookieB.hashCode());
-          assertFalse(cookieA.equals(cookieB));
+          assertNotEquals(cookieA.hashCode(), cookieB.hashCode());
+          assertNotEquals(cookieA, cookieB);
         }
       }
-      assertFalse(cookieA.equals(null));
+      assertNotEquals(null, cookieA);
     }
   }
 
diff --git a/okhttp-tests/src/test/java/okhttp3/CookiesTest.java b/okhttp-tests/src/test/java/okhttp3/CookiesTest.java
index 17489b045de2180b277376bae31848a242a15deb..add061e3fe9effca181528534170a4e59441e4a4 100644
--- a/okhttp-tests/src/test/java/okhttp3/CookiesTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/CookiesTest.java
@@ -36,6 +36,7 @@ import org.junit.Test;
 import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
 import static okhttp3.TestUtil.defaultClient;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -66,12 +67,12 @@ public class CookiesTest {
     HttpCookie cookie = cookies.get(0);
     assertEquals("a", cookie.getName());
     assertEquals("android", cookie.getValue());
-    assertEquals(null, cookie.getComment());
-    assertEquals(null, cookie.getCommentURL());
-    assertEquals(false, cookie.getDiscard());
+    assertNull(cookie.getComment());
+    assertNull(cookie.getCommentURL());
+    assertFalse(cookie.getDiscard());
     assertTrue(cookie.getMaxAge() > 100000000000L);
     assertEquals("/path", cookie.getPath());
-    assertEquals(true, cookie.getSecure());
+    assertTrue(cookie.getSecure());
     assertEquals(0, cookie.getVersion());
   }
 
@@ -98,11 +99,11 @@ public class CookiesTest {
     HttpCookie cookie = cookies.get(0);
     assertEquals("a", cookie.getName());
     assertEquals("android", cookie.getValue());
-    assertEquals(null, cookie.getCommentURL());
-    assertEquals(false, cookie.getDiscard());
+    assertNull(cookie.getCommentURL());
+    assertFalse(cookie.getDiscard());
     assertEquals(60.0, cookie.getMaxAge(), 1.0); // Converting to a fixed date can cause rounding!
     assertEquals("/path", cookie.getPath());
-    assertEquals(true, cookie.getSecure());
+    assertTrue(cookie.getSecure());
   }
 
   @Test public void testQuotedAttributeValues() throws Exception {
@@ -133,7 +134,7 @@ public class CookiesTest {
     assertEquals("android", cookie.getValue());
     assertEquals(60.0, cookie.getMaxAge(), 1.0); // Converting to a fixed date can cause rounding!
     assertEquals("/path", cookie.getPath());
-    assertEquals(true, cookie.getSecure());
+    assertTrue(cookie.getSecure());
   }
 
   @Test public void testSendingCookiesFromStore() throws Exception {
diff --git a/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java b/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java
index fc863c202205e25c98d31ccacd5f204d5c29315d..e6c931b6ad01bbe07dc5dec63a9c77c382dd35a8 100644
--- a/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java
+++ b/okhttp-tests/src/test/java/okhttp3/DelegatingSSLSocket.java
@@ -320,6 +320,7 @@ public abstract class DelegatingSSLSocket extends SSLSocket {
     }
   }
 
+  @SuppressWarnings("unchecked") // Using reflection to delegate.
   public <T> T getOption(SocketOption<T> name) throws IOException {
     try {
       return (T) SSLSocket.class.getMethod("getOption", SocketOption.class).invoke(delegate, name);
@@ -328,6 +329,7 @@ public abstract class DelegatingSSLSocket extends SSLSocket {
     }
   }
 
+  @SuppressWarnings("unchecked") // Using reflection to delegate.
   public Set<SocketOption<?>> supportedOptions() {
     try {
       return (Set<SocketOption<?>>) SSLSocket.class.getMethod("supportedOptions").invoke(delegate);
diff --git a/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java b/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java
index 80cdb78cfdb231c65f46cf9975f0fcbc2ec33c2f..fd7d499412b1652b3eee238ec0c230bd9d72658c 100644
--- a/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/DispatcherTest.java
@@ -138,6 +138,18 @@ public final class DispatcherTest {
     executor.assertJobs("http://a/1");
   }
 
+  @Test public void enqueuedCallsStillRespectMaxCallsPerHost() throws Exception {
+    dispatcher.setMaxRequests(1);
+    dispatcher.setMaxRequestsPerHost(1);
+    client.newCall(newRequest("http://a/1")).enqueue(callback);
+    client.newCall(newRequest("http://b/1")).enqueue(callback);
+    client.newCall(newRequest("http://b/2")).enqueue(callback);
+    client.newCall(newRequest("http://b/3")).enqueue(callback);
+    dispatcher.setMaxRequests(3);
+    executor.finishJob("http://a/1");
+    executor.assertJobs("http://b/1");
+  }
+
   @Test public void cancelingRunningJobTakesNoEffectUntilJobFinishes() throws Exception {
     dispatcher.setMaxRequests(1);
     Call c1 = client.newCall(newRequest("http://a/1", "tag1"));
@@ -169,21 +181,18 @@ public final class DispatcherTest {
   }
 
   @Test public void synchronousCallAccessors() throws Exception {
-    final CountDownLatch ready = new CountDownLatch(2);
-    final CountDownLatch waiting = new CountDownLatch(1);
+    CountDownLatch ready = new CountDownLatch(2);
+    CountDownLatch waiting = new CountDownLatch(1);
     client = client.newBuilder()
-        .addInterceptor(
-            new Interceptor() {
-              @Override public Response intercept(Chain chain) throws IOException {
-                try {
-                  ready.countDown();
-                  waiting.await();
-                } catch (InterruptedException e) {
-                  throw new AssertionError();
-                }
-                throw new IOException();
-              }
-            })
+        .addInterceptor(chain -> {
+          try {
+            ready.countDown();
+            waiting.await();
+          } catch (InterruptedException e) {
+            throw new AssertionError();
+          }
+          throw new IOException();
+        })
         .build();
 
     Call a1 = client.newCall(newRequest("http://a/1"));
@@ -231,31 +240,25 @@ public final class DispatcherTest {
   }
 
   @Test public void idleCallbackInvokedWhenIdle() throws Exception {
-    final AtomicBoolean idle = new AtomicBoolean();
-    dispatcher.setIdleCallback(new Runnable() {
-      @Override public void run() {
-        idle.set(true);
-      }
-    });
+    AtomicBoolean idle = new AtomicBoolean();
+    dispatcher.setIdleCallback(() -> idle.set(true));
 
     client.newCall(newRequest("http://a/1")).enqueue(callback);
     client.newCall(newRequest("http://a/2")).enqueue(callback);
     executor.finishJob("http://a/1");
     assertFalse(idle.get());
 
-    final CountDownLatch ready = new CountDownLatch(1);
-    final CountDownLatch proceed = new CountDownLatch(1);
+    CountDownLatch ready = new CountDownLatch(1);
+    CountDownLatch proceed = new CountDownLatch(1);
     client = client.newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            ready.countDown();
-            try {
-              proceed.await(5, SECONDS);
-            } catch (InterruptedException e) {
-              throw new RuntimeException(e);
-            }
-            return chain.proceed(chain.request());
+        .addInterceptor(chain -> {
+          ready.countDown();
+          try {
+            proceed.await(5, SECONDS);
+          } catch (InterruptedException e) {
+            throw new RuntimeException(e);
           }
+          return chain.proceed(chain.request());
         })
         .build();
 
@@ -317,7 +320,8 @@ public final class DispatcherTest {
         listener.recordedEventTypes());
   }
 
-  private <T> Set<T> set(T... values) {
+  @SafeVarargs
+  private final <T> Set<T> set(T... values) {
     return set(Arrays.asList(values));
   }
 
@@ -325,16 +329,14 @@ public final class DispatcherTest {
     return new LinkedHashSet<>(list);
   }
 
-  private Thread makeSynchronousCall(final Call call) {
-    Thread thread = new Thread() {
-      @Override public void run() {
-        try {
-          call.execute();
-          throw new AssertionError();
-        } catch (IOException expected) {
-        }
+  private Thread makeSynchronousCall(Call call) {
+    Thread thread = new Thread(() -> {
+      try {
+        call.execute();
+        throw new AssertionError();
+      } catch (IOException expected) {
       }
-    };
+    });
     thread.start();
     return thread;
   }
diff --git a/okhttp-tests/src/test/java/okhttp3/DuplexTest.java b/okhttp-tests/src/test/java/okhttp3/DuplexTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..71284133d0e876556f41a865c23d28991f4cf806
--- /dev/null
+++ b/okhttp-tests/src/test/java/okhttp3/DuplexTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okhttp3;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import okhttp3.internal.duplex.AsyncRequestBody;
+import okhttp3.internal.duplex.MwsDuplexAccess;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.internal.duplex.MockDuplexResponseBody;
+import okhttp3.tls.HandshakeCertificates;
+import okio.BufferedSink;
+import okio.BufferedSource;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+
+import static junit.framework.TestCase.assertTrue;
+import static okhttp3.TestUtil.defaultClient;
+import static okhttp3.tls.internal.TlsUtil.localhost;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public final class DuplexTest {
+  @Rule public final TestRule timeout = new Timeout(30_000, TimeUnit.MILLISECONDS);
+  @Rule public final MockWebServer server = new MockWebServer();
+
+  private HandshakeCertificates handshakeCertificates = localhost();
+  private OkHttpClient client = defaultClient();
+
+  @Test public void trueDuplexClientWritesFirst() throws Exception {
+    enableProtocol(Protocol.HTTP_2);
+    MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody(
+        new MockResponse()
+            .clearHeaders(),
+        new MockDuplexResponseBody()
+            .receiveRequest("request A\n")
+            .sendResponse("response B\n")
+            .receiveRequest("request C\n")
+            .sendResponse("response D\n")
+            .receiveRequest("request E\n")
+            .sendResponse("response F\n")
+            .exhaustRequest());
+
+    Call call = client.newCall(new Request.Builder()
+        .url(server.url("/"))
+        .post(new AsyncRequestBody())
+        .build());
+
+    try (Response response = call.execute()) {
+      BufferedSink requestBody = ((AsyncRequestBody) call.request().body()).takeSink();
+      requestBody.writeUtf8("request A\n");
+      requestBody.flush();
+
+      BufferedSource responseBody = response.body().source();
+      assertEquals("response B", responseBody.readUtf8Line());
+
+      requestBody.writeUtf8("request C\n");
+      requestBody.flush();
+      assertEquals("response D", responseBody.readUtf8Line());
+
+      requestBody.writeUtf8("request E\n");
+      requestBody.flush();
+      assertEquals("response F", responseBody.readUtf8Line());
+
+      requestBody.close();
+      assertNull(responseBody.readUtf8Line());
+    }
+
+    mockDuplexResponseBody.awaitSuccess();
+  }
+
+  @Test public void trueDuplexServerWritesFirst() throws Exception {
+    enableProtocol(Protocol.HTTP_2);
+    MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody(
+        new MockResponse()
+            .clearHeaders(),
+        new MockDuplexResponseBody()
+            .sendResponse("response A\n")
+            .receiveRequest("request B\n")
+            .sendResponse("response C\n")
+            .receiveRequest("request D\n")
+            .sendResponse("response E\n")
+            .receiveRequest("request F\n"));
+
+    Call call = client.newCall(new Request.Builder()
+        .url(server.url("/"))
+        .post(new AsyncRequestBody())
+        .build());
+
+    try (Response response = call.execute()) {
+      BufferedSink requestBody = ((AsyncRequestBody) call.request().body()).takeSink();
+      BufferedSource responseBody = response.body().source();
+
+      assertEquals("response A", responseBody.readUtf8Line());
+      requestBody.writeUtf8("request B\n");
+      requestBody.flush();
+
+      assertEquals("response C", responseBody.readUtf8Line());
+      requestBody.writeUtf8("request D\n");
+      requestBody.flush();
+
+      assertEquals("response E", responseBody.readUtf8Line());
+      requestBody.writeUtf8("request F\n");
+      requestBody.flush();
+
+      assertNull(responseBody.readUtf8Line());
+      requestBody.close();
+    }
+
+    mockDuplexResponseBody.awaitSuccess();
+  }
+
+  @Test public void clientReadsHeadersDataTrailers() throws Exception {
+    enableProtocol(Protocol.HTTP_2);
+    MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody(
+        new MockResponse()
+            .clearHeaders()
+            .addHeader("h1", "v1")
+            .addHeader("h2", "v2")
+            .setTrailers(Headers.of("trailers", "boom")),
+        new MockDuplexResponseBody()
+            .sendResponse("ok")
+            .exhaustResponse());
+
+    Call call = client.newCall(new Request.Builder()
+        .url(server.url("/"))
+        .build());
+
+    try (Response response = call.execute()) {
+      assertEquals(Headers.of("h1", "v1", "h2", "v2"), response.headers());
+
+      BufferedSource responseBody = response.body().source();
+      assertEquals("ok", responseBody.readUtf8(2));
+      assertTrue(responseBody.exhausted());
+      assertEquals(Headers.of("trailers", "boom"), response.trailers());
+    }
+
+    mockDuplexResponseBody.awaitSuccess();
+  }
+
+  @Test public void serverReadsHeadersData() throws Exception {
+    enableProtocol(Protocol.HTTP_2);
+    MockDuplexResponseBody mockDuplexResponseBody = enqueueResponseWithBody(
+        new MockResponse()
+            .clearHeaders()
+            .addHeader("h1", "v1")
+            .addHeader("h2", "v2"),
+        new MockDuplexResponseBody()
+            .exhaustResponse()
+            .receiveRequest("hey\n")
+            .receiveRequest("whats going on\n")
+            .exhaustRequest());
+
+    Request request = new Request.Builder()
+        .url(server.url("/"))
+        .method("POST", new AsyncRequestBody())
+        .build();
+    Call call = client.newCall(request);
+
+    try (Response response = call.execute()) {
+      BufferedSink sink = ((AsyncRequestBody) request.body()).takeSink();
+      sink.writeUtf8("hey\n");
+      sink.writeUtf8("whats going on\n");
+      sink.close();
+    }
+
+    mockDuplexResponseBody.awaitSuccess();
+  }
+
+  // TODO(oldergod) write tests for headers discarded with 100 Continue
+
+  private MockDuplexResponseBody enqueueResponseWithBody(
+      MockResponse response, MockDuplexResponseBody body) {
+    MwsDuplexAccess.instance.setBody(response, body);
+    server.enqueue(response);
+    return body;
+  }
+
+  /**
+   * Tests that use this will fail unless boot classpath is set. Ex. {@code
+   * -Xbootclasspath/p:/tmp/alpn-boot-8.0.0.v20140317}
+   */
+  private void enableProtocol(Protocol protocol) {
+    enableTls();
+    client = client.newBuilder()
+        .protocols(Arrays.asList(protocol, Protocol.HTTP_1_1))
+        .build();
+    server.setProtocols(client.protocols());
+  }
+
+  private void enableTls() {
+    client = client.newBuilder()
+        .sslSocketFactory(
+            handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
+        .hostnameVerifier(new RecordingHostnameVerifier())
+        .build();
+    server.useHttps(handshakeCertificates.sslSocketFactory(), false);
+  }
+}
diff --git a/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java b/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java
index 14758e244ce0d8eed93890fb7b9f71dea7ca5feb..fd5f2c2813b03bb5ef90376f09eee86a2e81841b 100644
--- a/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/EventListenerTest.java
@@ -16,6 +16,7 @@
 package okhttp3;
 
 import java.io.IOException;
+import java.net.HttpURLConnection;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Proxy;
@@ -475,11 +476,7 @@ public final class EventListenerTest {
   }
 
   @Test public void emptyDnsLookup() {
-    Dns emptyDns = new Dns() {
-      @Override public List<InetAddress> lookup(String hostname) {
-        return Collections.emptyList();
-      }
-    };
+    Dns emptyDns = hostname -> Collections.emptyList();
 
     client = client.newBuilder()
         .dns(emptyDns)
@@ -927,6 +924,25 @@ public final class EventListenerTest {
     assertEquals(expectedEvents, listener.recordedEventTypes());
   }
 
+  @Ignore("CallEnd not emitted")
+  @Test public void emptyResponseBodyConnectionClose() throws IOException {
+    server.enqueue(new MockResponse()
+        .addHeader("Connection", "close")
+        .setBody(""));
+
+    Call call = client.newCall(new Request.Builder()
+        .url(server.url("/"))
+        .build());
+    Response response = call.execute();
+    response.body().close();
+
+    List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd",
+        "ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart",
+        "RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart",
+        "ResponseBodyEnd", "ConnectionReleased", "CallEnd");
+    assertEquals(expectedEvents, listener.recordedEventTypes());
+  }
+
   @Ignore("this reports CallFailed not CallEnd")
   @Test public void responseBodyClosedClosedWithoutReadingAllData() throws IOException {
     server.enqueue(new MockResponse()
@@ -1082,4 +1098,45 @@ public final class EventListenerTest {
         .build();
     server.useHttps(handshakeCertificates.sslSocketFactory(), tunnelProxy);
   }
+
+  @Test public void redirectUsingSameConnectionEventSequence() throws IOException {
+    server.enqueue(
+        new MockResponse()
+            .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+            .addHeader("Location: /foo"));
+    server.enqueue(new MockResponse());
+
+    Call call = client.newCall(new Request.Builder().url(server.url("/")).build());
+    Response response = call.execute();
+
+    List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd",
+        "ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart",
+        "RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart",
+        "ResponseBodyEnd", "RequestHeadersStart", "RequestHeadersEnd", "ResponseHeadersStart",
+        "ResponseHeadersEnd", "ResponseBodyStart", "ResponseBodyEnd", "ConnectionReleased",
+        "CallEnd");
+    assertEquals(expectedEvents, listener.recordedEventTypes());
+  }
+
+  @Test
+  public void redirectUsingNewConnectionEventSequence() throws IOException {
+    MockWebServer otherServer = new MockWebServer();
+    server.enqueue(
+        new MockResponse()
+            .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+            .addHeader("Location: " + otherServer.url("/foo")));
+    otherServer.enqueue(new MockResponse());
+
+    Call call = client.newCall(new Request.Builder().url(server.url("/")).build());
+    Response response = call.execute();
+
+    List<String> expectedEvents = Arrays.asList("CallStart", "DnsStart", "DnsEnd",
+        "ConnectStart", "ConnectEnd", "ConnectionAcquired", "RequestHeadersStart",
+        "RequestHeadersEnd", "ResponseHeadersStart", "ResponseHeadersEnd", "ResponseBodyStart",
+        "ResponseBodyEnd", "ConnectionReleased", "DnsStart", "DnsEnd", "ConnectStart", "ConnectEnd",
+        "ConnectionAcquired", "RequestHeadersStart", "RequestHeadersEnd", "ResponseHeadersStart",
+        "ResponseHeadersEnd", "ResponseBodyStart", "ResponseBodyEnd", "ConnectionReleased",
+        "CallEnd");
+    assertEquals(expectedEvents, listener.recordedEventTypes());
+  }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/HeadersTest.java b/okhttp-tests/src/test/java/okhttp3/HeadersTest.java
index 494ac90b85632b6f730c4eebaef2b31a6d213ddc..ef30c15d2cc9cfab621e25ec561a927398ced14b 100644
--- a/okhttp-tests/src/test/java/okhttp3/HeadersTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/HeadersTest.java
@@ -16,6 +16,7 @@
 package okhttp3;
 
 import java.io.IOException;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
@@ -23,6 +24,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import okhttp3.internal.Internal;
+import okhttp3.internal.Util;
 import okhttp3.internal.http.HttpHeaders;
 import okhttp3.internal.http2.Header;
 import okhttp3.internal.http2.Http2Codec;
@@ -35,9 +37,8 @@ import static java.util.Collections.singletonList;
 import static java.util.Collections.singletonMap;
 import static okhttp3.TestUtil.headerEntries;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 public final class HeadersTest {
@@ -64,6 +65,7 @@ public final class HeadersTest {
         .header("Connection", "upgrade")
         .header("Upgrade", "websocket")
         .header("Host", "square.com")
+        .header("TE", "gzip")
         .build();
     List<Header> expected = headerEntries(
         ":method", "GET",
@@ -73,6 +75,19 @@ public final class HeadersTest {
     assertEquals(expected, Http2Codec.http2HeadersList(request));
   }
 
+  @Test public void http2HeadersListDontDropTeIfTrailersHttp2() {
+    Request request = new Request.Builder()
+        .url("http://square.com/")
+        .header("TE", "trailers")
+        .build();
+    List<Header> expected = headerEntries(
+        ":method", "GET",
+        ":path", "/",
+        ":scheme", "http",
+        "te", "trailers");
+    assertEquals(expected, Http2Codec.http2HeadersList(request));
+  }
+
   @Test public void ofTrims() {
     Headers headers = Headers.of("\t User-Agent \n", " \r OkHttp ");
     assertEquals("User-Agent", headers.name(0));
@@ -191,7 +206,7 @@ public final class HeadersTest {
 
   @Test public void ofMapThrowsOnNull() {
     try {
-      Headers.of(Collections.<String, String>singletonMap("User-Agent", null));
+      Headers.of(Collections.singletonMap("User-Agent", null));
       fail();
     } catch (IllegalArgumentException expected) {
     }
@@ -383,7 +398,7 @@ public final class HeadersTest {
         .add("Connection", "close")
         .add("Transfer-Encoding", "chunked")
         .build();
-    assertTrue(headers1.equals(headers2));
+    assertEquals(headers1, headers2);
     assertEquals(headers1.hashCode(), headers2.hashCode());
   }
 
@@ -396,8 +411,8 @@ public final class HeadersTest {
         .add("Connection", "keep-alive")
         .add("Transfer-Encoding", "chunked")
         .build();
-    assertFalse(headers1.equals(headers2));
-    assertFalse(headers1.hashCode() == headers2.hashCode());
+    assertNotEquals(headers1, headers2);
+    assertNotEquals(headers1.hashCode(), headers2.hashCode());
   }
 
   @Test public void headersToString() {
@@ -634,7 +649,7 @@ public final class HeadersTest {
         .build();
     assertEquals(Arrays.asList(
         new Challenge("Basic", singletonMap("realm", "myrealm")),
-        new Challenge("Digest", Collections.<String, String>emptyMap())),
+        new Challenge("Digest", Collections.emptyMap())),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
 
@@ -653,7 +668,7 @@ public final class HeadersTest {
         .add("WWW-Authenticate", "Digest, Basic ,,realm=\"myrealm\"")
         .build();
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap()),
+        new Challenge("Digest", Collections.emptyMap()),
         new Challenge("Basic", singletonMap("realm", "myrealm"))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
@@ -663,7 +678,7 @@ public final class HeadersTest {
         .add("WWW-Authenticate", "Digest,Basic realm=\"myrealm\"")
         .build();
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap()),
+        new Challenge("Digest", Collections.emptyMap()),
         new Challenge("Basic", singletonMap("realm", "myrealm"))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
@@ -673,7 +688,7 @@ public final class HeadersTest {
         .add("WWW-Authenticate", "Digest,,,, Basic ,,realm=\"myrealm\"")
         .build();
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap()),
+        new Challenge("Digest", Collections.emptyMap()),
         new Challenge("Basic", singletonMap("realm", "myrealm"))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
@@ -687,7 +702,7 @@ public final class HeadersTest {
     expectedAuthParams.put("realm", "myrealm");
     expectedAuthParams.put("foo", "bar");
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap()),
+        new Challenge("Digest", Collections.emptyMap()),
         new Challenge("Basic", expectedAuthParams)),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
@@ -698,7 +713,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap()),
+        new Challenge("Digest", Collections.emptyMap()),
         new Challenge("Basic", singletonMap("realm", "my\\\"realm"))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
@@ -709,7 +724,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap()),
+        new Challenge("Digest", Collections.emptyMap()),
         new Challenge("Basic", singletonMap("realm", "my, realm,"))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
@@ -720,7 +735,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap())),
+        new Challenge("Digest", Collections.emptyMap())),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
 
@@ -730,7 +745,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap())),
+        new Challenge("Digest", Collections.emptyMap())),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
 
@@ -741,7 +756,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap())),
+        new Challenge("Digest", Collections.emptyMap())),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
 
@@ -751,7 +766,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(singletonList(
-        new Challenge("Other", singletonMap(((String) null), "abc=="))),
+        new Challenge("Other", singletonMap(null, "abc=="))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
 
@@ -761,7 +776,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(Arrays.asList(
-        new Challenge("Other", singletonMap((String) null, "abc=="))),
+        new Challenge("Other", singletonMap(null, "abc=="))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
 
@@ -780,7 +795,7 @@ public final class HeadersTest {
         .build();
 
     assertEquals(Arrays.asList(
-        new Challenge("Digest", Collections.<String, String>emptyMap()),
+        new Challenge("Digest", Collections.emptyMap()),
         new Challenge("Basic", singletonMap("realm", "myrealm"))),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
@@ -793,7 +808,7 @@ public final class HeadersTest {
 
     assertEquals(Arrays.asList(
         new Challenge("Basic", singletonMap("realm", "myrealm")),
-        new Challenge("Digest", Collections.<String, String>emptyMap())),
+        new Challenge("Digest", Collections.emptyMap())),
         HttpHeaders.parseChallenges(headers, "WWW-Authenticate"));
   }
 
@@ -810,7 +825,7 @@ public final class HeadersTest {
   }
 
   @Test public void byteCount() {
-    assertEquals(0L, new Headers.Builder().build().byteCount());
+    assertEquals(0L, Util.EMPTY_HEADERS.byteCount());
     assertEquals(10L, new Headers.Builder()
         .add("abc", "def")
         .build()
@@ -823,11 +838,12 @@ public final class HeadersTest {
   }
 
   @Test public void addDate() {
-    Date expected = new Date(0);
+    Date expected = new Date(0L);
     Headers headers = new Headers.Builder()
         .add("testDate", expected)
         .build();
     assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", headers.get("testDate"));
+    assertEquals(new Date(0L), headers.getDate("testDate"));
   }
 
   @Test public void addDateNull() {
@@ -841,13 +857,34 @@ public final class HeadersTest {
     }
   }
 
+  @Test public void addInstant() {
+    Instant expected = Instant.ofEpochMilli(0L);
+    Headers headers = new Headers.Builder()
+        .add("Test-Instant", expected)
+        .build();
+    assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", headers.get("Test-Instant"));
+    assertEquals(expected, headers.getInstant("Test-Instant"));
+  }
+
+  @Test public void addInstantNull() {
+    try {
+      new Headers.Builder()
+          .add("Test-Instant", (Instant) null)
+          .build();
+      fail();
+    } catch (NullPointerException expected) {
+      assertEquals("value for name Test-Instant == null", expected.getMessage());
+    }
+  }
+
   @Test public void setDate() {
     Date expected = new Date(1000);
     Headers headers = new Headers.Builder()
-        .add("testDate", new Date(0))
+        .add("testDate", new Date(0L))
         .set("testDate", expected)
         .build();
     assertEquals("Thu, 01 Jan 1970 00:00:01 GMT", headers.get("testDate"));
+    assertEquals(expected, headers.getDate("testDate"));
   }
 
   @Test public void setDateNull() {
@@ -860,4 +897,25 @@ public final class HeadersTest {
       assertEquals("value for name testDate == null", expected.getMessage());
     }
   }
+
+  @Test public void setInstant() {
+    Instant expected = Instant.ofEpochMilli(1000L);
+    Headers headers = new Headers.Builder()
+        .add("Test-Instant", Instant.ofEpochMilli(0L))
+        .set("Test-Instant", expected)
+        .build();
+    assertEquals("Thu, 01 Jan 1970 00:00:01 GMT", headers.get("Test-Instant"));
+    assertEquals(expected, headers.getInstant("Test-Instant"));
+  }
+
+  @Test public void setInstantNull() {
+    try {
+      new Headers.Builder()
+          .set("Test-Instant", (Instant) null)
+          .build();
+      fail();
+    } catch (NullPointerException expected) {
+      assertEquals("value for name Test-Instant == null", expected.getMessage());
+    }
+  }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java b/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java
index e780d8bd9b9d618af83b16e364c22db455167a6a..23a32d7e92b13d9c0fb062179daf794d403f4013 100644
--- a/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/HttpUrlTest.java
@@ -42,16 +42,16 @@ public final class HttpUrlTest {
         new Object[] { false }
     );
   }
-  
+
   @Parameterized.Parameter
   public boolean useGet;
-  
+
   HttpUrl parse(String url) {
     return useGet
         ? HttpUrl.get(url)
         : HttpUrl.parse(url);
   }
-  
+
   @Test public void parseTrimsAsciiWhitespace() throws Exception {
     HttpUrl expected = parse("http://host/");
     assertEquals(expected, parse("http://host/\f\n\t \r")); // Leading.
@@ -138,10 +138,10 @@ public final class HttpUrlTest {
     assertEquals(parse("http://host/a/b?query"), base.newBuilder("?query").build());
     assertEquals(parse("http://host/a/b#fragment"), base.newBuilder("#fragment").build());
     assertEquals(parse("http://host/a/b"), base.newBuilder("").build());
-    assertEquals(null, base.newBuilder("ftp://b"));
-    assertEquals(null, base.newBuilder("ht+tp://b"));
-    assertEquals(null, base.newBuilder("ht-tp://b"));
-    assertEquals(null, base.newBuilder("ht.tp://b"));
+    assertNull(base.newBuilder("ftp://b"));
+    assertNull(base.newBuilder("ht+tp://b"));
+    assertNull(base.newBuilder("ht-tp://b"));
+    assertNull(base.newBuilder("ht.tp://b"));
   }
 
   @Test public void redactedUrl() {
@@ -166,10 +166,10 @@ public final class HttpUrlTest {
 
   @Test public void resolveUnsupportedScheme() throws Exception {
     HttpUrl base = parse("http://a/");
-    assertEquals(null, base.resolve("ftp://b"));
-    assertEquals(null, base.resolve("ht+tp://b"));
-    assertEquals(null, base.resolve("ht-tp://b"));
-    assertEquals(null, base.resolve("ht.tp://b"));
+    assertNull(base.resolve("ftp://b"));
+    assertNull(base.resolve("ht+tp://b"));
+    assertNull(base.resolve("ht-tp://b"));
+    assertNull(base.resolve("ht.tp://b"));
   }
 
   @Test public void resolveSchemeLikePath() throws Exception {
@@ -183,7 +183,7 @@ public final class HttpUrlTest {
   /** https://tools.ietf.org/html/rfc3986#section-5.4.1 */
   @Test public void rfc3886NormalExamples() {
     HttpUrl url = parse("http://a/b/c/d;p?q");
-    assertEquals(null, url.resolve("g:h")); // No 'g:' scheme in HttpUrl.
+    assertNull(url.resolve("g:h")); // No 'g:' scheme in HttpUrl.
     assertEquals(parse("http://a/b/c/g"), url.resolve("g"));
     assertEquals(parse("http://a/b/c/g"), url.resolve("./g"));
     assertEquals(parse("http://a/b/c/g/"), url.resolve("g/"));
@@ -534,6 +534,10 @@ public final class HttpUrlTest {
         "Invalid URL host: \"[0:0:0:0:0:1:255.255.255]\"");
   }
 
+  @Test public void hostIpv6Malformed() throws Exception {
+    assertInvalid("http://[::g]/", "Invalid URL host: \"[::g]\"");
+  }
+
   @Test public void hostIpv6CanonicalForm() throws Exception {
     assertEquals("abcd:ef01:2345:6789:abcd:ef01:2345:6789",
         parse("http://[abcd:ef01:2345:6789:abcd:ef01:2345:6789]/").host());
@@ -554,10 +558,11 @@ public final class HttpUrlTest {
     assertEquals("1::", parse("http://[1:0:0:0:0:0:0:0]/").host());
     assertEquals("::1", parse("http://[0:0:0:0:0:0:0:1]/").host());
     assertEquals("::", parse("http://[0:0:0:0:0:0:0:0]/").host());
+    assertEquals("192.168.1.254", parse("http://[::ffff:c0a8:1fe]/").host());
   }
 
   /** The builder permits square braces but does not require them. */
-  @Test public void hostIPv6Builder() throws Exception {
+  @Test public void hostIpv6Builder() throws Exception {
     HttpUrl base = parse("http://example.com/");
     assertEquals("http://[::1]/", base.newBuilder().host("[::1]").build().toString());
     assertEquals("http://[::1]/", base.newBuilder().host("[::0001]").build().toString());
@@ -571,7 +576,6 @@ public final class HttpUrlTest {
     assertEquals("0.0.0.0", parse("http://0.0.0.0/").host());
   }
 
-  @Ignore("java.net.IDN strips trailing trailing dots on Java 7, but not on Java 8.")
   @Test public void hostWithTrailingDot() throws Exception {
     assertEquals("host.", parse("http://host./").host());
   }
@@ -794,8 +798,8 @@ public final class HttpUrlTest {
     assertEquals("host", url.host());
     assertEquals(80, url.port());
     assertEquals("/", url.encodedPath());
-    assertEquals(null, url.query());
-    assertEquals(null, url.fragment());
+    assertNull(url.query());
+    assertNull(url.fragment());
   }
 
   @Test public void fullUrlComposition() throws Exception {
@@ -1368,7 +1372,7 @@ public final class HttpUrlTest {
 
   @Test public void fromJavaNetUrlUnsupportedScheme() throws Exception {
     URL javaNetUrl = new URL("mailto:user@example.com");
-    assertEquals(null, HttpUrl.get(javaNetUrl));
+    assertNull(HttpUrl.get(javaNetUrl));
   }
 
   @Test public void fromUri() throws Exception {
@@ -1379,12 +1383,12 @@ public final class HttpUrlTest {
 
   @Test public void fromUriUnsupportedScheme() throws Exception {
     URI uri = new URI("mailto:user@example.com");
-    assertEquals(null, HttpUrl.get(uri));
+    assertNull(HttpUrl.get(uri));
   }
 
   @Test public void fromUriPartial() throws Exception {
     URI uri = new URI("/path");
-    assertEquals(null, HttpUrl.get(uri));
+    assertNull(HttpUrl.get(uri));
   }
 
   @Test public void composeQueryWithComponents() throws Exception {
@@ -1414,7 +1418,7 @@ public final class HttpUrlTest {
         .removeAllQueryParameters("a+=& b")
         .build();
     assertEquals("http://host/", url.toString());
-    assertEquals(null, url.queryParameter("a+=& b"));
+    assertNull(url.queryParameter("a+=& b"));
   }
 
   @Test public void composeQueryRemoveEncodedQueryParameter() throws Exception {
@@ -1423,7 +1427,7 @@ public final class HttpUrlTest {
         .removeAllEncodedQueryParameters("a+=& b")
         .build();
     assertEquals("http://host/", url.toString());
-    assertEquals(null, url.queryParameter("a =& b"));
+    assertNull(url.queryParameter("a =& b"));
   }
 
   @Test public void composeQuerySetQueryParameter() throws Exception {
@@ -1469,7 +1473,7 @@ public final class HttpUrlTest {
         .build();
     assertEquals(1, url.querySize());
     assertEquals("", url.queryParameterName(0));
-    assertEquals(null, url.queryParameterValue(0));
+    assertNull(url.queryParameterValue(0));
   }
 
   @Test public void ampersandQueryIsTwoNameValuePairsWithEmptyKeys() throws Exception {
@@ -1478,9 +1482,9 @@ public final class HttpUrlTest {
         .build();
     assertEquals(2, url.querySize());
     assertEquals("", url.queryParameterName(0));
-    assertEquals(null, url.queryParameterValue(0));
+    assertNull(url.queryParameterValue(0));
     assertEquals("", url.queryParameterName(1));
-    assertEquals(null, url.queryParameterValue(1));
+    assertNull(url.queryParameterValue(1));
   }
 
   @Test public void removeAllDoesNotRemoveQueryIfNoParametersWereRemoved() throws Exception {
@@ -1496,9 +1500,9 @@ public final class HttpUrlTest {
     assertEquals(3, url.querySize());
     assertEquals(new LinkedHashSet<>(Arrays.asList("foo", "bar", "baz")),
         url.queryParameterNames());
-    assertEquals(null, url.queryParameterValue(0));
-    assertEquals(null, url.queryParameterValue(1));
-    assertEquals(null, url.queryParameterValue(2));
+    assertNull(url.queryParameterValue(0));
+    assertNull(url.queryParameterValue(1));
+    assertNull(url.queryParameterValue(2));
     assertEquals(singletonList((String) null), url.queryParameterValues("foo"));
     assertEquals(singletonList((String) null), url.queryParameterValues("bar"));
     assertEquals(singletonList((String) null), url.queryParameterValues("baz"));
@@ -1581,8 +1585,8 @@ public final class HttpUrlTest {
         .fragment(null)
         .build();
     assertEquals("http://host/", url.toString());
-    assertEquals(null, url.fragment());
-    assertEquals(null, url.encodedFragment());
+    assertNull(url.fragment());
+    assertNull(url.encodedFragment());
   }
 
   @Test public void clearEncodedFragment() throws Exception {
@@ -1591,8 +1595,8 @@ public final class HttpUrlTest {
         .encodedFragment(null)
         .build();
     assertEquals("http://host/", url.toString());
-    assertEquals(null, url.fragment());
-    assertEquals(null, url.encodedFragment());
+    assertNull(url.fragment());
+    assertNull(url.encodedFragment());
   }
 
   @Test public void topPrivateDomain() {
diff --git a/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java b/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java
index a1ad679f347539aafbb98fd18b2d20c9b2e3b202..7de63fc13870aabee04ae3e96a81fb8a32cde6ab 100644
--- a/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/InterceptorTest.java
@@ -16,6 +16,8 @@
 package okhttp3;
 
 import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
 import java.net.SocketTimeoutException;
 import java.util.Arrays;
 import java.util.Locale;
@@ -62,7 +64,7 @@ public final class InterceptorTest {
         .url("https://localhost:1/")
         .build();
 
-    final Response interceptorResponse = new Response.Builder()
+    Response interceptorResponse = new Response.Builder()
         .request(request)
         .protocol(Protocol.HTTP_1_1)
         .code(200)
@@ -71,11 +73,8 @@ public final class InterceptorTest {
         .build();
 
     client = client.newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            return interceptorResponse;
-          }
-        }).build();
+        .addInterceptor(chain -> interceptorResponse)
+        .build();
 
     Response response = client.newCall(request).execute();
     assertSame(interceptorResponse, response);
@@ -84,17 +83,13 @@ public final class InterceptorTest {
   @Test public void networkInterceptorsCannotShortCircuitResponses() throws Exception {
     server.enqueue(new MockResponse().setResponseCode(500));
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        return new Response.Builder()
-            .request(chain.request())
-            .protocol(Protocol.HTTP_1_1)
-            .code(200)
-            .message("Intercepted!")
-            .body(ResponseBody.create(MediaType.get("text/plain; charset=utf-8"), "abc"))
-            .build();
-      }
-    };
+    Interceptor interceptor = chain -> new Response.Builder()
+        .request(chain.request())
+        .protocol(Protocol.HTTP_1_1)
+        .code(200)
+        .message("Intercepted!")
+        .body(ResponseBody.create(MediaType.get("text/plain; charset=utf-8"), "abc"))
+        .build();
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
         .build();
@@ -116,11 +111,9 @@ public final class InterceptorTest {
     server.enqueue(new MockResponse());
     server.enqueue(new MockResponse());
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        chain.proceed(chain.request());
-        return chain.proceed(chain.request());
-      }
+    Interceptor interceptor = chain -> {
+      chain.proceed(chain.request());
+      return chain.proceed(chain.request());
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -142,15 +135,13 @@ public final class InterceptorTest {
   @Test public void networkInterceptorsCannotChangeServerAddress() throws Exception {
     server.enqueue(new MockResponse().setResponseCode(500));
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Address address = chain.connection().route().address();
-        String sameHost = address.url().host();
-        int differentPort = address.url().port() + 1;
-        return chain.proceed(chain.request().newBuilder()
-            .url("http://" + sameHost + ":" + differentPort + "/")
-            .build());
-      }
+    Interceptor interceptor = chain -> {
+      Address address = chain.connection().route().address();
+      String sameHost = address.url().host();
+      int differentPort = address.url().port() + 1;
+      return chain.proceed(chain.request().newBuilder()
+          .url("http://" + sameHost + ":" + differentPort + "/")
+          .build());
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -172,12 +163,10 @@ public final class InterceptorTest {
   @Test public void networkInterceptorsHaveConnectionAccess() throws Exception {
     server.enqueue(new MockResponse());
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Connection connection = chain.connection();
-        assertNotNull(connection);
-        return chain.proceed(chain.request());
-      }
+    Interceptor interceptor = chain -> {
+      Connection connection = chain.connection();
+      assertNotNull(connection);
+      return chain.proceed(chain.request());
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -194,20 +183,18 @@ public final class InterceptorTest {
         .setBody(gzip("abcabcabc"))
         .addHeader("Content-Encoding: gzip"));
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        // The network request has everything: User-Agent, Host, Accept-Encoding.
-        Request networkRequest = chain.request();
-        assertNotNull(networkRequest.header("User-Agent"));
-        assertEquals(server.getHostName() + ":" + server.getPort(),
-            networkRequest.header("Host"));
-        assertNotNull(networkRequest.header("Accept-Encoding"));
-
-        // The network response also has everything, including the raw gzipped content.
-        Response networkResponse = chain.proceed(networkRequest);
-        assertEquals("gzip", networkResponse.header("Content-Encoding"));
-        return networkResponse;
-      }
+    Interceptor interceptor = chain -> {
+      // The network request has everything: User-Agent, Host, Accept-Encoding.
+      Request networkRequest = chain.request();
+      assertNotNull(networkRequest.header("User-Agent"));
+      assertEquals(server.getHostName() + ":" + server.getPort(),
+          networkRequest.header("Host"));
+      assertNotNull(networkRequest.header("Accept-Encoding"));
+
+      // The network response also has everything, including the raw gzipped content.
+      Response networkResponse = chain.proceed(networkRequest);
+      assertEquals("gzip", networkResponse.header("Content-Encoding"));
+      return networkResponse;
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -231,17 +218,15 @@ public final class InterceptorTest {
   @Test public void networkInterceptorsCanChangeRequestMethodFromGetToPost() throws Exception {
     server.enqueue(new MockResponse());
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Request originalRequest = chain.request();
-        MediaType mediaType = MediaType.get("text/plain");
-        RequestBody body = RequestBody.create(mediaType, "abc");
-        return chain.proceed(originalRequest.newBuilder()
-            .method("POST", body)
-            .header("Content-Type", mediaType.toString())
-            .header("Content-Length", Long.toString(body.contentLength()))
-            .build());
-      }
+    Interceptor interceptor = chain -> {
+      Request originalRequest = chain.request();
+      MediaType mediaType = MediaType.get("text/plain");
+      RequestBody body = RequestBody.create(mediaType, "abc");
+      return chain.proceed(originalRequest.newBuilder()
+          .method("POST", body)
+          .header("Content-Type", mediaType.toString())
+          .header("Content-Length", Long.toString(body.contentLength()))
+          .build());
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -270,14 +255,12 @@ public final class InterceptorTest {
   private void rewriteRequestToServer(boolean network) throws Exception {
     server.enqueue(new MockResponse());
 
-    addInterceptor(network, new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Request originalRequest = chain.request();
-        return chain.proceed(originalRequest.newBuilder()
-            .method("POST", uppercase(originalRequest.body()))
-            .addHeader("OkHttp-Intercepted", "yep")
-            .build());
-      }
+    addInterceptor(network, chain -> {
+      Request originalRequest = chain.request();
+      return chain.proceed(originalRequest.newBuilder()
+          .method("POST", uppercase(originalRequest.body()))
+          .addHeader("OkHttp-Intercepted", "yep")
+          .build());
     });
 
     Request request = new Request.Builder()
@@ -308,14 +291,12 @@ public final class InterceptorTest {
         .addHeader("Original-Header: foo")
         .setBody("abc"));
 
-    addInterceptor(network, new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Response originalResponse = chain.proceed(chain.request());
-        return originalResponse.newBuilder()
-            .body(uppercase(originalResponse.body()))
-            .addHeader("OkHttp-Intercepted", "yep")
-            .build();
-      }
+    addInterceptor(network, chain -> {
+      Response originalResponse = chain.proceed(chain.request());
+      return originalResponse.newBuilder()
+          .body(uppercase(originalResponse.body()))
+          .addHeader("OkHttp-Intercepted", "yep")
+          .build();
     });
 
     Request request = new Request.Builder()
@@ -339,27 +320,23 @@ public final class InterceptorTest {
   private void multipleInterceptors(boolean network) throws Exception {
     server.enqueue(new MockResponse());
 
-    addInterceptor(network, new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Request originalRequest = chain.request();
-        Response originalResponse = chain.proceed(originalRequest.newBuilder()
-            .addHeader("Request-Interceptor", "Android") // 1. Added first.
-            .build());
-        return originalResponse.newBuilder()
-            .addHeader("Response-Interceptor", "Donut") // 4. Added last.
-            .build();
-      }
+    addInterceptor(network, chain -> {
+      Request originalRequest = chain.request();
+      Response originalResponse = chain.proceed(originalRequest.newBuilder()
+          .addHeader("Request-Interceptor", "Android") // 1. Added first.
+          .build());
+      return originalResponse.newBuilder()
+          .addHeader("Response-Interceptor", "Donut") // 4. Added last.
+          .build();
     });
-    addInterceptor(network, new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Request originalRequest = chain.request();
-        Response originalResponse = chain.proceed(originalRequest.newBuilder()
-            .addHeader("Request-Interceptor", "Bob") // 2. Added second.
-            .build());
-        return originalResponse.newBuilder()
-            .addHeader("Response-Interceptor", "Cupcake") // 3. Added third.
-            .build();
-      }
+    addInterceptor(network, chain -> {
+      Request originalRequest = chain.request();
+      Response originalResponse = chain.proceed(originalRequest.newBuilder()
+          .addHeader("Request-Interceptor", "Bob") // 2. Added second.
+          .build());
+      return originalResponse.newBuilder()
+          .addHeader("Response-Interceptor", "Cupcake") // 3. Added third.
+          .build();
     });
 
     Request request = new Request.Builder()
@@ -386,13 +363,11 @@ public final class InterceptorTest {
   private void asyncInterceptors(boolean network) throws Exception {
     server.enqueue(new MockResponse());
 
-    addInterceptor(network, new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Response originalResponse = chain.proceed(chain.request());
-        return originalResponse.newBuilder()
-            .addHeader("OkHttp-Intercepted", "yep")
-            .build();
-      }
+    addInterceptor(network, chain -> {
+      Response originalResponse = chain.proceed(chain.request());
+      return originalResponse.newBuilder()
+          .addHeader("OkHttp-Intercepted", "yep")
+          .build();
     });
 
     Request request = new Request.Builder()
@@ -410,13 +385,12 @@ public final class InterceptorTest {
     server.enqueue(new MockResponse().setBody("b"));
 
     client = client.newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            Response response1 = chain.proceed(chain.request());
-            response1.body().close();
-            return chain.proceed(chain.request());
-          }
-        }).build();
+        .addInterceptor(chain -> {
+          Response response1 = chain.proceed(chain.request());
+          response1.body().close();
+          return chain.proceed(chain.request());
+        })
+        .build();
 
     Request request = new Request.Builder()
         .url(server.url("/"))
@@ -432,19 +406,18 @@ public final class InterceptorTest {
     server.enqueue(new MockResponse().setBody("b")); // Fetched directly.
 
     client = client.newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            if (chain.request().url().encodedPath().equals("/b")) {
-              Request requestA = new Request.Builder()
-                  .url(server.url("/a"))
-                  .build();
-              Response responseA = client.newCall(requestA).execute();
-              assertEquals("a", responseA.body().string());
-            }
-
-            return chain.proceed(chain.request());
+        .addInterceptor(chain -> {
+          if (chain.request().url().encodedPath().equals("/b")) {
+            Request requestA = new Request.Builder()
+                .url(server.url("/a"))
+                .build();
+            Response responseA = client.newCall(requestA).execute();
+            assertEquals("a", responseA.body().string());
           }
-        }).build();
+
+          return chain.proceed(chain.request());
+        })
+        .build();
 
     Request requestB = new Request.Builder()
         .url(server.url("/b"))
@@ -459,25 +432,24 @@ public final class InterceptorTest {
     server.enqueue(new MockResponse().setBody("b")); // Fetched directly.
 
     client = client.newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            if (chain.request().url().encodedPath().equals("/b")) {
-              Request requestA = new Request.Builder()
-                  .url(server.url("/a"))
-                  .build();
-
-              try {
-                RecordingCallback callbackA = new RecordingCallback();
-                client.newCall(requestA).enqueue(callbackA);
-                callbackA.await(requestA.url()).assertBody("a");
-              } catch (Exception e) {
-                throw new RuntimeException(e);
-              }
+        .addInterceptor(chain -> {
+          if (chain.request().url().encodedPath().equals("/b")) {
+            Request requestA = new Request.Builder()
+                .url(server.url("/a"))
+                .build();
+
+            try {
+              RecordingCallback callbackA = new RecordingCallback();
+              client.newCall(requestA).enqueue(callbackA);
+              callbackA.await(requestA.url()).assertBody("a");
+            } catch (Exception e) {
+              throw new RuntimeException(e);
             }
-
-            return chain.proceed(chain.request());
           }
-        }).build();
+
+          return chain.proceed(chain.request());
+        })
+        .build();
 
     Request requestB = new Request.Builder()
         .url(server.url("/b"))
@@ -500,11 +472,7 @@ public final class InterceptorTest {
    * with it.
    */
   private void interceptorThrowsRuntimeExceptionSynchronous(boolean network) throws Exception {
-    addInterceptor(network, new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        throw new RuntimeException("boom!");
-      }
-    });
+    addInterceptor(network, chain -> { throw new RuntimeException("boom!"); });
 
     Request request = new Request.Builder()
         .url(server.url("/"))
@@ -521,12 +489,12 @@ public final class InterceptorTest {
   @Test public void networkInterceptorModifiedRequestIsReturned() throws IOException {
     server.enqueue(new MockResponse());
 
-    Interceptor modifyHeaderInterceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        return chain.proceed(chain.request().newBuilder()
-            .header("User-Agent", "intercepted request")
-            .build());
-      }
+    Interceptor modifyHeaderInterceptor = chain -> {
+      Request modifiedRequest = chain.request()
+          .newBuilder()
+          .header("User-Agent", "intercepted request")
+          .build();
+      return chain.proceed(modifiedRequest);
     };
 
     client = client.newBuilder()
@@ -557,11 +525,7 @@ public final class InterceptorTest {
    * exception goes to the uncaught exception handler.
    */
   private void interceptorThrowsRuntimeExceptionAsynchronous(boolean network) throws Exception {
-    addInterceptor(network, new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        throw new RuntimeException("boom!");
-      }
-    });
+    addInterceptor(network, chain -> { throw new RuntimeException("boom!"); });
 
     ExceptionCatchingExecutor executor = new ExceptionCatchingExecutor();
     client = client.newBuilder()
@@ -579,11 +543,9 @@ public final class InterceptorTest {
   @Test public void applicationInterceptorReturnsNull() throws Exception {
     server.enqueue(new MockResponse());
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        chain.proceed(chain.request());
-        return null;
-      }
+    Interceptor interceptor = chain -> {
+      chain.proceed(chain.request());
+      return null;
     };
     client = client.newBuilder()
         .addInterceptor(interceptor)
@@ -608,11 +570,9 @@ public final class InterceptorTest {
   @Test public void networkInterceptorReturnsNull() throws Exception {
     server.enqueue(new MockResponse());
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        chain.proceed(chain.request());
-        return null;
-      }
+    Interceptor interceptor = chain -> {
+      chain.proceed(chain.request());
+      return null;
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -639,12 +599,10 @@ public final class InterceptorTest {
         .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
         .addHeader("Connection", "Close"));
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Response response = chain.proceed(chain.request());
-        assertNotNull(chain.connection());
-        return response;
-      }
+    Interceptor interceptor = chain -> {
+      Response response = chain.proceed(chain.request());
+      assertNotNull(chain.connection());
+      return response;
     };
 
     client = client.newBuilder()
@@ -662,10 +620,11 @@ public final class InterceptorTest {
   @Test public void applicationInterceptorResponseMustHaveBody() throws Exception {
     server.enqueue(new MockResponse());
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        return chain.proceed(chain.request()).newBuilder().body(null).build();
-      }
+    Interceptor interceptor = chain -> {
+      Response response = chain.proceed(chain.request());
+      return response.newBuilder()
+          .body(null)
+          .build();
     };
     client = client.newBuilder()
         .addInterceptor(interceptor)
@@ -686,10 +645,11 @@ public final class InterceptorTest {
   @Test public void networkInterceptorResponseMustHaveBody() throws Exception {
     server.enqueue(new MockResponse());
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        return chain.proceed(chain.request()).newBuilder().body(null).build();
-      }
+    Interceptor interceptor = chain -> {
+      Response response = chain.proceed(chain.request());
+      return response.newBuilder()
+          .body(null)
+          .build();
     };
     client = client.newBuilder()
         .addNetworkInterceptor(interceptor)
@@ -708,33 +668,38 @@ public final class InterceptorTest {
   }
 
   @Test public void connectTimeout() throws Exception {
-    Interceptor interceptor1 = new Interceptor() {
-      @Override public Response intercept(Chain chainA) throws IOException {
-        assertEquals(5000, chainA.connectTimeoutMillis());
+    Interceptor interceptor1 = chainA -> {
+      assertEquals(5000, chainA.connectTimeoutMillis());
 
-        Chain chainB = chainA.withConnectTimeout(100, TimeUnit.MILLISECONDS);
-        assertEquals(100, chainB.connectTimeoutMillis());
+      Interceptor.Chain chainB = chainA.withConnectTimeout(100, TimeUnit.MILLISECONDS);
+      assertEquals(100, chainB.connectTimeoutMillis());
 
-        return chainB.proceed(chainA.request());
-      }
+      return chainB.proceed(chainA.request());
     };
 
-    Interceptor interceptor2 = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        assertEquals(100, chain.connectTimeoutMillis());
-        return chain.proceed(chain.request());
-      }
+    Interceptor interceptor2 = chain -> {
+      assertEquals(100, chain.connectTimeoutMillis());
+      return chain.proceed(chain.request());
     };
 
+    ServerSocket serverSocket = new ServerSocket(0, 1);
+    // Fill backlog queue with this request so subsequent requests will be blocked.
+    new Socket().connect(serverSocket.getLocalSocketAddress());
+
     client = client.newBuilder()
         .connectTimeout(5, TimeUnit.SECONDS)
         .addInterceptor(interceptor1)
         .addInterceptor(interceptor2)
         .build();
 
-    Request request1 = new Request.Builder()
-        .url("http://" + TestUtil.UNREACHABLE_ADDRESS)
-        .build();
+    Request request1 =
+        new Request.Builder()
+            .url(
+                "http://"
+                    + serverSocket.getInetAddress().getCanonicalHostName()
+                    + ":"
+                    + serverSocket.getLocalPort())
+            .build();
     Call call = client.newCall(request1);
 
     try {
@@ -742,25 +707,23 @@ public final class InterceptorTest {
       fail();
     } catch (SocketTimeoutException expected) {
     }
+
+    serverSocket.close();
   }
 
   @Test public void chainWithReadTimeout() throws Exception {
-    Interceptor interceptor1 = new Interceptor() {
-      @Override public Response intercept(Chain chainA) throws IOException {
-        assertEquals(5000, chainA.readTimeoutMillis());
+    Interceptor interceptor1 = chainA -> {
+      assertEquals(5000, chainA.readTimeoutMillis());
 
-        Chain chainB = chainA.withReadTimeout(100, TimeUnit.MILLISECONDS);
-        assertEquals(100, chainB.readTimeoutMillis());
+      Interceptor.Chain chainB = chainA.withReadTimeout(100, TimeUnit.MILLISECONDS);
+      assertEquals(100, chainB.readTimeoutMillis());
 
-        return chainB.proceed(chainA.request());
-      }
+      return chainB.proceed(chainA.request());
     };
 
-    Interceptor interceptor2 = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        assertEquals(100, chain.readTimeoutMillis());
-        return chain.proceed(chain.request());
-      }
+    Interceptor interceptor2 = chain -> {
+      assertEquals(100, chain.readTimeoutMillis());
+      return chain.proceed(chain.request());
     };
 
     client = client.newBuilder()
@@ -787,22 +750,18 @@ public final class InterceptorTest {
   }
 
   @Test public void chainWithWriteTimeout() throws Exception {
-    Interceptor interceptor1 = new Interceptor() {
-      @Override public Response intercept(Chain chainA) throws IOException {
-        assertEquals(5000, chainA.writeTimeoutMillis());
+    Interceptor interceptor1 = chainA -> {
+      assertEquals(5000, chainA.writeTimeoutMillis());
 
-        Chain chainB = chainA.withWriteTimeout(100, TimeUnit.MILLISECONDS);
-        assertEquals(100, chainB.writeTimeoutMillis());
+      Interceptor.Chain chainB = chainA.withWriteTimeout(100, TimeUnit.MILLISECONDS);
+      assertEquals(100, chainB.writeTimeoutMillis());
 
-        return chainB.proceed(chainA.request());
-      }
+      return chainB.proceed(chainA.request());
     };
 
-    Interceptor interceptor2 = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        assertEquals(100, chain.writeTimeoutMillis());
-        return chain.proceed(chain.request());
-      }
+    Interceptor interceptor2 = chain -> {
+      assertEquals(100, chain.writeTimeoutMillis());
+      return chain.proceed(chain.request());
     };
 
     client = client.newBuilder()
@@ -830,19 +789,17 @@ public final class InterceptorTest {
   }
 
   @Test public void chainCanCancelCall() throws Exception {
-    final AtomicReference<Call> callRef = new AtomicReference<>();
+    AtomicReference<Call> callRef = new AtomicReference<>();
 
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        Call call = chain.call();
-        callRef.set(call);
+    Interceptor interceptor = chain -> {
+      Call call = chain.call();
+      callRef.set(call);
 
-        assertFalse(call.isCanceled());
-        call.cancel();
-        assertTrue(call.isCanceled());
+      assertFalse(call.isCanceled());
+      call.cancel();
+      assertTrue(call.isCanceled());
 
-        return chain.proceed(chain.request());
-      }
+      return chain.proceed(chain.request());
     };
 
     client = client.newBuilder()
@@ -863,7 +820,7 @@ public final class InterceptorTest {
     assertSame(call, callRef.get());
   }
 
-  private RequestBody uppercase(final RequestBody original) {
+  private RequestBody uppercase(RequestBody original) {
     return new RequestBody() {
       @Override public MediaType contentType() {
         return original.contentType();
@@ -882,7 +839,7 @@ public final class InterceptorTest {
     };
   }
 
-  private Sink uppercase(final BufferedSink original) {
+  private Sink uppercase(BufferedSink original) {
     return new ForwardingSink(original) {
       @Override public void write(Buffer source, long byteCount) throws IOException {
         original.writeUtf8(source.readUtf8(byteCount).toUpperCase(Locale.US));
@@ -895,7 +852,7 @@ public final class InterceptorTest {
         Okio.buffer(uppercase(original.source())));
   }
 
-  private static Source uppercase(final Source original) {
+  private static Source uppercase(Source original) {
     return new ForwardingSource(original) {
       @Override public long read(Buffer sink, long byteCount) throws IOException {
         Buffer mixedCase = new Buffer();
@@ -929,17 +886,15 @@ public final class InterceptorTest {
     private final BlockingQueue<Exception> exceptions = new LinkedBlockingQueue<>();
 
     public ExceptionCatchingExecutor() {
-      super(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
+      super(1, 1, 0, TimeUnit.SECONDS, new SynchronousQueue<>());
     }
 
-    @Override public void execute(final Runnable runnable) {
-      super.execute(new Runnable() {
-        @Override public void run() {
-          try {
-            runnable.run();
-          } catch (Exception e) {
-            exceptions.add(e);
-          }
+    @Override public void execute(Runnable runnable) {
+      super.execute(() -> {
+        try {
+          runnable.run();
+        } catch (Exception e) {
+          exceptions.add(e);
         }
       });
     }
diff --git a/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java b/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java
index f0b194713dc90dd597b0cb86aba704b2c179674e..1520bcaf8e966b994788c30add9111346390c03c 100644
--- a/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/MediaTypeTest.java
@@ -19,14 +19,13 @@ package okhttp3;
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.Collection;
-import okhttp3.internal.Util;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /**
@@ -60,7 +59,7 @@ public class MediaTypeTest {
     assertEquals("plain", mediaType.subtype());
     assertEquals("UTF-8", mediaType.charset().name());
     assertEquals("text/plain;boundary=foo;charset=utf-8", mediaType.toString());
-    assertTrue(mediaType.equals(parse("text/plain;boundary=foo;charset=utf-8")));
+    assertEquals(mediaType, parse("text/plain;boundary=foo;charset=utf-8"));
     assertEquals(mediaType.hashCode(),
         parse("text/plain;boundary=foo;charset=utf-8").hashCode());
   }
@@ -188,11 +187,11 @@ public class MediaTypeTest {
 
   @Test public void testDefaultCharset() throws Exception {
     MediaType noCharset = parse("text/plain");
-    assertEquals("UTF-8", noCharset.charset(Util.UTF_8).name());
+    assertEquals("UTF-8", noCharset.charset(UTF_8).name());
     assertEquals("US-ASCII", noCharset.charset(Charset.forName("US-ASCII")).name());
 
     MediaType charset = parse("text/plain; charset=iso-8859-1");
-    assertEquals("ISO-8859-1", charset.charset(Util.UTF_8).name());
+    assertEquals("ISO-8859-1", charset.charset(UTF_8).name());
     assertEquals("ISO-8859-1", charset.charset(Charset.forName("US-ASCII")).name());
   }
 
diff --git a/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java b/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java
index f936d56a80475096eeb1fd8b49c3ad8a884dd8dd..7696ed9978fac77f0c55cb66ca6c55293689b92c 100644
--- a/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/MultipartBodyTest.java
@@ -20,7 +20,7 @@ import okio.Buffer;
 import okio.BufferedSink;
 import org.junit.Test;
 
-import static okhttp3.internal.Util.UTF_8;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -271,4 +271,25 @@ public final class MultipartBodyTest {
     assertEquals(Headers.of("Foo", "Bar"), part1.headers());
     assertEquals("Baz", part1Buffer.readUtf8());
   }
+
+  @Test public void nonAsciiFilename() throws Exception {
+    String expected = ""
+        + "--AaB03x\r\n"
+        + "Content-Disposition: form-data; name=\"attachment\"; filename=\"resumé.pdf\"\r\n"
+        + "Content-Type: application/pdf; charset=utf-8\r\n"
+        + "Content-Length: 17\r\n"
+        + "\r\n"
+        + "Jesse’s Resumé\r\n"
+        + "--AaB03x--\r\n";
+
+    MultipartBody body = new MultipartBody.Builder("AaB03x")
+        .setType(MultipartBody.FORM)
+        .addFormDataPart("attachment", "resumé.pdf",
+            RequestBody.create(MediaType.parse("application/pdf"), "Jesse’s Resumé"))
+        .build();
+
+    Buffer buffer = new Buffer();
+    body.writeTo(buffer);
+    assertEquals(expected, buffer.readUtf8());
+  }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java b/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java
index b4ee085371a3bcce07bb2f1d255d130190b1874b..caf43a52db64dcd85087909afc16c71008bc31fb 100644
--- a/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/OkHttpClientTest.java
@@ -15,13 +15,13 @@
  */
 package okhttp3;
 
-import java.io.IOException;
 import java.net.CookieHandler;
 import java.net.CookieManager;
 import java.net.ProxySelector;
 import java.net.ResponseCache;
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLSocketFactory;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
 import org.junit.After;
@@ -97,11 +97,7 @@ public final class OkHttpClientTest {
   }
 
   @Test public void clonedInterceptorsListsAreIndependent() throws Exception {
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        return chain.proceed(chain.request());
-      }
-    };
+    Interceptor interceptor = chain -> chain.proceed(chain.request());
     OkHttpClient original = defaultClient();
     original.newBuilder()
         .addInterceptor(interceptor)
@@ -230,4 +226,13 @@ public final class OkHttpClientTest {
     Response response = client.newCall(request).execute();
     assertEquals("abc", response.body().string());
   }
+
+  @Test public void sslSocketFactorySetAsSocketFactory() throws Exception {
+    OkHttpClient.Builder builder = new OkHttpClient.Builder();
+    try {
+      builder.socketFactory(SSLSocketFactory.getDefault());
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java b/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java
index d34380cc4f0220832974bc75a47c93d4275354e8..715cbda3831838511cb612e845acff9c87256490 100644
--- a/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java
+++ b/okhttp-tests/src/test/java/okhttp3/RecordedResponse.java
@@ -19,6 +19,7 @@ import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
+import javax.annotation.Nullable;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -31,13 +32,13 @@ import static org.junit.Assert.assertTrue;
  */
 public final class RecordedResponse {
   public final Request request;
-  public final Response response;
-  public final WebSocket webSocket;
-  public final String body;
-  public final IOException failure;
+  public final @Nullable Response response;
+  public final @Nullable WebSocket webSocket;
+  public final @Nullable String body;
+  public final @Nullable IOException failure;
 
-  public RecordedResponse(Request request, Response response, WebSocket webSocket, String body,
-      IOException failure) {
+  public RecordedResponse(Request request, @Nullable Response response,
+      @Nullable WebSocket webSocket, @Nullable String body, @Nullable IOException failure) {
     this.request = request;
     this.response = response;
     this.webSocket = webSocket;
diff --git a/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java b/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java
index 6799300cdfc7d75bc70821a13290852ec72179d6..ee26e6819f5962d692f2785c37eee549ffac1500 100644
--- a/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java
+++ b/okhttp-tests/src/test/java/okhttp3/RecordingEventListener.java
@@ -217,7 +217,7 @@ public final class RecordingEventListener extends EventListener {
       this.inetAddressList = inetAddressList;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new DnsStart(call, domainName);
     }
   }
@@ -265,7 +265,7 @@ public final class RecordingEventListener extends EventListener {
       this.ioe = ioe;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new ConnectStart(call, inetSocketAddress, proxy);
     }
   }
@@ -284,7 +284,7 @@ public final class RecordingEventListener extends EventListener {
       this.handshake = handshake;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new SecureConnectStart(call);
     }
   }
@@ -306,7 +306,7 @@ public final class RecordingEventListener extends EventListener {
       this.connection = connection;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new ConnectionAcquired(call, connection);
     }
   }
@@ -322,7 +322,7 @@ public final class RecordingEventListener extends EventListener {
       super(call);
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new CallStart(call);
     }
   }
@@ -350,7 +350,7 @@ public final class RecordingEventListener extends EventListener {
       this.headerLength = headerLength;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new RequestHeadersStart(call);
     }
   }
@@ -369,7 +369,7 @@ public final class RecordingEventListener extends EventListener {
       this.bytesWritten = bytesWritten;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new RequestBodyStart(call);
     }
   }
@@ -388,7 +388,7 @@ public final class RecordingEventListener extends EventListener {
       this.headerLength = headerLength;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new RequestHeadersStart(call);
     }
   }
@@ -407,7 +407,7 @@ public final class RecordingEventListener extends EventListener {
       this.bytesRead = bytesRead;
     }
 
-    @Nullable @Override public CallEvent closes() {
+    @Override public @Nullable CallEvent closes() {
       return new ResponseBodyStart(call);
     }
   }
diff --git a/okhttp-tests/src/test/java/okhttp3/RequestTest.java b/okhttp-tests/src/test/java/okhttp3/RequestTest.java
index 50b7bcb53d73e8605ea885cd1ce7e43e8c3a4b8e..a335abc2dc6591c9324a6b1c651895f30abd3573 100644
--- a/okhttp-tests/src/test/java/okhttp3/RequestTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/RequestTest.java
@@ -22,10 +22,10 @@ import java.net.URI;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.UUID;
-import okhttp3.internal.Util;
 import okio.Buffer;
 import org.junit.Test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -34,7 +34,7 @@ import static org.junit.Assert.fail;
 public final class RequestTest {
   @Test public void string() throws Exception {
     MediaType contentType = MediaType.get("text/plain; charset=utf-8");
-    RequestBody body = RequestBody.create(contentType, "abc".getBytes(Util.UTF_8));
+    RequestBody body = RequestBody.create(contentType, "abc".getBytes(UTF_8));
     assertEquals(contentType, body.contentType());
     assertEquals(3, body.contentLength());
     assertEquals("616263", bodyToHex(body));
@@ -59,7 +59,7 @@ public final class RequestTest {
 
   @Test public void byteArray() throws Exception {
     MediaType contentType = MediaType.get("text/plain");
-    RequestBody body = RequestBody.create(contentType, "abc".getBytes(Util.UTF_8));
+    RequestBody body = RequestBody.create(contentType, "abc".getBytes(UTF_8));
     assertEquals(contentType, body.contentType());
     assertEquals(3, body.contentLength());
     assertEquals("616263", bodyToHex(body));
@@ -68,7 +68,7 @@ public final class RequestTest {
 
   @Test public void byteArrayRange() throws Exception {
     MediaType contentType = MediaType.get("text/plain");
-    RequestBody body = RequestBody.create(contentType, ".abcd".getBytes(Util.UTF_8), 1, 3);
+    RequestBody body = RequestBody.create(contentType, ".abcd".getBytes(UTF_8), 1, 3);
     assertEquals(contentType, body.contentType());
     assertEquals(3, body.contentLength());
     assertEquals("616263", bodyToHex(body));
diff --git a/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java b/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java
index 40f994c5266b9133c8ce28d5925e2ff4bb031c22..a64ac17d52129d49267f06882de016aa6fa6bce8 100644
--- a/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/ResponseBodyTest.java
@@ -27,6 +27,7 @@ import okio.ForwardingSource;
 import okio.Okio;
 import org.junit.Test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -256,7 +257,7 @@ public final class ResponseBodyTest {
     assertEquals(0xef, bytes[0] & 0xff);
     assertEquals(0xbb, bytes[1] & 0xff);
     assertEquals(0xbf, bytes[2] & 0xff);
-    assertEquals("hello", new String(bytes, 3, 5, "UTF-8"));
+    assertEquals("hello", new String(bytes, 3, 5, UTF_8));
   }
 
   @Test public void bytesClosesUnderlyingSource() throws IOException {
@@ -340,7 +341,7 @@ public final class ResponseBodyTest {
     assertEquals(0xef, bytes.read());
     assertEquals(0xbb, bytes.read());
     assertEquals(0xbf, bytes.read());
-    assertEquals("hello", exhaust(new InputStreamReader(bytes, "utf-8")));
+    assertEquals("hello", exhaust(new InputStreamReader(bytes, UTF_8)));
   }
 
   @Test public void byteStreamClosesUnderlyingSource() throws IOException {
diff --git a/okhttp-tests/src/test/java/okhttp3/SocksProxy.java b/okhttp-tests/src/test/java/okhttp3/SocksProxy.java
index d56241ac9112b2b61929fd0acc15f33e834ff07a..467517179817dc4f2c4926c5fb93180d9418d745 100644
--- a/okhttp-tests/src/test/java/okhttp3/SocksProxy.java
+++ b/okhttp-tests/src/test/java/okhttp3/SocksProxy.java
@@ -61,8 +61,7 @@ public final class SocksProxy {
 
   private ServerSocket serverSocket;
   private AtomicInteger connectionCount = new AtomicInteger();
-  private final Set<Socket> openSockets =
-      Collections.newSetFromMap(new ConcurrentHashMap<Socket, Boolean>());
+  private final Set<Socket> openSockets = Collections.newSetFromMap(new ConcurrentHashMap<>());
 
   public void play() throws IOException {
     serverSocket = new ServerSocket(0);
diff --git a/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java b/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java
index 963f45f333c4b31e4c6c18812d6299f96ee7be7c..aad4bfa34b918cddf9eb6ba338e6cbfd00d6bfc3 100644
--- a/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/URLConnectionTest.java
@@ -87,11 +87,12 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Locale.US;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static okhttp3.TestUtil.defaultClient;
-import static okhttp3.internal.Util.UTF_8;
 import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT;
 import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT;
 import static okhttp3.internal.huc.OkHttpURLConnection.SELECTED_PROTOCOL;
@@ -325,7 +326,7 @@ public final class URLConnectionTest {
     connection = urlFactory.open(server.url("/def").url());
     connection.setDoOutput(true);
     transferKind.setForRequest(connection, 4);
-    connection.getOutputStream().write("body".getBytes("UTF-8"));
+    connection.getOutputStream().write("body".getBytes(UTF_8));
     assertContent("abc", connection);
 
     assertEquals("body", server.takeRequest().getBody().readUtf8());
@@ -342,7 +343,7 @@ public final class URLConnectionTest {
     connection.setDoOutput(true);
     connection.setChunkedStreamingMode(100);
     OutputStream os = connection.getOutputStream();
-    os.write("OutputStream is no fun.".getBytes("UTF-8"));
+    os.write("OutputStream is no fun.".getBytes(UTF_8));
     os.close();
 
     try {
@@ -469,12 +470,14 @@ public final class URLConnectionTest {
     assertContent("This comes after a busted connection", connection2);
 
     // Check that a fresh connection was created, either immediately or after attempting reuse.
+    // We know that a fresh connection was created if the server recorded a request with sequence
+    // number 0. Since the client may have attempted to reuse the broken connection just before
+    // creating a fresh connection, the server may have recorded 2 requests at this point. The order
+    // of recording is non-deterministic.
     RecordedRequest requestAfter = server.takeRequest();
-    if (server.getRequestCount() == 3) {
-      requestAfter = server.takeRequest(); // The failure consumed a response.
-    }
-    // sequence number 0 means the HTTP socket connection was not reused
-    assertEquals(0, requestAfter.getSequenceNumber());
+    assertTrue(
+        requestAfter.getSequenceNumber() == 0
+            || server.getRequestCount() == 3 && server.takeRequest().getSequenceNumber() == 0);
   }
 
   enum WriteKind {BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS}
@@ -907,11 +910,11 @@ public final class URLConnectionTest {
 
   @Test public void contentDisagreesWithChunkedHeaderBodyTooShort() throws IOException {
     MockResponse mockResponse = new MockResponse();
-    mockResponse.setChunkedBody("abcde", 5);
+    mockResponse.setChunkedBody("abcdefg", 5);
 
     Buffer truncatedBody = new Buffer();
     Buffer fullBody = mockResponse.getBody();
-    truncatedBody.write(fullBody, fullBody.indexOf((byte) 'e'));
+    truncatedBody.write(fullBody, 4);
     mockResponse.setBody(truncatedBody);
 
     mockResponse.clearHeaders();
@@ -921,9 +924,9 @@ public final class URLConnectionTest {
     server.enqueue(mockResponse);
 
     try {
-      readAscii(urlFactory.open(server.url("/").url()).getInputStream(), 5);
+      readAscii(urlFactory.open(server.url("/").url()).getInputStream(), 7);
       fail();
-    } catch (ProtocolException expected) {
+    } catch (IOException expected) {
     }
   }
 
@@ -1538,7 +1541,7 @@ public final class URLConnectionTest {
     connection.setChunkedStreamingMode(0); // OkHttp does not honor specific chunk sizes.
     connection.setDoOutput(true);
     OutputStream outputStream = connection.getOutputStream();
-    outputStream.write(body.getBytes("US-ASCII"));
+    outputStream.write(body.getBytes(US_ASCII));
     assertEquals(200, connection.getResponseCode());
     connection.getInputStream().close();
 
@@ -1619,7 +1622,7 @@ public final class URLConnectionTest {
     connection = urlFactory.open(server.url("/").url());
     connection.setDoOutput(true);
     OutputStream outputStream = connection.getOutputStream();
-    outputStream.write(body.getBytes("UTF-8"));
+    outputStream.write(body.getBytes(UTF_8));
     outputStream.close();
     assertEquals(200, connection.getResponseCode());
     connection.getInputStream().close();
@@ -2302,7 +2305,7 @@ public final class URLConnectionTest {
     connection.addRequestProperty("Content-Type", "text/plain; charset=utf-8");
     connection.addRequestProperty("Transfer-Encoding", "identity");
     OutputStream outputStream = connection.getOutputStream();
-    outputStream.write("ABCD".getBytes("UTF-8"));
+    outputStream.write("ABCD".getBytes(UTF_8));
     outputStream.close();
     assertEquals("Page 2", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
 
@@ -2487,7 +2490,7 @@ public final class URLConnectionTest {
 
     HttpURLConnection connection = urlFactory.open(server.url("/").url());
     connection.setRequestMethod("POST");
-    connection.getOutputStream().write("Hello".getBytes("UTF-8"));
+    connection.getOutputStream().write("Hello".getBytes(UTF_8));
 
     assertEquals(200, connection.getResponseCode());
     assertEquals("Body", readAscii(connection.getInputStream(), Integer.MAX_VALUE));
@@ -2505,7 +2508,7 @@ public final class URLConnectionTest {
     HttpURLConnection connection = urlFactory.open(server.url("/").url());
     connection.setRequestMethod("POST");
     connection.setChunkedStreamingMode(0);
-    connection.getOutputStream().write("Hello".getBytes("UTF-8"));
+    connection.getOutputStream().write("Hello".getBytes(UTF_8));
 
     assertEquals(408, connection.getResponseCode());
     assertEquals(1, server.getRequestCount());
@@ -2583,7 +2586,7 @@ public final class URLConnectionTest {
     connection = urlFactory.open(server.url("/").url());
     connection.setRequestProperty("Transfer-encoding", "chunked");
     connection.setDoOutput(true);
-    connection.getOutputStream().write("ABC".getBytes("UTF-8"));
+    connection.getOutputStream().write("ABC".getBytes(UTF_8));
     assertEquals(200, connection.getResponseCode());
 
     RecordedRequest request = server.takeRequest();
@@ -2703,7 +2706,7 @@ public final class URLConnectionTest {
 
     connection = urlFactory.open(server.url("/").url());
     connection.setDoOutput(true);
-    byte[] upload = "def".getBytes("UTF-8");
+    byte[] upload = "def".getBytes(UTF_8);
 
     if (transferKind == TransferKind.CHUNKED) {
       connection.setChunkedStreamingMode(0);
@@ -2717,7 +2720,7 @@ public final class URLConnectionTest {
 
     out.flush(); // Dubious but permitted.
     try {
-      out.write("ghi".getBytes("UTF-8"));
+      out.write("ghi".getBytes(UTF_8));
       fail();
     } catch (IOException expected) {
     }
@@ -3527,11 +3530,7 @@ public final class URLConnectionTest {
   }
 
   @Test public void interceptorsNotInvoked() throws Exception {
-    Interceptor interceptor = new Interceptor() {
-      @Override public Response intercept(Chain chain) {
-        throw new AssertionError();
-      }
-    };
+    Interceptor interceptor = chain -> { throw new AssertionError(); };
     urlFactory.setClient(urlFactory.client().newBuilder()
         .addInterceptor(interceptor)
         .addNetworkInterceptor(interceptor)
@@ -3612,11 +3611,7 @@ public final class URLConnectionTest {
   /** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */
   @Test public void unexpectedExceptionSync() throws Exception {
     urlFactory.setClient(urlFactory.client().newBuilder()
-        .dns(new Dns() {
-          @Override public List<InetAddress> lookup(String hostname) {
-            throw new RuntimeException("boom!");
-          }
-        })
+        .dns(hostname -> { throw new RuntimeException("boom!"); })
         .build());
 
     server.enqueue(new MockResponse());
@@ -3633,11 +3628,7 @@ public final class URLConnectionTest {
   /** Confirm that runtime exceptions thrown inside of OkHttp propagate to the caller. */
   @Test public void unexpectedExceptionAsync() throws Exception {
     urlFactory.setClient(urlFactory.client().newBuilder()
-        .dns(new Dns() {
-          @Override public List<InetAddress> lookup(String hostname) {
-            throw new RuntimeException("boom!");
-          }
-        })
+        .dns(hostname -> { throw new RuntimeException("boom!"); })
         .build());
 
     server.enqueue(new MockResponse());
@@ -3752,7 +3743,7 @@ public final class URLConnectionTest {
   }
 
   enum TransferKind {
-    CHUNKED() {
+    CHUNKED {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setChunkedBody(content, chunkSize);
       }
@@ -3761,7 +3752,7 @@ public final class URLConnectionTest {
         connection.setChunkedStreamingMode(5);
       }
     },
-    FIXED_LENGTH() {
+    FIXED_LENGTH {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setBody(content);
       }
@@ -3770,7 +3761,7 @@ public final class URLConnectionTest {
         connection.setFixedLengthStreamingMode(contentLength);
       }
     },
-    END_OF_STREAM() {
+    END_OF_STREAM {
       @Override void setBody(MockResponse response, Buffer content, int chunkSize) {
         response.setBody(content);
         response.setSocketPolicy(DISCONNECT_AT_END);
diff --git a/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java b/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java
index 605d606fb077358b56e38ab3e24c203b957f307b..d9b3d442f396a467fb5aa64a20d200e5aa33b1c2 100644
--- a/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/WebPlatformUrlTest.java
@@ -46,7 +46,7 @@ public final class WebPlatformUrlTest {
     }
   }
 
-  @Parameter(0)
+  @Parameter
   public WebPlatformUrlTestData testData;
 
   private static final List<String> HTTP_URL_SCHEMES
@@ -58,8 +58,6 @@ public final class WebPlatformUrlTest {
       "Parsing: <http://f:\n/c> against <http://example.org/foo/bar>",
       "Parsing: <http://f:999999/c> against <http://example.org/foo/bar>",
       "Parsing: <http://192.0x00A80001> against <about:blank>",
-      // This test fails on Java 7 but passes on Java 8. See HttpUrlTest.hostWithTrailingDot().
-      "Parsing: <http://%30%78%63%30%2e%30%32%35%30.01%2e> against <http://other.com/>",
       "Parsing: <http://%30%78%63%30%2e%30%32%35%30.01> against <http://other.com/>",
       "Parsing: <http://192.168.0.257> against <http://other.com/>",
       "Parsing: <http://0Xc0.0250.01> against <http://other.com/>"
diff --git a/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java b/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java
index 603f221397d9b3d816893cc73f33465305edd55b..09adba67f4bd12e68579ec8a93eb59c603d5a8ff 100644
--- a/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/WholeOperationTimeoutTest.java
@@ -256,6 +256,29 @@ public final class WholeOperationTimeoutTest {
     }
   }
 
+  @Test
+  public void timeoutFollowingRedirectOnNewConnection() throws Exception {
+    MockWebServer otherServer = new MockWebServer();
+
+    server.enqueue(
+        new MockResponse()
+            .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+            .setHeader("Location", otherServer.url("/")));
+
+    otherServer.enqueue(new MockResponse().setHeadersDelay(500, TimeUnit.MILLISECONDS));
+
+    Request request = new Request.Builder().url(server.url("/")).build();
+
+    Call call = client.newCall(request);
+    call.timeout().timeout(250, TimeUnit.MILLISECONDS);
+    try {
+      Response response = call.execute();
+      fail();
+    } catch (IOException e) {
+      assertTrue(call.isCanceled());
+    }
+  }
+
   @Test public void noTimeout() throws Exception {
     server.enqueue(new MockResponse()
         .setHeadersDelay(250, TimeUnit.MILLISECONDS)
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java b/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java
index af92f5eb8409c3c013ba862f42cab30e4b5625a6..dacf8ddaffc5752fb3cf31205d76c8c17624c27b 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/UtilTest.java
@@ -1,18 +1,18 @@
- /*
-  * Copyright (C) 2012 The Android Open Source Project
-  *
-  * Licensed under the Apache License, Version 2.0 (the "License");
-  * you may not use this file except in compliance with the License.
-  * You may obtain a copy of the License at
-  *
-  *      http://www.apache.org/licenses/LICENSE-2.0
-  *
-  * Unless required by applicable law or agreed to in writing, software
-  * distributed under the License is distributed on an "AS IS" BASIS,
-  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  * See the License for the specific language governing permissions and
-  * limitations under the License.
-  */
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package okhttp3.internal;
 
 import java.util.Collections;
@@ -21,17 +21,9 @@ import java.util.Map;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
- public class UtilTest {
-  @Test public void testAssertionError() {
-    NullPointerException nullPointerException = new NullPointerException();
-    AssertionError ae = Util.assertionError("npe", nullPointerException);
-    assertSame(nullPointerException, ae.getCause());
-    assertEquals("npe", ae.getMessage());
-  }
-
+public final class UtilTest {
   @Test public void immutableMap() {
     Map<String, String> map = new LinkedHashMap<>();
     map.put("a", "A");
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java b/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java
index 04ba57126bdf978b7b093c9ad769ac36782c4033..1d5bf3073127faa49640c3b8f9e73d6bc80c83ad 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/cache/DiskLruCacheTest.java
@@ -33,7 +33,6 @@ import okio.Okio;
 import okio.Source;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -129,35 +128,35 @@ public final class DiskLruCacheTest {
     try {
       key = "has_space ";
       cache.edit(key);
-      fail("Exepcting an IllegalArgumentException as the key was invalid.");
+      fail("Expecting an IllegalArgumentException as the key was invalid.");
     } catch (IllegalArgumentException iae) {
       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
     }
     try {
       key = "has_CR\r";
       cache.edit(key);
-      fail("Exepcting an IllegalArgumentException as the key was invalid.");
+      fail("Expecting an IllegalArgumentException as the key was invalid.");
     } catch (IllegalArgumentException iae) {
       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
     }
     try {
       key = "has_LF\n";
       cache.edit(key);
-      fail("Exepcting an IllegalArgumentException as the key was invalid.");
+      fail("Expecting an IllegalArgumentException as the key was invalid.");
     } catch (IllegalArgumentException iae) {
       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
     }
     try {
       key = "has_invalid/";
       cache.edit(key);
-      fail("Exepcting an IllegalArgumentException as the key was invalid.");
+      fail("Expecting an IllegalArgumentException as the key was invalid.");
     } catch (IllegalArgumentException iae) {
       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
     }
     try {
       key = "has_invalid\u2603";
       cache.edit(key);
-      fail("Exepcting an IllegalArgumentException as the key was invalid.");
+      fail("Expecting an IllegalArgumentException as the key was invalid.");
     } catch (IllegalArgumentException iae) {
       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
     }
@@ -165,7 +164,7 @@ public final class DiskLruCacheTest {
       key = "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long_"
           + "this_is_way_too_long_this_is_way_too_long_this_is_way_too_long";
       cache.edit(key);
-      fail("Exepcting an IllegalArgumentException as the key was too long.");
+      fail("Expecting an IllegalArgumentException as the key was too long.");
     } catch (IllegalArgumentException iae) {
       assertEquals("keys must match regex [a-z0-9_-]{1,120}: \"" + key + "\"", iae.getMessage());
     }
@@ -1249,7 +1248,7 @@ public final class DiskLruCacheTest {
     a.close();
     iterator.remove();
 
-    assertEquals(null, cache.get("a"));
+    assertNull(cache.get("a"));
   }
 
   @Test public void iteratorRemoveBeforeNext() throws Exception {
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java b/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java
index cbffa87d0992fe864d7715ccac05d8ebe393acbe..8ed0c911f79a6e2af5c18d6f10f9e9ad99146fbc 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/cache2/RelayTest.java
@@ -227,14 +227,12 @@ public final class RelayTest {
 
   /** Returns a callable that reads all of source, closes it, and returns the bytes. */
   private Callable<ByteString> sourceReader(final Source source) {
-    return new Callable<ByteString>() {
-      @Override public ByteString call() throws Exception {
-        Buffer buffer = new Buffer();
-        while (source.read(buffer, 16384) != -1) {
-        }
-        source.close();
-        return buffer.readByteString();
+    return () -> {
+      Buffer buffer = new Buffer();
+      while (source.read(buffer, 16384) != -1) {
       }
+      source.close();
+      return buffer.readByteString();
     };
   }
 
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java b/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java
index e366e4081b258a4a775e34198b45bf3c1af4b311..25e6b86a4a240ad6751c8cbe156a9c0dca14dfed 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/connection/ConnectionSpecSelectorTest.java
@@ -75,7 +75,8 @@ public class ConnectionSpecSelectorTest {
   public void retryableSSLHandshakeException() throws Exception {
     ConnectionSpecSelector connectionSpecSelector =
         createConnectionSpecSelector(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS);
-    SSLSocket socket = createSocketWithEnabledProtocols(TlsVersion.TLS_1_1, TlsVersion.TLS_1_0);
+    SSLSocket socket = createSocketWithEnabledProtocols(
+        TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0);
     connectionSpecSelector.configureSecureSocket(socket);
 
     boolean retry = connectionSpecSelector.connectionFailed(RETRYABLE_EXCEPTION);
@@ -85,20 +86,21 @@ public class ConnectionSpecSelectorTest {
 
   @Test
   public void someFallbacksSupported() throws Exception {
-    ConnectionSpec sslV3 =
-        new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
-            .tlsVersions(TlsVersion.SSL_3_0)
-            .build();
+    ConnectionSpec sslV3 = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+        .tlsVersions(TlsVersion.SSL_3_0)
+        .build();
 
     ConnectionSpecSelector connectionSpecSelector = createConnectionSpecSelector(
         ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, sslV3);
 
-    TlsVersion[] enabledSocketTlsVersions = {TlsVersion.TLS_1_1, TlsVersion.TLS_1_0};
+    TlsVersion[] enabledSocketTlsVersions = {
+        TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0
+    };
     SSLSocket socket = createSocketWithEnabledProtocols(enabledSocketTlsVersions);
 
     // MODERN_TLS is used here.
     connectionSpecSelector.configureSecureSocket(socket);
-    assertEnabledProtocols(socket, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0);
+    assertEnabledProtocols(socket, TlsVersion.TLS_1_2);
 
     boolean retry = connectionSpecSelector.connectionFailed(RETRYABLE_EXCEPTION);
     assertTrue(retry);
@@ -107,7 +109,7 @@ public class ConnectionSpecSelectorTest {
     // COMPATIBLE_TLS is used here.
     socket = createSocketWithEnabledProtocols(enabledSocketTlsVersions);
     connectionSpecSelector.configureSecureSocket(socket);
-    assertEnabledProtocols(socket, TlsVersion.TLS_1_0);
+    assertEnabledProtocols(socket, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0);
 
     retry = connectionSpecSelector.connectionFailed(RETRYABLE_EXCEPTION);
     assertFalse(retry);
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java
index aad2bfc8ddff07371c1051ecd11736e72f888fcf..0a89a1a56799d0db22bda1bea964cb1bff081678 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http/DisconnectTest.java
@@ -117,17 +117,15 @@ public final class DisconnectTest {
     responseBody.close();
   }
 
-  private void disconnectLater(final HttpURLConnection connection, final int delayMillis) {
-    Thread interruptingCow = new Thread() {
-      @Override public void run() {
-        try {
-          sleep(delayMillis);
-          connection.disconnect();
-        } catch (InterruptedException e) {
-          throw new RuntimeException(e);
-        }
+  private void disconnectLater(HttpURLConnection connection, int delayMillis) {
+    Thread interruptingCow = new Thread(() -> {
+      try {
+        Thread.sleep(delayMillis);
+        connection.disconnect();
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
       }
-    };
+    });
     interruptingCow.start();
   }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java
index ed2b05085f71127161b1b6c8f59f3ba2ce39b353..8926e10c314d01ae7c5339a2a0deba2accea72a0 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http/ThreadInterruptTest.java
@@ -123,18 +123,16 @@ public final class ThreadInterruptTest {
     responseBody.close();
   }
 
-  private void interruptLater(final int delayMillis) {
-    final Thread toInterrupt = Thread.currentThread();
-    Thread interruptingCow = new Thread() {
-      @Override public void run() {
-        try {
-          sleep(delayMillis);
-          toInterrupt.interrupt();
-        } catch (InterruptedException e) {
-          throw new AssertionError(e);
-        }
+  private void interruptLater(int delayMillis) {
+    Thread toInterrupt = Thread.currentThread();
+    Thread interruptingCow = new Thread(() -> {
+      try {
+        Thread.sleep(delayMillis);
+        toInterrupt.interrupt();
+      } catch (InterruptedException e) {
+        throw new AssertionError(e);
       }
-    };
+    });
     interruptingCow.start();
   }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java
index 454f5eec997b35e0f736817a6208d3bd4ba6a1e1..f8bd931282c17d88867a1a32471ff9c472734825 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/HpackTest.java
@@ -26,6 +26,7 @@ import org.junit.Test;
 import static okhttp3.TestUtil.headerEntries;
 import static okio.ByteString.decodeHex;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 public final class HpackTest {
@@ -384,7 +385,7 @@ public final class HpackTest {
     assertEquals(0, hpackReader.headerCount);
     assertEquals(0, hpackReader.dynamicTableByteCount);
 
-    assertEquals(null, hpackReader.dynamicTable[readerHeaderTableLength() - 1]);
+    assertNull(hpackReader.dynamicTable[readerHeaderTableLength() - 1]);
 
     assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList());
   }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java
index ea5b8ed60b91fc234899d512323bdb1bf963a935..f457f8f82283928b1ce357a966426803f74671f8 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2ConnectionTest.java
@@ -18,7 +18,6 @@ package okhttp3.internal.http2;
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.Socket;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -37,14 +36,18 @@ import okio.Sink;
 import okio.Source;
 import okio.Utf8;
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.rules.Timeout;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static okhttp3.TestUtil.headerEntries;
 import static okhttp3.TestUtil.repeat;
+import static okhttp3.internal.Internal.initializeInstanceForTests;
 import static okhttp3.internal.Util.EMPTY_BYTE_ARRAY;
+import static okhttp3.internal.Util.EMPTY_HEADERS;
 import static okhttp3.internal.http2.Http2Connection.Listener.REFUSE_INCOMING_STREAMS;
 import static okhttp3.internal.http2.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
 import static okhttp3.internal.http2.Settings.ENABLE_PUSH;
@@ -52,6 +55,7 @@ import static okhttp3.internal.http2.Settings.HEADER_TABLE_SIZE;
 import static okhttp3.internal.http2.Settings.INITIAL_WINDOW_SIZE;
 import static okhttp3.internal.http2.Settings.MAX_CONCURRENT_STREAMS;
 import static okhttp3.internal.http2.Settings.MAX_FRAME_SIZE;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -62,6 +66,10 @@ public final class Http2ConnectionTest {
 
   @Rule public final TestRule timeout = new Timeout(5_000);
 
+  @Before public void setup() {
+    initializeInstanceForTests();
+  }
+
   @After public void tearDown() throws Exception {
     peer.close();
   }
@@ -188,11 +196,11 @@ public final class Http2ConnectionTest {
     InFrame data1 = peer.takeFrame();
     assertEquals(Http2.TYPE_DATA, data1.type);
     assertEquals(3, data1.streamId);
-    assertTrue(Arrays.equals("abcde".getBytes("UTF-8"), data1.data));
+    assertArrayEquals("abcde".getBytes(UTF_8), data1.data);
     InFrame data2 = peer.takeFrame();
     assertEquals(Http2.TYPE_DATA, data2.type);
     assertEquals(3, data2.streamId);
-    assertTrue(Arrays.equals("fghi".getBytes("UTF-8"), data2.data));
+    assertArrayEquals("fghi".getBytes(UTF_8), data2.data);
   }
 
   /**
@@ -204,7 +212,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM 3
-    peer.sendFrame().headers(3, headerEntries("a", "apple"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "apple"));
     peer.sendFrame().data(false, 3, data(1024), 1024);
     peer.acceptFrame(); // RST_STREAM
     peer.sendFrame().data(true, 3, data(1024), 1024);
@@ -276,7 +284,7 @@ public final class Http2ConnectionTest {
     InFrame data1 = peer.takeFrame();
     assertEquals(Http2.TYPE_DATA, data1.type);
     assertEquals(3, data1.streamId);
-    assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data));
+    assertArrayEquals("abcdef".getBytes(UTF_8), data1.data);
   }
 
   @Test public void readSendsWindowUpdateHttp2() throws Exception {
@@ -287,7 +295,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     for (int i = 0; i < 3; i++) {
       // Send frames of summing to size 50, which is windowUpdateThreshold.
       peer.sendFrame().data(false, 3, data(24), 24);
@@ -331,7 +339,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.sendFrame().data(true, 3, data(0), 0);
     peer.play();
 
@@ -352,7 +360,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // DATA
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.play();
 
     // Play it back.
@@ -377,7 +385,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // DATA
     peer.acceptFrame(); // DATA
     peer.play();
@@ -403,7 +411,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     final List<Header> expectedRequestHeaders = Arrays.asList(
         new Header(Header.TARGET_METHOD, "GET"),
         new Header(Header.TARGET_SCHEME, "https"),
@@ -414,7 +422,7 @@ public final class Http2ConnectionTest {
     final List<Header> expectedResponseHeaders = Arrays.asList(
         new Header(Header.RESPONSE_STATUS, "200")
     );
-    peer.sendFrame().synReply(true, 2, expectedResponseHeaders);
+    peer.sendFrame().headers(true, 2, expectedResponseHeaders);
     peer.sendFrame().data(true, 3, data(0), 0);
     peer.play();
 
@@ -461,7 +469,7 @@ public final class Http2ConnectionTest {
         new Header(Header.TARGET_AUTHORITY, "squareup.com"),
         new Header(Header.TARGET_PATH, "/cached")
     ));
-    peer.sendFrame().synReply(true, 2, Arrays.asList(
+    peer.sendFrame().headers(true, 2, Arrays.asList(
         new Header(Header.RESPONSE_STATUS, "200")
     ));
     peer.acceptFrame(); // RST_STREAM
@@ -514,7 +522,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // DATA
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5);
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 1, 0); // PING
@@ -539,106 +547,193 @@ public final class Http2ConnectionTest {
     assertEquals(-1, synStream.associatedStreamId);
     assertEquals(headerEntries("b", "banana"), synStream.headerBlock);
     InFrame requestData = peer.takeFrame();
-    assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data));
+    assertArrayEquals("c3po".getBytes(UTF_8), requestData.data);
   }
 
-  static final class RecordingHeadersListener implements Header.Listener {
-    final ArrayDeque<Headers> receivedHeaders = new ArrayDeque<>();
+  @Test public void serverFinishesStreamWithHeaders() throws Exception {
+    // write the mocking script
+    peer.sendFrame().settings(new Settings());
+    peer.acceptFrame(); // ACK
+    peer.acceptFrame(); // SYN_STREAM
+    peer.acceptFrame(); // PING
+    peer.sendFrame().headers(true, 3, headerEntries("headers", "bam"));
+    peer.sendFrame().ping(true, 1, 0); // PONG
+    peer.play();
 
-    @Override public void onHeaders(Headers headers) {
-      receivedHeaders.add(headers);
-    }
+    // play it back
+    Http2Connection connection = connect(peer);
+    Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), false);
+    connection.writePingAndAwaitPong();
+    assertEquals(Headers.of("headers", "bam"), stream.takeHeaders());
+    assertEquals(EMPTY_HEADERS, stream.trailers());
+    assertEquals(0, connection.openStreamCount());
 
-    public List<Headers> takeAll() {
-      List<Headers> result = new ArrayList<>();
-      for (Headers headers; (headers = receivedHeaders.poll()) != null; ) {
-        result.add(headers);
-      }
-      return result;
-    }
+    // verify the peer received what was expected
+    InFrame synStream = peer.takeFrame();
+    assertEquals(Http2.TYPE_HEADERS, synStream.type);
+    assertFalse(synStream.outFinished);
+    assertEquals(3, synStream.streamId);
+    assertEquals(-1, synStream.associatedStreamId);
+    assertEquals(headerEntries("a", "artichaut"), synStream.headerBlock);
   }
 
-  @Test public void clientReadsHeadersDataHeadersData() throws Exception {
+  @Test public void serverWritesTrailersAndClientReadsTrailers() throws Exception {
+    // write the mocking script
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
-    peer.sendFrame().data(false, 3, new Buffer().writeUtf8("robot"), 5);
-    peer.sendFrame().synReply(false, 3, headerEntries("b", "banana"));
-    peer.sendFrame().data(true, 3, new Buffer().writeUtf8("cyborg"), 6);
+    peer.sendFrame().headers(false, 3, headerEntries("headers", "bam"));
     peer.acceptFrame(); // PING
+    peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom"));
     peer.sendFrame().ping(true, 1, 0); // PONG
     peer.play();
 
-    RecordingHeadersListener headersListener = new RecordingHeadersListener();
-
+    // play it back
     Http2Connection connection = connect(peer);
-    Http2Stream stream = connection.newStream(headerEntries(), false);
-    stream.setHeadersListener(headersListener);
-    assertStreamData("robotcyborg", stream.getSource());
-    assertEquals(Arrays.asList(Headers.of("a", "android"), Headers.of("b", "banana")),
-        headersListener.takeAll());
+    Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), false);
+    assertEquals(Headers.of("headers", "bam"), stream.takeHeaders());
     connection.writePingAndAwaitPong();
+    assertEquals(Headers.of("trailers", "boom"), stream.trailers());
     assertEquals(0, connection.openStreamCount());
+
+    // verify the peer received what was expected
+    InFrame synStream = peer.takeFrame();
+    assertEquals(Http2.TYPE_HEADERS, synStream.type);
+    assertFalse(synStream.outFinished);
+    assertEquals(3, synStream.streamId);
+    assertEquals(-1, synStream.associatedStreamId);
+    assertEquals(headerEntries("a", "artichaut"), synStream.headerBlock);
+  }
+
+  @Test public void serverWritesTrailersWithData() throws Exception {
+    // We buffer some outbound data and headers and confirm that the END_STREAM flag comes with the
+    // headers (and not with the data).
+
+    // write the mocking script for the client
+    peer.setClient(true);
+
+    // Write the mocking script.
+    peer.sendFrame().settings(new Settings());
+    peer.sendFrame().headers(true, 3, headerEntries("client", "abc"));
+    peer.acceptFrame(); // ACK
+    peer.acceptFrame(); // HEADERS STREAM 3
+    peer.acceptFrame(); // DATA STREAM 3 "abcde"
+    peer.acceptFrame(); // HEADERS STREAM 3
+    peer.play();
+
+    // Play it back.
+    Http2Connection connection = connect(peer);
+    Http2Stream stream = connection.newStream(headerEntries("a", "android"), true);
+    stream.enqueueTrailers(Headers.of("foo", "bar"));
+    BufferedSink sink = Okio.buffer(stream.getSink());
+    sink.writeUtf8("abcdefghi");
+    sink.close();
+
+    // Verify the peer received what was expected.
+    InFrame headers1 = peer.takeFrame();
+    assertEquals(Http2.TYPE_HEADERS, headers1.type);
+    InFrame data1 = peer.takeFrame();
+    assertEquals(Http2.TYPE_DATA, data1.type);
+    assertEquals(3, data1.streamId);
+    assertArrayEquals("abcdefghi".getBytes(UTF_8), data1.data);
+    assertFalse(data1.inFinished);
+    InFrame headers2 = peer.takeFrame();
+    assertEquals(Http2.TYPE_HEADERS, headers2.type);
+    assertTrue(headers2.inFinished);
   }
 
-  @Test public void clientReadsHeadersDataPingPongHeadersData() throws Exception {
+  @Test public void clientCannotReadTrailersWithoutExhaustingStream() throws Exception {
+    // write the mocking script
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
     peer.sendFrame().data(false, 3, new Buffer().writeUtf8("robot"), 5);
+    peer.sendFrame().headers(true, 3, headerEntries("trailers", "boom"));
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 1, 0); // PONG
-    peer.sendFrame().synReply(false, 3, headerEntries("b", "banana"));
-    peer.sendFrame().data(true, 3, new Buffer().writeUtf8("cyborg"), 6);
+    peer.play();
+
+    // play it back
+    Http2Connection connection = connect(peer);
+    Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), true);
+    connection.writePingAndAwaitPong();
+    try {
+      stream.trailers();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+  }
+
+  @Test public void clientCannotReadTrailersIfTheStreamFailed() throws Exception {
+    // write the mocking script
+    peer.sendFrame().settings(new Settings());
+    peer.acceptFrame(); // ACK
+    peer.acceptFrame(); // SYN_STREAM
+    peer.sendFrame().rstStream(3, ErrorCode.PROTOCOL_ERROR);
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 1, 0); // PONG
     peer.play();
 
-    RecordingHeadersListener headersListener = new RecordingHeadersListener();
-
+    // play it back
     Http2Connection connection = connect(peer);
-    Http2Stream stream = connection.newStream(headerEntries(), false);
-    stream.setHeadersListener(headersListener);
-    BufferedSource source = Okio.buffer(stream.getSource());
-
-    assertStreamPrefix("robot", source);
-    assertEquals(Arrays.asList(Headers.of("a", "android")), headersListener.takeAll());
+    Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), true);
     connection.writePingAndAwaitPong();
+    try {
+      stream.trailers();
+      fail();
+    } catch (StreamResetException expected) {
+    }
+  }
 
-    assertStreamPrefix("cyborg", source);
-    assertEquals(Arrays.asList(Headers.of("b", "banana")), headersListener.takeAll());
-    connection.writePingAndAwaitPong();
+  @Test public void serverCannotEnqueueTrailersAfterFinishingTheStream() throws Exception {
+    peer.setClient(true);
+    peer.sendFrame().settings(new Settings());
+    peer.acceptFrame(); // ACK
+    peer.acceptFrame(); // PING
+    peer.sendFrame().ping(true, 1, 0);
+    peer.play();
 
-    assertEquals(0, connection.openStreamCount());
+    // Play it back.
+    Http2Connection connection = connect(peer);
+    connection.writePingAndAwaitPong();
+    Http2Stream stream = connection.newStream(headerEntries("a", "android"), true);
+    // finish the stream
+    stream.writeHeaders(headerEntries("b", "berserk"), true, false);
+    try {
+      stream.enqueueTrailers(Headers.of("trailers", "boom"));
+      fail();
+    } catch (IllegalStateException expected) {
+    }
   }
 
-  @Test public void clientReadsHeadersDataHeadersClose() throws Exception {
+  @Test public void noTrailersFrameYieldsEmptyTrailers() throws Exception {
+    // write the mocking script
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
-    peer.sendFrame().data(false, 3, new Buffer().writeUtf8("robot"), 5);
+    peer.sendFrame().headers(false, 3, headerEntries("headers", "bam"));
+    peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5);
     peer.acceptFrame(); // PING
-    peer.sendFrame().synReply(false, 3, headerEntries("b", "banana"));
     peer.sendFrame().ping(true, 1, 0); // PONG
     peer.play();
 
-    RecordingHeadersListener headersListener = new RecordingHeadersListener();
-
+    // play it back
     Http2Connection connection = connect(peer);
-    Http2Stream stream = connection.newStream(headerEntries(), false);
-    stream.setHeadersListener(headersListener);
+    Http2Stream stream = connection.newStream(headerEntries("a", "artichaut"), false);
     BufferedSource source = Okio.buffer(stream.getSource());
-
-    assertStreamPrefix("robot", source);
-    assertEquals(Arrays.asList(Headers.of("a", "android")), headersListener.takeAll());
     connection.writePingAndAwaitPong();
-    source.close();
-    assertEquals(Arrays.asList(Headers.of("b", "banana")), headersListener.takeAll());
-
+    assertEquals(Headers.of("headers", "bam"), stream.takeHeaders());
+    assertEquals("robot", source.readUtf8(5));
+    assertEquals(EMPTY_HEADERS, stream.trailers());
     assertEquals(0, connection.openStreamCount());
+
+    // verify the peer received what was expected
+    InFrame synStream = peer.takeFrame();
+    assertEquals(Http2.TYPE_HEADERS, synStream.type);
+    assertFalse(synStream.outFinished);
+    assertEquals(3, synStream.streamId);
+    assertEquals(-1, synStream.associatedStreamId);
+    assertEquals(headerEntries("a", "artichaut"), synStream.headerBlock);
   }
 
   @Test public void serverReadsHeadersDataHeaders() throws Exception {
@@ -648,7 +743,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // DATA
     peer.acceptFrame(); // HEADERS
-    peer.sendFrame().synReply(true, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(true, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 1, 0); // PING
     peer.play();
@@ -659,7 +754,7 @@ public final class Http2ConnectionTest {
     BufferedSink out = Okio.buffer(stream.getSink());
     out.writeUtf8("c3po");
     out.close();
-    stream.writeHeaders(headerEntries("e", "elephant"), true);
+    stream.writeHeaders(headerEntries("e", "elephant"), false, false);
     connection.writePingAndAwaitPong();
     assertEquals(0, connection.openStreamCount());
 
@@ -671,7 +766,7 @@ public final class Http2ConnectionTest {
     assertEquals(-1, synStream.associatedStreamId);
     assertEquals(headerEntries("b", "banana"), synStream.headerBlock);
     InFrame requestData = peer.takeFrame();
-    assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data));
+    assertArrayEquals("c3po".getBytes(UTF_8), requestData.data);
 
     InFrame nextFrame = peer.takeFrame();
     assertEquals(headerEntries("e", "elephant"), nextFrame.headerBlock);
@@ -683,7 +778,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // PING
-    peer.sendFrame().synReply(true, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(true, 3, headerEntries("a", "android"));
     peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
@@ -883,7 +978,7 @@ public final class Http2ConnectionTest {
     // write the mocking script
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
-    peer.sendFrame().synReply(false, 41, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 41, headerEntries("a", "android"));
     peer.sendFrame().ping(false, 2, 0);
     peer.acceptFrame(); // PING
     peer.play();
@@ -1014,7 +1109,7 @@ public final class Http2ConnectionTest {
     assertFalse(synStream.outFinished);
     InFrame data = peer.takeFrame();
     assertEquals(Http2.TYPE_DATA, data.type);
-    assertTrue(Arrays.equals("square".getBytes("UTF-8"), data.data));
+    assertArrayEquals("square".getBytes(UTF_8), data.data);
     InFrame fin = peer.takeFrame();
     assertEquals(Http2.TYPE_DATA, fin.type);
     assertTrue(fin.inFinished);
@@ -1029,7 +1124,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("b", "banana"));
+    peer.sendFrame().headers(false, 3, headerEntries("b", "banana"));
     peer.sendFrame().data(true, 3, new Buffer().writeUtf8("square"), 6);
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 1, 0);
@@ -1055,9 +1150,9 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // PING
-    peer.sendFrame().synReply(false, 3, headerEntries("b", "banana"));
+    peer.sendFrame().headers(false, 3, headerEntries("b", "banana"));
     peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
@@ -1079,7 +1174,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.sendFrame().data(true, 3, new Buffer().writeUtf8("robot"), 5);
     peer.sendFrame().data(true, 3, new Buffer().writeUtf8("c3po"), 4);
     peer.acceptFrame(); // RST_STREAM
@@ -1110,7 +1205,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("b", "banana"));
+    peer.sendFrame().headers(false, 3, headerEntries("b", "banana"));
     peer.sendFrame().data(false, 3, new Buffer().write(new byte[dataLength]), dataLength);
     peer.sendFrame().data(false, 3, new Buffer().write(new byte[dataLength]), dataLength);
     peer.sendFrame().data(false, 3, new Buffer().write(new byte[dataLength]), dataLength);
@@ -1210,7 +1305,7 @@ public final class Http2ConnectionTest {
     InFrame data1 = peer.takeFrame();
     assertEquals(Http2.TYPE_DATA, data1.type);
     assertEquals(3, data1.streamId);
-    assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data));
+    assertArrayEquals("abcdef".getBytes(UTF_8), data1.data);
   }
 
   @Test public void sendGoAway() throws Exception {
@@ -1220,7 +1315,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // SYN_STREAM 1
     peer.acceptFrame(); // GOAWAY
     peer.acceptFrame(); // PING
-    peer.sendFrame().synStream(false, 2, 0, headerEntries("b", "b")); // Should be ignored!
+    peer.sendFrame().headers(false, 2, headerEntries("b", "b")); // Should be ignored!
     peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
@@ -1326,7 +1421,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // RST_STREAM
     peer.play();
 
@@ -1361,7 +1456,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 1, 0);
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // DATA
     peer.acceptFrame(); // RST_STREAM
     peer.play();
@@ -1403,7 +1498,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 1, 0);
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // PING
     peer.sendFrame().ping(true, 3, 0);
     peer.acceptFrame(); // DATA
@@ -1442,7 +1537,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // DATA
     peer.play();
 
@@ -1460,7 +1555,7 @@ public final class Http2ConnectionTest {
     assertEquals(Http2.TYPE_HEADERS, peer.takeFrame().type);
     InFrame data = peer.takeFrame();
     assertEquals(Http2.TYPE_DATA, data.type);
-    assertTrue(Arrays.equals("abcdefghij".getBytes("UTF-8"), data.data));
+    assertArrayEquals("abcdefghij".getBytes(UTF_8), data.data);
     assertTrue(data.inFinished);
   }
 
@@ -1470,8 +1565,8 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // PING
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
-    peer.sendFrame().headers(3, headerEntries("c", "c3po"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("c", "c3po"));
     peer.sendFrame().ping(true, 1, 0);
     peer.play();
 
@@ -1494,10 +1589,10 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // PING
-    peer.sendFrame().ping(true, 1, 0); // PING
-    peer.sendFrame().synReply(true, 3, headerEntries("c", "cola"));
+    peer.sendFrame().headers(true, 3, headerEntries("c", "cola"));
+    peer.sendFrame().ping(true, 1, 0); // PONG
     peer.play();
 
     // play it back
@@ -1506,7 +1601,7 @@ public final class Http2ConnectionTest {
     stream.getConnection().flush();
     assertEquals(Headers.of("a", "android"), stream.takeHeaders());
     connection.writePingAndAwaitPong();
-    assertEquals(Headers.of("c", "cola"), stream.takeHeaders());
+    assertEquals(Headers.of("c", "cola"), stream.trailers());
 
     // verify the peer received what was expected
     assertEquals(Http2.TYPE_HEADERS, peer.takeFrame().type);
@@ -1521,7 +1616,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     for (int i = 0; i < 3; i++) {
       // Send frames of summing to size 50, which is windowUpdateThreshold.
       peer.sendFrame().data(false, 3, data(24), 24);
@@ -1565,7 +1660,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.sendFrame().data(true, 3, data(0), 0);
     peer.play();
 
@@ -1586,7 +1681,7 @@ public final class Http2ConnectionTest {
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
     peer.acceptFrame(); // DATA
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.play();
 
     // Play it back.
@@ -1608,7 +1703,7 @@ public final class Http2ConnectionTest {
     peer.sendFrame().settings(new Settings());
     peer.acceptFrame(); // ACK
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.sendFrame().data(false, 3, data(1024), 1024);
     peer.truncateLastFrame(8 + 100);
     peer.play();
@@ -1671,7 +1766,7 @@ public final class Http2ConnectionTest {
   @Test public void remoteOmitsInitialSettings() throws Exception {
     // Write the mocking script. Note no SETTINGS frame is sent or acknowledged.
     peer.acceptFrame(); // SYN_STREAM
-    peer.sendFrame().synReply(false, 3, headerEntries("a", "android"));
+    peer.sendFrame().headers(false, 3, headerEntries("a", "android"));
     peer.acceptFrame(); // GOAWAY
     peer.play();
 
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java
index 24ec759c2d9261123ca934b37fd71a0053cafb99..684d2226933a6c6c387d92682886e38c4266109a 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/Http2Test.java
@@ -240,7 +240,7 @@ public final class Http2Test {
       @Override public void settings(boolean clearPrevious, Settings settings) {
         assertFalse(clearPrevious); // No clearPrevious in HTTP/2.
         assertEquals(reducedTableSizeBytes, settings.getHeaderTableSize());
-        assertEquals(false, settings.getEnablePush(true));
+        assertFalse(settings.getEnablePush(true));
       }
     });
   }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java
index eb0acf496313d9bf1c572477632de3174322ba40..6a8bcd0be49da0a1e05a0994c7c7b10f3e8e9bf7 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/HttpOverHttp2Test.java
@@ -46,7 +46,6 @@ import okhttp3.RecordingHostnameVerifier;
 import okhttp3.Request;
 import okhttp3.RequestBody;
 import okhttp3.Response;
-import okhttp3.Route;
 import okhttp3.TestLogHandler;
 import okhttp3.TestUtil;
 import okhttp3.internal.DoubleInetAddressDns;
@@ -75,6 +74,7 @@ import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static okhttp3.TestUtil.defaultClient;
@@ -149,7 +149,9 @@ public final class HttpOverHttp2Test {
     http2Logger.removeHandler(http2Handler);
     http2Logger.setLevel(previousLevel);
 
+    // Ensure a fresh connection pool for the next test.
     client.connectionPool().evictAll();
+    assertEquals(0, client.connectionPool().connectionCount());
   }
 
   @Test public void get() throws Exception {
@@ -186,7 +188,7 @@ public final class HttpOverHttp2Test {
   }
 
   @Test public void noDefaultContentLengthOnStreamingPost() throws Exception {
-    final byte[] postBytes = "FGHIJ".getBytes(Util.UTF_8);
+    byte[] postBytes = "FGHIJ".getBytes(UTF_8);
 
     server.enqueue(new MockResponse().setBody("ABCDE"));
 
@@ -213,7 +215,7 @@ public final class HttpOverHttp2Test {
   }
 
   @Test public void userSuppliedContentLengthHeader() throws Exception {
-    final byte[] postBytes = "FGHIJ".getBytes(Util.UTF_8);
+    byte[] postBytes = "FGHIJ".getBytes(UTF_8);
 
     server.enqueue(new MockResponse().setBody("ABCDE"));
 
@@ -244,7 +246,7 @@ public final class HttpOverHttp2Test {
   }
 
   @Test public void closeAfterFlush() throws Exception {
-    final byte[] postBytes = "FGHIJ".getBytes(Util.UTF_8);
+    byte[] postBytes = "FGHIJ".getBytes(UTF_8);
 
     server.enqueue(new MockResponse().setBody("ABCDE"));
 
@@ -352,7 +354,7 @@ public final class HttpOverHttp2Test {
     waitForDataFrames(Http2Connection.OKHTTP_CLIENT_WINDOW_SIZE);
 
     // Cancel the call and close the response body. This should discard the buffered data and update
-    // the connnection flow-control window.
+    // the connection flow-control window.
     call1.cancel();
     response1.close();
 
@@ -752,13 +754,7 @@ public final class HttpOverHttp2Test {
     cookieJar.assertResponseCookies("a=b; path=/");
   }
 
-  /** https://github.com/square/okhttp/issues/1191 */
-  @Ignore // TODO: recover gracefully when a connection is shutdown.
   @Test public void cancelWithStreamNotCompleted() throws Exception {
-    // Ensure that the (shared) connection pool is in a consistent state.
-    client.connectionPool().evictAll();
-    assertEquals(0, client.connectionPool().connectionCount());
-
     server.enqueue(new MockResponse()
         .setBody("abc"));
     server.enqueue(new MockResponse()
@@ -900,8 +896,10 @@ public final class HttpOverHttp2Test {
     Call call = client.newCall(new Request.Builder()
         .url(server.url("/"))
         .build());
+    CountDownLatch latch = new CountDownLatch(1);
     call.enqueue(new Callback() {
       @Override public void onFailure(Call call1, IOException e) {
+        latch.countDown();
       }
 
       @Override public void onResponse(Call call1, Response response) {
@@ -909,6 +907,7 @@ public final class HttpOverHttp2Test {
     });
     assertEquals(expectedSequenceNumber, server.takeRequest().getSequenceNumber());
     call.cancel();
+    latch.await();
   }
 
   @Test public void noRecoveryFromRefusedStreamWithRetryDisabled() throws Exception {
@@ -955,18 +954,16 @@ public final class HttpOverHttp2Test {
     server.enqueue(new MockResponse()
         .setBody("ABC"));
 
-    final CountDownLatch latch = new CountDownLatch(1);
-    final BlockingQueue<String> responses = new SynchronousQueue<>();
-    okhttp3.Authenticator authenticator = new okhttp3.Authenticator() {
-      @Override public Request authenticate(Route route, Response response) throws IOException {
-        responses.offer(response.body().string());
-        try {
-          latch.await();
-        } catch (InterruptedException e) {
-          throw new AssertionError();
-        }
-        return response.request();
+    CountDownLatch latch = new CountDownLatch(1);
+    BlockingQueue<String> responses = new SynchronousQueue<>();
+    okhttp3.Authenticator authenticator = (route, response) -> {
+      responses.offer(response.body().string());
+      try {
+        latch.await();
+      } catch (InterruptedException e) {
+        throw new AssertionError();
       }
+      return response.request();
     };
 
     OkHttpClient blockingAuthClient = client.newBuilder()
@@ -1314,7 +1311,7 @@ public final class HttpOverHttp2Test {
         .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END)
         .setBody("DEF"));
 
-    final BlockingQueue<String> bodies = new SynchronousQueue<>();
+    BlockingQueue<String> bodies = new SynchronousQueue<>();
     Callback callback = new Callback() {
       @Override public void onResponse(Call call, Response response) throws IOException {
         bodies.add(response.body().string());
@@ -1343,10 +1340,7 @@ public final class HttpOverHttp2Test {
 
     server.useHttps(handshakeCertificates.sslSocketFactory(), true);
 
-    // Force a fresh connection pool for the test.
-    client.connectionPool().evictAll();
-
-    final QueueDispatcher queueDispatcher = new QueueDispatcher();
+    QueueDispatcher queueDispatcher = new QueueDispatcher();
     queueDispatcher.enqueueResponse(new MockResponse()
         .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
         .clearHeaders());
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java
index 53642c0ea08828ae4d7c011897870870fb851d46..e92445f266043c4524e05cb1fb66619bbe3776b1 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/HuffmanTest.java
@@ -16,14 +16,13 @@
 package okhttp3.internal.http2;
 
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.Random;
 import okio.Buffer;
 import okio.ByteString;
 import org.junit.Test;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 /** Original version of this class was lifted from {@code com.twitter.hpack.HuffmanTest}. */
 public final class HuffmanTest {
@@ -45,6 +44,6 @@ public final class HuffmanTest {
     assertEquals(buffer.size(), Huffman.get().encodedLength(data));
 
     byte[] decodedBytes = Huffman.get().decode(buffer.readByteArray());
-    assertTrue(Arrays.equals(data.toByteArray(), decodedBytes));
+    assertArrayEquals(data.toByteArray(), decodedBytes);
   }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java
index 376c46d1f355e930c6e12e309767188a289a2283..1ade5b1dded99c0129e30dd430b3ce88bda20be1 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/MockHttp2Peer.java
@@ -110,14 +110,12 @@ public final class MockHttp2Peer implements Closeable {
     serverSocket.setReuseAddress(false);
     serverSocket.bind(new InetSocketAddress("localhost", 0), 1);
     port = serverSocket.getLocalPort();
-    executor.execute(new Runnable() {
-      @Override public void run() {
-        try {
-          readAndWriteFrames();
-        } catch (IOException e) {
-          Util.closeQuietly(MockHttp2Peer.this);
-          logger.info(MockHttp2Peer.this + " done: " + e.getMessage());
-        }
+    executor.execute(() -> {
+      try {
+        readAndWriteFrames();
+      } catch (IOException e) {
+        Util.closeQuietly(MockHttp2Peer.this);
+        logger.info(MockHttp2Peer.this + " done: " + e.getMessage());
       }
     });
   }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java b/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java
index a26506ca273ad22da313d0f49aef8612fb955b76..46ec0a82ab8abe1733046456c60e067931afd40b 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/http2/SettingsTest.java
@@ -20,6 +20,7 @@ import org.junit.Test;
 import static okhttp3.internal.http2.Settings.DEFAULT_INITIAL_WINDOW_SIZE;
 import static okhttp3.internal.http2.Settings.MAX_CONCURRENT_STREAMS;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 public final class SettingsTest {
   @Test public void unsetField() {
@@ -33,9 +34,9 @@ public final class SettingsTest {
     settings.set(Settings.HEADER_TABLE_SIZE, 8096);
     assertEquals(8096, settings.getHeaderTableSize());
 
-    assertEquals(true, settings.getEnablePush(true));
+    assertTrue(settings.getEnablePush(true));
     settings.set(Settings.ENABLE_PUSH, 1);
-    assertEquals(true, settings.getEnablePush(false));
+    assertTrue(settings.getEnablePush(false));
     settings.clear();
 
     assertEquals(-3, settings.getMaxConcurrentStreams(-3));
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/platform/JdkWithJettyBootPlatformTest.java b/okhttp-tests/src/test/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatformTest.java
similarity index 89%
rename from okhttp-tests/src/test/java/okhttp3/internal/platform/JdkWithJettyBootPlatformTest.java
rename to okhttp-tests/src/test/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatformTest.java
index afe7145a01f8830bfdaf34cee7641cf36f63222c..bc981e9a364adfafa80c97424c551dcff4f18bb6 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/platform/JdkWithJettyBootPlatformTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatformTest.java
@@ -21,11 +21,11 @@ import static okhttp3.internal.platform.PlatformTest.getPlatform;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
 
-public class JdkWithJettyBootPlatformTest {
+public class Jdk8WithJettyBootPlatformTest {
   @Test
   public void testBuildsWithJettyBoot() {
     assumeTrue(getPlatform().equals("jdk-with-jetty-boot"));
 
-    assertNotNull(JdkWithJettyBootPlatform.buildIfSupported());
+    assertNotNull(Jdk8WithJettyBootPlatform.buildIfSupported());
   }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/platform/OptionalMethodTest.java b/okhttp-tests/src/test/java/okhttp3/internal/platform/OptionalMethodTest.java
deleted file mode 100644
index 7e249bd61f4d9f46db1ac41b1fed34ebb5cecbc8..0000000000000000000000000000000000000000
--- a/okhttp-tests/src/test/java/okhttp3/internal/platform/OptionalMethodTest.java
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package okhttp3.internal.platform;
-
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-/**
- * Tests for {@link OptionalMethod}.
- */
-public class OptionalMethodTest {
-  @SuppressWarnings("unused")
-  private static class BaseClass {
-    public String stringMethod() {
-      return "string";
-    }
-
-    public void voidMethod() {
-    }
-  }
-
-  @SuppressWarnings("unused")
-  private static class SubClass1 extends BaseClass {
-    public String subclassMethod() {
-      return "subclassMethod1";
-    }
-
-    public String methodWithArgs(String arg) {
-      return arg;
-    }
-  }
-
-  @SuppressWarnings("unused")
-  private static class SubClass2 extends BaseClass {
-    public int subclassMethod() {
-      return 1234;
-    }
-
-    public String methodWithArgs(String arg) {
-      return arg;
-    }
-
-    public void throwsException() throws IOException {
-      throw new IOException();
-    }
-
-    public void throwsRuntimeException() throws Exception {
-      throw new NumberFormatException();
-    }
-
-    protected void nonPublic() {
-    }
-  }
-
-  private final static OptionalMethod<BaseClass> STRING_METHOD_RETURNS_ANY =
-      new OptionalMethod<>(null, "stringMethod");
-  private final static OptionalMethod<BaseClass> STRING_METHOD_RETURNS_STRING =
-      new OptionalMethod<>(String.class, "stringMethod");
-  private final static OptionalMethod<BaseClass> STRING_METHOD_RETURNS_INT =
-      new OptionalMethod<>(Integer.TYPE, "stringMethod");
-  private final static OptionalMethod<BaseClass> VOID_METHOD_RETURNS_ANY =
-      new OptionalMethod<>(null, "voidMethod");
-  private final static OptionalMethod<BaseClass> VOID_METHOD_RETURNS_VOID =
-      new OptionalMethod<>(Void.TYPE, "voidMethod");
-  private final static OptionalMethod<BaseClass> SUBCLASS_METHOD_RETURNS_ANY =
-      new OptionalMethod<>(null, "subclassMethod");
-  private final static OptionalMethod<BaseClass> SUBCLASS_METHOD_RETURNS_STRING =
-      new OptionalMethod<>(String.class, "subclassMethod");
-  private final static OptionalMethod<BaseClass> SUBCLASS_METHOD_RETURNS_INT =
-      new OptionalMethod<>(Integer.TYPE, "subclassMethod");
-  private final static OptionalMethod<BaseClass> METHOD_WITH_ARGS_WRONG_PARAMS =
-      new OptionalMethod<>(null, "methodWithArgs", Integer.class);
-  private final static OptionalMethod<BaseClass> METHOD_WITH_ARGS_CORRECT_PARAMS =
-      new OptionalMethod<>(null, "methodWithArgs", String.class);
-
-  private final static OptionalMethod<BaseClass> THROWS_EXCEPTION =
-      new OptionalMethod<>(null, "throwsException");
-  private final static OptionalMethod<BaseClass> THROWS_RUNTIME_EXCEPTION =
-      new OptionalMethod<>(null, "throwsRuntimeException");
-  private final static OptionalMethod<BaseClass> NON_PUBLIC =
-      new OptionalMethod<>(null, "nonPublic");
-
-  @Test
-  public void isSupported() throws Exception {
-    {
-      BaseClass base = new BaseClass();
-      assertTrue(STRING_METHOD_RETURNS_ANY.isSupported(base));
-      assertTrue(STRING_METHOD_RETURNS_STRING.isSupported(base));
-      assertFalse(STRING_METHOD_RETURNS_INT.isSupported(base));
-      assertTrue(VOID_METHOD_RETURNS_ANY.isSupported(base));
-      assertTrue(VOID_METHOD_RETURNS_VOID.isSupported(base));
-      assertFalse(SUBCLASS_METHOD_RETURNS_ANY.isSupported(base));
-      assertFalse(SUBCLASS_METHOD_RETURNS_STRING.isSupported(base));
-      assertFalse(SUBCLASS_METHOD_RETURNS_INT.isSupported(base));
-      assertFalse(METHOD_WITH_ARGS_WRONG_PARAMS.isSupported(base));
-      assertFalse(METHOD_WITH_ARGS_CORRECT_PARAMS.isSupported(base));
-    }
-    {
-      SubClass1 subClass1 = new SubClass1();
-      assertTrue(STRING_METHOD_RETURNS_ANY.isSupported(subClass1));
-      assertTrue(STRING_METHOD_RETURNS_STRING.isSupported(subClass1));
-      assertFalse(STRING_METHOD_RETURNS_INT.isSupported(subClass1));
-      assertTrue(VOID_METHOD_RETURNS_ANY.isSupported(subClass1));
-      assertTrue(VOID_METHOD_RETURNS_VOID.isSupported(subClass1));
-      assertTrue(SUBCLASS_METHOD_RETURNS_ANY.isSupported(subClass1));
-      assertTrue(SUBCLASS_METHOD_RETURNS_STRING.isSupported(subClass1));
-      assertFalse(SUBCLASS_METHOD_RETURNS_INT.isSupported(subClass1));
-      assertFalse(METHOD_WITH_ARGS_WRONG_PARAMS.isSupported(subClass1));
-      assertTrue(METHOD_WITH_ARGS_CORRECT_PARAMS.isSupported(subClass1));
-    }
-    {
-      SubClass2 subClass2 = new SubClass2();
-      assertTrue(STRING_METHOD_RETURNS_ANY.isSupported(subClass2));
-      assertTrue(STRING_METHOD_RETURNS_STRING.isSupported(subClass2));
-      assertFalse(STRING_METHOD_RETURNS_INT.isSupported(subClass2));
-      assertTrue(VOID_METHOD_RETURNS_ANY.isSupported(subClass2));
-      assertTrue(VOID_METHOD_RETURNS_VOID.isSupported(subClass2));
-      assertTrue(SUBCLASS_METHOD_RETURNS_ANY.isSupported(subClass2));
-      assertFalse(SUBCLASS_METHOD_RETURNS_STRING.isSupported(subClass2));
-      assertTrue(SUBCLASS_METHOD_RETURNS_INT.isSupported(subClass2));
-      assertFalse(METHOD_WITH_ARGS_WRONG_PARAMS.isSupported(subClass2));
-      assertTrue(METHOD_WITH_ARGS_CORRECT_PARAMS.isSupported(subClass2));
-    }
-  }
-
-  @Test
-  public void invoke() throws Exception {
-    {
-      BaseClass base = new BaseClass();
-      assertEquals("string", STRING_METHOD_RETURNS_STRING.invoke(base));
-      assertEquals("string", STRING_METHOD_RETURNS_ANY.invoke(base));
-      assertErrorOnInvoke(STRING_METHOD_RETURNS_INT, base);
-      assertNull(VOID_METHOD_RETURNS_ANY.invoke(base));
-      assertNull(VOID_METHOD_RETURNS_VOID.invoke(base));
-      assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_ANY, base);
-      assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_STRING, base);
-      assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_INT, base);
-      assertErrorOnInvoke(METHOD_WITH_ARGS_WRONG_PARAMS, base);
-      assertErrorOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, base);
-    }
-    {
-      SubClass1 subClass1 = new SubClass1();
-      assertEquals("string", STRING_METHOD_RETURNS_STRING.invoke(subClass1));
-      assertEquals("string", STRING_METHOD_RETURNS_ANY.invoke(subClass1));
-      assertErrorOnInvoke(STRING_METHOD_RETURNS_INT, subClass1);
-      assertNull(VOID_METHOD_RETURNS_ANY.invoke(subClass1));
-      assertNull(VOID_METHOD_RETURNS_VOID.invoke(subClass1));
-      assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_ANY.invoke(subClass1));
-      assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_STRING.invoke(subClass1));
-      assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_INT, subClass1);
-      assertErrorOnInvoke(METHOD_WITH_ARGS_WRONG_PARAMS, subClass1);
-      assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invoke(subClass1, "arg"));
-    }
-
-    {
-      SubClass2 subClass2 = new SubClass2();
-      assertEquals("string", STRING_METHOD_RETURNS_STRING.invoke(subClass2));
-      assertEquals("string", STRING_METHOD_RETURNS_ANY.invoke(subClass2));
-      assertErrorOnInvoke(STRING_METHOD_RETURNS_INT, subClass2);
-      assertNull(VOID_METHOD_RETURNS_ANY.invoke(subClass2));
-      assertNull(VOID_METHOD_RETURNS_VOID.invoke(subClass2));
-      assertEquals(1234, SUBCLASS_METHOD_RETURNS_ANY.invoke(subClass2));
-      assertErrorOnInvoke(SUBCLASS_METHOD_RETURNS_STRING, subClass2);
-      assertEquals(1234, SUBCLASS_METHOD_RETURNS_INT.invoke(subClass2));
-      assertErrorOnInvoke(METHOD_WITH_ARGS_WRONG_PARAMS, subClass2);
-      assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invoke(subClass2, "arg"));
-    }
-  }
-
-  @Test
-  public void invokeBadArgs() throws Exception {
-    SubClass1 subClass1 = new SubClass1();
-    assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1); // no args
-    assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, 123);
-    assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, true);
-    assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1,
-        new Object());
-    assertIllegalArgumentExceptionOnInvoke(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, "one",
-        "two");
-  }
-
-  @Test
-  public void invokeWithException() throws Exception {
-    SubClass2 subClass2 = new SubClass2();
-    try {
-      THROWS_EXCEPTION.invoke(subClass2);
-    } catch (InvocationTargetException expected) {
-      assertTrue(expected.getTargetException() instanceof IOException);
-    }
-
-    try {
-      THROWS_RUNTIME_EXCEPTION.invoke(subClass2);
-    } catch (InvocationTargetException expected) {
-      assertTrue(expected.getTargetException() instanceof NumberFormatException);
-    }
-  }
-
-  @Test
-  public void invokeNonPublic() throws Exception {
-    SubClass2 subClass2 = new SubClass2();
-    assertFalse(NON_PUBLIC.isSupported(subClass2));
-    assertErrorOnInvoke(NON_PUBLIC, subClass2);
-  }
-
-  @Test
-  public void invokeOptional() throws Exception {
-    {
-      BaseClass base = new BaseClass();
-      assertEquals("string", STRING_METHOD_RETURNS_STRING.invokeOptional(base));
-      assertEquals("string", STRING_METHOD_RETURNS_ANY.invokeOptional(base));
-      assertNull(STRING_METHOD_RETURNS_INT.invokeOptional(base));
-      assertNull(VOID_METHOD_RETURNS_ANY.invokeOptional(base));
-      assertNull(VOID_METHOD_RETURNS_VOID.invokeOptional(base));
-      assertNull(SUBCLASS_METHOD_RETURNS_ANY.invokeOptional(base));
-      assertNull(SUBCLASS_METHOD_RETURNS_STRING.invokeOptional(base));
-      assertNull(SUBCLASS_METHOD_RETURNS_INT.invokeOptional(base));
-      assertNull(METHOD_WITH_ARGS_WRONG_PARAMS.invokeOptional(base));
-      assertNull(METHOD_WITH_ARGS_CORRECT_PARAMS.invokeOptional(base));
-    }
-    {
-      SubClass1 subClass1 = new SubClass1();
-      assertEquals("string", STRING_METHOD_RETURNS_STRING.invokeOptional(subClass1));
-      assertEquals("string", STRING_METHOD_RETURNS_ANY.invokeOptional(subClass1));
-      assertNull(STRING_METHOD_RETURNS_INT.invokeOptional(subClass1));
-      assertNull(VOID_METHOD_RETURNS_ANY.invokeOptional(subClass1));
-      assertNull(VOID_METHOD_RETURNS_VOID.invokeOptional(subClass1));
-      assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_ANY.invokeOptional(subClass1));
-      assertEquals("subclassMethod1", SUBCLASS_METHOD_RETURNS_STRING.invokeOptional(subClass1));
-      assertNull(SUBCLASS_METHOD_RETURNS_INT.invokeOptional(subClass1));
-      assertNull(METHOD_WITH_ARGS_WRONG_PARAMS.invokeOptional(subClass1));
-      assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invokeOptional(subClass1, "arg"));
-    }
-
-    {
-      SubClass2 subClass2 = new SubClass2();
-      assertEquals("string", STRING_METHOD_RETURNS_STRING.invokeOptional(subClass2));
-      assertEquals("string", STRING_METHOD_RETURNS_ANY.invokeOptional(subClass2));
-      assertNull(STRING_METHOD_RETURNS_INT.invokeOptional(subClass2));
-      assertNull(VOID_METHOD_RETURNS_ANY.invokeOptional(subClass2));
-      assertNull(VOID_METHOD_RETURNS_VOID.invokeOptional(subClass2));
-      assertEquals(1234, SUBCLASS_METHOD_RETURNS_ANY.invokeOptional(subClass2));
-      assertNull(SUBCLASS_METHOD_RETURNS_STRING.invokeOptional(subClass2));
-      assertEquals(1234, SUBCLASS_METHOD_RETURNS_INT.invokeOptional(subClass2));
-      assertNull(METHOD_WITH_ARGS_WRONG_PARAMS.invokeOptional(subClass2));
-      assertEquals("arg", METHOD_WITH_ARGS_CORRECT_PARAMS.invokeOptional(subClass2, "arg"));
-    }
-  }
-
-  @Test
-  public void invokeOptionalBadArgs() throws Exception {
-    SubClass1 subClass1 = new SubClass1();
-    assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS,
-        subClass1); // no args
-    assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1, 123);
-    assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1,
-        true);
-    assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1,
-        new Object());
-    assertIllegalArgumentExceptionOnInvokeOptional(METHOD_WITH_ARGS_CORRECT_PARAMS, subClass1,
-        "one", "two");
-  }
-
-  @Test
-  public void invokeOptionalWithException() throws Exception {
-    SubClass2 subClass2 = new SubClass2();
-    try {
-      THROWS_EXCEPTION.invokeOptional(subClass2);
-    } catch (InvocationTargetException expected) {
-      assertTrue(expected.getTargetException() instanceof IOException);
-    }
-
-    try {
-      THROWS_RUNTIME_EXCEPTION.invokeOptional(subClass2);
-    } catch (InvocationTargetException expected) {
-      assertTrue(expected.getTargetException() instanceof NumberFormatException);
-    }
-  }
-
-  @Test
-  @Ignore("Despite returning false for isSupported, invocation actually succeeds.")
-  public void invokeOptionalNonPublic() throws Exception {
-    SubClass2 subClass2 = new SubClass2();
-    assertFalse(NON_PUBLIC.isSupported(subClass2));
-    assertErrorOnInvokeOptional(NON_PUBLIC, subClass2);
-  }
-
-  private static <T> void assertErrorOnInvoke(
-      OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception {
-    try {
-      optionalMethod.invoke(base, args);
-    } catch (Error expected) {
-      return;
-    }
-    fail();
-  }
-
-  private static <T> void assertIllegalArgumentExceptionOnInvoke(
-      OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception {
-    try {
-      optionalMethod.invoke(base, args);
-      fail();
-    } catch (IllegalArgumentException expected) {
-    }
-  }
-
-  private static <T> void assertErrorOnInvokeOptional(
-      OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception {
-    try {
-      optionalMethod.invokeOptional(base, args);
-    } catch (Error expected) {
-      return;
-    }
-    fail();
-  }
-
-  private static <T> void assertIllegalArgumentExceptionOnInvokeOptional(
-      OptionalMethod<T> optionalMethod, T base, Object... args) throws Exception {
-    try {
-      optionalMethod.invokeOptional(base, args);
-      fail();
-    } catch (IllegalArgumentException expected) {
-    }
-  }
-}
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java b/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java
index ffb116eea7a35db35e56a4676ab99bc078b2773d..256afe67fec974641ee1beaaa3a4feaa43c07dc9 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/tls/CertificatePinnerChainValidationTest.java
@@ -37,8 +37,8 @@ import okhttp3.internal.platform.Platform;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
 import okhttp3.mockwebserver.SocketPolicy;
-import okhttp3.tls.HeldCertificate;
 import okhttp3.tls.HandshakeCertificates;
+import okhttp3.tls.HeldCertificate;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -335,8 +335,7 @@ public final class CertificatePinnerChainValidationTest {
     // http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/2c1c21d11e58/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java#l596
     String keystoreType = getPlatform().equals("jdk9") ? "JKS" : null;
     X509KeyManager x509KeyManager = newKeyManager(keystoreType, heldCertificate, intermediates);
-    X509TrustManager trustManager = newTrustManager(
-        keystoreType, Collections.<X509Certificate>emptyList());
+    X509TrustManager trustManager = newTrustManager(keystoreType, Collections.emptyList());
     SSLContext sslContext = Platform.get().getSSLContext();
     sslContext.init(new KeyManager[] { x509KeyManager }, new TrustManager[] { trustManager },
         new SecureRandom());
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java b/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java
index ff3fb12270848af1eafe71a8f2775c24dec578f8..e88b9fef2548218d78081806600dadcf8423b0d2 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/tls/ClientAuthTest.java
@@ -36,8 +36,8 @@ import okhttp3.Request;
 import okhttp3.Response;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
-import okhttp3.tls.HeldCertificate;
 import okhttp3.tls.HandshakeCertificates;
+import okhttp3.tls.HeldCertificate;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -47,7 +47,7 @@ import static okhttp3.internal.platform.PlatformTest.getPlatform;
 import static okhttp3.tls.internal.TlsUtil.newKeyManager;
 import static okhttp3.tls.internal.TlsUtil.newTrustManager;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 public final class ClientAuthTest {
@@ -149,7 +149,7 @@ public final class ClientAuthTest {
     Call call = client.newCall(new Request.Builder().url(server.url("/")).build());
     Response response = call.execute();
     assertEquals(new X500Principal("CN=Local Host"), response.handshake().peerPrincipal());
-    assertEquals(null, response.handshake().localPrincipal());
+    assertNull(response.handshake().localPrincipal());
     assertEquals("abc", response.body().string());
   }
 
@@ -165,7 +165,7 @@ public final class ClientAuthTest {
     Call call = client.newCall(new Request.Builder().url(server.url("/")).build());
     Response response = call.execute();
     assertEquals(new X500Principal("CN=Local Host"), response.handshake().peerPrincipal());
-    assertEquals(null, response.handshake().localPrincipal());
+    assertNull(response.handshake().localPrincipal());
     assertEquals("abc", response.body().string());
   }
 
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java b/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java
index 6dd5bbbd7c2f2630354799960c60d5e6f04559cd..81f41b0442772611fdb8701d7edf5ccedad13937 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/tls/HostnameVerifierTest.java
@@ -28,6 +28,7 @@ import okhttp3.internal.Util;
 import org.junit.Ignore;
 import org.junit.Test;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -544,7 +545,7 @@ public final class HostnameVerifierTest {
 
   private X509Certificate certificate(String certificate) throws Exception {
     return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
-        new ByteArrayInputStream(certificate.getBytes(Util.UTF_8)));
+        new ByteArrayInputStream(certificate.getBytes(UTF_8)));
   }
 
   private SSLSession session(String certificate) throws Exception {
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java
index 1e17baa9c2ab4f0f45273744bcb46eef708facff..e4c29b837483f4305c157f9ecb2eb032e5dfe835 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketHttpTest.java
@@ -27,7 +27,6 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.logging.Logger;
-import okhttp3.Interceptor;
 import okhttp3.OkHttpClient;
 import okhttp3.Protocol;
 import okhttp3.RecordingEventListener;
@@ -70,17 +69,17 @@ public final class WebSocketHttpTest {
   private OkHttpClient client = defaultClient().newBuilder()
       .writeTimeout(500, TimeUnit.MILLISECONDS)
       .readTimeout(500, TimeUnit.MILLISECONDS)
-      .addInterceptor(new Interceptor() {
-        @Override public Response intercept(Chain chain) throws IOException {
-          Response response = chain.proceed(chain.request());
-          assertNotNull(response.body()); // Ensure application interceptors never see a null body.
-          return response;
-        }
+      .addInterceptor(chain -> {
+        Response response = chain.proceed(chain.request());
+        assertNotNull(response.body()); // Ensure application interceptors never see a null body.
+        return response;
       })
       .build();
 
   @After public void tearDown() {
     clientListener.assertExhausted();
+
+    // TODO: assert all connections are released once leaks are fixed
   }
 
   @Test public void textMessage() {
@@ -88,10 +87,12 @@ public final class WebSocketHttpTest {
     WebSocket webSocket = newWebSocket();
 
     clientListener.assertOpen();
-    serverListener.assertOpen();
+    WebSocket server = serverListener.assertOpen();
 
     webSocket.send("Hello, WebSockets!");
     serverListener.assertTextMessage("Hello, WebSockets!");
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void binaryMessage() {
@@ -99,10 +100,12 @@ public final class WebSocketHttpTest {
     WebSocket webSocket = newWebSocket();
 
     clientListener.assertOpen();
-    serverListener.assertOpen();
+    WebSocket server = serverListener.assertOpen();
 
     webSocket.send(ByteString.encodeUtf8("Hello!"));
     serverListener.assertBinaryMessage(ByteString.of(new byte[] {'H', 'e', 'l', 'l', 'o', '!'}));
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void nullStringThrows() {
@@ -110,12 +113,15 @@ public final class WebSocketHttpTest {
     WebSocket webSocket = newWebSocket();
 
     clientListener.assertOpen();
+    WebSocket server = serverListener.assertOpen();
     try {
       webSocket.send((String) null);
       fail();
     } catch (NullPointerException e) {
       assertEquals("text == null", e.getMessage());
     }
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void nullByteStringThrows() {
@@ -123,23 +129,28 @@ public final class WebSocketHttpTest {
     WebSocket webSocket = newWebSocket();
 
     clientListener.assertOpen();
+    WebSocket server = serverListener.assertOpen();
     try {
       webSocket.send((ByteString) null);
       fail();
     } catch (NullPointerException e) {
       assertEquals("bytes == null", e.getMessage());
     }
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void serverMessage() {
     webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
-    newWebSocket();
+    WebSocket webSocket = newWebSocket();
 
     clientListener.assertOpen();
     WebSocket server = serverListener.assertOpen();
 
     server.send("Hello, WebSockets!");
     clientListener.assertTextMessage("Hello, WebSockets!");
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void throwingOnOpenFailsImmediately() {
@@ -243,13 +254,10 @@ public final class WebSocketHttpTest {
     webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
     newWebSocket();
 
-    clientListener.assertOpen();
+    WebSocket webSocket = clientListener.assertOpen();
     WebSocket server = serverListener.assertOpen();
 
-    server.close(1001, "bye");
-    clientListener.assertClosing(1001, "bye");
-    clientListener.assertExhausted();
-    serverListener.assertExhausted();
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void non101RetainsBody() throws IOException {
@@ -281,6 +289,8 @@ public final class WebSocketHttpTest {
 
     server.send("def");
     clientListener.assertTextMessage("def");
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void missingConnectionHeader() throws IOException {
@@ -356,16 +366,15 @@ public final class WebSocketHttpTest {
     final AtomicInteger interceptedCount = new AtomicInteger();
 
     client = client.newBuilder()
-        .addInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            assertNull(chain.request().body());
-            Response response = chain.proceed(chain.request());
-            assertEquals("Upgrade", response.header("Connection"));
-            assertTrue(response.body().source().exhausted());
-            interceptedCount.incrementAndGet();
-            return response;
-          }
-        }).build();
+        .addInterceptor(chain -> {
+          assertNull(chain.request().body());
+          Response response = chain.proceed(chain.request());
+          assertEquals("Upgrade", response.header("Connection"));
+          assertTrue(response.body().source().exhausted());
+          interceptedCount.incrementAndGet();
+          return response;
+        })
+        .build();
 
     webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
 
@@ -376,24 +385,24 @@ public final class WebSocketHttpTest {
 
     WebSocket server = serverListener.assertOpen();
     server.close(1000, null);
+    clientListener.assertClosing(1000, "");
+    clientListener.assertClosed(1000, "");
   }
 
   @Test public void webSocketAndNetworkInterceptors() {
     client = client.newBuilder()
-        .addNetworkInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) {
-            throw new AssertionError(); // Network interceptors don't execute.
-          }
-        }).build();
+        .addNetworkInterceptor(chain -> {
+          throw new AssertionError(); // Network interceptors don't execute.
+        })
+        .build();
 
     webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
 
     WebSocket webSocket = newWebSocket();
     clientListener.assertOpen();
-    webSocket.close(1000, null);
-
     WebSocket server = serverListener.assertOpen();
-    server.close(1000, null);
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void overflowOutgoingQueue() {
@@ -538,7 +547,7 @@ public final class WebSocketHttpTest {
 
   @Test public void readTimeoutDoesNotApplyAcrossFrames() throws Exception {
     webServer.enqueue(new MockResponse().withWebSocketUpgrade(serverListener));
-    newWebSocket();
+    WebSocket webSocket = newWebSocket();
 
     clientListener.assertOpen();
     WebSocket server = serverListener.assertOpen();
@@ -548,6 +557,8 @@ public final class WebSocketHttpTest {
 
     server.send("abc");
     clientListener.assertTextMessage("abc");
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void clientPingsServerOnInterval() throws Exception {
@@ -577,6 +588,8 @@ public final class WebSocketHttpTest {
     // The server has never pinged the client.
     assertEquals(0, server.receivedPongCount());
     assertEquals(0, webSocket.receivedPingCount());
+
+    closeWebSockets(webSocket, server);
   }
 
   @Test public void clientDoesNotPingServerByDefault() throws Exception {
@@ -595,6 +608,8 @@ public final class WebSocketHttpTest {
     assertEquals(0, server.sentPingCount());
     assertEquals(0, server.receivedPingCount());
     assertEquals(0, server.receivedPongCount());
+
+    closeWebSockets(webSocket, server);
   }
 
   /**
@@ -687,7 +702,7 @@ public final class WebSocketHttpTest {
 
     webServer.enqueue(new MockResponse()
         .withWebSocketUpgrade(serverListener));
-    newWebSocket();
+    WebSocket webSocket = newWebSocket();
 
     clientListener.assertOpen();
     WebSocket server = serverListener.assertOpen();
@@ -696,6 +711,8 @@ public final class WebSocketHttpTest {
 
     server.send("Hello, WebSockets!");
     clientListener.assertTextMessage("Hello, WebSockets!");
+
+    closeWebSockets(webSocket, server);
   }
 
   /**
@@ -745,10 +762,12 @@ public final class WebSocketHttpTest {
 
     RealWebSocket webSocket = newWebSocket(request);
     clientListener.assertOpen();
-    serverListener.assertOpen();
+    WebSocket server = serverListener.assertOpen();
 
     webSocket.send("abc");
     serverListener.assertTextMessage("abc");
+
+    closeWebSockets(webSocket, server);
   }
 
   private RealWebSocket newWebSocket() {
@@ -761,4 +780,15 @@ public final class WebSocketHttpTest {
     webSocket.connect(client);
     return webSocket;
   }
+
+  private void closeWebSockets(WebSocket webSocket, WebSocket server) {
+    server.close(1001, "");
+    clientListener.assertClosing(1001, "");
+    webSocket.close(1000, "");
+    serverListener.assertClosing(1000, "");
+    clientListener.assertClosed(1001, "");
+    serverListener.assertClosed(1000, "");
+    clientListener.assertExhausted();
+    serverListener.assertExhausted();
+  }
 }
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java
index f6256ac42f9757c5ae525406da63b9072fb11f46..e8d4353829818688543874236a160669909c838d 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketRecorder.java
@@ -17,6 +17,7 @@ package okhttp3.internal.ws;
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Objects;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -24,7 +25,6 @@ import javax.annotation.Nullable;
 import okhttp3.Response;
 import okhttp3.WebSocket;
 import okhttp3.WebSocketListener;
-import okhttp3.internal.Util;
 import okhttp3.internal.platform.Platform;
 import okio.ByteString;
 
@@ -86,7 +86,7 @@ public final class WebSocketRecorder extends WebSocketListener {
   }
 
   @Override public void onClosing(WebSocket webSocket, int code, String reason) {
-    Platform.get().log(Platform.INFO, "[WS " + name + "] onClose " + code, null);
+    Platform.get().log(Platform.INFO, "[WS " + name + "] onClosing " + code, null);
 
     WebSocketListener delegate = this.delegate;
     if (delegate != null) {
@@ -98,7 +98,7 @@ public final class WebSocketRecorder extends WebSocketListener {
   }
 
   @Override public void onClosed(WebSocket webSocket, int code, String reason) {
-    Platform.get().log(Platform.INFO, "[WS " + name + "] onClose " + code, null);
+    Platform.get().log(Platform.INFO, "[WS " + name + "] onClosed " + code, null);
 
     WebSocketListener delegate = this.delegate;
     if (delegate != null) {
@@ -309,8 +309,8 @@ public final class WebSocketRecorder extends WebSocketListener {
 
     @Override public boolean equals(Object other) {
       return other instanceof Message
-          && Util.equal(((Message) other).bytes, bytes)
-          && Util.equal(((Message) other).string, string);
+          && Objects.equals(((Message) other).bytes, bytes)
+          && Objects.equals(((Message) other).string, string);
     }
   }
 
diff --git a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java
index e183d0e2a8d34a56c79493b305d9060b68772503..48684f553a4e681116455b82ffa0f76836b187f2 100644
--- a/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java
+++ b/okhttp-tests/src/test/java/okhttp3/internal/ws/WebSocketWriterTest.java
@@ -28,7 +28,6 @@ import okio.Sink;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
-import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 import static okhttp3.TestUtil.repeat;
@@ -36,7 +35,6 @@ import static okhttp3.internal.ws.WebSocketProtocol.OPCODE_BINARY;
 import static okhttp3.internal.ws.WebSocketProtocol.OPCODE_TEXT;
 import static okhttp3.internal.ws.WebSocketProtocol.PAYLOAD_BYTE_MAX;
 import static okhttp3.internal.ws.WebSocketProtocol.PAYLOAD_SHORT_MAX;
-import static okhttp3.internal.ws.WebSocketProtocol.toggleMask;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -49,14 +47,10 @@ public final class WebSocketWriterTest {
    * Check all data as verified inside of the test. We do this in a rule instead of @After so that
    * exceptions thrown from the test do not cause this check to fail.
    */
-  @Rule public final TestRule noDataLeftBehind = new TestRule() {
-    @Override public Statement apply(final Statement base, Description description) {
-      return new Statement() {
-        @Override public void evaluate() throws Throwable {
-          base.evaluate();
-          assertEquals("Data not empty", "", data.readByteString().hex());
-        }
-      };
+  @Rule public final TestRule noDataLeftBehind = (base, description) -> new Statement() {
+    @Override public void evaluate() throws Throwable {
+      base.evaluate();
+      assertEquals("Data not empty", "", data.readByteString().hex());
     }
   };
 
diff --git a/okhttp-tls/pom.xml b/okhttp-tls/pom.xml
index 7ee4267c7277505b1bec98ba7f00f335b898f798..2f0d9537abec629b99b737a6fe0a953b4513ad34 100644
--- a/okhttp-tls/pom.xml
+++ b/okhttp-tls/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-tls</artifactId>
@@ -46,8 +46,9 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.4</version>
+        <version>3.0.1</version>
         <configuration>
+          <excludePackageNames>okhttp3.tls.internal:okhttp3.tls.internal.*</excludePackageNames>
           <links>
             <link>http://square.github.io/okhttp/javadoc/</link>
           </links>
@@ -56,7 +57,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java b/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java
index 8709dc14382eb36bda957911d6aa98d1932e6cda..c659f58196517ee29d059da0f184c0cf3604e860 100644
--- a/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java
+++ b/okhttp-tls/src/main/java/okhttp3/tls/HeldCertificate.java
@@ -400,8 +400,7 @@ public final class HeldCertificate {
       }
 
       try {
-        X509Certificate certificate = generator.generateX509Certificate(
-            signedByKeyPair.getPrivate());
+        X509Certificate certificate = generator.generate(signedByKeyPair.getPrivate());
         return new HeldCertificate(heldKeyPair, certificate);
       } catch (GeneralSecurityException e) {
         throw new AssertionError(e);
diff --git a/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java b/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java
index c2e4f3a45f552c7eeaa84eb2319d8ac167859ffc..6892e14f0be07b962d80bd1c9d59ee24d6ad0253 100644
--- a/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java
+++ b/okhttp-tls/src/test/java/okhttp3/tls/HandshakeCertificatesTest.java
@@ -25,7 +25,6 @@ import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.Set;
-import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -33,12 +32,12 @@ import javax.net.ServerSocketFactory;
 import javax.net.SocketFactory;
 import javax.net.ssl.SSLSocket;
 import okhttp3.Handshake;
-import okhttp3.internal.Util;
 import okio.ByteString;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import static okhttp3.internal.Util.closeQuietly;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -52,7 +51,7 @@ public final class HandshakeCertificatesTest {
 
   @After public void tearDown() {
     executorService.shutdown();
-    Util.closeQuietly(serverSocket);
+    closeQuietly(serverSocket);
   }
 
   @Test public void clientAndServer() throws Exception {
@@ -149,45 +148,39 @@ public final class HandshakeCertificatesTest {
     return new InetSocketAddress(serverAddress, serverSocket.getLocalPort());
   }
 
-  private Future<Handshake> doServerHandshake(final HandshakeCertificates server) {
-    return executorService.submit(new Callable<Handshake>() {
-      @Override public Handshake call() throws Exception {
-        Socket rawSocket = null;
-        SSLSocket sslSocket = null;
-        try {
-          rawSocket = serverSocket.accept();
-          sslSocket = (SSLSocket) server.sslSocketFactory().createSocket(rawSocket,
-              rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(),
-              true /* autoClose */);
-          sslSocket.setUseClientMode(false);
-          sslSocket.setWantClientAuth(true);
-          sslSocket.startHandshake();
-          return Handshake.get(sslSocket.getSession());
-        } finally {
-          Util.closeQuietly(rawSocket);
-          Util.closeQuietly(sslSocket);
-        }
+  private Future<Handshake> doServerHandshake(HandshakeCertificates server) {
+    return executorService.submit(() -> {
+      Socket rawSocket = null;
+      SSLSocket sslSocket = null;
+      try {
+        rawSocket = serverSocket.accept();
+        sslSocket = (SSLSocket) server.sslSocketFactory().createSocket(rawSocket,
+            rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(), true /* autoClose */);
+        sslSocket.setUseClientMode(false);
+        sslSocket.setWantClientAuth(true);
+        sslSocket.startHandshake();
+        return Handshake.get(sslSocket.getSession());
+      } finally {
+        closeQuietly(rawSocket);
+        closeQuietly(sslSocket);
       }
     });
   }
 
   private Future<Handshake> doClientHandshake(
-      final HandshakeCertificates client, final InetSocketAddress serverAddress) {
-    return executorService.submit(new Callable<Handshake>() {
-      @Override public Handshake call() throws Exception {
-        Socket rawSocket = SocketFactory.getDefault().createSocket();
-        rawSocket.connect(serverAddress);
-        SSLSocket sslSocket = null;
-        try {
-          sslSocket = (SSLSocket) client.sslSocketFactory().createSocket(rawSocket,
-              rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(),
-              true /* autoClose */);
-          sslSocket.startHandshake();
-          return Handshake.get(sslSocket.getSession());
-        } finally {
-          Util.closeQuietly(rawSocket);
-          Util.closeQuietly(sslSocket);
-        }
+      HandshakeCertificates client, InetSocketAddress serverAddress) {
+    return executorService.submit(() -> {
+      Socket rawSocket = SocketFactory.getDefault().createSocket();
+      rawSocket.connect(serverAddress);
+      SSLSocket sslSocket = null;
+      try {
+        sslSocket = (SSLSocket) client.sslSocketFactory().createSocket(rawSocket,
+            rawSocket.getInetAddress().getHostAddress(), rawSocket.getPort(), true /* autoClose */);
+        sslSocket.startHandshake();
+        return Handshake.get(sslSocket.getSession());
+      } finally {
+        closeQuietly(rawSocket);
+        closeQuietly(sslSocket);
       }
     });
   }
diff --git a/okhttp-urlconnection/pom.xml b/okhttp-urlconnection/pom.xml
index 1bdc421954d5689dee9b4d9492d22c77fee6ce46..04b16b2bb4c7f39fcb4bd68e3012e25e0750d986 100644
--- a/okhttp-urlconnection/pom.xml
+++ b/okhttp-urlconnection/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp-urlconnection</artifactId>
@@ -60,7 +60,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.4</version>
+        <version>3.0.1</version>
         <configuration>
           <excludePackageNames>okhttp3.internal:okhttp3.internal.*</excludePackageNames>
           <links>
@@ -71,7 +71,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java
index 94eb4558c2949b457d7a5200b8cc377490cb0590..f1892b7199ac1f2388e1372811114efb05f80da3 100644
--- a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java
+++ b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetAuthenticator.java
@@ -68,7 +68,7 @@ public final class JavaNetAuthenticator implements Authenticator {
   }
 
   private InetAddress getConnectToInetAddress(Proxy proxy, HttpUrl url) throws IOException {
-    return (proxy != null && proxy.type() != Proxy.Type.DIRECT)
+    return proxy.type() != Proxy.Type.DIRECT
         ? ((InetSocketAddress) proxy.address()).getAddress()
         : InetAddress.getByName(url.host());
   }
diff --git a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java
index c6cca30184e1583ad643551b2039490521afdc1e..6faf288de7fd5ad20f7823bfecb63ad134d733c9 100644
--- a/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java
+++ b/okhttp-urlconnection/src/main/java/okhttp3/JavaNetCookieJar.java
@@ -78,7 +78,7 @@ public final class JavaNetCookieJar implements CookieJar {
 
     return cookies != null
         ? Collections.unmodifiableList(cookies)
-        : Collections.<Cookie>emptyList();
+        : Collections.emptyList();
   }
 
   /**
diff --git a/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java b/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java
index f4e88161c9d757207afae8381bbcdabdff0fa881..8e4591e7844c3705a3bebb4942d879d68bad7f76 100644
--- a/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java
+++ b/okhttp-urlconnection/src/main/java/okhttp3/OkUrlFactory.java
@@ -21,6 +21,7 @@ import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLStreamHandler;
 import java.net.URLStreamHandlerFactory;
+import javax.annotation.Nullable;
 import okhttp3.internal.URLFilter;
 import okhttp3.internal.annotations.EverythingIsNonNull;
 import okhttp3.internal.huc.OkHttpURLConnection;
@@ -65,7 +66,7 @@ public final class OkUrlFactory implements URLStreamHandlerFactory, Cloneable {
     return open(url, client.proxy());
   }
 
-  HttpURLConnection open(URL url, Proxy proxy) {
+  HttpURLConnection open(URL url, @Nullable Proxy proxy) {
     String protocol = url.getProtocol();
     OkHttpClient copy = client.newBuilder()
         .proxy(proxy)
diff --git a/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java b/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java
index 0b7a02e1f9619c0d4832a3f401c1680bbf5170b2..1b4290a7ef38956cc8b48afcc2927033ae08b8c5 100644
--- a/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java
+++ b/okhttp-urlconnection/src/main/java/okhttp3/internal/JavaNetHeaders.java
@@ -27,18 +27,16 @@ public final class JavaNetHeaders {
   private JavaNetHeaders() {
   }
 
-  private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
+  private static final Comparator<String> FIELD_NAME_COMPARATOR = (a, b) -> {
     // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
-    @Override public int compare(String a, String b) {
-      if (a == b) {
-        return 0;
-      } else if (a == null) {
-        return -1;
-      } else if (b == null) {
-        return 1;
-      } else {
-        return String.CASE_INSENSITIVE_ORDER.compare(a, b);
-      }
+    if (a == b) {
+      return 0;
+    } else if (a == null) {
+      return -1;
+    } else if (b == null) {
+      return 1;
+    } else {
+      return String.CASE_INSENSITIVE_ORDER.compare(a, b);
     }
   };
 
diff --git a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java
index a051bf21982b01707de9953d99c1d95f564b9417..ae066f64b702bb0315a94ae937d06b6a8ca16dbd 100644
--- a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java
+++ b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/DelegatingHttpsURLConnection.java
@@ -134,7 +134,6 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
     return delegate.getContent();
   }
 
-  @SuppressWarnings("unchecked") // Spec does not generify
   @Override public Object getContent(Class[] types) throws IOException {
     return delegate.getContent(types);
   }
@@ -147,7 +146,7 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
     return delegate.getContentLength();
   }
 
-  @IgnoreJRERequirement // Should only be invoked on Java 7+.
+  @IgnoreJRERequirement // Should only be invoked on Java 8+ or Android API 24+.
   @Override public long getContentLengthLong() {
     return delegate.getContentLengthLong();
   }
@@ -196,7 +195,7 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
     return delegate.getHeaderField(key);
   }
 
-  @IgnoreJRERequirement // Should only be invoked on Java 7+.
+  @IgnoreJRERequirement // Should only be invoked on Java 8+ or Android API 24+.
   @Override public long getHeaderFieldLong(String field, long defaultValue) {
     return delegate.getHeaderFieldLong(field, defaultValue);
   }
@@ -261,7 +260,7 @@ abstract class DelegatingHttpsURLConnection extends HttpsURLConnection {
     delegate.setDoOutput(newValue);
   }
 
-  @IgnoreJRERequirement // Should only be invoked on Java 7+.
+  @IgnoreJRERequirement // Should only be invoked on Java 8+ or Android API 24+.
   @Override public void setFixedLengthStreamingMode(long contentLength) {
     delegate.setFixedLengthStreamingMode(contentLength);
   }
diff --git a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java
index 31e532d0ad40684f3c6ee7b3c8b235bc8ddb9d21..1286cb7f53861886a3ae63d685d2b4d40b9ce630 100644
--- a/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java
+++ b/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/OkHttpURLConnection.java
@@ -52,6 +52,7 @@ import okhttp3.Response;
 import okhttp3.internal.Internal;
 import okhttp3.internal.JavaNetHeaders;
 import okhttp3.internal.URLFilter;
+import okhttp3.internal.Util;
 import okhttp3.internal.Version;
 import okhttp3.internal.http.HttpDate;
 import okhttp3.internal.http.HttpHeaders;
@@ -414,7 +415,7 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call
   }
 
   private String defaultUserAgent() {
-    String agent = System.getProperty("http.agent");
+    String agent = Util.getSystemProperty("http.agent", null);
     return agent != null ? toHumanReadableAscii(agent) : Version.userAgent();
   }
 
@@ -594,13 +595,11 @@ public final class OkHttpURLConnection extends HttpURLConnection implements Call
   }
 
   static final class UnexpectedException extends IOException {
-    static final Interceptor INTERCEPTOR = new Interceptor() {
-      @Override public Response intercept(Chain chain) throws IOException {
-        try {
-          return chain.proceed(chain.request());
-        } catch (Error | RuntimeException e) {
-          throw new UnexpectedException(e);
-        }
+    static final Interceptor INTERCEPTOR = chain -> {
+      try {
+        return chain.proceed(chain.request());
+      } catch (Error | RuntimeException e) {
+        throw new UnexpectedException(e);
       }
     };
 
diff --git a/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java b/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java
index 914725fb84cf5806c981d02b14d2885a0aa97512..1476dc69c4a9817bcc801e62d9093e88d570a7a9 100644
--- a/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java
+++ b/okhttp-urlconnection/src/test/java/okhttp3/OkUrlFactoryTest.java
@@ -14,7 +14,6 @@ import java.util.Locale;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
 import javax.net.ssl.HttpsURLConnection;
-import okhttp3.internal.URLFilter;
 import okhttp3.internal.huc.OkHttpURLConnection;
 import okhttp3.internal.io.InMemoryFileSystem;
 import okhttp3.mockwebserver.MockResponse;
@@ -162,13 +161,8 @@ public class OkUrlFactoryTest {
     server.enqueue(new MockResponse()
         .setBody("B"));
     final URL blockedURL = server.url("/a").url();
-    factory.setUrlFilter(new URLFilter() {
-      @Override
-      public void checkURLPermitted(URL url) throws IOException {
-        if (blockedURL.equals(url)) {
-          throw new IOException("Blocked");
-        }
-      }
+    factory.setUrlFilter(url -> {
+      if (blockedURL.equals(url)) throw new IOException("Blocked");
     });
     try {
       HttpURLConnection connection = factory.open(server.url("/a").url());
@@ -195,13 +189,8 @@ public class OkUrlFactoryTest {
             handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
         .followSslRedirects(true)
         .build());
-    factory.setUrlFilter(new URLFilter() {
-      @Override
-      public void checkURLPermitted(URL url) throws IOException {
-        if (blockedURL.equals(url)) {
-          throw new IOException("Blocked");
-        }
-      }
+    factory.setUrlFilter(url -> {
+      if (blockedURL.equals(url)) throw new IOException("Blocked");
     });
 
     server.enqueue(new MockResponse()
diff --git a/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java b/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java
index 23ff40c89ebb124de5293320de49ba51a5af2d1a..d9434abddfbbe77a296e333a529e1cc230cea856 100644
--- a/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java
+++ b/okhttp-urlconnection/src/test/java/okhttp3/UrlConnectionCacheTest.java
@@ -16,12 +16,10 @@
 
 package okhttp3;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.net.CookieManager;
 import java.net.HttpURLConnection;
@@ -40,7 +38,6 @@ import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLSession;
 import okhttp3.internal.Internal;
 import okhttp3.internal.io.InMemoryFileSystem;
 import okhttp3.internal.platform.Platform;
@@ -50,6 +47,7 @@ import okhttp3.mockwebserver.RecordedRequest;
 import okhttp3.tls.HandshakeCertificates;
 import okio.Buffer;
 import okio.BufferedSink;
+import okio.BufferedSource;
 import okio.GzipSink;
 import okio.Okio;
 import org.junit.After;
@@ -70,11 +68,7 @@ import static org.junit.Assume.assumeFalse;
 
 /** Test caching with {@link OkUrlFactory}. */
 public final class UrlConnectionCacheTest {
-  private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() {
-    @Override public boolean verify(String s, SSLSession sslSession) {
-      return true;
-    }
-  };
+  private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = (name, session) -> true;
 
   @Rule public MockWebServer server = new MockWebServer();
   @Rule public MockWebServer server2 = new MockWebServer();
@@ -428,15 +422,15 @@ public final class UrlConnectionCacheTest {
     server.enqueue(truncateViolently(response, 16));
     server.enqueue(new MockResponse().setBody("Request #2"));
 
-    BufferedReader reader = new BufferedReader(
-        new InputStreamReader(urlFactory.open(server.url("/").url()).getInputStream()));
-    assertEquals("ABCDE", reader.readLine());
+    BufferedSource source = Okio.buffer(Okio.source(
+        urlFactory.open(server.url("/").url()).getInputStream()));
+    assertEquals("ABCDE\n", source.readUtf8(6));
     try {
-      reader.readLine();
+      source.readUtf8(21);
       fail("This implementation silently ignored a truncated HTTP body.");
     } catch (IOException expected) {
     } finally {
-      reader.close();
+      source.close();
     }
 
     assertEquals(1, cache.writeAbortCount());
@@ -1501,7 +1495,7 @@ public final class UrlConnectionCacheTest {
 
     URLConnection connection2 = urlFactory.open(server.url("/").url());
     assertEquals("A", readAscii(connection2));
-    assertEquals(null, connection2.getHeaderField("Warning"));
+    assertNull(connection2.getHeaderField("Warning"));
   }
 
   @Test public void getHeadersRetainsCached200LevelWarnings() throws Exception {
@@ -1539,7 +1533,7 @@ public final class UrlConnectionCacheTest {
     // cache miss; seed the cache
     HttpURLConnection connection1 = urlFactory.open(server.url("/a").url());
     assertEquals("A", readAscii(connection1));
-    assertEquals(null, connection1.getHeaderField("Allow"));
+    assertNull(connection1.getHeaderField("Allow"));
 
     // conditional cache hit; update the cache
     HttpURLConnection connection2 = urlFactory.open(server.url("/a").url());
diff --git a/okhttp/pom.xml b/okhttp/pom.xml
index b7adb196faaa83085149e5297786076a21e929b9..4f28280c06e1690b008e655c0fe512fa39cbf66f 100644
--- a/okhttp/pom.xml
+++ b/okhttp/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>okhttp</artifactId>
@@ -45,7 +45,7 @@
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>templating-maven-plugin</artifactId>
-        <version>1.0-alpha-3</version>
+        <version>1.0.0</version>
         <executions>
           <execution>
             <goals>
@@ -57,7 +57,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.4</version>
+        <version>3.0.1</version>
         <configuration>
           <excludePackageNames>okhttp3.internal:okhttp3.internal.*</excludePackageNames>
           <links>
@@ -68,7 +68,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.1.1</version>
         <configuration>
           <archive>
             <manifestEntries>
diff --git a/okhttp/src/main/java/okhttp3/Address.java b/okhttp/src/main/java/okhttp3/Address.java
index 9df666ad786f3214c9d065da2dcfd1c7aab8c286..5ad2a8d455e8a336da36c9269c556e43b04410c4 100644
--- a/okhttp/src/main/java/okhttp3/Address.java
+++ b/okhttp/src/main/java/okhttp3/Address.java
@@ -18,14 +18,13 @@ package okhttp3;
 import java.net.Proxy;
 import java.net.ProxySelector;
 import java.util.List;
+import java.util.Objects;
 import javax.annotation.Nullable;
 import javax.net.SocketFactory;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLSocketFactory;
 import okhttp3.internal.Util;
 
-import static okhttp3.internal.Util.equal;
-
 /**
  * A specification for a connection to an origin server. For simple connections, this is the
  * server's hostname and port. If an explicit proxy is requested (or {@linkplain Proxy#NO_PROXY no
@@ -165,10 +164,10 @@ public final class Address {
     result = 31 * result + protocols.hashCode();
     result = 31 * result + connectionSpecs.hashCode();
     result = 31 * result + proxySelector.hashCode();
-    result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
-    result = 31 * result + (sslSocketFactory != null ? sslSocketFactory.hashCode() : 0);
-    result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
-    result = 31 * result + (certificatePinner != null ? certificatePinner.hashCode() : 0);
+    result = 31 * result + Objects.hashCode(proxy);
+    result = 31 * result + Objects.hashCode(sslSocketFactory);
+    result = 31 * result + Objects.hashCode(hostnameVerifier);
+    result = 31 * result + Objects.hashCode(certificatePinner);
     return result;
   }
 
@@ -178,10 +177,10 @@ public final class Address {
         && this.protocols.equals(that.protocols)
         && this.connectionSpecs.equals(that.connectionSpecs)
         && this.proxySelector.equals(that.proxySelector)
-        && equal(this.proxy, that.proxy)
-        && equal(this.sslSocketFactory, that.sslSocketFactory)
-        && equal(this.hostnameVerifier, that.hostnameVerifier)
-        && equal(this.certificatePinner, that.certificatePinner)
+        && Objects.equals(this.proxy, that.proxy)
+        && Objects.equals(this.sslSocketFactory, that.sslSocketFactory)
+        && Objects.equals(this.hostnameVerifier, that.hostnameVerifier)
+        && Objects.equals(this.certificatePinner, that.certificatePinner)
         && this.url().port() == that.url().port();
   }
 
diff --git a/okhttp/src/main/java/okhttp3/Authenticator.java b/okhttp/src/main/java/okhttp3/Authenticator.java
index 10482ffeada4d1f56221570cbf5c823faeb081c5..6e1d1a4fd5e703763604a62041e802573bdcb07c 100644
--- a/okhttp/src/main/java/okhttp3/Authenticator.java
+++ b/okhttp/src/main/java/okhttp3/Authenticator.java
@@ -97,11 +97,7 @@ import javax.annotation.Nullable;
  */
 public interface Authenticator {
   /** An authenticator that knows no credentials and makes no attempt to authenticate. */
-  Authenticator NONE = new Authenticator() {
-    @Override public Request authenticate(@Nullable Route route, Response response) {
-      return null;
-    }
-  };
+  Authenticator NONE = (route, response) -> null;
 
   /**
    * Returns a request that includes a credential to satisfy an authentication challenge in {@code
diff --git a/okhttp/src/main/java/okhttp3/Cache.java b/okhttp/src/main/java/okhttp3/Cache.java
index 343df45113a1182afd88236ce9c37466af41c1ac..2ba97ffd0fa814a7833275b3701049ebdae24a10 100644
--- a/okhttp/src/main/java/okhttp3/Cache.java
+++ b/okhttp/src/main/java/okhttp3/Cache.java
@@ -141,11 +141,11 @@ public final class Cache implements Closeable, Flushable {
   private static final int ENTRY_COUNT = 2;
 
   final InternalCache internalCache = new InternalCache() {
-    @Override public Response get(Request request) throws IOException {
+    @Override public @Nullable Response get(Request request) throws IOException {
       return Cache.this.get(request);
     }
 
-    @Override public CacheRequest put(Response response) throws IOException {
+    @Override public @Nullable CacheRequest put(Response response) throws IOException {
       return Cache.this.put(response);
     }
 
@@ -339,16 +339,13 @@ public final class Cache implements Closeable, Flushable {
 
         canRemove = false; // Prevent delegate.remove() on the wrong item!
         while (delegate.hasNext()) {
-          DiskLruCache.Snapshot snapshot = delegate.next();
-          try {
+          try (DiskLruCache.Snapshot snapshot = delegate.next()) {
             BufferedSource metadata = Okio.buffer(snapshot.getSource(ENTRY_METADATA));
             nextUrl = metadata.readUtf8LineStrict();
             return true;
           } catch (IOException ignored) {
             // We couldn't read the metadata for this snapshot; possibly because the host filesystem
             // has disappeared! Skip it.
-          } finally {
-            snapshot.close();
           }
         }
 
diff --git a/okhttp/src/main/java/okhttp3/CertificatePinner.java b/okhttp/src/main/java/okhttp3/CertificatePinner.java
index d5093645448afb2b443b87c9e92cc69e815b37d9..474c245ae6eaa30b5127e0d5af61b33d56485e99 100644
--- a/okhttp/src/main/java/okhttp3/CertificatePinner.java
+++ b/okhttp/src/main/java/okhttp3/CertificatePinner.java
@@ -22,14 +22,13 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import javax.annotation.Nullable;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import okhttp3.internal.tls.CertificateChainCleaner;
 import okio.ByteString;
 
-import static okhttp3.internal.Util.equal;
-
 /**
  * Constrains which certificates are trusted. Pinning certificates defends against attacks on
  * certificate authorities. It also prevents connections through man-in-the-middle certificate
@@ -139,12 +138,13 @@ public final class CertificatePinner {
   @Override public boolean equals(@Nullable Object other) {
     if (other == this) return true;
     return other instanceof CertificatePinner
-        && (equal(certificateChainCleaner, ((CertificatePinner) other).certificateChainCleaner)
+        && (Objects.equals(certificateChainCleaner,
+        ((CertificatePinner) other).certificateChainCleaner)
         && pins.equals(((CertificatePinner) other).pins));
   }
 
   @Override public int hashCode() {
-    int result = certificateChainCleaner != null ? certificateChainCleaner.hashCode() : 0;
+    int result = Objects.hashCode(certificateChainCleaner);
     result = 31 * result + pins.hashCode();
     return result;
   }
@@ -228,7 +228,7 @@ public final class CertificatePinner {
   /** Returns a certificate pinner that uses {@code certificateChainCleaner}. */
   CertificatePinner withCertificateChainCleaner(
       @Nullable CertificateChainCleaner certificateChainCleaner) {
-    return equal(this.certificateChainCleaner, certificateChainCleaner)
+    return Objects.equals(this.certificateChainCleaner, certificateChainCleaner)
         ? this
         : new CertificatePinner(pins, certificateChainCleaner);
   }
diff --git a/okhttp/src/main/java/okhttp3/Challenge.java b/okhttp/src/main/java/okhttp3/Challenge.java
index bff1cb830e322864b1bf476a757ae28118714a6f..0fb4c868a60ab1363656ce91e9e78176a9e53679 100644
--- a/okhttp/src/main/java/okhttp3/Challenge.java
+++ b/okhttp/src/main/java/okhttp3/Challenge.java
@@ -21,10 +21,10 @@ import java.util.Map;
 import java.util.Map.Entry;
 import javax.annotation.Nullable;
 
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
 import static java.util.Collections.singletonMap;
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Locale.US;
-import static okhttp3.internal.Util.ISO_8859_1;
 
 /** An RFC 7235 challenge. */
 public final class Challenge {
diff --git a/okhttp/src/main/java/okhttp3/CipherSuite.java b/okhttp/src/main/java/okhttp3/CipherSuite.java
index ec1875359b18342ca987e5d0860a59e28844b76a..376e1fe0fabc9a5ea62dd75f9a1be9e406ddb2a7 100644
--- a/okhttp/src/main/java/okhttp3/CipherSuite.java
+++ b/okhttp/src/main/java/okhttp3/CipherSuite.java
@@ -46,18 +46,16 @@ public final class CipherSuite {
    * the "TLS_" or "SSL_" prefix which is not consistent across platforms. In particular some IBM
    * JVMs use the "SSL_" prefix everywhere whereas Oracle JVMs mix "TLS_" and "SSL_".
    */
-  static final Comparator<String> ORDER_BY_NAME = new Comparator<String>() {
-    @Override public int compare(String a, String b) {
-      for (int i = 4, limit = Math.min(a.length(), b.length()); i < limit; i++) {
-        char charA = a.charAt(i);
-        char charB = b.charAt(i);
-        if (charA != charB) return charA < charB ? -1 : 1;
-      }
-      int lengthA = a.length();
-      int lengthB = b.length();
-      if (lengthA != lengthB) return lengthA < lengthB ? -1 : 1;
-      return 0;
+  static final Comparator<String> ORDER_BY_NAME = (a, b) -> {
+    for (int i = 4, limit = Math.min(a.length(), b.length()); i < limit; i++) {
+      char charA = a.charAt(i);
+      char charB = b.charAt(i);
+      if (charA != charB) return charA < charB ? -1 : 1;
     }
+    int lengthA = a.length();
+    int lengthB = b.length();
+    if (lengthA != lengthB) return lengthA < lengthB ? -1 : 1;
+    return 0;
   };
 
   /**
diff --git a/okhttp/src/main/java/okhttp3/ConnectionPool.java b/okhttp/src/main/java/okhttp3/ConnectionPool.java
index 605f27566106485a09f27695c868726f446ca87f..2c0c6fffbd7d079220b0381abd356cb2f690eea2 100644
--- a/okhttp/src/main/java/okhttp3/ConnectionPool.java
+++ b/okhttp/src/main/java/okhttp3/ConnectionPool.java
@@ -49,24 +49,22 @@ public final class ConnectionPool {
    */
   private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
       Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
-      new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));
+      new SynchronousQueue<>(), Util.threadFactory("OkHttp ConnectionPool", true));
 
   /** The maximum number of idle connections for each address. */
   private final int maxIdleConnections;
   private final long keepAliveDurationNs;
-  private final Runnable cleanupRunnable = new Runnable() {
-    @Override public void run() {
-      while (true) {
-        long waitNanos = cleanup(System.nanoTime());
-        if (waitNanos == -1) return;
-        if (waitNanos > 0) {
-          long waitMillis = waitNanos / 1000000L;
-          waitNanos -= (waitMillis * 1000000L);
-          synchronized (ConnectionPool.this) {
-            try {
-              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
-            } catch (InterruptedException ignored) {
-            }
+  private final Runnable cleanupRunnable = () -> {
+    while (true) {
+      long waitNanos = cleanup(System.nanoTime());
+      if (waitNanos == -1) return;
+      if (waitNanos > 0) {
+        long waitMillis = waitNanos / 1000000L;
+        waitNanos -= (waitMillis * 1000000L);
+        synchronized (ConnectionPool.this) {
+          try {
+            ConnectionPool.this.wait(waitMillis, (int) waitNanos);
+          } catch (InterruptedException ignored) {
           }
         }
       }
@@ -105,29 +103,23 @@ public final class ConnectionPool {
     return total;
   }
 
-  /**
-   * Returns total number of connections in the pool. Note that prior to OkHttp 2.7 this included
-   * only idle connections and HTTP/2 connections. Since OkHttp 2.7 this includes all connections,
-   * both active and inactive. Use {@link #idleConnectionCount()} to count connections not currently
-   * in use.
-   */
+  /** Returns total number of connections in the pool. */
   public synchronized int connectionCount() {
     return connections.size();
   }
 
   /**
-   * Returns a recycled connection to {@code address}, or null if no such connection exists. The
-   * route is null if the address has not yet been routed.
+   * Acquires a recycled connection to {@code address} for {@code streamAllocation}. If non-null
+   * {@code route} is the resolved route for a connection.
    */
-  @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
+  void acquire(Address address, StreamAllocation streamAllocation, @Nullable Route route) {
     assert (Thread.holdsLock(this));
     for (RealConnection connection : connections) {
       if (connection.isEligible(address, route)) {
         streamAllocation.acquire(connection, true);
-        return connection;
+        return;
       }
     }
-    return null;
   }
 
   /**
diff --git a/okhttp/src/main/java/okhttp3/ConnectionSpec.java b/okhttp/src/main/java/okhttp3/ConnectionSpec.java
index 004a97065bc24317a7d5a28b976b5e815cc27c1c..83efef48391795a8f2cf362d3cfd58eb9e79499a 100644
--- a/okhttp/src/main/java/okhttp3/ConnectionSpec.java
+++ b/okhttp/src/main/java/okhttp3/ConnectionSpec.java
@@ -17,6 +17,7 @@ package okhttp3;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import javax.annotation.Nullable;
 import javax.net.ssl.SSLSocket;
 import okhttp3.internal.Util;
@@ -37,6 +38,12 @@ import static okhttp3.internal.Util.nonEmptyIntersection;
  *
  * <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to
  * defer all feature selection to the underlying SSL socket.
+ *
+ * <p>The configuration of each spec changes with each OkHttp release. This is annoying: upgrading
+ * your OkHttp library can break connectivity to certain web servers! But it’s a necessary annoyance
+ * because the TLS ecosystem is dynamic and staying up to date is necessary to stay secure. See
+ * <a href="https://github.com/square/okhttp/wiki/TLS-Configuration-History">OkHttp's TLS
+ * Configuration History</a> to track these changes.
  */
 public final class ConnectionSpec {
 
@@ -87,24 +94,31 @@ public final class ConnectionSpec {
       CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
   };
 
-  /** A secure TLS connection assuming a modern client platform and server. */
+  /** A secure TLS connection that requires a recent client platform and a recent server. */
   public static final ConnectionSpec RESTRICTED_TLS = new Builder(true)
       .cipherSuites(RESTRICTED_CIPHER_SUITES)
       .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
       .supportsTlsExtensions(true)
       .build();
 
-  /** A modern TLS connection with extensions like SNI and ALPN available. */
+  /**
+   * A modern TLS configuration that works on most client platforms and can connect to most servers.
+   * This is OkHttp's default configuration.
+   */
   public static final ConnectionSpec MODERN_TLS = new Builder(true)
       .cipherSuites(APPROVED_CIPHER_SUITES)
-      .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
+      .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
       .supportsTlsExtensions(true)
       .build();
 
-  /** A backwards-compatible fallback connection for interop with obsolete servers. */
+  /**
+   * A backwards-compatible fallback configuration that works on obsolete client platforms and can
+   * connect to obsolete servers. When possible, prefer to upgrade your client platform or server
+   * rather than using this configuration.
+   */
   public static final ConnectionSpec COMPATIBLE_TLS = new Builder(true)
       .cipherSuites(APPROVED_CIPHER_SUITES)
-      .tlsVersions(TlsVersion.TLS_1_0)
+      .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
       .supportsTlsExtensions(true)
       .build();
 
@@ -247,11 +261,9 @@ public final class ConnectionSpec {
       return "ConnectionSpec()";
     }
 
-    String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]";
-    String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]";
     return "ConnectionSpec("
-        + "cipherSuites=" + cipherSuitesString
-        + ", tlsVersions=" + tlsVersionsString
+        + "cipherSuites=" + Objects.toString(cipherSuites(), "[all enabled]")
+        + ", tlsVersions=" + Objects.toString(tlsVersions(), "[all enabled]")
         + ", supportsTlsExtensions=" + supportsTlsExtensions
         + ")";
   }
@@ -328,6 +340,11 @@ public final class ConnectionSpec {
       return this;
     }
 
+    /**
+     * @deprecated since OkHttp 3.13 all TLS-connections are expected to support TLS extensions.
+     *     In a future release setting this to true will be unnecessary and setting it to false will
+     *     have no effect.
+     */
     public Builder supportsTlsExtensions(boolean supportsTlsExtensions) {
       if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections");
       this.supportsTlsExtensions = supportsTlsExtensions;
diff --git a/okhttp/src/main/java/okhttp3/Cookie.java b/okhttp/src/main/java/okhttp3/Cookie.java
index 7a4f24ba20ad9cadfa8578f906dbffb3db6fc671..867451e387f7d47188a7f3e8469ffe712b28674d 100644
--- a/okhttp/src/main/java/okhttp3/Cookie.java
+++ b/okhttp/src/main/java/okhttp3/Cookie.java
@@ -450,7 +450,7 @@ public final class Cookie {
 
     return cookies != null
         ? Collections.unmodifiableList(cookies)
-        : Collections.<Cookie>emptyList();
+        : Collections.emptyList();
   }
 
   /**
diff --git a/okhttp/src/main/java/okhttp3/Credentials.java b/okhttp/src/main/java/okhttp3/Credentials.java
index 530863e4a8172337cf997b34e43e94189ae36e8b..c06c1d61f030f542b336378ce2045e774bcb1983 100644
--- a/okhttp/src/main/java/okhttp3/Credentials.java
+++ b/okhttp/src/main/java/okhttp3/Credentials.java
@@ -18,7 +18,7 @@ package okhttp3;
 import java.nio.charset.Charset;
 import okio.ByteString;
 
-import static okhttp3.internal.Util.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
 
 /** Factory for HTTP authorization credentials. */
 public final class Credentials {
diff --git a/okhttp/src/main/java/okhttp3/Dispatcher.java b/okhttp/src/main/java/okhttp3/Dispatcher.java
index 3e33dff6bf59e1afe6ee6634cb9cc96d3e214b3d..d21e9df1b9769afa91b260fafe6e448f2cffd651 100644
--- a/okhttp/src/main/java/okhttp3/Dispatcher.java
+++ b/okhttp/src/main/java/okhttp3/Dispatcher.java
@@ -63,7 +63,7 @@ public final class Dispatcher {
   public synchronized ExecutorService executorService() {
     if (executorService == null) {
       executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
-          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
+          new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
     }
     return executorService;
   }
@@ -133,10 +133,27 @@ public final class Dispatcher {
   void enqueue(AsyncCall call) {
     synchronized (this) {
       readyAsyncCalls.add(call);
+
+      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
+      // the same host.
+      if (!call.get().forWebSocket) {
+        AsyncCall existingCall = findExistingCallWithHost(call.host());
+        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
+      }
     }
     promoteAndExecute();
   }
 
+  @Nullable private AsyncCall findExistingCallWithHost(String host) {
+    for (AsyncCall existingCall : runningAsyncCalls) {
+      if (existingCall.host().equals(host)) return existingCall;
+    }
+    for (AsyncCall existingCall : readyAsyncCalls) {
+      if (existingCall.host().equals(host)) return existingCall;
+    }
+    return null;
+  }
+
   /**
    * Cancel all calls currently enqueued or executing. Includes calls executed both {@linkplain
    * Call#execute() synchronously} and {@linkplain Call#enqueue asynchronously}.
@@ -172,9 +189,10 @@ public final class Dispatcher {
         AsyncCall asyncCall = i.next();
 
         if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
-        if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
+        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
 
         i.remove();
+        asyncCall.callsPerHost().incrementAndGet();
         executableCalls.add(asyncCall);
         runningAsyncCalls.add(asyncCall);
       }
@@ -189,16 +207,6 @@ public final class Dispatcher {
     return isRunning;
   }
 
-  /** Returns the number of running calls that share a host with {@code call}. */
-  private int runningCallsForHost(AsyncCall call) {
-    int result = 0;
-    for (AsyncCall c : runningAsyncCalls) {
-      if (c.get().forWebSocket) continue;
-      if (c.host().equals(call.host())) result++;
-    }
-    return result;
-  }
-
   /** Used by {@code Call#execute} to signal it is in-flight. */
   synchronized void executed(RealCall call) {
     runningSyncCalls.add(call);
@@ -206,6 +214,7 @@ public final class Dispatcher {
 
   /** Used by {@code AsyncCall#run} to signal completion. */
   void finished(AsyncCall call) {
+    call.callsPerHost().decrementAndGet();
     finished(runningAsyncCalls, call);
   }
 
diff --git a/okhttp/src/main/java/okhttp3/Dns.java b/okhttp/src/main/java/okhttp3/Dns.java
index de3b4cb215de4182e8ccd73ec72da230890e0c03..feba0db7c9153fdebbf3ac35243c8fd7027aef3b 100644
--- a/okhttp/src/main/java/okhttp3/Dns.java
+++ b/okhttp/src/main/java/okhttp3/Dns.java
@@ -33,17 +33,15 @@ public interface Dns {
    * A DNS that uses {@link InetAddress#getAllByName} to ask the underlying operating system to
    * lookup IP addresses. Most custom {@link Dns} implementations should delegate to this instance.
    */
-  Dns SYSTEM = new Dns() {
-    @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
-      if (hostname == null) throw new UnknownHostException("hostname == null");
-      try {
-        return Arrays.asList(InetAddress.getAllByName(hostname));
-      } catch (NullPointerException e) {
-        UnknownHostException unknownHostException =
-            new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
-        unknownHostException.initCause(e);
-        throw unknownHostException;
-      }
+  Dns SYSTEM = hostname -> {
+    if (hostname == null) throw new UnknownHostException("hostname == null");
+    try {
+      return Arrays.asList(InetAddress.getAllByName(hostname));
+    } catch (NullPointerException e) {
+      UnknownHostException unknownHostException =
+          new UnknownHostException("Broken system behaviour for dns lookup of " + hostname);
+      unknownHostException.initCause(e);
+      throw unknownHostException;
     }
   };
 
diff --git a/okhttp/src/main/java/okhttp3/EventListener.java b/okhttp/src/main/java/okhttp3/EventListener.java
index fb8d4c7f4a6367fc72df8dc8b1851b9069eb9032..bdd9f050c5c4d158b714a9a0f74b34380f879407 100644
--- a/okhttp/src/main/java/okhttp3/EventListener.java
+++ b/okhttp/src/main/java/okhttp3/EventListener.java
@@ -53,12 +53,8 @@ public abstract class EventListener {
   public static final EventListener NONE = new EventListener() {
   };
 
-  static EventListener.Factory factory(final EventListener listener) {
-    return new EventListener.Factory() {
-      public EventListener create(Call call) {
-        return listener;
-      }
-    };
+  static EventListener.Factory factory(EventListener listener) {
+    return call -> listener;
   }
 
   /**
diff --git a/okhttp/src/main/java/okhttp3/FormBody.java b/okhttp/src/main/java/okhttp3/FormBody.java
index d3937834c3c54f1e85518d0a5875197adf4115a7..4216daccd57cf478a0315c8ee218ba95dc6710b2 100644
--- a/okhttp/src/main/java/okhttp3/FormBody.java
+++ b/okhttp/src/main/java/okhttp3/FormBody.java
@@ -105,13 +105,13 @@ public final class FormBody extends RequestBody {
   public static final class Builder {
     private final List<String> names = new ArrayList<>();
     private final List<String> values = new ArrayList<>();
-    private final Charset charset;
+    private final @Nullable Charset charset;
 
     public Builder() {
       this(null);
     }
 
-    public Builder(Charset charset) {
+    public Builder(@Nullable Charset charset) {
       this.charset = charset;
     }
 
diff --git a/okhttp/src/main/java/okhttp3/Handshake.java b/okhttp/src/main/java/okhttp3/Handshake.java
index fefe3a57288aef1ae20e57c3f48f478d9805b8fd..d2740c8f8751b1c3e51a18d736f39ed1dd7dbc5d 100644
--- a/okhttp/src/main/java/okhttp3/Handshake.java
+++ b/okhttp/src/main/java/okhttp3/Handshake.java
@@ -19,6 +19,7 @@ import java.io.IOException;
 import java.security.Principal;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.Nullable;
@@ -68,12 +69,12 @@ public final class Handshake {
     }
     List<Certificate> peerCertificatesList = peerCertificates != null
         ? Util.immutableList(peerCertificates)
-        : Collections.<Certificate>emptyList();
+        : Collections.emptyList();
 
     Certificate[] localCertificates = session.getLocalCertificates();
     List<Certificate> localCertificatesList = localCertificates != null
         ? Util.immutableList(localCertificates)
-        : Collections.<Certificate>emptyList();
+        : Collections.emptyList();
 
     return new Handshake(tlsVersion, cipherSuite, peerCertificatesList, localCertificatesList);
   }
@@ -140,4 +141,31 @@ public final class Handshake {
     result = 31 * result + localCertificates.hashCode();
     return result;
   }
+
+  @Override public String toString() {
+    return "Handshake{"
+        + "tlsVersion="
+        + tlsVersion
+        + " cipherSuite="
+        + cipherSuite
+        + " peerCertificates="
+        + names(peerCertificates)
+        + " localCertificates="
+        + names(localCertificates)
+        + '}';
+  }
+
+  private List<String> names(List<Certificate> certificates) {
+    ArrayList<String> strings = new ArrayList<>();
+
+    for (Certificate cert : certificates) {
+      if (cert instanceof X509Certificate) {
+        strings.add(String.valueOf(((X509Certificate) cert).getSubjectDN()));
+      } else {
+        strings.add(cert.getType());
+      }
+    }
+
+    return strings;
+  }
 }
diff --git a/okhttp/src/main/java/okhttp3/Headers.java b/okhttp/src/main/java/okhttp3/Headers.java
index 8b662c96ac98adfa0e585e9c0355ddcf408748a4..06d2a96ccdbbe2177d6d5b10c80919207113a032 100644
--- a/okhttp/src/main/java/okhttp3/Headers.java
+++ b/okhttp/src/main/java/okhttp3/Headers.java
@@ -17,6 +17,7 @@
 
 package okhttp3;
 
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -30,6 +31,7 @@ import java.util.TreeSet;
 import javax.annotation.Nullable;
 import okhttp3.internal.Util;
 import okhttp3.internal.http.HttpDate;
+import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
 
 /**
  * The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request}
@@ -72,6 +74,16 @@ public final class Headers {
     return value != null ? HttpDate.parse(value) : null;
   }
 
+  /**
+   * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if
+   * either the field is absent or cannot be parsed as a date.
+   */
+  @IgnoreJRERequirement
+  public @Nullable Instant getInstant(String name) {
+    Date value = getDate(name);
+    return value != null ? value.toInstant() : null;
+  }
+
   /** Returns the number of field values. */
   public int size() {
     return namesAndValues.length / 2;
@@ -107,7 +119,7 @@ public final class Headers {
     }
     return result != null
         ? Collections.unmodifiableList(result)
-        : Collections.<String>emptyList();
+        : Collections.emptyList();
   }
 
   /**
@@ -190,7 +202,7 @@ public final class Headers {
     return result;
   }
 
-  private static String get(String[] namesAndValues, String name) {
+  private static @Nullable String get(String[] namesAndValues, String name) {
     for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {
       if (name.equalsIgnoreCase(namesAndValues[i])) {
         return namesAndValues[i + 1];
@@ -326,8 +338,7 @@ public final class Headers {
      * Adds all headers from an existing collection.
      */
     public Builder addAll(Headers headers) {
-      int size = headers.size();
-      for (int i = 0; i < size; i++) {
+      for (int i = 0, size = headers.size(); i < size; i++) {
         addLenient(headers.name(i), headers.value(i));
       }
 
@@ -335,8 +346,8 @@ public final class Headers {
     }
 
     /**
-     * Add a header with the specified name and formatted Date.
-     * Does validation of header names and values.
+     * Add a header with the specified name and formatted date. Does validation of header names and
+     * value.
      */
     public Builder add(String name, Date value) {
       if (value == null) throw new NullPointerException("value for name " + name + " == null");
@@ -344,6 +355,16 @@ public final class Headers {
       return this;
     }
 
+    /**
+     * Add a header with the specified name and formatted instant. Does validation of header names
+     * and value.
+     */
+    @IgnoreJRERequirement
+    public Builder add(String name, Instant value) {
+      if (value == null) throw new NullPointerException("value for name " + name + " == null");
+      return add(name, new Date(value.toEpochMilli()));
+    }
+
     /**
      * Set a field with the specified date. If the field is not found, it is added. If the field is
      * found, the existing values are replaced.
@@ -354,6 +375,16 @@ public final class Headers {
       return this;
     }
 
+    /**
+     * Set a field with the specified instant. If the field is not found, it is added. If the field
+     * is found, the existing values are replaced.
+     */
+    @IgnoreJRERequirement
+    public Builder set(String name, Instant value) {
+      if (value == null) throw new NullPointerException("value for name " + name + " == null");
+      return set(name, new Date(value.toEpochMilli()));
+    }
+
     /**
      * Add a field with the specified value without any validation. Only appropriate for headers
      * from the remote peer or cache.
@@ -388,7 +419,7 @@ public final class Headers {
     }
 
     /** Equivalent to {@code build().get(name)}, but potentially faster. */
-    public String get(String name) {
+    public @Nullable String get(String name) {
       for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
         if (name.equalsIgnoreCase(namesAndValues.get(i))) {
           return namesAndValues.get(i + 1);
diff --git a/okhttp/src/main/java/okhttp3/HttpUrl.java b/okhttp/src/main/java/okhttp3/HttpUrl.java
index 9ca007cefd37b69c6c552a46236275c0bc46dfc4..26125e306deebbbb28a83e2deefe8234abe35462 100644
--- a/okhttp/src/main/java/okhttp3/HttpUrl.java
+++ b/okhttp/src/main/java/okhttp3/HttpUrl.java
@@ -31,6 +31,7 @@ import okhttp3.internal.Util;
 import okhttp3.internal.publicsuffix.PublicSuffixDatabase;
 import okio.Buffer;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static okhttp3.internal.Util.decodeHexDigit;
 import static okhttp3.internal.Util.delimiterOffset;
 import static okhttp3.internal.Util.skipLeadingAsciiWhitespace;
@@ -1087,9 +1088,8 @@ public final class HttpUrl {
 
     public Builder setPathSegment(int index, String pathSegment) {
       if (pathSegment == null) throw new NullPointerException("pathSegment == null");
-      String canonicalPathSegment = canonicalize(
-          pathSegment, 0, pathSegment.length(), PATH_SEGMENT_ENCODE_SET, false, false, false, true,
-              null);
+      String canonicalPathSegment = canonicalize(pathSegment, 0, pathSegment.length(),
+          PATH_SEGMENT_ENCODE_SET, false, false, false, true, null);
       if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) {
         throw new IllegalArgumentException("unexpected path segment: " + pathSegment);
       }
@@ -1101,9 +1101,8 @@ public final class HttpUrl {
       if (encodedPathSegment == null) {
         throw new NullPointerException("encodedPathSegment == null");
       }
-      String canonicalPathSegment = canonicalize(encodedPathSegment,
-          0, encodedPathSegment.length(), PATH_SEGMENT_ENCODE_SET, true, false, false, true,
-          null);
+      String canonicalPathSegment = canonicalize(encodedPathSegment, 0, encodedPathSegment.length(),
+          PATH_SEGMENT_ENCODE_SET, true, false, false, true, null);
       encodedPathSegments.set(index, canonicalPathSegment);
       if (isDot(canonicalPathSegment) || isDotDot(canonicalPathSegment)) {
         throw new IllegalArgumentException("unexpected path segment: " + encodedPathSegment);
@@ -1361,9 +1360,8 @@ public final class HttpUrl {
               if (!hasPassword) {
                 int passwordColonOffset = delimiterOffset(
                     input, pos, componentDelimiterOffset, ':');
-                String canonicalUsername = canonicalize(
-                    input, pos, passwordColonOffset, USERNAME_ENCODE_SET, true, false, false, true,
-                    null);
+                String canonicalUsername = canonicalize(input, pos, passwordColonOffset,
+                    USERNAME_ENCODE_SET, true, false, false, true, null);
                 this.encodedUsername = hasUsername
                     ? this.encodedUsername + "%40" + canonicalUsername
                     : canonicalUsername;
@@ -1376,8 +1374,7 @@ public final class HttpUrl {
                 hasUsername = true;
               } else {
                 this.encodedPassword = this.encodedPassword + "%40" + canonicalize(input, pos,
-                    componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true,
-                    null);
+                    componentDelimiterOffset, PASSWORD_ENCODE_SET, true, false, false, true, null);
               }
               pos = componentDelimiterOffset + 1;
               break;
@@ -1585,7 +1582,7 @@ public final class HttpUrl {
       return limit; // No colon.
     }
 
-    private static String canonicalizeHost(String input, int pos, int limit) {
+    private static @Nullable String canonicalizeHost(String input, int pos, int limit) {
       // Start by percent decoding the host. The WHATWG spec suggests doing this only after we've
       // checked for IPv6 square braces. But Chrome does it first, and that's more lenient.
       String percentDecoded = percentDecode(input, pos, limit, false);
@@ -1681,7 +1678,7 @@ public final class HttpUrl {
    */
   static String canonicalize(String input, int pos, int limit, String encodeSet,
       boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly,
-      Charset charset) {
+      @Nullable Charset charset) {
     int codePoint;
     for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
       codePoint = input.codePointAt(i);
@@ -1706,7 +1703,7 @@ public final class HttpUrl {
 
   static void canonicalize(Buffer out, String input, int pos, int limit, String encodeSet,
       boolean alreadyEncoded, boolean strict, boolean plusIsSpace, boolean asciiOnly,
-      Charset charset) {
+      @Nullable Charset charset) {
     Buffer encodedCharBuffer = null; // Lazily allocated.
     int codePoint;
     for (int i = pos; i < limit; i += Character.charCount(codePoint)) {
@@ -1727,7 +1724,7 @@ public final class HttpUrl {
           encodedCharBuffer = new Buffer();
         }
 
-        if (charset == null || charset.equals(Util.UTF_8)) {
+        if (charset == null || charset.equals(UTF_8)) {
           encodedCharBuffer.writeUtf8CodePoint(codePoint);
         } else {
           encodedCharBuffer.writeString(input, i, i + Character.charCount(codePoint), charset);
@@ -1747,10 +1744,9 @@ public final class HttpUrl {
   }
 
   static String canonicalize(String input, String encodeSet, boolean alreadyEncoded, boolean strict,
-      boolean plusIsSpace, boolean asciiOnly, Charset charset) {
-    return canonicalize(
-        input, 0, input.length(), encodeSet, alreadyEncoded, strict, plusIsSpace, asciiOnly,
-            charset);
+      boolean plusIsSpace, boolean asciiOnly, @Nullable Charset charset) {
+    return canonicalize(input, 0, input.length(), encodeSet, alreadyEncoded, strict, plusIsSpace,
+        asciiOnly, charset);
   }
 
   static String canonicalize(String input, String encodeSet, boolean alreadyEncoded, boolean strict,
diff --git a/okhttp/src/main/java/okhttp3/MultipartBody.java b/okhttp/src/main/java/okhttp3/MultipartBody.java
index 800064cfb5e072ca36ae0e1b0a8d4c5dfb7c4cf2..a3f4863fba4e8122ee608ed4ca73a0205a7121e9 100644
--- a/okhttp/src/main/java/okhttp3/MultipartBody.java
+++ b/okhttp/src/main/java/okhttp3/MultipartBody.java
@@ -199,7 +199,7 @@ public final class MultipartBody extends RequestBody {
    * want to have a good chance of things working, please avoid double-quotes, newlines, percent
    * signs, and the like in your field names.
    */
-  static StringBuilder appendQuotedString(StringBuilder target, String key) {
+  static void appendQuotedString(StringBuilder target, String key) {
     target.append('"');
     for (int i = 0, len = key.length(); i < len; i++) {
       char ch = key.charAt(i);
@@ -219,7 +219,6 @@ public final class MultipartBody extends RequestBody {
       }
     }
     target.append('"');
-    return target;
   }
 
   public static final class Part {
@@ -256,7 +255,11 @@ public final class MultipartBody extends RequestBody {
         appendQuotedString(disposition, filename);
       }
 
-      return create(Headers.of("Content-Disposition", disposition.toString()), body);
+      Headers headers = new Headers.Builder()
+          .addUnsafeNonAscii("Content-Disposition", disposition.toString())
+          .build();
+
+      return create(headers, body);
     }
 
     final @Nullable Headers headers;
diff --git a/okhttp/src/main/java/okhttp3/OkHttpClient.java b/okhttp/src/main/java/okhttp3/OkHttpClient.java
index 1686af2aa969981cf6f5a176065ac2254227c321..433982e13644bf017b30d05acc9691684836a31e 100644
--- a/okhttp/src/main/java/okhttp3/OkHttpClient.java
+++ b/okhttp/src/main/java/okhttp3/OkHttpClient.java
@@ -41,6 +41,7 @@ import okhttp3.internal.cache.InternalCache;
 import okhttp3.internal.connection.RealConnection;
 import okhttp3.internal.connection.RouteDatabase;
 import okhttp3.internal.connection.StreamAllocation;
+import okhttp3.internal.http.HttpCodec;
 import okhttp3.internal.platform.Platform;
 import okhttp3.internal.proxy.NullProxySelector;
 import okhttp3.internal.tls.CertificateChainCleaner;
@@ -50,7 +51,6 @@ import okio.Sink;
 import okio.Source;
 import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
 
-import static okhttp3.internal.Util.assertionError;
 import static okhttp3.internal.Util.checkDuration;
 
 /**
@@ -149,16 +149,16 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
         return pool.connectionBecameIdle(connection);
       }
 
-      @Override public RealConnection get(ConnectionPool pool, Address address,
-          StreamAllocation streamAllocation, Route route) {
-        return pool.get(address, streamAllocation, route);
+      @Override public void acquire(ConnectionPool pool, Address address,
+          StreamAllocation streamAllocation, @Nullable Route route) {
+        pool.acquire(address, streamAllocation, route);
       }
 
       @Override public boolean equalsNonHost(Address a, Address b) {
         return a.equalsNonHost(b);
       }
 
-      @Override public Socket deduplicate(
+      @Override public @Nullable Socket deduplicate(
           ConnectionPool pool, Address address, StreamAllocation streamAllocation) {
         return pool.deduplicate(address, streamAllocation);
       }
@@ -195,6 +195,10 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
       @Override public Call newWebSocketCall(OkHttpClient client, Request originalRequest) {
         return RealCall.newRealCall(client, originalRequest, true);
       }
+
+      @Override public void initCodec(Response.Builder responseBuilder, HttpCodec httpCodec) {
+        responseBuilder.initCodec(httpCodec);
+      }
     };
   }
 
@@ -293,31 +297,34 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
       sslContext.init(null, new TrustManager[] { trustManager }, null);
       return sslContext.getSocketFactory();
     } catch (GeneralSecurityException e) {
-      throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
+      throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up.
     }
   }
 
-  /** Default call timeout (in milliseconds). */
+  /**
+   * Default call timeout (in milliseconds). By default there is no timeout for complete calls, but
+   * there is for the connect, write, and read actions within a call.
+   */
   public int callTimeoutMillis() {
     return callTimeout;
   }
 
-  /** Default connect timeout (in milliseconds). */
+  /** Default connect timeout (in milliseconds). The default is 10 seconds. */
   public int connectTimeoutMillis() {
     return connectTimeout;
   }
 
-  /** Default read timeout (in milliseconds). */
+  /** Default read timeout (in milliseconds). The default is 10 seconds. */
   public int readTimeoutMillis() {
     return readTimeout;
   }
 
-  /** Default write timeout (in milliseconds). */
+  /** Default write timeout (in milliseconds). The default is 10 seconds. */
   public int writeTimeoutMillis() {
     return writeTimeout;
   }
 
-  /** Web socket ping interval (in milliseconds). */
+  /** Web socket and HTTP/2 ping interval (in milliseconds). By default pings are not sent. */
   public int pingIntervalMillis() {
     return pingInterval;
   }
@@ -338,7 +345,7 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
     return cache;
   }
 
-  InternalCache internalCache() {
+  @Nullable InternalCache internalCache() {
     return cache != null ? cache.internalCache : internalCache;
   }
 
@@ -535,6 +542,8 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
      * <p>The call timeout spans the entire call: resolving DNS, connecting, writing the request
      * body, server processing, and reading the response body. If the call requires redirects or
      * retries all must complete within one timeout period.
+     *
+     * <p>The default value is 0 which imposes no timeout.
      */
     public Builder callTimeout(long timeout, TimeUnit unit) {
       callTimeout = checkDuration("timeout", timeout, unit);
@@ -548,6 +557,8 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
      * <p>The call timeout spans the entire call: resolving DNS, connecting, writing the request
      * body, server processing, and reading the response body. If the call requires redirects or
      * retries all must complete within one timeout period.
+     *
+     * <p>The default value is 0 which imposes no timeout.
      */
     @IgnoreJRERequirement
     public Builder callTimeout(Duration duration) {
@@ -749,6 +760,9 @@ public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory
      */
     public Builder socketFactory(SocketFactory socketFactory) {
       if (socketFactory == null) throw new NullPointerException("socketFactory == null");
+      if (socketFactory instanceof SSLSocketFactory) {
+        throw new IllegalArgumentException("socketFactory instanceof SSLSocketFactory");
+      }
       this.socketFactory = socketFactory;
       return this;
     }
diff --git a/okhttp/src/main/java/okhttp3/RealCall.java b/okhttp/src/main/java/okhttp3/RealCall.java
index a9ba5be366b43fcae33f3a9bf91db4feeb42ac8d..92201fe8d6901ad189f35e61692cb6fb150d83c6 100644
--- a/okhttp/src/main/java/okhttp3/RealCall.java
+++ b/okhttp/src/main/java/okhttp3/RealCall.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
 import javax.annotation.Nullable;
 import okhttp3.internal.NamedRunnable;
 import okhttp3.internal.cache.CacheInterceptor;
@@ -59,7 +60,7 @@ final class RealCall implements Call {
     this.client = client;
     this.originalRequest = originalRequest;
     this.forWebSocket = forWebSocket;
-    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
+    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);
     this.timeout = new AsyncTimeout() {
       @Override protected void timedOut() {
         cancel();
@@ -153,12 +154,21 @@ final class RealCall implements Call {
 
   final class AsyncCall extends NamedRunnable {
     private final Callback responseCallback;
+    private volatile AtomicInteger callsPerHost = new AtomicInteger(0);
 
     AsyncCall(Callback responseCallback) {
       super("OkHttp %s", redactedUrl());
       this.responseCallback = responseCallback;
     }
 
+    AtomicInteger callsPerHost() {
+      return callsPerHost;
+    }
+
+    void reuseCallsPerHostFrom(AsyncCall other) {
+      this.callsPerHost = other.callsPerHost;
+    }
+
     String host() {
       return originalRequest.url().host();
     }
diff --git a/okhttp/src/main/java/okhttp3/Request.java b/okhttp/src/main/java/okhttp3/Request.java
index df0bebd021a609b37ff05d808ce44b4679bb78d5..9bbdc86407c9ca0237dd5a8e202c10005fecb974 100644
--- a/okhttp/src/main/java/okhttp3/Request.java
+++ b/okhttp/src/main/java/okhttp3/Request.java
@@ -135,7 +135,7 @@ public final class Request {
       this.method = request.method;
       this.body = request.body;
       this.tags = request.tags.isEmpty()
-          ? Collections.<Class<?>, Object>emptyMap()
+          ? Collections.emptyMap()
           : new LinkedHashMap<>(request.tags);
       this.headers = request.headers.newBuilder();
     }
diff --git a/okhttp/src/main/java/okhttp3/RequestBody.java b/okhttp/src/main/java/okhttp3/RequestBody.java
index baf33ce6c65349efe077eaf93436e33faf0cce60..c0279f30811380df0ebbd8fa1cff5e0199fc4dee 100644
--- a/okhttp/src/main/java/okhttp3/RequestBody.java
+++ b/okhttp/src/main/java/okhttp3/RequestBody.java
@@ -25,6 +25,8 @@ import okio.ByteString;
 import okio.Okio;
 import okio.Source;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 public abstract class RequestBody {
   /** Returns the Content-Type header for this body. */
   public abstract @Nullable MediaType contentType();
@@ -45,11 +47,11 @@ public abstract class RequestBody {
    * and lacks a charset, this will use UTF-8.
    */
   public static RequestBody create(@Nullable MediaType contentType, String content) {
-    Charset charset = Util.UTF_8;
+    Charset charset = UTF_8;
     if (contentType != null) {
       charset = contentType.charset();
       if (charset == null) {
-        charset = Util.UTF_8;
+        charset = UTF_8;
         contentType = MediaType.parse(contentType + "; charset=utf-8");
       }
     }
@@ -114,12 +116,8 @@ public abstract class RequestBody {
       }
 
       @Override public void writeTo(BufferedSink sink) throws IOException {
-        Source source = null;
-        try {
-          source = Okio.source(file);
+        try (Source source = Okio.source(file)) {
           sink.writeAll(source);
-        } finally {
-          Util.closeQuietly(source);
         }
       }
     };
diff --git a/okhttp/src/main/java/okhttp3/Response.java b/okhttp/src/main/java/okhttp3/Response.java
index b9ec3993806c7fdd83cbc60a0c7fab273005bf06..b58c9aac217e0c402d4ec87958eb66eb84837969 100644
--- a/okhttp/src/main/java/okhttp3/Response.java
+++ b/okhttp/src/main/java/okhttp3/Response.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import javax.annotation.Nullable;
+import okhttp3.internal.http.HttpCodec;
 import okhttp3.internal.http.HttpHeaders;
 import okio.Buffer;
 import okio.BufferedSource;
@@ -53,6 +54,7 @@ public final class Response implements Closeable {
   final @Nullable Response priorResponse;
   final long sentRequestAtMillis;
   final long receivedResponseAtMillis;
+  final @Nullable HttpCodec httpCodec;
 
   private volatile @Nullable CacheControl cacheControl; // Lazily initialized.
 
@@ -69,6 +71,7 @@ public final class Response implements Closeable {
     this.priorResponse = builder.priorResponse;
     this.sentRequestAtMillis = builder.sentRequestAtMillis;
     this.receivedResponseAtMillis = builder.receivedResponseAtMillis;
+    this.httpCodec = builder.httpCodec;
   }
 
   /**
@@ -136,6 +139,14 @@ public final class Response implements Closeable {
     return headers;
   }
 
+  /**
+   * Returns the trailers after the HTTP response, which may be empty. It is an error to call this
+   * before the entire HTTP response body has been consumed.
+   */
+  public Headers trailers() throws IOException {
+    return httpCodec.trailers();
+  }
+
   /**
    * Peeks up to {@code byteCount} bytes from the response body and returns them as a new response
    * body. If fewer than {@code byteCount} bytes are in the response body, the full response body is
@@ -148,21 +159,11 @@ public final class Response implements Closeable {
    * applications should set a modest limit on {@code byteCount}, such as 1 MiB.
    */
   public ResponseBody peekBody(long byteCount) throws IOException {
-    BufferedSource source = body.source();
-    source.request(byteCount);
-    Buffer copy = source.buffer().clone();
-
-    // There may be more than byteCount bytes in source.buffer(). If there is, return a prefix.
-    Buffer result;
-    if (copy.size() > byteCount) {
-      result = new Buffer();
-      result.write(copy, byteCount);
-      copy.clear();
-    } else {
-      result = copy;
-    }
-
-    return ResponseBody.create(body.contentType(), result.size(), result);
+    BufferedSource peeked = body.source().peek();
+    Buffer buffer = new Buffer();
+    peeked.request(byteCount);
+    buffer.write(peeked, Math.min(byteCount, peeked.getBuffer().size()));
+    return ResponseBody.create(body.contentType(), buffer.size(), buffer);
   }
 
   /**
@@ -313,6 +314,7 @@ public final class Response implements Closeable {
     @Nullable Response priorResponse;
     long sentRequestAtMillis;
     long receivedResponseAtMillis;
+    @Nullable HttpCodec httpCodec;
 
     public Builder() {
       headers = new Headers.Builder();
@@ -331,6 +333,7 @@ public final class Response implements Closeable {
       this.priorResponse = response.priorResponse;
       this.sentRequestAtMillis = response.sentRequestAtMillis;
       this.receivedResponseAtMillis = response.receivedResponseAtMillis;
+      this.httpCodec = response.httpCodec;
     }
 
     public Builder request(Request request) {
@@ -438,6 +441,10 @@ public final class Response implements Closeable {
       return this;
     }
 
+    void initCodec(HttpCodec httpCodec) {
+      this.httpCodec = httpCodec;
+    }
+
     public Response build() {
       if (request == null) throw new IllegalStateException("request == null");
       if (protocol == null) throw new IllegalStateException("protocol == null");
diff --git a/okhttp/src/main/java/okhttp3/ResponseBody.java b/okhttp/src/main/java/okhttp3/ResponseBody.java
index 9b06b6ea8f772744ed58a278ff666211ba6ce219..96b7e38a0f42b63a3f3de859adc0d162dfa82f26 100644
--- a/okhttp/src/main/java/okhttp3/ResponseBody.java
+++ b/okhttp/src/main/java/okhttp3/ResponseBody.java
@@ -27,7 +27,7 @@ import okio.Buffer;
 import okio.BufferedSource;
 import okio.ByteString;
 
-import static okhttp3.internal.Util.UTF_8;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
  * A one-shot stream from the origin server to the client application with the raw bytes of the
@@ -131,12 +131,9 @@ public abstract class ResponseBody implements Closeable {
       throw new IOException("Cannot buffer entire body for content length: " + contentLength);
     }
 
-    BufferedSource source = source();
     byte[] bytes;
-    try {
+    try (BufferedSource source = source()) {
       bytes = source.readByteArray();
-    } finally {
-      Util.closeQuietly(source);
     }
     if (contentLength != -1 && contentLength != bytes.length) {
       throw new IOException("Content-Length ("
@@ -149,10 +146,15 @@ public abstract class ResponseBody implements Closeable {
   }
 
   /**
-   * Returns the response as a character stream decoded with the charset of the Content-Type header.
-   * If that header is either absent or lacks a charset, this will attempt to decode the response
-   * body in accordance to <a href="https://en.wikipedia.org/wiki/Byte_order_mark">its BOM</a> or
-   * UTF-8.
+   * Returns the response as a character stream.
+   *
+   * <p>If the response starts with a <a href="https://en.wikipedia.org/wiki/Byte_order_mark">Byte
+   * Order Mark (BOM)</a>, it is consumed and used to determine the charset of the response bytes.
+   *
+   * <p>Otherwise if the response has a Content-Type header that specifies a charset, that is used
+   * to determine the charset of the response bytes.
+   *
+   * <p>Otherwise the response bytes are decoded as UTF-8.
    */
   public final Reader charStream() {
     Reader r = reader;
@@ -160,22 +162,24 @@ public abstract class ResponseBody implements Closeable {
   }
 
   /**
-   * Returns the response as a string decoded with the charset of the Content-Type header. If that
-   * header is either absent or lacks a charset, this will attempt to decode the response body in
-   * accordance to <a href="https://en.wikipedia.org/wiki/Byte_order_mark">its BOM</a> or UTF-8.
-   * Closes {@link ResponseBody} automatically.
+   * Returns the response as a string.
+   *
+   * <p>If the response starts with a <a href="https://en.wikipedia.org/wiki/Byte_order_mark">Byte
+   * Order Mark (BOM)</a>, it is consumed and used to determine the charset of the response bytes.
+   *
+   * <p>Otherwise if the response has a Content-Type header that specifies a charset, that is used
+   * to determine the charset of the response bytes.
+   *
+   * <p>Otherwise the response bytes are decoded as UTF-8.
    *
    * <p>This method loads entire response body into memory. If the response body is very large this
    * may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
    * possibility for your response.
    */
   public final String string() throws IOException {
-    BufferedSource source = source();
-    try {
+    try (BufferedSource source = source()) {
       Charset charset = Util.bomAwareCharset(source, charset());
       return source.readString(charset);
-    } finally {
-      Util.closeQuietly(source);
     }
   }
 
diff --git a/okhttp/src/main/java/okhttp3/internal/Internal.java b/okhttp/src/main/java/okhttp3/internal/Internal.java
index 62fcfaa732746b7fad2bf26bc03a95843e9a62f9..ac4d607eb42e2538ba7b5fd0ab1459fb7973a9ce 100644
--- a/okhttp/src/main/java/okhttp3/internal/Internal.java
+++ b/okhttp/src/main/java/okhttp3/internal/Internal.java
@@ -32,6 +32,7 @@ import okhttp3.internal.cache.InternalCache;
 import okhttp3.internal.connection.RealConnection;
 import okhttp3.internal.connection.RouteDatabase;
 import okhttp3.internal.connection.StreamAllocation;
+import okhttp3.internal.http.HttpCodec;
 
 /**
  * Escalate internal APIs in {@code okhttp3} so they can be used from OkHttp's implementation
@@ -52,12 +53,12 @@ public abstract class Internal {
 
   public abstract void setCache(OkHttpClient.Builder builder, InternalCache internalCache);
 
-  public abstract RealConnection get(ConnectionPool pool, Address address,
-      StreamAllocation streamAllocation, Route route);
+  public abstract void acquire(ConnectionPool pool, Address address,
+      StreamAllocation streamAllocation, @Nullable Route route);
 
   public abstract boolean equalsNonHost(Address a, Address b);
 
-  public abstract Socket deduplicate(
+  public abstract @Nullable Socket deduplicate(
       ConnectionPool pool, Address address, StreamAllocation streamAllocation);
 
   public abstract void put(ConnectionPool pool, RealConnection connection);
@@ -78,4 +79,7 @@ public abstract class Internal {
   public abstract @Nullable IOException timeoutExit(Call call, @Nullable IOException e);
 
   public abstract Call newWebSocketCall(OkHttpClient client, Request request);
+
+  public abstract void initCodec(
+      Response.Builder responseBuilder, HttpCodec httpCodec);
 }
diff --git a/okhttp/src/main/java/okhttp3/internal/Util.java b/okhttp/src/main/java/okhttp3/internal/Util.java
index 66e559cc18b6eae9cf326f2866e92083a6264935..5315a6b6856ce69752c2addb5e386c75680dcfc5 100644
--- a/okhttp/src/main/java/okhttp3/internal/Util.java
+++ b/okhttp/src/main/java/okhttp3/internal/Util.java
@@ -26,6 +26,7 @@ import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.UnknownHostException;
 import java.nio.charset.Charset;
+import java.security.AccessControlException;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.util.ArrayList;
@@ -52,37 +53,38 @@ import okhttp3.internal.http2.Header;
 import okio.Buffer;
 import okio.BufferedSource;
 import okio.ByteString;
+import okio.Options;
 import okio.Source;
 
+import static java.nio.charset.StandardCharsets.UTF_16BE;
+import static java.nio.charset.StandardCharsets.UTF_16LE;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 /** Junk drawer of utility methods. */
 public final class Util {
   public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
   public static final String[] EMPTY_STRING_ARRAY = new String[0];
+  public static final Headers EMPTY_HEADERS = Headers.of();
 
   public static final ResponseBody EMPTY_RESPONSE = ResponseBody.create(null, EMPTY_BYTE_ARRAY);
   public static final RequestBody EMPTY_REQUEST = RequestBody.create(null, EMPTY_BYTE_ARRAY);
 
-  private static final ByteString UTF_8_BOM = ByteString.decodeHex("efbbbf");
-  private static final ByteString UTF_16_BE_BOM = ByteString.decodeHex("feff");
-  private static final ByteString UTF_16_LE_BOM = ByteString.decodeHex("fffe");
-  private static final ByteString UTF_32_BE_BOM = ByteString.decodeHex("0000ffff");
-  private static final ByteString UTF_32_LE_BOM = ByteString.decodeHex("ffff0000");
+  /** Byte order marks. */
+  private static final Options UNICODE_BOMS = Options.of(
+      ByteString.decodeHex("efbbbf"),   // UTF-8
+      ByteString.decodeHex("feff"),     // UTF-16BE
+      ByteString.decodeHex("fffe"),     // UTF-16LE
+      ByteString.decodeHex("0000ffff"), // UTF-32BE
+      ByteString.decodeHex("ffff0000")  // UTF-32LE
+  );
 
-  public static final Charset UTF_8 = Charset.forName("UTF-8");
-  public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
-  private static final Charset UTF_16_BE = Charset.forName("UTF-16BE");
-  private static final Charset UTF_16_LE = Charset.forName("UTF-16LE");
-  private static final Charset UTF_32_BE = Charset.forName("UTF-32BE");
-  private static final Charset UTF_32_LE = Charset.forName("UTF-32LE");
+  private static final Charset UTF_32BE = Charset.forName("UTF-32BE");
+  private static final Charset UTF_32LE = Charset.forName("UTF-32LE");
 
   /** GMT and UTC are equivalent for our purposes. */
   public static final TimeZone UTC = TimeZone.getTimeZone("GMT");
 
-  public static final Comparator<String> NATURAL_ORDER = new Comparator<String>() {
-    @Override public int compare(String a, String b) {
-      return a.compareTo(b);
-    }
-  };
+  public static final Comparator<String> NATURAL_ORDER = String::compareTo;
 
   private static final Method addSuppressedExceptionMethod;
 
@@ -127,11 +129,6 @@ public final class Util {
     }
   }
 
-  /** Returns true if two possibly-null objects are equal. */
-  public static boolean equal(Object a, Object b) {
-    return a == b || (a != null && a.equals(b));
-  }
-
   /**
    * Closes {@code closeable}, ignoring any checked exceptions. Does nothing if {@code closeable} is
    * null.
@@ -227,22 +224,21 @@ public final class Util {
   /** Returns an immutable copy of {@code map}. */
   public static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
     return map.isEmpty()
-        ? Collections.<K, V>emptyMap()
+        ? Collections.emptyMap()
         : Collections.unmodifiableMap(new LinkedHashMap<>(map));
   }
 
   /** Returns an immutable list containing {@code elements}. */
+  @SafeVarargs
   public static <T> List<T> immutableList(T... elements) {
     return Collections.unmodifiableList(Arrays.asList(elements.clone()));
   }
 
-  public static ThreadFactory threadFactory(final String name, final boolean daemon) {
-    return new ThreadFactory() {
-      @Override public Thread newThread(Runnable runnable) {
-        Thread result = new Thread(runnable, name);
-        result.setDaemon(daemon);
-        return result;
-      }
+  public static ThreadFactory threadFactory(String name, boolean daemon) {
+    return runnable -> {
+      Thread result = new Thread(runnable, name);
+      result.setDaemon(daemon);
+      return result;
     };
   }
 
@@ -250,7 +246,6 @@ public final class Util {
    * Returns an array containing only elements found in {@code first} and also in {@code
    * second}. The returned elements are in the same order as in {@code first}.
    */
-  @SuppressWarnings("unchecked")
   public static String[] intersect(
       Comparator<? super String> comparator, String[] first, String[] second) {
     List<String> result = new ArrayList<>();
@@ -405,6 +400,7 @@ public final class Util {
       if (inetAddress == null) return null;
       byte[] address = inetAddress.getAddress();
       if (address.length == 16) return inet6AddressToAscii(address);
+      if (address.length == 4) return inetAddress.getHostAddress(); // An IPv4-mapped IPv6 address.
       throw new AssertionError("Invalid IPv6 address: '" + host + "'");
     }
 
@@ -468,27 +464,15 @@ public final class Util {
   }
 
   public static Charset bomAwareCharset(BufferedSource source, Charset charset) throws IOException {
-    if (source.rangeEquals(0, UTF_8_BOM)) {
-      source.skip(UTF_8_BOM.size());
-      return UTF_8;
+    switch (source.select(UNICODE_BOMS)) {
+      case 0: return UTF_8;
+      case 1: return UTF_16BE;
+      case 2: return UTF_16LE;
+      case 3: return UTF_32BE;
+      case 4: return UTF_32LE;
+      case -1: return charset;
+      default: throw new AssertionError();
     }
-    if (source.rangeEquals(0, UTF_16_BE_BOM)) {
-      source.skip(UTF_16_BE_BOM.size());
-      return UTF_16_BE;
-    }
-    if (source.rangeEquals(0, UTF_16_LE_BOM)) {
-      source.skip(UTF_16_LE_BOM.size());
-      return UTF_16_LE;
-    }
-    if (source.rangeEquals(0, UTF_32_BE_BOM)) {
-      source.skip(UTF_32_BE_BOM.size());
-      return UTF_32_BE;
-    }
-    if (source.rangeEquals(0, UTF_32_LE_BOM)) {
-      source.skip(UTF_32_LE_BOM.size());
-      return UTF_32_LE;
-    }
-    return charset;
   }
 
   public static int checkDuration(String name, long duration, TimeUnit unit) {
@@ -500,16 +484,6 @@ public final class Util {
     return (int) millis;
   }
 
-  public static AssertionError assertionError(String message, Exception e) {
-    AssertionError assertionError = new AssertionError(message);
-    try {
-      assertionError.initCause(e);
-    } catch (IllegalStateException ise) {
-      // ignored, shouldn't happen
-    }
-    return assertionError;
-  }
-
   public static int decodeHexDigit(char c) {
     if (c >= '0' && c <= '9') return c - '0';
     if (c >= 'a' && c <= 'f') return c - 'a' + 10;
@@ -671,7 +645,7 @@ public final class Util {
       }
       return (X509TrustManager) trustManagers[0];
     } catch (GeneralSecurityException e) {
-      throw assertionError("No System TLS", e); // The system has no TLS. Just give up.
+      throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up.
     }
   }
 
@@ -682,4 +656,26 @@ public final class Util {
     }
     return builder.build();
   }
+
+  public static List<Header> toHeaderBlock(Headers headers) {
+    List<Header> result = new ArrayList<>();
+    for (int i = 0; i < headers.size(); i++) {
+      result.add(new Header(headers.name(i), headers.value(i)));
+    }
+    return result;
+  }
+
+  /**
+   * Returns the system property, or defaultValue if the system property is null or
+   * cannot be read (e.g. because of security policy restrictions).
+   */
+  public static String getSystemProperty(String key, @Nullable String defaultValue) {
+    String value;
+    try {
+      value = System.getProperty(key);
+    } catch (AccessControlException ex) {
+      return defaultValue;
+    }
+    return value != null ? value : defaultValue;
+  }
 }
diff --git a/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java b/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java
index d46494d95b9ea019b04762c12e62599e915e6b30..c00e3b0cff65cf13e21ade6eea5797b70cf6e1b4 100644
--- a/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java
+++ b/okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java
@@ -17,6 +17,7 @@
 package okhttp3.internal.cache;
 
 import java.io.IOException;
+import javax.annotation.Nullable;
 import okhttp3.Headers;
 import okhttp3.Interceptor;
 import okhttp3.Protocol;
@@ -43,9 +44,9 @@ import static okhttp3.internal.Util.discard;
 
 /** Serves requests from the cache and writes responses to the cache. */
 public final class CacheInterceptor implements Interceptor {
-  final InternalCache cache;
+  final @Nullable InternalCache cache;
 
-  public CacheInterceptor(InternalCache cache) {
+  public CacheInterceptor(@Nullable InternalCache cache) {
     this.cache = cache;
   }
 
@@ -224,8 +225,9 @@ public final class CacheInterceptor implements Interceptor {
       if ("Warning".equalsIgnoreCase(fieldName) && value.startsWith("1")) {
         continue; // Drop 100-level freshness warnings.
       }
-      if (isContentSpecificHeader(fieldName) || !isEndToEnd(fieldName)
-              || networkHeaders.get(fieldName) == null) {
+      if (isContentSpecificHeader(fieldName)
+          || !isEndToEnd(fieldName)
+          || networkHeaders.get(fieldName) == null) {
         Internal.instance.addLenient(result, fieldName, value);
       }
     }
diff --git a/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java b/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java
index c3fb740e28227fe60f46ee08b8ed39483ebe2af9..3b193f3b91f093fa300665d45f02b582b055b3c1 100644
--- a/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java
+++ b/okhttp/src/main/java/okhttp3/internal/cache/DiskLruCache.java
@@ -267,14 +267,13 @@ public final class DiskLruCache implements Closeable, Flushable {
 
     // Use a single background thread to evict entries.
     Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
-        new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));
+        new LinkedBlockingQueue<>(), Util.threadFactory("OkHttp DiskLruCache", true));
 
     return new DiskLruCache(fileSystem, directory, appVersion, valueCount, maxSize, executor);
   }
 
   private void readJournal() throws IOException {
-    BufferedSource source = Okio.buffer(fileSystem.source(journalFile));
-    try {
+    try (BufferedSource source = Okio.buffer(fileSystem.source(journalFile))) {
       String magic = source.readUtf8LineStrict();
       String version = source.readUtf8LineStrict();
       String appVersionString = source.readUtf8LineStrict();
@@ -306,8 +305,6 @@ public final class DiskLruCache implements Closeable, Flushable {
       } else {
         journalWriter = newJournalWriter();
       }
-    } finally {
-      Util.closeQuietly(source);
     }
   }
 
@@ -393,8 +390,7 @@ public final class DiskLruCache implements Closeable, Flushable {
       journalWriter.close();
     }
 
-    BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp));
-    try {
+    try (BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp))) {
       writer.writeUtf8(MAGIC).writeByte('\n');
       writer.writeUtf8(VERSION_1).writeByte('\n');
       writer.writeDecimalLong(appVersion).writeByte('\n');
@@ -413,8 +409,6 @@ public final class DiskLruCache implements Closeable, Flushable {
           writer.writeByte('\n');
         }
       }
-    } finally {
-      writer.close();
     }
 
     if (fileSystem.exists(journalFile)) {
diff --git a/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java b/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java
index 6c70465b596e88c75ce4d6e1dcdfffb5c7c7f84a..abb953472d44d5e380fb1799799f76338b34163c 100644
--- a/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java
+++ b/okhttp/src/main/java/okhttp3/internal/cache/InternalCache.java
@@ -16,6 +16,7 @@
 package okhttp3.internal.cache;
 
 import java.io.IOException;
+import javax.annotation.Nullable;
 import okhttp3.Request;
 import okhttp3.Response;
 
@@ -24,9 +25,9 @@ import okhttp3.Response;
  * okhttp3.Cache}.
  */
 public interface InternalCache {
-  Response get(Request request) throws IOException;
+  @Nullable Response get(Request request) throws IOException;
 
-  CacheRequest put(Response response) throws IOException;
+  @Nullable CacheRequest put(Response response) throws IOException;
 
   /**
    * Remove any cache entries for the supplied {@code request}. This is invoked when the client
diff --git a/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java b/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java
index 34a116464d80b83817489cf92d0d23814d5bdbeb..285a10ce46af058a0664e323dff542c64b4aebf7 100644
--- a/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java
+++ b/okhttp/src/main/java/okhttp3/internal/connection/ConnectionSpecSelector.java
@@ -25,7 +25,6 @@ import java.util.List;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLProtocolException;
 import javax.net.ssl.SSLSocket;
 import okhttp3.ConnectionSpec;
 import okhttp3.internal.Internal;
@@ -111,8 +110,7 @@ public final class ConnectionSpecSelector {
     // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
     // again with a different connection spec.
     if (e instanceof SSLHandshakeException) {
-      // If the problem was a CertificateException from the X509TrustManager,
-      // do not retry.
+      // If the problem was a CertificateException from the X509TrustManager, do not retry.
       if (e.getCause() instanceof CertificateException) {
         return false;
       }
@@ -122,11 +120,8 @@ public final class ConnectionSpecSelector {
       return false;
     }
 
-    // On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we
-    // retry those when we probably should not.
-    return (e instanceof SSLHandshakeException
-        || e instanceof SSLProtocolException
-        || e instanceof SSLException);
+    // Retry for all other SSL failures.
+    return e instanceof SSLException;
   }
 
   /**
diff --git a/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java b/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java
index 25445fac50663254afb54fb863f52ea0ab4bda38..d087e5da6577524c530d95c5c81ca765d31a57d0 100644
--- a/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java
+++ b/okhttp/src/main/java/okhttp3/internal/connection/RealConnection.java
@@ -26,6 +26,7 @@ import java.net.Socket;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.net.UnknownServiceException;
+import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
@@ -323,11 +324,18 @@ public final class RealConnection extends Http2Connection.Listener implements Co
 
       // Verify that the socket's certificates are acceptable for the target host.
       if (!address.hostnameVerifier().verify(address.url().host(), sslSocketSession)) {
-        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
-        throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
-            + "\n    certificate: " + CertificatePinner.pin(cert)
-            + "\n    DN: " + cert.getSubjectDN().getName()
-            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
+        List<Certificate> peerCertificates = unverifiedHandshake.peerCertificates();
+        if (!peerCertificates.isEmpty()) {
+          X509Certificate cert = (X509Certificate) peerCertificates.get(0);
+          throw new SSLPeerUnverifiedException(
+              "Hostname " + address.url().host() + " not verified:"
+                  + "\n    certificate: " + CertificatePinner.pin(cert)
+                  + "\n    DN: " + cert.getSubjectDN().getName()
+                  + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
+        } else {
+          throw new SSLPeerUnverifiedException(
+              "Hostname " + address.url().host() + " not verified (no certificates)");
+        }
       }
 
       // Check that the certificate pinner is satisfied by the certificates presented.
@@ -392,7 +400,7 @@ public final class RealConnection extends Http2Connection.Listener implements Co
           // that happens, then we will have buffered bytes that are needed by the SSLSocket!
           // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
           // that it will almost certainly fail because the proxy has sent unexpected data.
-          if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
+          if (!source.getBuffer().exhausted() || !sink.buffer().exhausted()) {
             throw new IOException("TLS tunnel buffered too many bytes!");
           }
           return null;
diff --git a/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java b/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java
index dcad3d80dbafc8d2c5c388aea87ff908dfcd0164..b36219290707f1a52fe946b178b516552b79d0c1 100644
--- a/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java
+++ b/okhttp/src/main/java/okhttp3/internal/connection/StreamAllocation.java
@@ -57,14 +57,14 @@ import static okhttp3.internal.Util.closeQuietly;
  * connections. This class has APIs to release each of the above resources:
  *
  * <ul>
- *     <li>{@link #noNewStreams()} prevents the connection from being used for new streams in the
+ *     <li>{@link #noNewStreams} prevents the connection from being used for new streams in the
  *         future. Use this after a {@code Connection: close} header, or when the connection may be
  *         inconsistent.
- *     <li>{@link #streamFinished streamFinished()} releases the active stream from this allocation.
+ *     <li>{@link #streamFinished streamFinished} releases the active stream from this allocation.
  *         Note that only one stream may be active at a given time, so it is necessary to call
  *         {@link #streamFinished streamFinished()} before creating a subsequent stream with {@link
  *         #newStream newStream()}.
- *     <li>{@link #release()} removes the call's hold on the connection. Note that this won't
+ *     <li>{@link #release} removes the call's hold on the connection. Note that this won't
  *         immediately free the connection if there is a stream still lingering. That happens when a
  *         call is complete but its response body has yet to be fully consumed.
  * </ul>
@@ -185,7 +185,7 @@ public final class StreamAllocation {
 
       if (result == null) {
         // Attempt to get a connection from the pool.
-        Internal.instance.get(connectionPool, address, this, null);
+        Internal.instance.acquire(connectionPool, address, this, null);
         if (connection != null) {
           foundPooledConnection = true;
           result = connection;
@@ -223,7 +223,7 @@ public final class StreamAllocation {
         List<Route> routes = routeSelection.getAll();
         for (int i = 0, size = routes.size(); i < size; i++) {
           Route route = routes.get(i);
-          Internal.instance.get(connectionPool, address, this, route);
+          Internal.instance.acquire(connectionPool, address, this, route);
           if (connection != null) {
             foundPooledConnection = true;
             result = connection;
@@ -343,7 +343,7 @@ public final class StreamAllocation {
     return connection;
   }
 
-  public void release() {
+  public void release(boolean callEnd) {
     Socket socket;
     Connection releasedConnection;
     synchronized (connectionPool) {
@@ -353,9 +353,13 @@ public final class StreamAllocation {
     }
     closeQuietly(socket);
     if (releasedConnection != null) {
-      Internal.instance.timeoutExit(call, null);
+      if (callEnd) {
+        Internal.instance.timeoutExit(call, null);
+      }
       eventListener.connectionReleased(call, releasedConnection);
-      eventListener.callEnd(call);
+      if (callEnd) {
+        eventListener.callEnd(call);
+      }
     }
   }
 
diff --git a/okhttp/src/main/java/okhttp3/internal/duplex/DuplexRequestBody.java b/okhttp/src/main/java/okhttp3/internal/duplex/DuplexRequestBody.java
new file mode 100644
index 0000000000000000000000000000000000000000..ea00037d61c44b6e54c901f07f2036c5be517610
--- /dev/null
+++ b/okhttp/src/main/java/okhttp3/internal/duplex/DuplexRequestBody.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okhttp3.internal.duplex;
+
+import okhttp3.RequestBody;
+import okhttp3.ResponseBody;
+import okio.Sink;
+
+/**
+ * A request body that is special in how it is <strong>transmitted</strong> on the network and in
+ * the <strong>API contract</strong> between OkHttp and the application.
+ *
+ * <h3>Duplex Transmission</h3>
+ *
+ * <p>With regular HTTP calls the request always completes sending before the response may begin
+ * receiving. With duplex the request and response may be interleaved! That is, request body bytes
+ * may be sent after response headers or body bytes have been received.
+ *
+ * <p>Though any call may be initiated as a duplex call, only web servers that are specially
+ * designed for this nonstandard interaction will use it. As of 2019-01, the only widely-used
+ * implementation of this pattern is gRPC.
+ *
+ * <p>Because the encoding of interleaved data is not well-defined for HTTP/1, duplex request bodies
+ * may only be used with HTTP/2. Calls to HTTP/1 servers will fail before the HTTP request is
+ * transmitted.
+ *
+ * <p>Duplex APIs</p>
+ *
+ * <p>With regular request bodies it is not legal to write bytes to the sink passed to {@link
+ * RequestBody#writeTo} after that method returns. For duplex sinks that condition is lifted. Such
+ * writes occur on an application-provided thread and may occur concurrently with reads of the
+ * {@link ResponseBody}.
+ *
+ * <p>Signal the end of a duplex request body by calling {@link Sink#close()}.
+ */
+public interface DuplexRequestBody {
+  // TODO(jwilson): replace this internal marker interface with a public isDuplex() method?
+}
diff --git a/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java b/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java
index 62531aa5140d5996de1e01bb39df139ce5c05e74..ca8a6f517d4f1cf6d0c8221b7eeefe259802c8d0 100644
--- a/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java
+++ b/okhttp/src/main/java/okhttp3/internal/http/CallServerInterceptor.java
@@ -17,12 +17,15 @@ package okhttp3.internal.http;
 
 import java.io.IOException;
 import java.net.ProtocolException;
+import okhttp3.Call;
 import okhttp3.Interceptor;
 import okhttp3.Request;
 import okhttp3.Response;
+import okhttp3.internal.Internal;
 import okhttp3.internal.Util;
 import okhttp3.internal.connection.RealConnection;
 import okhttp3.internal.connection.StreamAllocation;
+import okhttp3.internal.duplex.DuplexRequestBody;
 import okio.Buffer;
 import okio.BufferedSink;
 import okio.ForwardingSink;
@@ -38,17 +41,18 @@ public final class CallServerInterceptor implements Interceptor {
   }
 
   @Override public Response intercept(Chain chain) throws IOException {
-    RealInterceptorChain realChain = (RealInterceptorChain) chain;
-    HttpCodec httpCodec = realChain.httpStream();
+    final RealInterceptorChain realChain = (RealInterceptorChain) chain;
+    Call call = realChain.call();
+    final HttpCodec httpCodec = realChain.httpStream();
     StreamAllocation streamAllocation = realChain.streamAllocation();
     RealConnection connection = (RealConnection) realChain.connection();
     Request request = realChain.request();
 
     long sentRequestMillis = System.currentTimeMillis();
 
-    realChain.eventListener().requestHeadersStart(realChain.call());
+    realChain.eventListener().requestHeadersStart(call);
     httpCodec.writeRequestHeaders(request);
-    realChain.eventListener().requestHeadersEnd(realChain.call(), request);
+    realChain.eventListener().requestHeadersEnd(call, request);
 
     Response.Builder responseBuilder = null;
     if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
@@ -57,22 +61,29 @@ public final class CallServerInterceptor implements Interceptor {
       // what we did get (such as a 4xx response) without ever transmitting the request body.
       if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
         httpCodec.flushRequest();
-        realChain.eventListener().responseHeadersStart(realChain.call());
+        realChain.eventListener().responseHeadersStart(call);
         responseBuilder = httpCodec.readResponseHeaders(true);
       }
 
       if (responseBuilder == null) {
-        // Write the request body if the "Expect: 100-continue" expectation was met.
-        realChain.eventListener().requestBodyStart(realChain.call());
-        long contentLength = request.body().contentLength();
-        CountingSink requestBodyOut =
-            new CountingSink(httpCodec.createRequestBody(request, contentLength));
-        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
-
-        request.body().writeTo(bufferedRequestBody);
-        bufferedRequestBody.close();
-        realChain.eventListener()
-            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
+        if (request.body() instanceof DuplexRequestBody) {
+          // Prepare a duplex body so that the application can send a request body later.
+          httpCodec.flushRequest();
+          CountingSink requestBodyOut = new CountingSink(httpCodec.createRequestBody(request, -1L));
+          BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
+          request.body().writeTo(bufferedRequestBody);
+        } else {
+          // Write the request body if the "Expect: 100-continue" expectation was met.
+          realChain.eventListener().requestBodyStart(call);
+          long contentLength = request.body().contentLength();
+          CountingSink requestBodyOut =
+              new CountingSink(httpCodec.createRequestBody(request, contentLength));
+          BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
+
+          request.body().writeTo(bufferedRequestBody);
+          bufferedRequestBody.close();
+          realChain.eventListener().requestBodyEnd(call, requestBodyOut.successfulCount);
+        }
       } else if (!connection.isMultiplexed()) {
         // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
         // from being reused. Otherwise we're still obligated to transmit the request body to
@@ -81,19 +92,22 @@ public final class CallServerInterceptor implements Interceptor {
       }
     }
 
-    httpCodec.finishRequest();
+    if (!(request.body() instanceof DuplexRequestBody)) {
+      httpCodec.finishRequest();
+    }
 
     if (responseBuilder == null) {
-      realChain.eventListener().responseHeadersStart(realChain.call());
+      realChain.eventListener().responseHeadersStart(call);
       responseBuilder = httpCodec.readResponseHeaders(false);
     }
 
-    Response response = responseBuilder
+    responseBuilder
         .request(request)
         .handshake(streamAllocation.connection().handshake())
         .sentRequestAtMillis(sentRequestMillis)
-        .receivedResponseAtMillis(System.currentTimeMillis())
-        .build();
+        .receivedResponseAtMillis(System.currentTimeMillis());
+    Internal.instance.initCodec(responseBuilder, httpCodec);
+    Response response = responseBuilder.build();
 
     int code = response.code();
     if (code == 100) {
@@ -101,18 +115,18 @@ public final class CallServerInterceptor implements Interceptor {
       // try again to read the actual response
       responseBuilder = httpCodec.readResponseHeaders(false);
 
-      response = responseBuilder
-              .request(request)
-              .handshake(streamAllocation.connection().handshake())
-              .sentRequestAtMillis(sentRequestMillis)
-              .receivedResponseAtMillis(System.currentTimeMillis())
-              .build();
+      responseBuilder
+          .request(request)
+          .handshake(streamAllocation.connection().handshake())
+          .sentRequestAtMillis(sentRequestMillis)
+          .receivedResponseAtMillis(System.currentTimeMillis());
+      Internal.instance.initCodec(responseBuilder, httpCodec);
+      response = responseBuilder.build();
 
       code = response.code();
     }
 
-    realChain.eventListener()
-            .responseHeadersEnd(realChain.call(), response);
+    realChain.eventListener().responseHeadersEnd(call, response);
 
     if (forWebSocket && code == 101) {
       // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
diff --git a/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java b/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java
index ad9759acce3ed220a38a513d07acec850dc4958a..103fce6662e9f943efb0d3aa6276d24636fadeea 100644
--- a/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java
+++ b/okhttp/src/main/java/okhttp3/internal/http/HttpCodec.java
@@ -16,6 +16,7 @@
 package okhttp3.internal.http;
 
 import java.io.IOException;
+import okhttp3.Headers;
 import okhttp3.Request;
 import okhttp3.Response;
 import okhttp3.ResponseBody;
@@ -53,6 +54,9 @@ public interface HttpCodec {
   /** Returns a stream that reads the response body. */
   ResponseBody openResponseBody(Response response) throws IOException;
 
+  /** Returns the trailers after the HTTP response. May be empty. */
+  Headers trailers() throws IOException;
+
   /**
    * Cancel this stream. Resources held by this stream will be cleaned up, though not synchronously.
    * That may happen later by the connection pool thread.
diff --git a/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java b/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java
index 479fe07a24fff2acedee0a5eef5abd55dcc33717..7712934efbbf88cc6908a67a655dbc3ac05368cb 100644
--- a/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java
+++ b/okhttp/src/main/java/okhttp3/internal/http/HttpHeaders.java
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import okhttp3.Challenge;
@@ -36,7 +37,7 @@ import okio.ByteString;
 
 import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
 import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
-import static okhttp3.internal.Util.equal;
+import static okhttp3.internal.Util.EMPTY_HEADERS;
 import static okhttp3.internal.http.StatusLine.HTTP_CONTINUE;
 
 /** Headers and utilities for internal use by OkHttp. */
@@ -71,7 +72,7 @@ public final class HttpHeaders {
   public static boolean varyMatches(
       Response cachedResponse, Headers cachedRequest, Request newRequest) {
     for (String field : varyFields(cachedResponse)) {
-      if (!equal(cachedRequest.values(field), newRequest.headers(field))) return false;
+      if (!Objects.equals(cachedRequest.values(field), newRequest.headers(field))) return false;
     }
     return true;
   }
@@ -132,7 +133,7 @@ public final class HttpHeaders {
    */
   public static Headers varyHeaders(Headers requestHeaders, Headers responseHeaders) {
     Set<String> varyFields = varyFields(responseHeaders);
-    if (varyFields.isEmpty()) return new Headers.Builder().build();
+    if (varyFields.isEmpty()) return EMPTY_HEADERS;
 
     Headers.Builder result = new Headers.Builder();
     for (int i = 0, size = requestHeaders.size(); i < size; i++) {
@@ -194,7 +195,7 @@ public final class HttpHeaders {
       peek = readToken(header);
       if (peek == null) {
         if (!header.exhausted()) return; // Expected a token; got something else.
-        result.add(new Challenge(schemeName, Collections.<String, String>emptyMap()));
+        result.add(new Challenge(schemeName, Collections.emptyMap()));
         return;
       }
 
@@ -204,7 +205,7 @@ public final class HttpHeaders {
       // It's a token68 because there isn't a value after it.
       if (!commaPrefixed && (commaSuffixed || header.exhausted())) {
         result.add(new Challenge(schemeName, Collections.singletonMap(
-            (String) null, peek + repeat('=', eqCount))));
+            null, peek + repeat('=', eqCount))));
         peek = null;
         continue;
       }
diff --git a/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java b/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java
index d682de8bf04f637d7667d8c6e64bcd8aee5f315a..fa72df373b22af48773bb5a5e8325f66f142878f 100644
--- a/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java
+++ b/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java
@@ -15,6 +15,7 @@
  */
 package okhttp3.internal.http;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.HttpRetryException;
@@ -65,14 +66,12 @@ public final class RetryAndFollowUpInterceptor implements Interceptor {
   private static final int MAX_FOLLOW_UPS = 20;
 
   private final OkHttpClient client;
-  private final boolean forWebSocket;
   private volatile StreamAllocation streamAllocation;
   private Object callStackTrace;
   private volatile boolean canceled;
 
-  public RetryAndFollowUpInterceptor(OkHttpClient client, boolean forWebSocket) {
+  public RetryAndFollowUpInterceptor(OkHttpClient client) {
     this.client = client;
-    this.forWebSocket = forWebSocket;
   }
 
   /**
@@ -116,7 +115,7 @@ public final class RetryAndFollowUpInterceptor implements Interceptor {
     Response priorResponse = null;
     while (true) {
       if (canceled) {
-        streamAllocation.release();
+        streamAllocation.release(true);
         throw new IOException("Canceled");
       }
 
@@ -142,7 +141,7 @@ public final class RetryAndFollowUpInterceptor implements Interceptor {
         // We're throwing an unchecked exception. Release any resources.
         if (releaseConnection) {
           streamAllocation.streamFailed(null);
-          streamAllocation.release();
+          streamAllocation.release(true);
         }
       }
 
@@ -159,29 +158,29 @@ public final class RetryAndFollowUpInterceptor implements Interceptor {
       try {
         followUp = followUpRequest(response, streamAllocation.route());
       } catch (IOException e) {
-        streamAllocation.release();
+        streamAllocation.release(true);
         throw e;
       }
 
       if (followUp == null) {
-        streamAllocation.release();
+        streamAllocation.release(true);
         return response;
       }
 
       closeQuietly(response.body());
 
       if (++followUpCount > MAX_FOLLOW_UPS) {
-        streamAllocation.release();
+        streamAllocation.release(true);
         throw new ProtocolException("Too many follow-up requests: " + followUpCount);
       }
 
       if (followUp.body() instanceof UnrepeatableRequestBody) {
-        streamAllocation.release();
+        streamAllocation.release(true);
         throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
       }
 
       if (!sameConnection(response, followUp.url())) {
-        streamAllocation.release();
+        streamAllocation.release(false);
         streamAllocation = new StreamAllocation(client.connectionPool(),
             createAddress(followUp.url()), call, eventListener, callStackTrace);
         this.streamAllocation = streamAllocation;
@@ -224,7 +223,7 @@ public final class RetryAndFollowUpInterceptor implements Interceptor {
     if (!client.retryOnConnectionFailure()) return false;
 
     // We can't send the request body again.
-    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
+    if (requestSendStarted && requestIsUnrepeatable(e, userRequest)) return false;
 
     // This exception is fatal.
     if (!isRecoverable(e, requestSendStarted)) return false;
@@ -236,6 +235,11 @@ public final class RetryAndFollowUpInterceptor implements Interceptor {
     return true;
   }
 
+  private boolean requestIsUnrepeatable(IOException e, Request userRequest) {
+    return userRequest.body() instanceof UnrepeatableRequestBody
+        || e instanceof FileNotFoundException;
+  }
+
   private boolean isRecoverable(IOException e, boolean requestSendStarted) {
     // If there was a protocol problem, don't recover.
     if (e instanceof ProtocolException) {
diff --git a/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java b/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java
index 6c7b4373c441d21945f5057f5df32ebff44ae93b..6dc70c48651b054b069e77c3ae019bfc4b9b2b09 100644
--- a/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java
+++ b/okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java
@@ -86,6 +86,12 @@ public final class Http1Codec implements HttpCodec {
   int state = STATE_IDLE;
   private long headerLimit = HEADER_LIMIT;
 
+  /**
+   * Received trailers. Null unless the response body uses chunked transfer-encoding and includes
+   * trailers. Undefined until the end of the response body.
+   */
+  private Headers trailers;
+
   public Http1Codec(OkHttpClient client, StreamAllocation streamAllocation, BufferedSource source,
       BufferedSink sink) {
     this.client = client;
@@ -153,6 +159,13 @@ public final class Http1Codec implements HttpCodec {
     return new RealResponseBody(contentType, -1L, Okio.buffer(newUnknownLengthSource()));
   }
 
+  @Override public Headers trailers() throws IOException {
+    if (state != STATE_CLOSED) {
+      throw new IllegalStateException("too early; can't read the trailers yet");
+    }
+    return trailers != null ? trailers : Util.EMPTY_HEADERS;
+  }
+
   /** Returns true if this connection is closed. */
   public boolean isClosed() {
     return state == STATE_CLOSED;
@@ -205,9 +218,7 @@ public final class Http1Codec implements HttpCodec {
       return responseBuilder;
     } catch (EOFException e) {
       // Provide more context if the server ends the stream before sending a response.
-      IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
-      exception.initCause(e);
-      throw exception;
+      throw new IOException("unexpected end of stream on " + streamAllocation, e);
     }
   }
 
@@ -477,7 +488,8 @@ public final class Http1Codec implements HttpCodec {
       }
       if (bytesRemainingInChunk == 0L) {
         hasMoreChunks = false;
-        HttpHeaders.receiveHeaders(client.cookieJar(), url, readHeaders());
+        trailers = readHeaders();
+        HttpHeaders.receiveHeaders(client.cookieJar(), url, trailers);
         endOfInput(true, null);
       }
     }
diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Header.java b/okhttp/src/main/java/okhttp3/internal/http2/Header.java
index 362570a2bcd285b8a60be6bf8a79e2a0b19f9ada..a306ac4622ec65da23015d1d1e9307e6cc319c05 100644
--- a/okhttp/src/main/java/okhttp3/internal/http2/Header.java
+++ b/okhttp/src/main/java/okhttp3/internal/http2/Header.java
@@ -15,7 +15,6 @@
  */
 package okhttp3.internal.http2;
 
-import okhttp3.Headers;
 import okhttp3.internal.Util;
 import okio.ByteString;
 
@@ -77,9 +76,4 @@ public final class Header {
   @Override public String toString() {
     return Util.format("%s: %s", name.utf8(), value.utf8());
   }
-
-  // TODO(jwilson): move this to Headers?
-  interface Listener {
-    void onHeaders(Headers headers);
-  }
 }
diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java b/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java
index 258ac9b2693c609d2758cfd7eaef2216d14ebabd..615756cc4864e7fe154f0bde0b0e25713d667033 100644
--- a/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java
+++ b/okhttp/src/main/java/okhttp3/internal/http2/Hpack.java
@@ -22,7 +22,7 @@ import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import okhttp3.internal.Util;
+import java.util.Objects;
 import okio.Buffer;
 import okio.BufferedSource;
 import okio.ByteString;
@@ -483,9 +483,9 @@ final class Hpack {
             // it's unnecessary to waste cycles looking at them. This check is built on the
             // observation that the header entries we care about are in adjacent pairs, and we
             // always know the first index of the pair.
-            if (Util.equal(STATIC_HEADER_TABLE[headerNameIndex - 1].value, value)) {
+            if (Objects.equals(STATIC_HEADER_TABLE[headerNameIndex - 1].value, value)) {
               headerIndex = headerNameIndex;
-            } else if (Util.equal(STATIC_HEADER_TABLE[headerNameIndex].value, value)) {
+            } else if (Objects.equals(STATIC_HEADER_TABLE[headerNameIndex].value, value)) {
               headerIndex = headerNameIndex + 1;
             }
           }
@@ -493,8 +493,8 @@ final class Hpack {
 
         if (headerIndex == -1) {
           for (int j = nextHeaderIndex + 1, length = dynamicTable.length; j < length; j++) {
-            if (Util.equal(dynamicTable[j].name, name)) {
-              if (Util.equal(dynamicTable[j].value, value)) {
+            if (Objects.equals(dynamicTable[j].name, name)) {
+              if (Objects.equals(dynamicTable[j].value, value)) {
                 headerIndex = j - nextHeaderIndex + STATIC_HEADER_TABLE.length;
                 break;
               } else if (headerNameIndex == -1) {
diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java
index 9a81d536f2bc8ec34498fa669e26b84889d36e5e..9200e6ef1a089248dc540bd61ef2f87508b053bc 100644
--- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java
+++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java
@@ -37,7 +37,6 @@ import okhttp3.internal.http.RealResponseBody;
 import okhttp3.internal.http.RequestLine;
 import okhttp3.internal.http.StatusLine;
 import okio.Buffer;
-import okio.ByteString;
 import okio.ForwardingSource;
 import okio.Okio;
 import okio.Sink;
@@ -92,8 +91,9 @@ public final class Http2Codec implements HttpCodec {
   private final Interceptor.Chain chain;
   final StreamAllocation streamAllocation;
   private final Http2Connection connection;
-  private Http2Stream stream;
+  private volatile Http2Stream stream;
   private final Protocol protocol;
+  private volatile boolean canceled;
 
   public Http2Codec(OkHttpClient client, Interceptor.Chain chain, StreamAllocation streamAllocation,
       Http2Connection connection) {
@@ -115,6 +115,12 @@ public final class Http2Codec implements HttpCodec {
     boolean hasRequestBody = request.body() != null;
     List<Header> requestHeaders = http2HeadersList(request);
     stream = connection.newStream(requestHeaders, hasRequestBody);
+    // We may have been asked to cancel while creating the new stream and sending the request
+    // headers, but there was still no stream to close.
+    if (canceled) {
+      stream.closeLater(ErrorCode.CANCEL);
+      throw new IOException("Canceled");
+    }
     stream.readTimeout().timeout(chain.readTimeoutMillis(), TimeUnit.MILLISECONDS);
     stream.writeTimeout().timeout(chain.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
   }
@@ -149,8 +155,9 @@ public final class Http2Codec implements HttpCodec {
 
     for (int i = 0, size = headers.size(); i < size; i++) {
       // header names must be lowercase.
-      ByteString name = ByteString.encodeUtf8(headers.name(i).toLowerCase(Locale.US));
-      if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name.utf8())) {
+      String name = headers.name(i).toLowerCase(Locale.US);
+      if (!HTTP_2_SKIPPED_REQUEST_HEADERS.contains(name)
+          || name.equals(TE) && headers.value(i).equals("trailers")) {
         result.add(new Header(name, headers.value(i)));
       }
     }
@@ -188,7 +195,12 @@ public final class Http2Codec implements HttpCodec {
     return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
   }
 
+  @Override public Headers trailers() throws IOException {
+    return stream.trailers();
+  }
+
   @Override public void cancel() {
+    canceled = true;
     if (stream != null) stream.closeLater(ErrorCode.CANCEL);
   }
 
diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java
index aa77584e233051d3e00abd1d4c5e626b1f21a19a..cf1413f886b1739bc8dd0cc76b1b9327dd7509af 100644
--- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java
+++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Connection.java
@@ -20,6 +20,7 @@ import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.InetSocketAddress;
 import java.net.Socket;
+import java.net.SocketAddress;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -34,7 +35,6 @@ import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import okhttp3.Headers;
-import okhttp3.Protocol;
 import okhttp3.internal.NamedRunnable;
 import okhttp3.internal.Util;
 import okhttp3.internal.platform.Platform;
@@ -79,7 +79,7 @@ public final class Http2Connection implements Closeable {
    * threads because listeners are not required to return promptly.
    */
   private static final ExecutorService listenerExecutor = new ThreadPoolExecutor(0,
-      Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
+      Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<>(),
       Util.threadFactory("OkHttp Http2Connection", true));
 
   /** True if this peer initiated the connection. */
@@ -91,7 +91,7 @@ public final class Http2Connection implements Closeable {
    */
   final Listener listener;
   final Map<Integer, Http2Stream> streams = new LinkedHashMap<>();
-  final String hostname;
+  final String connectionName;
   int lastGoodStreamId;
   int nextStreamId;
   boolean shutdown;
@@ -153,19 +153,18 @@ public final class Http2Connection implements Closeable {
       okHttpSettings.set(Settings.INITIAL_WINDOW_SIZE, OKHTTP_CLIENT_WINDOW_SIZE);
     }
 
-    hostname = builder.hostname;
+    connectionName = builder.connectionName;
 
     writerExecutor = new ScheduledThreadPoolExecutor(1,
-        Util.threadFactory(Util.format("OkHttp %s Writer", hostname), false));
+        Util.threadFactory(Util.format("OkHttp %s Writer", connectionName), false));
     if (builder.pingIntervalMillis != 0) {
       writerExecutor.scheduleAtFixedRate(new PingRunnable(false, 0, 0),
           builder.pingIntervalMillis, builder.pingIntervalMillis, MILLISECONDS);
     }
 
     // Like newSingleThreadExecutor, except lazy creates the thread.
-    pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS,
-        new LinkedBlockingQueue<Runnable>(),
-        Util.threadFactory(Util.format("OkHttp %s Push Observer", hostname), true));
+    pushExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
+        Util.threadFactory(Util.format("OkHttp %s Push Observer", connectionName), true));
     peerSettings.set(Settings.INITIAL_WINDOW_SIZE, DEFAULT_INITIAL_WINDOW_SIZE);
     peerSettings.set(Settings.MAX_FRAME_SIZE, Http2.INITIAL_MAX_FRAME_SIZE);
     bytesLeftInWriteWindow = peerSettings.getInitialWindowSize();
@@ -175,11 +174,6 @@ public final class Http2Connection implements Closeable {
     readerRunnable = new ReaderRunnable(new Http2Reader(builder.source, client));
   }
 
-  /** The protocol as selected using ALPN. */
-  public Protocol getProtocol() {
-    return Protocol.HTTP_2;
-  }
-
   /**
    * Returns the number of {@link Http2Stream#isOpen() open streams} on this connection.
    */
@@ -256,7 +250,7 @@ public final class Http2Connection implements Closeable {
         }
       }
       if (associatedStreamId == 0) {
-        writer.synStream(outFinished, streamId, associatedStreamId, requestHeaders);
+        writer.headers(outFinished, streamId, requestHeaders);
       } else if (client) {
         throw new IllegalArgumentException("client streams shouldn't have associated stream IDs");
       } else { // HTTP/2 has a PUSH_PROMISE frame.
@@ -271,9 +265,9 @@ public final class Http2Connection implements Closeable {
     return stream;
   }
 
-  void writeSynReply(int streamId, boolean outFinished, List<Header> alternating)
+  void writeHeaders(int streamId, boolean outFinished, List<Header> alternating)
       throws IOException {
-    writer.synReply(outFinished, streamId, alternating);
+    writer.headers(outFinished, streamId, alternating);
   }
 
   /**
@@ -324,7 +318,7 @@ public final class Http2Connection implements Closeable {
 
   void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
     try {
-      writerExecutor.execute(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) {
+      writerExecutor.execute(new NamedRunnable("OkHttp %s stream %d", connectionName, streamId) {
         @Override public void execute() {
           try {
             writeSynReset(streamId, errorCode);
@@ -345,7 +339,7 @@ public final class Http2Connection implements Closeable {
   void writeWindowUpdateLater(final int streamId, final long unacknowledgedBytesRead) {
     try {
       writerExecutor.execute(
-          new NamedRunnable("OkHttp Window Update %s stream %d", hostname, streamId) {
+          new NamedRunnable("OkHttp Window Update %s stream %d", connectionName, streamId) {
             @Override public void execute() {
               try {
                 writer.windowUpdate(streamId, unacknowledgedBytesRead);
@@ -365,7 +359,7 @@ public final class Http2Connection implements Closeable {
     final int payload2;
 
     PingRunnable(boolean reply, int payload1, int payload2) {
-      super("OkHttp %s ping %08x%08x", hostname, payload1, payload2);
+      super("OkHttp %s ping %08x%08x", connectionName, payload1, payload2);
       this.reply = reply;
       this.payload1 = payload1;
       this.payload2 = payload2;
@@ -540,7 +534,7 @@ public final class Http2Connection implements Closeable {
 
   public static class Builder {
     Socket socket;
-    String hostname;
+    String connectionName;
     BufferedSource source;
     BufferedSink sink;
     Listener listener = Listener.REFUSE_INCOMING_STREAMS;
@@ -557,14 +551,18 @@ public final class Http2Connection implements Closeable {
     }
 
     public Builder socket(Socket socket) throws IOException {
-      return socket(socket, ((InetSocketAddress) socket.getRemoteSocketAddress()).getHostName(),
+      SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
+      String connectionName = remoteSocketAddress instanceof InetSocketAddress
+          ? ((InetSocketAddress) remoteSocketAddress).getHostName()
+          : remoteSocketAddress.toString();
+      return socket(socket, connectionName,
           Okio.buffer(Okio.source(socket)), Okio.buffer(Okio.sink(socket)));
     }
 
     public Builder socket(
-        Socket socket, String hostname, BufferedSource source, BufferedSink sink) {
+        Socket socket, String connectionName, BufferedSource source, BufferedSink sink) {
       this.socket = socket;
-      this.hostname = hostname;
+      this.connectionName = connectionName;
       this.source = source;
       this.sink = sink;
       return this;
@@ -598,7 +596,7 @@ public final class Http2Connection implements Closeable {
     final Http2Reader reader;
 
     ReaderRunnable(Http2Reader reader) {
-      super("OkHttp %s", hostname);
+      super("OkHttp %s", connectionName);
       this.reader = reader;
     }
 
@@ -638,7 +636,7 @@ public final class Http2Connection implements Closeable {
       }
       dataStream.receiveData(source, length);
       if (inFinished) {
-        dataStream.receiveFin();
+        dataStream.receiveHeaders(Util.EMPTY_HEADERS, true);
       }
     }
 
@@ -668,12 +666,14 @@ public final class Http2Connection implements Closeable {
               false, inFinished, headers);
           lastGoodStreamId = streamId;
           streams.put(streamId, newStream);
-          listenerExecutor.execute(new NamedRunnable("OkHttp %s stream %d", hostname, streamId) {
+          listenerExecutor.execute(new NamedRunnable(
+              "OkHttp %s stream %d", connectionName, streamId) {
             @Override public void execute() {
               try {
                 listener.onStream(newStream);
               } catch (IOException e) {
-                Platform.get().log(INFO, "Http2Connection.Listener failure for " + hostname, e);
+                Platform.get().log(
+                    INFO, "Http2Connection.Listener failure for " + connectionName, e);
                 try {
                   newStream.close(ErrorCode.PROTOCOL_ERROR);
                 } catch (IOException ignored) {
@@ -686,8 +686,7 @@ public final class Http2Connection implements Closeable {
       }
 
       // Update an existing stream.
-      stream.receiveHeaders(headerBlock);
-      if (inFinished) stream.receiveFin();
+      stream.receiveHeaders(Util.toHeaders(headerBlock), inFinished);
     }
 
     @Override public void rstStream(int streamId, ErrorCode errorCode) {
@@ -719,7 +718,7 @@ public final class Http2Connection implements Closeable {
             streamsToNotify = streams.values().toArray(new Http2Stream[streams.size()]);
           }
         }
-        listenerExecutor.execute(new NamedRunnable("OkHttp %s settings", hostname) {
+        listenerExecutor.execute(new NamedRunnable("OkHttp %s settings", connectionName) {
           @Override public void execute() {
             listener.onSettings(Http2Connection.this);
           }
@@ -736,7 +735,7 @@ public final class Http2Connection implements Closeable {
 
     private void applyAndAckSettings(final Settings peerSettings) {
       try {
-        writerExecutor.execute(new NamedRunnable("OkHttp %s ACK Settings", hostname) {
+        writerExecutor.execute(new NamedRunnable("OkHttp %s ACK Settings", connectionName) {
           @Override public void execute() {
             try {
               writer.applyAndAckSettings(peerSettings);
@@ -839,7 +838,8 @@ public final class Http2Connection implements Closeable {
       currentPushRequests.add(streamId);
     }
     try {
-      pushExecutorExecute(new NamedRunnable("OkHttp %s Push Request[%s]", hostname, streamId) {
+      pushExecutorExecute(new NamedRunnable(
+          "OkHttp %s Push Request[%s]", connectionName, streamId) {
         @Override public void execute() {
           boolean cancel = pushObserver.onRequest(streamId, requestHeaders);
           try {
@@ -861,7 +861,8 @@ public final class Http2Connection implements Closeable {
   void pushHeadersLater(final int streamId, final List<Header> requestHeaders,
       final boolean inFinished) {
     try {
-      pushExecutorExecute(new NamedRunnable("OkHttp %s Push Headers[%s]", hostname, streamId) {
+      pushExecutorExecute(new NamedRunnable(
+          "OkHttp %s Push Headers[%s]", connectionName, streamId) {
         @Override public void execute() {
           boolean cancel = pushObserver.onHeaders(streamId, requestHeaders, inFinished);
           try {
@@ -890,7 +891,7 @@ public final class Http2Connection implements Closeable {
     source.require(byteCount); // Eagerly read the frame before firing client thread.
     source.read(buffer, byteCount);
     if (buffer.size() != byteCount) throw new IOException(buffer.size() + " != " + byteCount);
-    pushExecutorExecute(new NamedRunnable("OkHttp %s Push Data[%s]", hostname, streamId) {
+    pushExecutorExecute(new NamedRunnable("OkHttp %s Push Data[%s]", connectionName, streamId) {
       @Override public void execute() {
         try {
           boolean cancel = pushObserver.onData(streamId, buffer, byteCount, inFinished);
@@ -907,7 +908,7 @@ public final class Http2Connection implements Closeable {
   }
 
   void pushResetLater(final int streamId, final ErrorCode errorCode) {
-    pushExecutorExecute(new NamedRunnable("OkHttp %s Push Reset[%s]", hostname, streamId) {
+    pushExecutorExecute(new NamedRunnable("OkHttp %s Push Reset[%s]", connectionName, streamId) {
       @Override public void execute() {
         pushObserver.onReset(streamId, errorCode);
         synchronized (Http2Connection.this) {
diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java
index c391b389523c986c8ddd9c0ab13f2755777a9aab..3431901fa5af201bc3a8663f10319995ddf01f45 100644
--- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java
+++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Stream.java
@@ -20,7 +20,6 @@ import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.SocketTimeoutException;
 import java.util.ArrayDeque;
-import java.util.ArrayList;
 import java.util.Deque;
 import java.util.List;
 import javax.annotation.Nullable;
@@ -61,7 +60,6 @@ public final class Http2Stream {
    * read}.
    */
   private final Deque<Headers> headersQueue = new ArrayDeque<>();
-  private Header.Listener headersListener;
 
   /** True if response headers have been sent or received. */
   private boolean hasResponseHeaders;
@@ -158,6 +156,20 @@ public final class Http2Stream {
     throw new StreamResetException(errorCode);
   }
 
+  /**
+   * Returns the trailers. It is only safe to call this once the source stream has been completely
+   * exhausted.
+   */
+  public synchronized Headers trailers() throws IOException {
+    if (errorCode != null) {
+      throw new StreamResetException(errorCode);
+    }
+    if (!source.finished || !source.receiveBuffer.exhausted() || !source.readBuffer.exhausted()) {
+      throw new IllegalStateException("too early; can't read the trailers yet");
+    }
+    return source.trailers != null ? source.trailers : Util.EMPTY_HEADERS;
+  }
+
   /**
    * Returns the reason why this stream was closed, or null if it closed normally or has not yet
    * been closed.
@@ -169,22 +181,21 @@ public final class Http2Stream {
   /**
    * Sends a reply to an incoming stream.
    *
-   * @param out true to create an output stream that we can use to send data to the remote peer.
-   * Corresponds to {@code FLAG_FIN}.
+   * @param outFinished true to eagerly finish the output stream to send data to the remote peer.
+   *     Corresponds to {@code FLAG_FIN}.
+   * @param flushHeaders true to force flush the response headers. This should be true unless the
+   *     response body exists and will be written immediately.
    */
-  public void writeHeaders(List<Header> responseHeaders, boolean out) throws IOException {
+  public void writeHeaders(List<Header> responseHeaders, boolean outFinished, boolean flushHeaders)
+      throws IOException {
     assert (!Thread.holdsLock(Http2Stream.this));
     if (responseHeaders == null) {
       throw new NullPointerException("headers == null");
     }
-    boolean outFinished = false;
-    boolean flushHeaders = false;
     synchronized (this) {
       this.hasResponseHeaders = true;
-      if (!out) {
+      if (outFinished) {
         this.sink.finished = true;
-        flushHeaders = true;
-        outFinished = true;
       }
     }
 
@@ -196,14 +207,21 @@ public final class Http2Stream {
       }
     }
 
-    // TODO(jwilson): rename to writeHeaders
-    connection.writeSynReply(id, outFinished, responseHeaders);
+    connection.writeHeaders(id, outFinished, responseHeaders);
 
     if (flushHeaders) {
       connection.flush();
     }
   }
 
+  public void enqueueTrailers(Headers trailers) {
+    synchronized (this) {
+      if (sink.finished) throw new IllegalStateException("already finished");
+      if (trailers.size() == 0) throw new IllegalArgumentException("trailers.size() == 0");
+      this.sink.trailers = trailers;
+    }
+  }
+
   public Timeout readTimeout() {
     return readTimeout;
   }
@@ -221,7 +239,7 @@ public final class Http2Stream {
    * Returns a sink that can be used to write data to the peer.
    *
    * @throws IllegalStateException if this stream was initiated by the peer and a {@link
-   * #writeHeaders} has not yet been sent.
+   *     #writeHeaders} has not yet been sent.
    */
   public Sink getSink() {
     synchronized (this) {
@@ -271,34 +289,28 @@ public final class Http2Stream {
     return true;
   }
 
-  /**
-   * Accept headers from the network and store them until the client calls {@link #takeHeaders}, or
-   * {@link FramingSource#read} them.
-   */
-  void receiveHeaders(List<Header> headers) {
-    assert (!Thread.holdsLock(Http2Stream.this));
-    boolean open;
-    synchronized (this) {
-      hasResponseHeaders = true;
-      headersQueue.add(Util.toHeaders(headers));
-      open = isOpen();
-      notifyAll();
-    }
-    if (!open) {
-      connection.removeStream(id);
-    }
-  }
-
   void receiveData(BufferedSource in, int length) throws IOException {
     assert (!Thread.holdsLock(Http2Stream.this));
     this.source.receive(in, length);
   }
 
-  void receiveFin() {
+  /**
+   * Accept headers from the network and store them until the client calls {@link #takeHeaders}, or
+   * {@link FramingSource#read} them.
+   */
+  void receiveHeaders(Headers headers, boolean inFinished) {
     assert (!Thread.holdsLock(Http2Stream.this));
     boolean open;
     synchronized (this) {
-      this.source.finished = true;
+      if (!hasResponseHeaders || !inFinished) {
+        hasResponseHeaders = true;
+        headersQueue.add(headers);
+      } else {
+        this.source.trailers = headers;
+      }
+      if (inFinished) {
+        this.source.finished = true;
+      }
       open = isOpen();
       notifyAll();
     }
@@ -314,13 +326,6 @@ public final class Http2Stream {
     }
   }
 
-  public synchronized void setHeadersListener(Header.Listener headersListener) {
-    this.headersListener = headersListener;
-    if (!headersQueue.isEmpty() && headersListener != null) {
-      notifyAll(); // We now have somewhere to deliver headers!
-    }
-  }
-
   /**
    * A source that reads the incoming data frames of a stream. Although this class uses
    * synchronization to safely receive incoming data frames, it is not intended for use by multiple
@@ -336,6 +341,12 @@ public final class Http2Stream {
     /** Maximum number of bytes to buffer before reporting a flow control error. */
     private final long maxByteCount;
 
+    /**
+     * Received trailers. Null unless the server has provided trailers. Undefined until the stream
+     * is exhausted. Guarded by Http2Stream.this.
+     */
+    private Headers trailers;
+
     /** True if the caller has closed this stream. */
     boolean closed;
 
@@ -353,8 +364,6 @@ public final class Http2Stream {
       if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
 
       while (true) {
-        Headers headersToDeliver = null;
-        Header.Listener headersListenerToNotify = null;
         long readBytesDelivered = -1;
         ErrorCode errorCodeToDeliver = null;
 
@@ -371,11 +380,6 @@ public final class Http2Stream {
             if (closed) {
               throw new IOException("stream closed");
 
-            } else if (!headersQueue.isEmpty() && headersListener != null) {
-              // Prepare to deliver headers.
-              headersToDeliver = headersQueue.removeFirst();
-              headersListenerToNotify = headersListener;
-
             } else if (readBuffer.size() > 0) {
               // Prepare to read bytes. Start by moving them to the caller's buffer.
               readBytesDelivered = readBuffer.read(sink, Math.min(byteCount, readBuffer.size()));
@@ -401,11 +405,6 @@ public final class Http2Stream {
 
         // 2. Do it outside of the synchronized block and timeout.
 
-        if (headersToDeliver != null && headersListenerToNotify != null) {
-          headersListenerToNotify.onHeaders(headersToDeliver);
-          continue;
-        }
-
         if (readBytesDelivered != -1) {
           // Update connection.unacknowledgedBytesRead outside the synchronized block.
           updateConnectionFlowControl(readBytesDelivered);
@@ -475,28 +474,16 @@ public final class Http2Stream {
 
     @Override public void close() throws IOException {
       long bytesDiscarded;
-      List<Headers> headersToDeliver = null;
-      Header.Listener headersListenerToNotify = null;
       synchronized (Http2Stream.this) {
         closed = true;
         bytesDiscarded = readBuffer.size();
         readBuffer.clear();
-        if (!headersQueue.isEmpty() && headersListener != null) {
-          headersToDeliver = new ArrayList<>(headersQueue);
-          headersQueue.clear();
-          headersListenerToNotify = headersListener;
-        }
         Http2Stream.this.notifyAll(); // TODO(jwilson): Unnecessary?
       }
       if (bytesDiscarded > 0) {
         updateConnectionFlowControl(bytesDiscarded);
       }
       cancelStreamIfNecessary();
-      if (headersListenerToNotify != null) {
-        for (Headers headers : headersToDeliver) {
-          headersListenerToNotify.onHeaders(headers);
-        }
-      }
     }
   }
 
@@ -529,6 +516,9 @@ public final class Http2Stream {
      */
     private final Buffer sendBuffer = new Buffer();
 
+    /** Trailers to send at the end of the stream. */
+    private Headers trailers;
+
     boolean closed;
 
     /**
@@ -548,7 +538,7 @@ public final class Http2Stream {
      * Emit a single data frame to the connection. The frame's size be limited by this stream's
      * write window. This method will block until the write window is nonempty.
      */
-    private void emitFrame(boolean outFinished) throws IOException {
+    private void emitFrame(boolean outFinishedOnLastFrame) throws IOException {
       long toWrite;
       synchronized (Http2Stream.this) {
         writeTimeout.enter();
@@ -567,7 +557,8 @@ public final class Http2Stream {
 
       writeTimeout.enter();
       try {
-        connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite);
+        boolean outFinished = outFinishedOnLastFrame && toWrite == sendBuffer.size();
+        connection.writeData(id, outFinished, sendBuffer, toWrite);
       } finally {
         writeTimeout.exitAndThrowIfTimedOut();
       }
@@ -594,13 +585,21 @@ public final class Http2Stream {
         if (closed) return;
       }
       if (!sink.finished) {
-        // Emit the remaining data, setting the END_STREAM flag on the last frame.
-        if (sendBuffer.size() > 0) {
+        // We have 0 or more frames of data, and 0 or more frames of trailers. We need to send at
+        // least one frame with the END_STREAM flag set. That must be the last frame, and the
+        // trailers must be sent after all of the data.
+        boolean hasData = sendBuffer.size() > 0;
+        boolean hasTrailers = trailers != null;
+        if (hasTrailers) {
+          while (sendBuffer.size() > 0) {
+            emitFrame(false);
+          }
+          connection.writeHeaders(id, true, Util.toHeaderBlock(trailers));
+        } else if (hasData) {
           while (sendBuffer.size() > 0) {
             emitFrame(true);
           }
         } else {
-          // Send an empty frame just so we can set the END_STREAM flag.
           connection.writeData(id, true, null, 0);
         }
       }
diff --git a/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java b/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java
index 5ef6bb9c4a67a1881a416e7f78936344a618691d..b6456949de0a0ed052b2f5b64e3cd34a477a1f26 100644
--- a/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java
+++ b/okhttp/src/main/java/okhttp3/internal/http2/Http2Writer.java
@@ -121,24 +121,6 @@ final class Http2Writer implements Closeable {
     sink.flush();
   }
 
-  public synchronized void synStream(boolean outFinished, int streamId,
-      int associatedStreamId, List<Header> headerBlock) throws IOException {
-    if (closed) throw new IOException("closed");
-    headers(outFinished, streamId, headerBlock);
-  }
-
-  public synchronized void synReply(boolean outFinished, int streamId,
-      List<Header> headerBlock) throws IOException {
-    if (closed) throw new IOException("closed");
-    headers(outFinished, streamId, headerBlock);
-  }
-
-  public synchronized void headers(int streamId, List<Header> headerBlock)
-      throws IOException {
-    if (closed) throw new IOException("closed");
-    headers(false, streamId, headerBlock);
-  }
-
   public synchronized void rstStream(int streamId, ErrorCode errorCode)
       throws IOException {
     if (closed) throw new IOException("closed");
@@ -294,7 +276,8 @@ final class Http2Writer implements Closeable {
     }
   }
 
-  void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
+  public synchronized void headers(
+      boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
     if (closed) throw new IOException("closed");
     hpackWriter.writeHeaders(headerBlock);
 
diff --git a/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java b/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java
index 08b102c6e06811b6ed12666988c4b85016e5a062..cf184d1fdd68ab5b1ff4528e4a6db8100b93a423 100644
--- a/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java
+++ b/okhttp/src/main/java/okhttp3/internal/platform/AndroidPlatform.java
@@ -24,7 +24,6 @@ import java.lang.reflect.Method;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.security.NoSuchAlgorithmException;
-import java.security.Security;
 import java.security.cert.Certificate;
 import java.security.cert.TrustAnchor;
 import java.security.cert.X509Certificate;
@@ -41,26 +40,25 @@ import okhttp3.internal.tls.BasicTrustRootIndex;
 import okhttp3.internal.tls.CertificateChainCleaner;
 import okhttp3.internal.tls.TrustRootIndex;
 
-import static okhttp3.internal.Util.assertionError;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
-/** Android 2.3 or better. */
+/** Android 5+. */
 class AndroidPlatform extends Platform {
   private static final int MAX_LOG_LENGTH = 4000;
 
   private final Class<?> sslParametersClass;
-  private final OptionalMethod<Socket> setUseSessionTickets;
-  private final OptionalMethod<Socket> setHostname;
-
-  // Non-null on Android 5.0+.
-  private final OptionalMethod<Socket> getAlpnSelectedProtocol;
-  private final OptionalMethod<Socket> setAlpnProtocols;
+  private final Class<?> sslSocketClass;
+  private final Method setUseSessionTickets;
+  private final Method setHostname;
+  private final Method getAlpnSelectedProtocol;
+  private final Method setAlpnProtocols;
 
   private final CloseGuard closeGuard = CloseGuard.get();
 
-  AndroidPlatform(Class<?> sslParametersClass, OptionalMethod<Socket> setUseSessionTickets,
-      OptionalMethod<Socket> setHostname, OptionalMethod<Socket> getAlpnSelectedProtocol,
-      OptionalMethod<Socket> setAlpnProtocols) {
+  AndroidPlatform(Class<?> sslParametersClass, Class<?> sslSocketClass, Method setUseSessionTickets,
+      Method setHostname, Method getAlpnSelectedProtocol, Method setAlpnProtocols) {
     this.sslParametersClass = sslParametersClass;
+    this.sslSocketClass = sslSocketClass;
     this.setUseSessionTickets = setUseSessionTickets;
     this.setHostname = setHostname;
     this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;
@@ -74,19 +72,11 @@ class AndroidPlatform extends Platform {
     } catch (AssertionError e) {
       if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
       throw e;
-    } catch (SecurityException e) {
-      // Before android 4.3, socket.connect could throw a SecurityException
-      // if opening a socket resulted in an EACCES error.
-      IOException ioException = new IOException("Exception in connect");
-      ioException.initCause(e);
-      throw ioException;
     } catch (ClassCastException e) {
       // On android 8.0, socket.connect throws a ClassCastException due to a bug
       // see https://issuetracker.google.com/issues/63649622
       if (Build.VERSION.SDK_INT == 26) {
-        IOException ioException = new IOException("Exception in connect");
-        ioException.initCause(e);
-        throw ioException;
+        throw new IOException("Exception in connect", e);
       } else {
         throw e;
       }
@@ -117,25 +107,34 @@ class AndroidPlatform extends Platform {
 
   @Override public void configureTlsExtensions(
       SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
-    // Enable SNI and session tickets.
-    if (hostname != null) {
-      setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);
-      setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);
+    if (!sslSocketClass.isInstance(sslSocket)) {
+      return; // No TLS extensions if the socket class is custom.
     }
+    try {
+      // Enable SNI and session tickets.
+      if (hostname != null) {
+        setUseSessionTickets.invoke(sslSocket, true);
+        // This is SSLParameters.setServerNames() in API 24+.
+        setHostname.invoke(sslSocket, hostname);
+      }
 
-    // Enable ALPN.
-    if (setAlpnProtocols != null && setAlpnProtocols.isSupported(sslSocket)) {
-      Object[] parameters = {concatLengthPrefixed(protocols)};
-      setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);
+      // Enable ALPN.
+      setAlpnProtocols.invoke(sslSocket, concatLengthPrefixed(protocols));
+    } catch (IllegalAccessException | InvocationTargetException e) {
+      throw new AssertionError(e);
     }
   }
 
   @Override public @Nullable String getSelectedProtocol(SSLSocket socket) {
-    if (getAlpnSelectedProtocol == null) return null;
-    if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
-
-    byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
-    return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
+    if (!sslSocketClass.isInstance(socket)) {
+      return null; // No TLS extensions if the socket class is custom.
+    }
+    try {
+      byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invoke(socket);
+      return alpnResult != null ? new String(alpnResult, UTF_8) : null;
+    } catch (IllegalAccessException | InvocationTargetException e) {
+      throw new AssertionError(e);
+    }
   }
 
   @Override public void log(int level, String message, @Nullable Throwable t) {
@@ -175,7 +174,7 @@ class AndroidPlatform extends Platform {
     } catch (ClassNotFoundException | NoSuchMethodException e) {
       return super.isCleartextTrafficPermitted(hostname);
     } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
-      throw assertionError("unable to determine cleartext support", e);
+      throw new AssertionError("unable to determine cleartext support", e);
     }
   }
 
@@ -201,23 +200,6 @@ class AndroidPlatform extends Platform {
     }
   }
 
-  /**
-   * Checks to see if Google Play Services Dynamic Security Provider is present which provides ALPN
-   * support. If it isn't checks to see if device is Android 5.0+ since 4.x device have broken
-   * ALPN support.
-   */
-  private static boolean supportsAlpn() {
-    if (Security.getProvider("GMSCore_OpenSSL") != null) {
-      return true;
-    } else {
-      try {
-        Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
-        return true;
-      } catch (ClassNotFoundException ignored) { }
-    }
-    return false;
-  }
-
   public CertificateChainCleaner buildCertificateChainCleaner(X509TrustManager trustManager) {
     try {
       Class<?> extensionsClass = Class.forName("android.net.http.X509TrustManagerExtensions");
@@ -231,51 +213,40 @@ class AndroidPlatform extends Platform {
     }
   }
 
-  public static Platform buildIfSupported() {
-    // Attempt to find Android 2.3+ APIs.
+  public static @Nullable Platform buildIfSupported() {
+    // Attempt to find Android 5+ APIs.
+    Class<?> sslParametersClass;
+    Class<?> sslSocketClass;
     try {
-      Class<?> sslParametersClass;
+      sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
+      sslSocketClass = Class.forName("com.android.org.conscrypt.OpenSSLSocketImpl");
+    } catch (ClassNotFoundException ignored) {
+      return null; // Not an Android runtime.
+    }
+    if (Build.VERSION.SDK_INT >= 21) {
       try {
-        sslParametersClass = Class.forName("com.android.org.conscrypt.SSLParametersImpl");
-      } catch (ClassNotFoundException e) {
-        // Older platform before being unbundled.
-        sslParametersClass = Class.forName(
-            "org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");
-      }
-
-      OptionalMethod<Socket> setUseSessionTickets = new OptionalMethod<>(
-          null, "setUseSessionTickets", boolean.class);
-      OptionalMethod<Socket> setHostname = new OptionalMethod<>(
-          null, "setHostname", String.class);
-      OptionalMethod<Socket> getAlpnSelectedProtocol = null;
-      OptionalMethod<Socket> setAlpnProtocols = null;
-
-      if (supportsAlpn()) {
-        getAlpnSelectedProtocol
-            = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
-        setAlpnProtocols
-            = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
+        Method setUseSessionTickets = sslSocketClass.getDeclaredMethod(
+            "setUseSessionTickets", boolean.class);
+        Method setHostname = sslSocketClass.getMethod("setHostname", String.class);
+        Method getAlpnSelectedProtocol = sslSocketClass.getMethod("getAlpnSelectedProtocol");
+        Method setAlpnProtocols = sslSocketClass.getMethod("setAlpnProtocols", byte[].class);
+        return new AndroidPlatform(sslParametersClass, sslSocketClass, setUseSessionTickets,
+            setHostname, getAlpnSelectedProtocol, setAlpnProtocols);
+      } catch (NoSuchMethodException ignored) {
       }
-
-      return new AndroidPlatform(sslParametersClass, setUseSessionTickets, setHostname,
-          getAlpnSelectedProtocol, setAlpnProtocols);
-    } catch (ClassNotFoundException ignored) {
-      // This isn't an Android runtime.
     }
-
-    return null;
+    throw new IllegalStateException(
+        "Expected Android API level 21+ but was " + Build.VERSION.SDK_INT);
   }
 
-  @Override
-  public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) {
-
+  @Override public TrustRootIndex buildTrustRootIndex(X509TrustManager trustManager) {
     try {
       // From org.conscrypt.TrustManagerImpl, we want the method with this signature:
       // private TrustAnchor findTrustAnchorByIssuerAndSignature(X509Certificate lastCert);
       Method method = trustManager.getClass().getDeclaredMethod(
-              "findTrustAnchorByIssuerAndSignature", X509Certificate.class);
+          "findTrustAnchorByIssuerAndSignature", X509Certificate.class);
       method.setAccessible(true);
-      return new AndroidTrustRootIndex(trustManager, method);
+      return new CustomTrustRootIndex(trustManager, method);
     } catch (NoSuchMethodException e) {
       return super.buildTrustRootIndex(trustManager);
     }
@@ -380,19 +351,17 @@ class AndroidPlatform extends Platform {
   }
 
   /**
-   * An index of trusted root certificates that exploits knowledge of Android implementation
-   * details. This class is potentially much faster to initialize than {@link BasicTrustRootIndex}
-   * because it doesn't need to load and index trusted CA certificates.
+   * A trust manager for Android applications that customize the trust manager.
    *
-   * <p>This class uses APIs added to Android in API 14 (Android 4.0, released October 2011). This
-   * class shouldn't be used in Android API 17 or better because those releases are better served by
-   * {@link AndroidPlatform.AndroidCertificateChainCleaner}.
+   * <p>This class exploits knowledge of Android implementation details. This class is potentially
+   * much faster to initialize than {@link BasicTrustRootIndex} because it doesn't need to load and
+   * index trusted CA certificates.
    */
-  static final class AndroidTrustRootIndex implements TrustRootIndex {
+  static final class CustomTrustRootIndex implements TrustRootIndex {
     private final X509TrustManager trustManager;
     private final Method findByIssuerAndSignatureMethod;
 
-    AndroidTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) {
+    CustomTrustRootIndex(X509TrustManager trustManager, Method findByIssuerAndSignatureMethod) {
       this.findByIssuerAndSignatureMethod = findByIssuerAndSignatureMethod;
       this.trustManager = trustManager;
     }
@@ -400,32 +369,30 @@ class AndroidPlatform extends Platform {
     @Override public X509Certificate findByIssuerAndSignature(X509Certificate cert) {
       try {
         TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke(
-                trustManager, cert);
+            trustManager, cert);
         return trustAnchor != null
-                ? trustAnchor.getTrustedCert()
-                : null;
+            ? trustAnchor.getTrustedCert()
+            : null;
       } catch (IllegalAccessException e) {
-        throw assertionError("unable to get issues and signature", e);
+        throw new AssertionError("unable to get issues and signature", e);
       } catch (InvocationTargetException e) {
         return null;
       }
     }
 
-    @Override
-    public boolean equals(Object obj) {
+    @Override public boolean equals(Object obj) {
       if (obj == this) {
         return true;
       }
-      if (!(obj instanceof AndroidTrustRootIndex)) {
+      if (!(obj instanceof CustomTrustRootIndex)) {
         return false;
       }
-      AndroidTrustRootIndex that = (AndroidTrustRootIndex) obj;
+      CustomTrustRootIndex that = (CustomTrustRootIndex) obj;
       return trustManager.equals(that.trustManager)
-              && findByIssuerAndSignatureMethod.equals(that.findByIssuerAndSignatureMethod);
+          && findByIssuerAndSignatureMethod.equals(that.findByIssuerAndSignatureMethod);
     }
 
-    @Override
-    public int hashCode() {
+    @Override public int hashCode() {
       return trustManager.hashCode() + 31 * findByIssuerAndSignatureMethod.hashCode();
     }
   }
diff --git a/okhttp/src/main/java/okhttp3/internal/platform/JdkWithJettyBootPlatform.java b/okhttp/src/main/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatform.java
similarity index 74%
rename from okhttp/src/main/java/okhttp3/internal/platform/JdkWithJettyBootPlatform.java
rename to okhttp/src/main/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatform.java
index dee0b3fe53576ee991683ad616be6f5b92bbb388..64d5096e66c66e5d9c67d602c736ef42678079db 100644
--- a/okhttp/src/main/java/okhttp3/internal/platform/JdkWithJettyBootPlatform.java
+++ b/okhttp/src/main/java/okhttp3/internal/platform/Jdk8WithJettyBootPlatform.java
@@ -25,19 +25,15 @@ import javax.net.ssl.SSLSocket;
 import okhttp3.Protocol;
 import okhttp3.internal.Util;
 
-import static okhttp3.internal.Util.assertionError;
-
-/**
- * OpenJDK 7 or OpenJDK 8 with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
- */
-class JdkWithJettyBootPlatform extends Platform {
+/** OpenJDK 8 with {@code org.mortbay.jetty.alpn:alpn-boot} in the boot class path. */
+class Jdk8WithJettyBootPlatform extends Platform {
   private final Method putMethod;
   private final Method getMethod;
   private final Method removeMethod;
   private final Class<?> clientProviderClass;
   private final Class<?> serverProviderClass;
 
-  JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod,
+  Jdk8WithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod,
       Class<?> clientProviderClass, Class<?> serverProviderClass) {
     this.putMethod = putMethod;
     this.getMethod = getMethod;
@@ -51,11 +47,11 @@ class JdkWithJettyBootPlatform extends Platform {
     List<String> names = alpnProtocolNames(protocols);
 
     try {
-      Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
-          new Class[] {clientProviderClass, serverProviderClass}, new JettyNegoProvider(names));
-      putMethod.invoke(null, sslSocket, provider);
+      Object alpnProvider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
+          new Class[] {clientProviderClass, serverProviderClass}, new AlpnProvider(names));
+      putMethod.invoke(null, sslSocket, alpnProvider);
     } catch (InvocationTargetException | IllegalAccessException e) {
-      throw assertionError("unable to set alpn", e);
+      throw new AssertionError("failed to set ALPN", e);
     }
   }
 
@@ -63,14 +59,14 @@ class JdkWithJettyBootPlatform extends Platform {
     try {
       removeMethod.invoke(null, sslSocket);
     } catch (IllegalAccessException | InvocationTargetException e) {
-      throw assertionError("unable to remove alpn", e);
+      throw new AssertionError("failed to remove ALPN", e);
     }
   }
 
   @Override public @Nullable String getSelectedProtocol(SSLSocket socket) {
     try {
-      JettyNegoProvider provider =
-          (JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
+      AlpnProvider provider =
+          (AlpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
       if (!provider.unsupported && provider.selected == null) {
         Platform.get().log(INFO, "ALPN callback dropped: HTTP/2 is disabled. "
             + "Is alpn-boot on the boot class path?", null);
@@ -78,22 +74,22 @@ class JdkWithJettyBootPlatform extends Platform {
       }
       return provider.unsupported ? null : provider.selected;
     } catch (InvocationTargetException | IllegalAccessException e) {
-      throw assertionError("unable to get selected protocol", e);
+      throw new AssertionError("failed to get ALPN selected protocol", e);
     }
   }
 
   public static Platform buildIfSupported() {
     // Find Jetty's ALPN extension for OpenJDK.
     try {
-      String negoClassName = "org.eclipse.jetty.alpn.ALPN";
-      Class<?> negoClass = Class.forName(negoClassName);
-      Class<?> providerClass = Class.forName(negoClassName + "$Provider");
-      Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
-      Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
-      Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
-      Method getMethod = negoClass.getMethod("get", SSLSocket.class);
-      Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
-      return new JdkWithJettyBootPlatform(
+      String alpnClassName = "org.eclipse.jetty.alpn.ALPN";
+      Class<?> alpnClass = Class.forName(alpnClassName);
+      Class<?> providerClass = Class.forName(alpnClassName + "$Provider");
+      Class<?> clientProviderClass = Class.forName(alpnClassName + "$ClientProvider");
+      Class<?> serverProviderClass = Class.forName(alpnClassName + "$ServerProvider");
+      Method putMethod = alpnClass.getMethod("put", SSLSocket.class, providerClass);
+      Method getMethod = alpnClass.getMethod("get", SSLSocket.class);
+      Method removeMethod = alpnClass.getMethod("remove", SSLSocket.class);
+      return new Jdk8WithJettyBootPlatform(
           putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);
     } catch (ClassNotFoundException | NoSuchMethodException ignored) {
     }
@@ -105,7 +101,7 @@ class JdkWithJettyBootPlatform extends Platform {
    * Handle the methods of ALPN's ClientProvider and ServerProvider without a compile-time
    * dependency on those interfaces.
    */
-  private static class JettyNegoProvider implements InvocationHandler {
+  private static class AlpnProvider implements InvocationHandler {
     /** This peer's supported protocols. */
     private final List<String> protocols;
     /** Set when remote peer notifies ALPN is unsupported. */
@@ -113,7 +109,7 @@ class JdkWithJettyBootPlatform extends Platform {
     /** The protocol the server selected. */
     String selected;
 
-    JettyNegoProvider(List<String> protocols) {
+    AlpnProvider(List<String> protocols) {
       this.protocols = protocols;
     }
 
@@ -132,11 +128,12 @@ class JdkWithJettyBootPlatform extends Platform {
         return protocols; // Client advertises these protocols.
       } else if ((methodName.equals("selectProtocol") || methodName.equals("select"))
           && String.class == returnType && args.length == 1 && args[0] instanceof List) {
-        List<String> peerProtocols = (List) args[0];
+        List<?> peerProtocols = (List) args[0];
         // Pick the first known protocol the peer advertises.
         for (int i = 0, size = peerProtocols.size(); i < size; i++) {
-          if (protocols.contains(peerProtocols.get(i))) {
-            return selected = peerProtocols.get(i);
+          String protocol = (String) peerProtocols.get(i);
+          if (protocols.contains(protocol)) {
+            return selected = protocol;
           }
         }
         return selected = protocols.get(0); // On no intersection, try peer's first protocol.
diff --git a/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java b/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java
index 455e8bcbb8dc0be5024c12ed073630ced9e5f12d..07cb67c88538fc7c37ce176c08cd6d2e94a4b749 100644
--- a/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java
+++ b/okhttp/src/main/java/okhttp3/internal/platform/Jdk9Platform.java
@@ -25,11 +25,7 @@ import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.X509TrustManager;
 import okhttp3.Protocol;
 
-import static okhttp3.internal.Util.assertionError;
-
-/**
- * OpenJDK 9+.
- */
+/** OpenJDK 9+. */
 final class Jdk9Platform extends Platform {
   final Method setProtocolMethod;
   final Method getProtocolMethod;
@@ -52,7 +48,7 @@ final class Jdk9Platform extends Platform {
 
       sslSocket.setSSLParameters(sslParameters);
     } catch (IllegalAccessException | InvocationTargetException e) {
-      throw assertionError("unable to set ssl parameters", e);
+      throw new AssertionError("failed to set SSL parameters", e);
     }
   }
 
@@ -69,7 +65,7 @@ final class Jdk9Platform extends Platform {
 
       return protocol;
     } catch (IllegalAccessException | InvocationTargetException e) {
-      throw assertionError("unable to get selected protocols", e);
+      throw new AssertionError("failed to get ALPN selected protocol", e);
     }
   }
 
diff --git a/okhttp/src/main/java/okhttp3/internal/platform/OptionalMethod.java b/okhttp/src/main/java/okhttp3/internal/platform/OptionalMethod.java
deleted file mode 100644
index c26132fedb777b2efeffa1a9be9d9fa272823742..0000000000000000000000000000000000000000
--- a/okhttp/src/main/java/okhttp3/internal/platform/OptionalMethod.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You under the Apache License, Version 2.0
- *  (the "License"); you may not use this file except in compliance with
- *  the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-package okhttp3.internal.platform;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-
-/**
- * Duck-typing for methods: Represents a method that may or may not be present on an object.
- *
- * @param <T> the type of the object the method might be on, typically an interface or base class
- */
-class OptionalMethod<T> {
-
-  /** The return type of the method. null means "don't care". */
-  private final Class<?> returnType;
-
-  private final String methodName;
-
-  private final Class[] methodParams;
-
-  /**
-   * Creates an optional method.
-   *
-   * @param returnType the return type to required, null if it does not matter
-   * @param methodName the name of the method
-   * @param methodParams the method parameter types
-   */
-  OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
-    this.returnType = returnType;
-    this.methodName = methodName;
-    this.methodParams = methodParams;
-  }
-
-  /**
-   * Returns true if the method exists on the supplied {@code target}.
-   */
-  public boolean isSupported(T target) {
-    return getMethod(target.getClass()) != null;
-  }
-
-  /**
-   * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
-   * public then {@code null} is returned. See also {@link #invokeOptionalWithoutCheckedException}.
-   *
-   * @throws IllegalArgumentException if the arguments are invalid
-   * @throws InvocationTargetException if the invocation throws an exception
-   */
-  public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
-    Method m = getMethod(target.getClass());
-    if (m == null) {
-      return null;
-    }
-    try {
-      return m.invoke(target, args);
-    } catch (IllegalAccessException e) {
-      return null;
-    }
-  }
-
-  /**
-   * Invokes the method on {@code target}.  If the method does not exist or is not public then
-   * {@code null} is returned. Any RuntimeException thrown by the method is thrown, checked
-   * exceptions are wrapped in an {@link AssertionError}.
-   *
-   * @throws IllegalArgumentException if the arguments are invalid
-   */
-  public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
-    try {
-      return invokeOptional(target, args);
-    } catch (InvocationTargetException e) {
-      Throwable targetException = e.getTargetException();
-      if (targetException instanceof RuntimeException) {
-        throw (RuntimeException) targetException;
-      }
-      AssertionError error = new AssertionError("Unexpected exception");
-      error.initCause(targetException);
-      throw error;
-    }
-  }
-
-  /**
-   * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
-   * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
-   *
-   * @throws IllegalArgumentException if the arguments are invalid
-   * @throws InvocationTargetException if the invocation throws an exception
-   */
-  public Object invoke(T target, Object... args) throws InvocationTargetException {
-    Method m = getMethod(target.getClass());
-    if (m == null) {
-      throw new AssertionError("Method " + methodName + " not supported for object " + target);
-    }
-    try {
-      return m.invoke(target, args);
-    } catch (IllegalAccessException e) {
-      // Method should be public: we checked.
-      AssertionError error = new AssertionError("Unexpectedly could not call: " + m);
-      error.initCause(e);
-      throw error;
-    }
-  }
-
-  /**
-   * Invokes the method on {@code target}. Throws an error if the method is not supported. Any
-   * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in an {@link
-   * AssertionError}.
-   *
-   * @throws IllegalArgumentException if the arguments are invalid
-   */
-  public Object invokeWithoutCheckedException(T target, Object... args) {
-    try {
-      return invoke(target, args);
-    } catch (InvocationTargetException e) {
-      Throwable targetException = e.getTargetException();
-      if (targetException instanceof RuntimeException) {
-        throw (RuntimeException) targetException;
-      }
-      AssertionError error = new AssertionError("Unexpected exception");
-      error.initCause(targetException);
-      throw error;
-    }
-  }
-
-  /**
-   * Perform a lookup for the method. No caching. In order to return a method the method name and
-   * arguments must match those specified when the {@link OptionalMethod} was created. If the return
-   * type is specified (i.e. non-null) it must also be compatible. The method must also be public.
-   */
-  private Method getMethod(Class<?> clazz) {
-    Method method = null;
-    if (methodName != null) {
-      method = getPublicMethod(clazz, methodName, methodParams);
-      if (method != null
-          && returnType != null
-          && !returnType.isAssignableFrom(method.getReturnType())) {
-
-        // If the return type is non-null it must be compatible.
-        method = null;
-      }
-    }
-    return method;
-  }
-
-  private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) {
-    Method method = null;
-    try {
-      method = clazz.getMethod(methodName, parameterTypes);
-      if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
-        method = null;
-      }
-    } catch (NoSuchMethodException e) {
-      // None.
-    }
-    return method;
-  }
-}
-
diff --git a/okhttp/src/main/java/okhttp3/internal/platform/Platform.java b/okhttp/src/main/java/okhttp3/internal/platform/Platform.java
index ac152338677f8d3ea5a5ee8a2808b7c812c1e322..fdfd244acc2c4dfdda7dc666e999dca0d859ee0c 100644
--- a/okhttp/src/main/java/okhttp3/internal/platform/Platform.java
+++ b/okhttp/src/main/java/okhttp3/internal/platform/Platform.java
@@ -33,6 +33,7 @@ import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.X509TrustManager;
 import okhttp3.OkHttpClient;
 import okhttp3.Protocol;
+import okhttp3.internal.Util;
 import okhttp3.internal.tls.BasicCertificateChainCleaner;
 import okhttp3.internal.tls.BasicTrustRootIndex;
 import okhttp3.internal.tls.CertificateChainCleaner;
@@ -46,7 +47,7 @@ import okio.Buffer;
  *
  * <p>Supported on Android 2.3+.
  *
- * Supported on OpenJDK 7+
+ * <p>Supported on OpenJDK 7+
  *
  * <h3>Session Tickets</h3>
  *
@@ -61,9 +62,9 @@ import okio.Buffer;
  * <p>Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was
  * unstable.
  *
- * Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
+ * <p>Supported on OpenJDK 8 via the JettyALPN-boot library.
  *
- * Supported on OpenJDK 9 via SSLParameters and SSLSocket features.
+ * <p>Supported on OpenJDK 9+ via SSLParameters and SSLSocket features.
  *
  * <h3>Trust Manager Extraction</h3>
  *
@@ -187,7 +188,7 @@ public class Platform {
 
   public static boolean isConscryptPreferred() {
     // mainly to allow tests to run cleanly
-    if ("conscrypt".equals(System.getProperty("okhttp.platform"))) {
+    if ("conscrypt".equals(Util.getSystemProperty("okhttp.platform", null))) {
       return true;
     }
 
@@ -218,7 +219,7 @@ public class Platform {
       return jdk9;
     }
 
-    Platform jdkWithJettyBoot = JdkWithJettyBootPlatform.buildIfSupported();
+    Platform jdkWithJettyBoot = Jdk8WithJettyBootPlatform.buildIfSupported();
 
     if (jdkWithJettyBoot != null) {
       return jdkWithJettyBoot;
@@ -249,7 +250,7 @@ public class Platform {
         Field field = c.getDeclaredField(fieldName);
         field.setAccessible(true);
         Object value = field.get(instance);
-        if (value == null || !fieldType.isInstance(value)) return null;
+        if (!fieldType.isInstance(value)) return null;
         return fieldType.cast(value);
       } catch (NoSuchFieldException ignored) {
       } catch (IllegalAccessException e) {
@@ -267,16 +268,6 @@ public class Platform {
   }
 
   public SSLContext getSSLContext() {
-    String jvmVersion = System.getProperty("java.specification.version");
-    if ("1.7".equals(jvmVersion)) {
-      try {
-        // JDK 1.7 (public version) only support > TLSv1 with named protocols
-        return SSLContext.getInstance("TLSv1.2");
-      } catch (NoSuchAlgorithmException e) {
-        // fallback to TLS
-      }
-    }
-
     try {
       return SSLContext.getInstance("TLS");
     } catch (NoSuchAlgorithmException e) {
diff --git a/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java b/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java
index 0e39bd0f876512b091f397aa5298916af47f3c4c..74fbd89b79988b6406ddd395690a6ee819559294 100644
--- a/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java
+++ b/okhttp/src/main/java/okhttp3/internal/publicsuffix/PublicSuffixDatabase.java
@@ -21,13 +21,12 @@ import java.io.InterruptedIOException;
 import java.net.IDN;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
-import okhttp3.internal.Util;
 import okhttp3.internal.platform.Platform;
 import okio.BufferedSource;
 import okio.GzipSource;
 import okio.Okio;
 
-import static okhttp3.internal.Util.closeQuietly;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 /**
  * A database of public suffixes provided by
@@ -128,7 +127,7 @@ public final class PublicSuffixDatabase {
     // Break apart the domain into UTF-8 labels, i.e. foo.bar.com turns into [foo, bar, com].
     byte[][] domainLabelsUtf8Bytes = new byte[domainLabels.length][];
     for (int i = 0; i < domainLabels.length; i++) {
-      domainLabelsUtf8Bytes[i] = domainLabels[i].getBytes(Util.UTF_8);
+      domainLabelsUtf8Bytes[i] = domainLabels[i].getBytes(UTF_8);
     }
 
     // Start by looking for exact matches. We start at the leftmost label. For example, foo.bar.com
@@ -271,7 +270,7 @@ public final class PublicSuffixDatabase {
           low = mid + end + 1;
         } else {
           // Found a match.
-          match = new String(bytesToSearch, mid, publicSuffixLength, Util.UTF_8);
+          match = new String(bytesToSearch, mid, publicSuffixLength, UTF_8);
           break;
         }
       }
@@ -313,8 +312,7 @@ public final class PublicSuffixDatabase {
     InputStream resource = PublicSuffixDatabase.class.getResourceAsStream(PUBLIC_SUFFIX_RESOURCE);
     if (resource == null) return;
 
-    BufferedSource bufferedSource = Okio.buffer(new GzipSource(Okio.source(resource)));
-    try {
+    try (BufferedSource bufferedSource = Okio.buffer(new GzipSource(Okio.source(resource)))) {
       int totalBytes = bufferedSource.readInt();
       publicSuffixListBytes = new byte[totalBytes];
       bufferedSource.readFully(publicSuffixListBytes);
@@ -322,8 +320,6 @@ public final class PublicSuffixDatabase {
       int totalExceptionBytes = bufferedSource.readInt();
       publicSuffixExceptionListBytes = new byte[totalExceptionBytes];
       bufferedSource.readFully(publicSuffixExceptionListBytes);
-    } finally {
-      closeQuietly(bufferedSource);
     }
 
     synchronized (this) {
diff --git a/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java b/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java
index baee049f1bc96df6124edeb75678cde8954ab23a..61a0d27e5e4cbca59b3ad97d720a37ef646f89bd 100644
--- a/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java
+++ b/okhttp/src/main/java/okhttp3/internal/ws/RealWebSocket.java
@@ -153,14 +153,12 @@ public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCall
     random.nextBytes(nonce);
     this.key = ByteString.of(nonce).base64();
 
-    this.writerRunnable = new Runnable() {
-      @Override public void run() {
-        try {
-          while (writeOneFrame()) {
-          }
-        } catch (IOException e) {
-          failWebSocket(e, null);
+    this.writerRunnable = () -> {
+      try {
+        while (writeOneFrame()) {
         }
+      } catch (IOException e) {
+        failWebSocket(e, null);
       }
     };
   }
@@ -192,24 +190,26 @@ public final class RealWebSocket implements WebSocket, WebSocketReader.FrameCall
     call.timeout().clearTimeout();
     call.enqueue(new Callback() {
       @Override public void onResponse(Call call, Response response) {
+        StreamAllocation streamAllocation = Internal.instance.streamAllocation(call);
+
         try {
           checkResponse(response);
         } catch (ProtocolException e) {
           failWebSocket(e, response);
           closeQuietly(response);
+          streamAllocation.streamFailed(e);
           return;
         }
 
         // Promote the HTTP streams into web socket streams.
-        StreamAllocation streamAllocation = Internal.instance.streamAllocation(call);
         streamAllocation.noNewStreams(); // Prevent connection pooling!
         Streams streams = streamAllocation.connection().newWebSocketStreams(streamAllocation);
 
         // Process all web socket messages.
         try {
-          listener.onOpen(RealWebSocket.this, response);
           String name = "OkHttp WebSocket " + request.url().redact();
           initReaderAndWriter(name, streams);
+          listener.onOpen(RealWebSocket.this, response);
           streamAllocation.connection().socket().setSoTimeout(0);
           loopReader();
         } catch (Exception e) {
diff --git a/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java b/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java
index baffe06b198b53682f7797eef5ca2a54ba79eae1..3ba1204987d38ae5aefad42c58259a43f3226ff0 100644
--- a/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java
+++ b/okhttp/src/main/java/okhttp3/internal/ws/WebSocketWriter.java
@@ -246,7 +246,6 @@ final class WebSocketWriter {
       return sink.timeout();
     }
 
-    @SuppressWarnings("PointlessBitwiseExpression")
     @Override public void close() throws IOException {
       if (closed) throw new IOException("closed");
 
diff --git a/pom.xml b/pom.xml
index ffd392c06a89ba026c27862edf2369b795f3a111..6238941bf6d8eddfebda011c1192ae5e18ebdf48 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
 
   <groupId>com.squareup.okhttp3</groupId>
   <artifactId>parent</artifactId>
-  <version>3.12.1</version>
+  <version>3.13.1</version>
   <packaging>pom</packaging>
 
   <name>OkHttp (Parent)</name>
@@ -36,28 +36,24 @@
 
     <module>okcurl</module>
     <module>mockwebserver</module>
-    <module>bom</module>
     <module>samples</module>
-    <module>benchmarks</module>
   </modules>
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
     <!-- Compilation -->
-    <airlift.version>0.7</airlift.version>
-    <!-- ALPN library targeted to Java 7 -->
-    <alpn.jdk7.version>7.1.2.v20141202</alpn.jdk7.version>
+    <airlift.version>0.8</airlift.version>
     <android.version>4.1.1.4</android.version>
-    <animal.sniffer.version>1.15</animal.sniffer.version>
-    <apache.http.version>4.2.6</apache.http.version>
+    <animal.sniffer.version>1.17</animal.sniffer.version>
+    <apache.http.version>4.5.6</apache.http.version>
     <bouncycastle.version>1.60</bouncycastle.version>
-    <guava.version>16.0</guava.version>
-    <java.version>1.7</java.version>
-    <moshi.version>1.1.0</moshi.version>
-    <jnr-unixsocket.version>0.19</jnr-unixsocket.version>
-    <okio.version>1.15.0</okio.version>
-    <conscrypt.version>1.4.0</conscrypt.version>
+    <guava.version>27.0.1-jre</guava.version>
+    <java.version>1.8</java.version>
+    <moshi.version>1.8.0</moshi.version>
+    <jnr-unixsocket.version>0.21</jnr-unixsocket.version>
+    <okio.version>1.17.2</okio.version>
+    <conscrypt.version>1.4.2</conscrypt.version>
 
     <!-- Test Dependencies -->
     <junit.version>4.12</junit.version>
@@ -70,7 +66,7 @@
     <url>https://github.com/square/okhttp/</url>
     <connection>scm:git:https://github.com/square/okhttp.git</connection>
     <developerConnection>scm:git:git@github.com:square/okhttp.git</developerConnection>
-    <tag>parent-3.12.1</tag>
+    <tag>parent-3.13.1</tag>
   </scm>
 
   <issueManagement>
@@ -152,7 +148,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
-          <version>3.7.0</version>
+          <version>3.8.0</version>
           <configuration>
             <compilerId>javac-with-errorprone</compilerId>
             <forceJavacCompilerUse>true</forceJavacCompilerUse>
@@ -163,12 +159,12 @@
             <dependency>
               <groupId>org.codehaus.plexus</groupId>
               <artifactId>plexus-compiler-javac-errorprone</artifactId>
-              <version>2.8.4</version>
+              <version>2.8.5</version>
             </dependency>
             <dependency>
               <groupId>com.google.errorprone</groupId>
               <artifactId>error_prone_core</artifactId>
-              <version>2.3.1</version>
+              <version>2.3.2</version>
             </dependency>
           </dependencies>
         </plugin>
@@ -176,7 +172,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.21.0</version>
+          <version>2.22.1</version>
           <configuration>
             <systemPropertyVariables>
               <okhttp.platform>${okhttp.platform}</okhttp.platform>
@@ -198,7 +194,7 @@
             <dependency>
               <groupId>org.apache.maven.surefire</groupId>
               <artifactId>surefire-junit47</artifactId>
-              <version>2.21.0</version>
+              <version>2.22.1</version>
             </dependency>
           </dependencies>
         </plugin>
@@ -206,7 +202,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-javadoc-plugin</artifactId>
-          <version>2.10.4</version>
+          <version>3.0.1</version>
         </plugin>
       </plugins>
     </pluginManagement>
@@ -215,14 +211,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-release-plugin</artifactId>
-        <version>2.4.2</version>
-        <dependencies>
-          <dependency>
-            <groupId>org.apache.maven.scm</groupId>
-            <artifactId>maven-scm-provider-gitexe</artifactId>
-            <version>1.9</version>
-          </dependency>
-        </dependencies>
+        <version>2.5.3</version>
         <configuration>
           <autoVersionSubmodules>true</autoVersionSubmodules>
         </configuration>
@@ -231,12 +220,12 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
-        <version>2.17</version>
+        <version>3.0.0</version>
         <dependencies>
           <dependency>
             <groupId>com.puppycrawl.tools</groupId>
             <artifactId>checkstyle</artifactId>
-            <version>7.7</version>
+            <version>8.15</version>
           </dependency>
         </dependencies>
         <configuration>
@@ -260,56 +249,39 @@
         <version>${animal.sniffer.version}</version>
         <executions>
           <execution>
+            <id>sniff-java18</id>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+            <configuration>
+              <signature>
+                <groupId>org.codehaus.mojo.signature</groupId>
+                <artifactId>java18</artifactId>
+                <version>1.0</version>
+              </signature>
+            </configuration>
+          </execution>
+          <execution>
+            <id>sniff-android5</id>
             <phase>test</phase>
             <goals>
               <goal>check</goal>
             </goals>
+            <configuration>
+              <signature>
+                <groupId>net.sf.androidscents.signature</groupId>
+                <artifactId>android-api-level-21</artifactId>
+                <version>5.0.1_r2</version>
+              </signature>
+            </configuration>
           </execution>
         </executions>
-        <configuration>
-          <signature>
-            <groupId>org.codehaus.mojo.signature</groupId>
-            <artifactId>java16</artifactId>
-            <version>1.1</version>
-          </signature>
-        </configuration>
       </plugin>
     </plugins>
   </build>
 
   <profiles>
-    <profile>
-      <id>alpn-when-jdk7</id>
-      <activation>
-        <jdk>1.7</jdk>
-      </activation>
-      <properties>
-        <bootclasspathPrefix>
-          ${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/${alpn.jdk7.version}/alpn-boot-${alpn.jdk7.version}.jar
-        </bootclasspathPrefix>
-        <okhttp.platform>jdk-with-jetty-boot</okhttp.platform>
-      </properties>
-      <build>
-        <pluginManagement>
-          <plugins>
-            <plugin>
-              <groupId>org.apache.maven.plugins</groupId>
-              <artifactId>maven-surefire-plugin</artifactId>
-              <configuration>
-                <argLine>-Xbootclasspath/p:${bootclasspathPrefix} -Xms512m -Xmx512m</argLine>
-              </configuration>
-              <dependencies>
-                <dependency>
-                  <groupId>org.mortbay.jetty.alpn</groupId>
-                  <artifactId>alpn-boot</artifactId>
-                  <version>${alpn.jdk7.version}</version>
-                </dependency>
-              </dependencies>
-            </plugin>
-          </plugins>
-        </pluginManagement>
-      </build>
-    </profile>
     <profile>
       <id>alpn-when-jdk8</id>
       <activation>
@@ -681,5 +653,23 @@
         <alpn.jdk8.version>8.1.13.v20181017</alpn.jdk8.version>
       </properties>
     </profile>
+    <profile>
+      <id>alpn-when-jdk8_201</id>
+      <activation>
+        <jdk>1.8.0_201</jdk>
+      </activation>
+      <properties>
+        <alpn.jdk8.version>8.1.13.v20181017</alpn.jdk8.version>
+      </properties>
+    </profile>
+    <profile>
+      <id>alpn-when-jdk8_202</id>
+      <activation>
+        <jdk>1.8.0_202</jdk>
+      </activation>
+      <properties>
+        <alpn.jdk8.version>8.1.13.v20181017</alpn.jdk8.version>
+      </properties>
+    </profile>
   </profiles>
 </project>
diff --git a/samples/crawler/pom.xml b/samples/crawler/pom.xml
index 1744c10773cc8c5864a9a986fd5d45dd555fe1bb..68884ca5a8a9d22ecd53a8714acf76ff5856aa4c 100644
--- a/samples/crawler/pom.xml
+++ b/samples/crawler/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3.sample</groupId>
     <artifactId>sample-parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>crawler</artifactId>
@@ -21,7 +21,7 @@
     <dependency>
       <groupId>org.jsoup</groupId>
       <artifactId>jsoup</artifactId>
-      <version>1.7.3</version>
+      <version>1.11.3</version>
     </dependency>
   </dependencies>
 </project>
diff --git a/samples/crawler/src/main/java/okhttp3/sample/Crawler.java b/samples/crawler/src/main/java/okhttp3/sample/Crawler.java
index 07f61032d31d8a22244e805529e14f9cb279716a..95ba357def6fad03cde343ce2c6faa38c33d058d 100644
--- a/samples/crawler/src/main/java/okhttp3/sample/Crawler.java
+++ b/samples/crawler/src/main/java/okhttp3/sample/Crawler.java
@@ -41,8 +41,7 @@ import org.jsoup.nodes.Element;
  */
 public final class Crawler {
   private final OkHttpClient client;
-  private final Set<HttpUrl> fetchedUrls = Collections.synchronizedSet(
-      new LinkedHashSet<HttpUrl>());
+  private final Set<HttpUrl> fetchedUrls = Collections.synchronizedSet(new LinkedHashSet<>());
   private final LinkedBlockingQueue<HttpUrl> queue = new LinkedBlockingQueue<>();
   private final ConcurrentHashMap<String, AtomicInteger> hostnames = new ConcurrentHashMap<>();
 
diff --git a/samples/guide/pom.xml b/samples/guide/pom.xml
index ec79832110d519e81a9609a7a1d58ee1c00accbb..86adfce40d34763c519164427d65e7b247ed703d 100644
--- a/samples/guide/pom.xml
+++ b/samples/guide/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3.sample</groupId>
     <artifactId>sample-parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>guide</artifactId>
diff --git a/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java b/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java
index 644f638300fe3f15385b11ec3d10d62c6223b534..58a791adc729cdd720dcb8ddda1e10c749b082b2 100644
--- a/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java
+++ b/samples/guide/src/main/java/okhttp3/recipes/CancelCall.java
@@ -37,12 +37,10 @@ public class CancelCall {
     final Call call = client.newCall(request);
 
     // Schedule a job to cancel the call in 1 second.
-    executor.schedule(new Runnable() {
-      @Override public void run() {
-        System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
-        call.cancel();
-        System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
-      }
+    executor.schedule(() -> {
+      System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
+      call.cancel();
+      System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
     }, 1, TimeUnit.SECONDS);
 
     System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
diff --git a/samples/guide/src/main/java/okhttp3/recipes/Progress.java b/samples/guide/src/main/java/okhttp3/recipes/Progress.java
index e75a3cc9495b287103b0fef943de9a9a1d01c7ee..197401f205fa51041921c36867bfd1143216d592 100644
--- a/samples/guide/src/main/java/okhttp3/recipes/Progress.java
+++ b/samples/guide/src/main/java/okhttp3/recipes/Progress.java
@@ -16,7 +16,6 @@
 package okhttp3.recipes;
 
 import java.io.IOException;
-import okhttp3.Interceptor;
 import okhttp3.MediaType;
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
@@ -61,13 +60,11 @@ public final class Progress {
     };
 
     OkHttpClient client = new OkHttpClient.Builder()
-        .addNetworkInterceptor(new Interceptor() {
-          @Override public Response intercept(Chain chain) throws IOException {
-            Response originalResponse = chain.proceed(chain.request());
-            return originalResponse.newBuilder()
-                .body(new ProgressResponseBody(originalResponse.body(), progressListener))
-                .build();
-          }
+        .addNetworkInterceptor(chain -> {
+          Response originalResponse = chain.proceed(chain.request());
+          return originalResponse.newBuilder()
+              .body(new ProgressResponseBody(originalResponse.body(), progressListener))
+              .build();
         })
         .build();
 
diff --git a/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java b/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java
index 92d65d8169b43842335a517ee871795b58177b02..4d876bb201f68a2dab7faa8cac8e59f6f8842e70 100644
--- a/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java
+++ b/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java
@@ -25,13 +25,11 @@ import okhttp3.Response;
 
 public final class RewriteResponseCacheControl {
   /** Dangerous interceptor that rewrites the server's cache-control header. */
-  private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
-    @Override public Response intercept(Chain chain) throws IOException {
-      Response originalResponse = chain.proceed(chain.request());
-      return originalResponse.newBuilder()
-          .header("Cache-Control", "max-age=60")
-          .build();
-    }
+  private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> {
+    Response originalResponse = chain.proceed(chain.request());
+    return originalResponse.newBuilder()
+        .header("Cache-Control", "max-age=60")
+        .build();
   };
 
   private final OkHttpClient client;
diff --git a/samples/pom.xml b/samples/pom.xml
index eb8a78bbe07f409d85f1fec91a69c7f14328af8c..63f76a59952566d49a29322692b9e2449bc6b5c2 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3</groupId>
     <artifactId>parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <groupId>com.squareup.okhttp3.sample</groupId>
diff --git a/samples/simple-client/pom.xml b/samples/simple-client/pom.xml
index 223317fbde22a86b1040969d31a9a00371a338bc..44e1c926675e156a6ed683dd4e9732c5773e40c6 100644
--- a/samples/simple-client/pom.xml
+++ b/samples/simple-client/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3.sample</groupId>
     <artifactId>sample-parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>simple-client</artifactId>
diff --git a/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java b/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java
index f2ac973db2b89c40db80230d18d4d4a9658dd65a..cfd8632cb86ec8fdd22a66874b0ae3f5854c2ae0 100644
--- a/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java
+++ b/samples/simple-client/src/main/java/okhttp3/sample/OkHttpContributors.java
@@ -4,7 +4,6 @@ import com.squareup.moshi.JsonAdapter;
 import com.squareup.moshi.Moshi;
 import com.squareup.moshi.Types;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import okhttp3.OkHttpClient;
 import okhttp3.Request;
@@ -37,11 +36,7 @@ public class OkHttpContributors {
       List<Contributor> contributors = CONTRIBUTORS_JSON_ADAPTER.fromJson(body.source());
 
       // Sort list by the most contributions.
-      Collections.sort(contributors, new Comparator<Contributor>() {
-        @Override public int compare(Contributor c1, Contributor c2) {
-          return c2.contributions - c1.contributions;
-        }
-      });
+      Collections.sort(contributors, (c1, c2) -> c2.contributions - c1.contributions);
 
       // Output list of contributors.
       for (Contributor contributor : contributors) {
diff --git a/samples/slack/pom.xml b/samples/slack/pom.xml
index d2c65164232421872df259c0d5c1a4c62a91fcba..bf946d4e3f521622c10224e8d7466f2481b3ccb6 100644
--- a/samples/slack/pom.xml
+++ b/samples/slack/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3.sample</groupId>
     <artifactId>sample-parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>slack</artifactId>
diff --git a/samples/slack/src/main/java/okhttp3/slack/SlackClient.java b/samples/slack/src/main/java/okhttp3/slack/SlackClient.java
index 2aa26089866a0f7b3ba8535c9413a2c4d54fcbb3..508a5fd46775b464ba9cd865ad4310ec379f2034 100644
--- a/samples/slack/src/main/java/okhttp3/slack/SlackClient.java
+++ b/samples/slack/src/main/java/okhttp3/slack/SlackClient.java
@@ -39,13 +39,10 @@ public final class SlackClient {
       sessionFactory.start();
     }
 
-    HttpUrl authorizeUrl = sessionFactory.newAuthorizeUrl(scopes, team,
-        new OAuthSessionFactory.Listener() {
-          @Override public void sessionGranted(OAuthSession session) {
-            initOauthSession(session);
-            System.out.printf("session granted: %s\n", session);
-          }
-        });
+    HttpUrl authorizeUrl = sessionFactory.newAuthorizeUrl(scopes, team, session -> {
+      initOauthSession(session);
+      System.out.printf("session granted: %s\n", session);
+    });
 
     System.out.printf("open this URL in a browser: %s\n", authorizeUrl);
   }
diff --git a/samples/static-server/pom.xml b/samples/static-server/pom.xml
index 57b03ae7f386cff18822d9a17f7e05b9a0618bdc..a160f9f46e68f3caae91889039077841dcca712b 100644
--- a/samples/static-server/pom.xml
+++ b/samples/static-server/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3.sample</groupId>
     <artifactId>sample-parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>static-server</artifactId>
@@ -26,7 +26,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-shade-plugin</artifactId>
-        <version>2.1</version>
+        <version>3.2.1</version>
         <configuration>
           <shadedArtifactAttached>true</shadedArtifactAttached>
           <shadedClassifierName>shaded</shadedClassifierName>
diff --git a/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java b/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java
index e21432ed20d05971d6e536ad39b977007393fd0d..88e0251bdb68d9e2c9fdd2a7990145850a98cd0d 100644
--- a/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java
+++ b/samples/static-server/src/main/java/okhttp3/sample/SampleServer.java
@@ -11,7 +11,6 @@ import java.security.SecureRandom;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManagerFactory;
-import okhttp3.internal.Util;
 import okhttp3.mockwebserver.Dispatcher;
 import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
@@ -119,11 +118,8 @@ public class SampleServer extends Dispatcher {
   private static SSLContext sslContext(String keystoreFile, String password)
       throws GeneralSecurityException, IOException {
     KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
-    InputStream in = new FileInputStream(keystoreFile);
-    try {
+    try (InputStream in = new FileInputStream(keystoreFile)) {
       keystore.load(in, password.toCharArray());
-    } finally {
-      Util.closeQuietly(in);
     }
     KeyManagerFactory keyManagerFactory =
         KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
diff --git a/samples/unixdomainsockets/pom.xml b/samples/unixdomainsockets/pom.xml
index 756a254afe9157a4110eca25ff2e8fbeafafd800..07a0c3c953b9412c29839ded8ea5d428853a14b5 100644
--- a/samples/unixdomainsockets/pom.xml
+++ b/samples/unixdomainsockets/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.squareup.okhttp3.sample</groupId>
     <artifactId>sample-parent</artifactId>
-    <version>3.12.1</version>
+    <version>3.13.1</version>
   </parent>
 
   <artifactId>unixdomainsockets</artifactId>
diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/BlockingUnixSocket.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/BlockingUnixSocket.java
new file mode 100644
index 0000000000000000000000000000000000000000..3ba58448c2bd3284e6e0a57d7bd23b22b52d59c6
--- /dev/null
+++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/BlockingUnixSocket.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okhttp3.unixdomainsockets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.WritableByteChannel;
+import jnr.unixsocket.UnixSocket;
+import jnr.unixsocket.UnixSocketAddress;
+import jnr.unixsocket.UnixSocketChannel;
+
+/**
+ * Subtype UNIX socket for a higher-fidelity impersonation of TCP sockets.
+ *
+ * <p>This class doesn't pass {@link SelectableChannel} implementations to create input and output
+ * streams. Though that type isn't in the public API, if the channel passed in implements that
+ * interface then additional synchronization is used. This additional synchronization harms
+ * concurrency and can cause deadlocks.
+ *
+ * <p>This class remembers which socket address was connected so that a non-null value can be
+ * returned on calls to {@link #getInetAddress}.
+ */
+final class BlockingUnixSocket extends UnixSocket {
+  private final File path;
+  private final InputStream in;
+  private final OutputStream out;
+  private InetSocketAddress inetSocketAddress;
+
+  BlockingUnixSocket(File path, UnixSocketChannel channel) {
+    super(channel);
+    this.path = path;
+    this.in = Channels.newInputStream(new UnselectableReadableByteChannel());
+    this.out = Channels.newOutputStream(new UnselectableWritableByteChannel());
+  }
+
+  BlockingUnixSocket(File path, UnixSocketChannel channel, InetSocketAddress address) {
+    this(path, channel);
+    this.inetSocketAddress = address;
+  }
+
+  @Override public void connect(SocketAddress endpoint) throws IOException {
+    connect(endpoint, Integer.valueOf(0));
+  }
+
+  @Override public void connect(SocketAddress endpoint, int timeout) throws IOException {
+    connect(endpoint, Integer.valueOf(timeout));
+  }
+
+  @Override public void connect(SocketAddress endpoint, Integer timeout) throws IOException {
+    this.inetSocketAddress = (InetSocketAddress) endpoint;
+    super.connect(new UnixSocketAddress(path), timeout);
+  }
+
+  @Override public InetAddress getInetAddress() {
+    return inetSocketAddress.getAddress(); // TODO(jwilson): fake the remote address?
+  }
+
+  @Override public InputStream getInputStream() throws IOException {
+    if (!isConnected()) throw new IOException("not connected");
+    return in;
+  }
+
+  @Override public OutputStream getOutputStream() throws IOException {
+    if (!isConnected()) throw new IOException("not connected");
+    return out;
+  }
+
+  /** A readable byte channel that doesn't implement {@link SelectableChannel}. */
+  final class UnselectableReadableByteChannel implements ReadableByteChannel {
+    @Override public int read(ByteBuffer dst) throws IOException {
+      return getChannel().read(dst);
+    }
+
+    @Override public boolean isOpen() {
+      return getChannel().isOpen();
+    }
+
+    @Override public void close() throws IOException {
+      getChannel().close();
+    }
+  }
+
+  /** A writable byte channel that doesn't implement {@link SelectableChannel}. */
+  final class UnselectableWritableByteChannel implements WritableByteChannel {
+    @Override public int write(ByteBuffer src) throws IOException {
+      return getChannel().write(src);
+    }
+
+    @Override public boolean isOpen() {
+      return getChannel().isOpen();
+    }
+
+    @Override public void close() throws IOException {
+      getChannel().close();
+    }
+  }
+}
diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java
index 8512c2d38d070d3e2282da7278335871332c451d..f897565f52389cb906444da1dd2151ee6c941187 100644
--- a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java
+++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/ClientAndServer.java
@@ -16,7 +16,9 @@
 package okhttp3.unixdomainsockets;
 
 import java.io.File;
+import java.util.Collections;
 import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
 import okhttp3.Request;
 import okhttp3.Response;
 import okhttp3.mockwebserver.MockResponse;
@@ -33,11 +35,13 @@ public class ClientAndServer {
 
     MockWebServer server = new MockWebServer();
     server.setServerSocketFactory(new UnixDomainServerSocketFactory(socketFile));
+    server.setProtocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE));
     server.enqueue(new MockResponse().setBody("hello"));
     server.start();
 
     OkHttpClient client = new OkHttpClient.Builder()
         .socketFactory(new UnixDomainSocketFactory(socketFile))
+        .protocols(Collections.singletonList(Protocol.H2_PRIOR_KNOWLEDGE))
         .build();
 
     Request request = new Request.Builder()
diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java
index f9ec4a08f892eec56e07a212dd722d8f60d89178..c1d63b5a321d837067d5bab07c0e32428e48aedf 100644
--- a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java
+++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainServerSocketFactory.java
@@ -24,7 +24,6 @@ import java.net.Socket;
 import java.net.SocketAddress;
 import javax.net.ServerSocketFactory;
 import jnr.unixsocket.UnixServerSocketChannel;
-import jnr.unixsocket.UnixSocket;
 import jnr.unixsocket.UnixSocketAddress;
 import jnr.unixsocket.UnixSocketChannel;
 
@@ -82,13 +81,8 @@ public final class UnixDomainServerSocketFactory extends ServerSocketFactory {
     }
 
     @Override public Socket accept() throws IOException {
-      UnixSocketChannel socketChannel = serverSocketChannel.accept();
-
-      return new UnixSocket(socketChannel) {
-        @Override public InetAddress getInetAddress() {
-          return endpoint.getAddress(); // TODO(jwilson): fake the remote address?
-        }
-      };
+      UnixSocketChannel channel = serverSocketChannel.accept();
+      return new BlockingUnixSocket(path, channel, endpoint);
     }
 
     @Override public void close() throws IOException {
diff --git a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java
index 8b16e398f7f7ce6f382a779b86fcd37e8a80926b..e288dc5287c0c995c5dee0537f395ea5705a578b 100644
--- a/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java
+++ b/samples/unixdomainsockets/src/main/java/okhttp3/unixdomainsockets/UnixDomainSocketFactory.java
@@ -18,12 +18,8 @@ package okhttp3.unixdomainsockets;
 import java.io.File;
 import java.io.IOException;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
 import java.net.Socket;
-import java.net.SocketAddress;
 import javax.net.SocketFactory;
-import jnr.unixsocket.UnixSocket;
-import jnr.unixsocket.UnixSocketAddress;
 import jnr.unixsocket.UnixSocketChannel;
 
 /** Impersonate TCP-style SocketFactory over UNIX domain sockets. */
@@ -36,27 +32,7 @@ public final class UnixDomainSocketFactory extends SocketFactory {
 
   private Socket createUnixDomainSocket() throws IOException {
     UnixSocketChannel channel = UnixSocketChannel.open();
-
-    return new UnixSocket(channel) {
-      private InetSocketAddress inetSocketAddress;
-
-      @Override public void connect(SocketAddress endpoint) throws IOException {
-        connect(endpoint, Integer.valueOf(0));
-      }
-
-      @Override public void connect(SocketAddress endpoint, int timeout) throws IOException {
-        connect(endpoint, Integer.valueOf(timeout));
-      }
-
-      @Override public void connect(SocketAddress endpoint, Integer timeout) throws IOException {
-        this.inetSocketAddress = (InetSocketAddress) endpoint;
-        super.connect(new UnixSocketAddress(path), timeout);
-      }
-
-      @Override public InetAddress getInetAddress() {
-        return inetSocketAddress.getAddress(); // TODO(jwilson): fake the remote address?
-      }
-    };
+    return new BlockingUnixSocket(path, channel);
   }
 
   @Override public Socket createSocket() throws IOException {