Merge changes from topic "google-java-format-jdk17" am: 6393823b21 am: 95df6f6cad am: 24a44d71c9

Original change: https://android-review.googlesource.com/c/platform/external/auto/+/2008542

Change-Id: I2f6c9fe46f8f56ad35781713a93ea1a5cc2504ab
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..18cc4ce
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+version: 2
+updates:
+  - package-ecosystem: "maven"
+    directory: "/common"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "maven"
+    directory: "/factory"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "maven"
+    directory: "/service"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "maven"
+    directory: "/value"
+    schedule:
+      interval: "daily"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "daily"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..ec92243
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,97 @@
+name: CI
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  test:
+    name: "JDK ${{ matrix.java }}"
+    strategy:
+      matrix:
+        java: [ 8, 11 ]
+    runs-on: ubuntu-latest
+    steps:
+      # Cancel any previous runs for the same branch that are still running.
+      - name: 'Cancel previous runs'
+        uses: styfle/cancel-workflow-action@0.9.0
+        with:
+          access_token: ${{ github.token }}
+      - name: 'Check out repository'
+        uses: actions/checkout@v2.3.4
+      - name: 'Cache local Maven repository'
+        uses: actions/cache@v2.1.6
+        with:
+          path: ~/.m2/repository
+          key: maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            maven-
+      - name: 'Set up JDK ${{ matrix.java }}'
+        uses: actions/setup-java@v2
+        with:
+          java-version: ${{ matrix.java }}
+          distribution: 'zulu'
+      - name: 'Install'
+        shell: bash
+        run: mvn -B dependency:go-offline test clean -U --quiet --fail-never -DskipTests=true -f build-pom.xml
+      - name: 'Test'
+        shell: bash
+        run: mvn -B verify -U --fail-at-end -Dsource.skip=true -Dmaven.javadoc.skip=true -f build-pom.xml
+
+  publish_snapshot:
+    name: 'Publish snapshot'
+    needs: test
+    if: github.event_name == 'push' && github.repository == 'google/auto'
+    runs-on: ubuntu-latest
+    steps:
+      - name: 'Check out repository'
+        uses: actions/checkout@v2.3.4
+      - name: 'Cache local Maven repository'
+        uses: actions/cache@v2.1.6
+        with:
+          path: ~/.m2/repository
+          key: maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            maven-
+      - name: 'Set up JDK 11'
+        uses: actions/setup-java@v2
+        with:
+          java-version: 11
+          distribution: 'zulu'
+          server-id: sonatype-nexus-snapshots
+          server-username: CI_DEPLOY_USERNAME
+          server-password: CI_DEPLOY_PASSWORD
+      - name: 'Publish'
+        env:
+          CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }}
+          CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }}
+        run: ./util/publish-snapshot-on-commit.sh
+
+  generate_docs:
+    name: 'Generate latest docs'
+    needs: test
+    if: github.event_name == 'push' && github.repository == 'google/auto'
+    runs-on: ubuntu-latest
+    steps:
+      - name: 'Check out repository'
+        uses: actions/checkout@v2.3.4
+      - name: 'Cache local Maven repository'
+        uses: actions/cache@v2.1.6
+        with:
+          path: ~/.m2/repository
+          key: maven-${{ hashFiles('**/pom.xml') }}
+          restore-keys: |
+            maven-
+      - name: 'Set up JDK 11'
+        uses: actions/setup-java@v2
+        with:
+          java-version: 11
+          distribution: 'zulu'
+      - name: 'Generate latest docs'
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: ./util/generate-latest-docs.sh
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b0be6fa..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-sudo: false
-
-language: java
-
-install:
-  - mvn -B -U -f build-pom.xml dependency:go-offline test clean --quiet --fail-never -DskipTests=true
-
-script:
-  - mvn -B -U -f build-pom.xml verify --fail-at-end -Dsource.skip=true -Dmaven.javadoc.skip=true
-
-jdk:
-  - openjdk9
-  - openjdk8
-
-env:
-  global:
-  - secure: "bWNSSMURwYC0oZWIMZRd7dy5+JdoyZ060d427TAqFRJmOkYtlR+dBbBggjeJmM0PEkQDzeBrWwln/Vq3lnRPv8czA7hSg/R33r3GzTyi1GZhjCYN2mPW8qp4qgqlloh78aaOODUNSJsOtQqPDJPmhLLfD6UCY0eq9zHhweIjYdw="
-  - secure: "s5V9d8MKl7ZHqCxuYLljLSD4sp9KLtYkk9hVxEPqCLAi4zA70WkX9h+GZI1gAOpcavomfrWcgSDT2ZReiuNpwx7OtczdS4zB+s6mo4F598iRs4bhSLiPT+Hzvx6BSwf1ZKZTYEhrUPGmKOp2T29AxMV7D0Q+P7n574ubvpUuZmA="
-  - secure: "T24JAd60zthkeLBmenvZn6+qI43uvfuLwVb70Ljhbc19XDYEZV4Zm/kaafsisP5+F6kV4GjFaT+NCq2sJlwvPSMMRvU1JJgmNVh8TmtswkC/PHKonkMkOsj2KmFP0RRSPdvQv2NrSguZUq8mg+2pvnPO0qoPg4VeIODPGtAxNb8="
-
-after_success:
-  - util/generate-latest-docs.sh
-  - util/publish-snapshot-on-commit.sh
-
-branches:
-  only:
-    - master
-    - /^release.*$/
diff --git a/README.md b/README.md
index 366f940..bbefd27 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # Auto
 
-[![Build Status](https://travis-ci.org/google/auto.svg?branch=master)](https://travis-ci.org/google/auto)
+[![Build Status](https://github.com/google/auto/actions/workflows/ci.yml/badge.svg)](https://github.com/google/auto/actions/workflows/ci.yml)
 
 A collection of source code generators for [Java][java].
 
diff --git a/android-annotation-stubs/gen_annotations.sh b/android-annotation-stubs/gen_annotations.sh
index 90c9fcf..4766af9 100755
--- a/android-annotation-stubs/gen_annotations.sh
+++ b/android-annotation-stubs/gen_annotations.sh
@@ -6,6 +6,7 @@
 
 ANNOTATIONS=(
     net.ltgt.gradle.incap.IncrementalAnnotationProcessor
+    org.checkerframework.checker.nullness.qual.Nullable
 )
 
 PARAMETER["net.ltgt.gradle.incap.IncrementalAnnotationProcessor"]="IncrementalAnnotationProcessorType"
diff --git a/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java b/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java
new file mode 100644
index 0000000..276d64c
--- /dev/null
+++ b/android-annotation-stubs/src/org/checkerframework/checker/nullness/qual/Nullable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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 org.checkerframework.checker.nullness.qual;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/* This is an annotation stub to avoid dependencies on annotations that aren't
+ * in the Android platform source tree. */
+
+@Target({
+  ElementType.ANNOTATION_TYPE,
+  ElementType.CONSTRUCTOR,
+  ElementType.FIELD,
+  ElementType.LOCAL_VARIABLE,
+  ElementType.METHOD,
+  ElementType.PACKAGE,
+  ElementType.PARAMETER,
+  ElementType.TYPE,
+  ElementType.TYPE_PARAMETER,
+  ElementType.TYPE_USE
+})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Nullable {}
diff --git a/common/Android.bp b/common/Android.bp
index 25f7099..19e2acd 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -11,6 +11,7 @@
     name: "auto_common",
     srcs: ["src/main/java/**/*.java"],
     libs: [
+        "auto_android_annotation_stubs",
         "guava",
         "javapoet",
     ],
diff --git a/common/README.md b/common/README.md
index 990aa31..9f8eb79 100644
--- a/common/README.md
+++ b/common/README.md
@@ -1,28 +1,30 @@
-Auto Common Utilities
-========
+# Auto Common Utilities
 
 ## Overview
 
-The Auto project has a set of common utilities to help ease use of the annotation processing
-environment.
+The Auto project has a set of common utilities to help ease use of the
+annotation processing environment.
 
 ## Utility classes of note
 
-  * MoreTypes - utilities and Equivalence wrappers for TypeMirror and related subtypes
-  * MoreElements - utilities for Element and related subtypes
-  * SuperficialValidation - very simple scanner to ensure an Element is valid and free from
-    distortion from upstream compilation errors
-  * Visibility - utilities for working with Elements' visibility levels (public, protected, etc.)
-  * BasicAnnotationProcessor/ProcessingStep - simple types that
-    - implement a validating annotation processor
-    - defer invalid elements until later
-    - break processor actions into multiple steps (which may each handle different annotations)
+*   MoreTypes - utilities and Equivalence wrappers for TypeMirror and related
+    subtypes
+*   MoreElements - utilities for Element and related subtypes
+*   SuperficialValidation - very simple scanner to ensure an Element is valid
+    and free from distortion from upstream compilation errors
+*   Visibility - utilities for working with Elements' visibility levels (public,
+    protected, etc.)
+*   BasicAnnotationProcessor/ProcessingStep - simple types that
+    -   implement a validating annotation processor
+    -   defer invalid elements until later
+    -   break processor actions into multiple steps (which may each handle
+        different annotations)
 
 ## Usage/Setup
 
-Auto common utilities have a standard [Maven](http://maven.apache.org) setup which can also be
-used from Gradle, Ivy, Ant, or other systems which consume binary artifacts from the central Maven
-binary artifact repositories.
+Auto common utilities have a standard [Maven](http://maven.apache.org) setup
+which can also be used from Gradle, Ivy, Ant, or other systems which consume
+binary artifacts from the central Maven binary artifact repositories.
 
 ```xml
 <dependency>
@@ -31,49 +33,3 @@
   <version>1.0-SNAPSHOT</version> <!-- or use a known release version -->
 </dependency>
 ```
-
-## Processor Resilience
-
-Auto Common Utilities is used by a variety of annotation processors in Google and new versions
-may have breaking changes.  Users of auto-common are urged to use
-[shade](https://maven.apache.org/plugins/maven-shade-plugin/) or
-[jarjar](https://code.google.com/p/jarjar/) (or something similar) in packaging their processors
-so that conflicting versions of this library do not adversely interact with each other.
-
-For example, in a Maven build you can repackage `com.google.auto.common` into
-`your.processor.shaded.auto.common` like this:
-
-```xml
-<project>
-  <!-- your other config -->
-  <build>
-    <plugins>
-      <plugin>
-        <artifactId>maven-shade-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>shade</goal>
-            </goals>
-            <configuration>
-              <artifactSet>
-                <excludes>
-                  <!-- exclude dependencies you don't want to bundle in your processor -->
-                </excludes>
-              </artifactSet>
-              <relocations>
-                <relocation>
-                  <pattern>com.google.auto.common</pattern>
-                  <shadedPattern>your.processor.shaded.auto.common</shadedPattern>
-                </relocation>
-              </relocations>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-</project>
-```
-
diff --git a/common/pom.xml b/common/pom.xml
index e4a23e0..2754b81 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -36,8 +36,8 @@
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <java.version>1.8</java.version>
-    <guava.version>29.0-jre</guava.version>
-    <truth.version>1.0.1</truth.version>
+    <guava.version>30.1.1-jre</guava.version>
+    <truth.version>1.1.3</truth.version>
   </properties>
 
   <scm>
@@ -89,13 +89,13 @@
     <dependency>
       <groupId>com.google.testing.compile</groupId>
       <artifactId>compile-testing</artifactId>
-      <version>0.18</version>
+      <version>0.19</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>4.12</version>
+      <version>4.13.2</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -107,7 +107,7 @@
     <dependency>
       <groupId>org.eclipse.jdt</groupId>
       <artifactId>ecj</artifactId>
-      <version>3.22.0</version>
+      <version>3.25.0</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
@@ -116,7 +116,7 @@
     <plugins>
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.7.0</version>
+        <version>3.8.1</version>
         <configuration>
           <source>${java.version}</source>
           <target>${java.version}</target>
@@ -128,14 +128,14 @@
           <dependency>
             <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-java</artifactId>
-            <version>0.9.4</version>
+            <version>1.0.7</version>
           </dependency>
         </dependencies>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.2.0</version>
       </plugin>
     </plugins>
   </build>
diff --git a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
index 62e5834..9ce5cd9 100644
--- a/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
+++ b/common/src/main/java/com/google/auto/common/AnnotationMirrors.java
@@ -16,21 +16,21 @@
 package com.google.auto.common;
 
 import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Collections.unmodifiableMap;
 
 import com.google.common.base.Equivalence;
-import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.lang.annotation.Annotation;
 import java.util.Arrays;
-import java.util.List;
 import java.util.Map;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.util.ElementFilter;
 import javax.lang.model.util.Elements;
@@ -45,20 +45,32 @@
       new Equivalence<AnnotationMirror>() {
         @Override
         protected boolean doEquivalent(AnnotationMirror left, AnnotationMirror right) {
-          return MoreTypes.equivalence().equivalent(left.getAnnotationType(),
-              right.getAnnotationType()) && AnnotationValues.equivalence().pairwise().equivalent(
-              getAnnotationValuesWithDefaults(left).values(),
-              getAnnotationValuesWithDefaults(right).values());
+          return MoreTypes.equivalence()
+                  .equivalent(left.getAnnotationType(), right.getAnnotationType())
+              && AnnotationValues.equivalence()
+                  .pairwise()
+                  .equivalent(
+                      getAnnotationValuesWithDefaults(left).values(),
+                      getAnnotationValuesWithDefaults(right).values());
         }
+
         @Override
         protected int doHash(AnnotationMirror annotation) {
           DeclaredType type = annotation.getAnnotationType();
           Iterable<AnnotationValue> annotationValues =
               getAnnotationValuesWithDefaults(annotation).values();
-          return Arrays.hashCode(new int[] {MoreTypes.equivalence().hash(type),
-              AnnotationValues.equivalence().pairwise().hash(annotationValues)});
+          return Arrays.hashCode(
+              new int[] {
+                MoreTypes.equivalence().hash(type),
+                AnnotationValues.equivalence().pairwise().hash(annotationValues)
+              });
         }
-    };
+
+        @Override
+        public String toString() {
+          return "AnnotationMirrors.equivalence()";
+        }
+      };
 
   /**
    * Returns an {@link Equivalence} for {@link AnnotationMirror} as some implementations
@@ -83,8 +95,10 @@
   public static ImmutableMap<ExecutableElement, AnnotationValue> getAnnotationValuesWithDefaults(
       AnnotationMirror annotation) {
     ImmutableMap.Builder<ExecutableElement, AnnotationValue> values = ImmutableMap.builder();
-    Map<? extends ExecutableElement, ? extends AnnotationValue> declaredValues =
-        annotation.getElementValues();
+    // Use unmodifiableMap to eliminate wildcards, which cause issues for our nullness checker.
+    @SuppressWarnings("GetElementValues")
+    Map<ExecutableElement, AnnotationValue> declaredValues =
+        unmodifiableMap(annotation.getElementValues());
     for (ExecutableElement method :
         ElementFilter.methodsIn(annotation.getAnnotationType().asElement().getEnclosedElements())) {
       // Must iterate and put in this order, to ensure consistency in generated code.
@@ -95,8 +109,10 @@
       } else {
         throw new IllegalStateException(
             "Unset annotation value without default should never happen: "
-            + MoreElements.asType(method.getEnclosingElement()).getQualifiedName()
-            + '.' + method.getSimpleName() + "()");
+                + MoreElements.asType(method.getEnclosingElement()).getQualifiedName()
+                + '.'
+                + method.getSimpleName()
+                + "()");
       }
     }
     return values.build();
@@ -131,25 +147,48 @@
         return entry;
       }
     }
-    throw new IllegalArgumentException(String.format("@%s does not define an element %s()",
-        MoreElements.asType(annotationMirror.getAnnotationType().asElement()).getQualifiedName(),
-        elementName));
+    throw new IllegalArgumentException(
+        String.format(
+            "@%s does not define an element %s()",
+            MoreElements.asType(annotationMirror.getAnnotationType().asElement())
+                .getQualifiedName(),
+            elementName));
   }
 
   /**
-   * Returns all {@linkplain AnnotationMirror annotations} that are present on the given
-   * {@link Element} which are themselves annotated with {@code annotationType}.
+   * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link
+   * Element} which are themselves annotated with {@code annotationClass}.
    */
-  public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(Element element,
-      final Class<? extends Annotation> annotationType) {
-    List<? extends AnnotationMirror> annotations = element.getAnnotationMirrors();
-    return FluentIterable.from(annotations)
-        .filter(new Predicate<AnnotationMirror>() {
-          @Override public boolean apply(AnnotationMirror input) {
-            return isAnnotationPresent(input.getAnnotationType().asElement(), annotationType);
-          }
-        })
-        .toSet();
+  public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(
+      Element element, Class<? extends Annotation> annotationClass) {
+    String name = annotationClass.getCanonicalName();
+    if (name == null) {
+      return ImmutableSet.of();
+    }
+    return getAnnotatedAnnotations(element, name);
+  }
+
+  /**
+   * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link
+   * Element} which are themselves annotated with {@code annotation}.
+   */
+  public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(
+      Element element, TypeElement annotation) {
+    return element.getAnnotationMirrors().stream()
+        .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotation))
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Returns all {@linkplain AnnotationMirror annotations} that are present on the given {@link
+   * Element} which are themselves annotated with an annotation whose type's canonical name is
+   * {@code annotationName}.
+   */
+  public static ImmutableSet<? extends AnnotationMirror> getAnnotatedAnnotations(
+      Element element, String annotationName) {
+    return element.getAnnotationMirrors().stream()
+        .filter(input -> isAnnotationPresent(input.getAnnotationType().asElement(), annotationName))
+        .collect(toImmutableSet());
   }
 
   private AnnotationMirrors() {}
diff --git a/common/src/main/java/com/google/auto/common/AnnotationValues.java b/common/src/main/java/com/google/auto/common/AnnotationValues.java
index e233901..0712e56 100644
--- a/common/src/main/java/com/google/auto/common/AnnotationValues.java
+++ b/common/src/main/java/com/google/auto/common/AnnotationValues.java
@@ -15,8 +15,8 @@
  */
 package com.google.auto.common;
 
+import static com.google.auto.common.MoreStreams.toImmutableList;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.ImmutableList.toImmutableList;
 
 import com.google.common.base.Equivalence;
 import com.google.common.collect.ImmutableList;
@@ -37,91 +37,120 @@
 public final class AnnotationValues {
   private static final Equivalence<AnnotationValue> ANNOTATION_VALUE_EQUIVALENCE =
       new Equivalence<AnnotationValue>() {
-        @Override protected boolean doEquivalent(AnnotationValue left, AnnotationValue right) {
-          return left.accept(new SimpleAnnotationValueVisitor8<Boolean, AnnotationValue>() {
-            // LHS is not an annotation or array of annotation values, so just test equality.
-            @Override protected Boolean defaultAction(Object left, AnnotationValue right) {
-              return left.equals(right.accept(
-                  new SimpleAnnotationValueVisitor8<Object, Void>() {
-                    @Override protected Object defaultAction(Object object, Void unused) {
-                      return object;
-                    }
-                  }, null));
-            }
+        @Override
+        protected boolean doEquivalent(AnnotationValue left, AnnotationValue right) {
+          return left.accept(
+              new SimpleAnnotationValueVisitor8<Boolean, AnnotationValue>() {
+                // LHS is not an annotation or array of annotation values, so just test equality.
+                @Override
+                protected Boolean defaultAction(Object left, AnnotationValue right) {
+                  return left.equals(
+                      right.accept(
+                          new SimpleAnnotationValueVisitor8<Object, Void>() {
+                            @Override
+                            protected Object defaultAction(Object object, Void unused) {
+                              return object;
+                            }
+                          },
+                          null));
+                }
 
-            // LHS is an annotation mirror so test equivalence for RHS annotation mirrors
-            // and false for other types.
-            @Override public Boolean visitAnnotation(AnnotationMirror left, AnnotationValue right) {
-              return right.accept(
-                  new SimpleAnnotationValueVisitor8<Boolean, AnnotationMirror>() {
-                    @Override protected Boolean defaultAction(Object right, AnnotationMirror left) {
-                      return false; // Not an annotation mirror, so can't be equal to such.
-                    }
-                    @Override
-                    public Boolean visitAnnotation(AnnotationMirror right, AnnotationMirror left) {
-                      return AnnotationMirrors.equivalence().equivalent(left, right);
-                    }
-                  }, left);
-            }
+                // LHS is an annotation mirror so test equivalence for RHS annotation mirrors
+                // and false for other types.
+                @Override
+                public Boolean visitAnnotation(AnnotationMirror left, AnnotationValue right) {
+                  return right.accept(
+                      new SimpleAnnotationValueVisitor8<Boolean, AnnotationMirror>() {
+                        @Override
+                        protected Boolean defaultAction(Object right, AnnotationMirror left) {
+                          return false; // Not an annotation mirror, so can't be equal to such.
+                        }
 
-            // LHS is a list of annotation values have to collect-test equivalences, or false
-            // for any other types.
-            @Override
-            public Boolean visitArray(List<? extends AnnotationValue> left, AnnotationValue right) {
-              return right.accept(
-                  new SimpleAnnotationValueVisitor8<Boolean, List<? extends AnnotationValue>>() {
-                    @Override protected Boolean defaultAction(
-                        Object ignored, List<? extends AnnotationValue> alsoIgnored) {
-                      return false; // Not an array, so can't be equal to such.
-                    }
+                        @Override
+                        public Boolean visitAnnotation(
+                            AnnotationMirror right, AnnotationMirror left) {
+                          return AnnotationMirrors.equivalence().equivalent(left, right);
+                        }
+                      },
+                      left);
+                }
 
-                    @SuppressWarnings("unchecked") // safe covariant cast
-                    @Override public Boolean visitArray(
-                        List<? extends AnnotationValue> right ,
-                        List<? extends AnnotationValue> left) {
-                      return AnnotationValues.equivalence().pairwise().equivalent(
-                          (List<AnnotationValue>) left, (List<AnnotationValue>) right);
-                    }
-                  }, left);
-            }
+                // LHS is a list of annotation values have to collect-test equivalences, or false
+                // for any other types.
+                @Override
+                public Boolean visitArray(
+                    List<? extends AnnotationValue> left, AnnotationValue right) {
+                  return right.accept(
+                      new SimpleAnnotationValueVisitor8<
+                          Boolean, List<? extends AnnotationValue>>() {
+                        @Override
+                        protected Boolean defaultAction(
+                            Object ignored, List<? extends AnnotationValue> alsoIgnored) {
+                          return false; // Not an array, so can't be equal to such.
+                        }
 
-            @Override
-            public Boolean visitType(TypeMirror left, AnnotationValue right) {
-              return right.accept(
-                  new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() {
-                    @Override protected Boolean defaultAction(
-                        Object ignored, TypeMirror alsoIgnored) {
-                      return false; // Not an annotation mirror, so can't be equal to such.
-                    }
+                        @SuppressWarnings("unchecked") // safe covariant cast
+                        @Override
+                        public Boolean visitArray(
+                            List<? extends AnnotationValue> right,
+                            List<? extends AnnotationValue> left) {
+                          return AnnotationValues.equivalence()
+                              .pairwise()
+                              .equivalent(
+                                  (List<AnnotationValue>) left, (List<AnnotationValue>) right);
+                        }
+                      },
+                      left);
+                }
 
-                    @Override public Boolean visitType(TypeMirror right, TypeMirror left) {
-                      return MoreTypes.equivalence().equivalent(left, right);
-                    }
-                  }, left);
-            }
-          }, right);
+                @Override
+                public Boolean visitType(TypeMirror left, AnnotationValue right) {
+                  return right.accept(
+                      new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() {
+                        @Override
+                        protected Boolean defaultAction(Object ignored, TypeMirror alsoIgnored) {
+                          return false; // Not an annotation mirror, so can't be equal to such.
+                        }
+
+                        @Override
+                        public Boolean visitType(TypeMirror right, TypeMirror left) {
+                          return MoreTypes.equivalence().equivalent(left, right);
+                        }
+                      },
+                      left);
+                }
+              },
+              right);
         }
 
-        @Override protected int doHash(AnnotationValue value) {
-          return value.accept(new SimpleAnnotationValueVisitor8<Integer, Void>() {
-            @Override public Integer visitAnnotation(AnnotationMirror value, Void ignore) {
-              return AnnotationMirrors.equivalence().hash(value);
-            }
+        @Override
+        protected int doHash(AnnotationValue value) {
+          return value.accept(
+              new SimpleAnnotationValueVisitor8<Integer, Void>() {
+                @Override
+                public Integer visitAnnotation(AnnotationMirror value, Void ignore) {
+                  return AnnotationMirrors.equivalence().hash(value);
+                }
 
-            @SuppressWarnings("unchecked") // safe covariant cast
-            @Override public Integer visitArray(
-                List<? extends AnnotationValue> values, Void ignore) {
-              return AnnotationValues.equivalence().pairwise().hash((List<AnnotationValue>) values);
-            }
+                @SuppressWarnings("unchecked") // safe covariant cast
+                @Override
+                public Integer visitArray(List<? extends AnnotationValue> values, Void ignore) {
+                  return AnnotationValues.equivalence()
+                      .pairwise()
+                      .hash((List<AnnotationValue>) values);
+                }
 
-            @Override public Integer visitType(TypeMirror value, Void ignore) {
-              return MoreTypes.equivalence().hash(value);
-            }
+                @Override
+                public Integer visitType(TypeMirror value, Void ignore) {
+                  return MoreTypes.equivalence().hash(value);
+                }
 
-            @Override protected Integer defaultAction(Object value, Void ignored) {
-              return value.hashCode();
-            }
-          }, null);
+                @Override
+                protected Integer defaultAction(Object value, Void ignored) {
+                  return value.hashCode();
+                }
+              },
+              null);
         }
       };
 
@@ -209,7 +238,6 @@
       return value;
     }
   }
-  ;
 
   /**
    * Returns the value as a VariableElement.
@@ -486,4 +514,3 @@
 
   private AnnotationValues() {}
 }
-
diff --git a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
index 375a4cb..d951aaf 100644
--- a/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
+++ b/common/src/main/java/com/google/auto/common/BasicAnnotationProcessor.java
@@ -17,14 +17,14 @@
 
 import static com.google.auto.common.MoreElements.asExecutable;
 import static com.google.auto.common.MoreElements.asPackage;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
 import static com.google.auto.common.SuperficialValidation.validateElement;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Iterables.transform;
 import static com.google.common.collect.Multimaps.filterKeys;
-import static java.util.stream.Collectors.collectingAndThen;
-import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toMap;
+import static java.util.Objects.requireNonNull;
 import static javax.lang.model.element.ElementKind.PACKAGE;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
@@ -56,6 +56,7 @@
 import javax.lang.model.type.ErrorType;
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.SimpleElementVisitor8;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * An abstract {@link Processor} implementation that defers processing of {@link Element}s to later
@@ -162,14 +163,14 @@
     checkState(steps != null);
     return steps.stream()
         .flatMap(step -> getSupportedAnnotationTypeElements(step).stream())
-        .collect(collectingAndThen(toList(), ImmutableSet::copyOf));
+        .collect(toImmutableSet());
   }
 
   private ImmutableSet<TypeElement> getSupportedAnnotationTypeElements(Step step) {
     return step.annotations().stream()
         .map(elements::getTypeElement)
         .filter(Objects::nonNull)
-        .collect(collectingAndThen(toList(), ImmutableSet::copyOf));
+        .collect(toImmutableSet());
   }
 
   /**
@@ -181,7 +182,7 @@
     checkState(steps != null);
     return steps.stream()
         .flatMap(step -> step.annotations().stream())
-        .collect(collectingAndThen(toList(), ImmutableSet::copyOf));
+        .collect(toImmutableSet());
   }
 
   @Override
@@ -287,10 +288,7 @@
 
     // Look at the elements we've found and the new elements from this round and validate them.
     for (TypeElement annotationType : getSupportedAnnotationTypeElements()) {
-      Set<? extends Element> roundElements =
-          (annotationType == null)
-              ? ImmutableSet.of()
-              : roundEnv.getElementsAnnotatedWith(annotationType);
+      Set<? extends Element> roundElements = roundEnv.getElementsAnnotatedWith(annotationType);
       ImmutableSet<Element> prevRoundElements = deferredElementsByAnnotation.get(annotationType);
       for (Element element : Sets.union(roundElements, prevRoundElements)) {
         ElementName elementName = ElementName.forAnnotatedElement(element);
@@ -376,7 +374,7 @@
    * IllegalArgumentException} if the provided {@link Element} is a {@link PackageElement} or is
    * otherwise not enclosed by a type.
    */
-  // TODO(cgruber) move to MoreElements and make public.
+  // TODO(user) move to MoreElements and make public.
   private static TypeElement getEnclosingType(Element element) {
     return element.accept(
         new SimpleElementVisitor8<TypeElement, Void>() {
@@ -478,10 +476,9 @@
       this.annotationsByName =
           processingStep.annotations().stream()
               .collect(
-                  collectingAndThen(
-                      toMap(
-                          Class::getCanonicalName, (Class<? extends Annotation> aClass) -> aClass),
-                      ImmutableMap::copyOf));
+                  toImmutableMap(
+                      c -> requireNonNull(c.getCanonicalName()),
+                      (Class<? extends Annotation> aClass) -> aClass));
     }
 
     @Override
@@ -502,8 +499,12 @@
       elements
           .asMap()
           .forEach(
-              (annotation, annotatedElements) ->
-                  builder.putAll(annotationsByName.get(annotation), annotatedElements));
+              (annotationName, annotatedElements) -> {
+                Class<? extends Annotation> annotation = annotationsByName.get(annotationName);
+                if (annotation != null) { // should not be null
+                  builder.putAll(annotation, annotatedElements);
+                }
+              });
       return builder.build();
     }
   }
@@ -561,7 +562,7 @@
     }
 
     @Override
-    public boolean equals(Object object) {
+    public boolean equals(@Nullable Object object) {
       if (!(object instanceof ElementName)) {
         return false;
       }
diff --git a/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java b/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java
index bb35e22..09d9029 100644
--- a/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java
+++ b/common/src/main/java/com/google/auto/common/GeneratedAnnotationSpecs.java
@@ -27,7 +27,7 @@
   private GeneratedAnnotationSpecs() {}
 
   /**
-   * Returns {@code @Generated("processorClass"} if either {@code
+   * Returns {@code @Generated("processorClass")} if either {@code
    * javax.annotation.processing.Generated} or {@code javax.annotation.Generated} is {@linkplain
    * GeneratedAnnotations#generatedAnnotation(Elements) available at compile time}.
    *
@@ -41,7 +41,7 @@
   }
 
   /**
-   * Returns {@code @Generated(value = "processorClass", comments = "comments"} if either {@code
+   * Returns {@code @Generated(value = "processorClass", comments = "comments")} if either {@code
    * javax.annotation.processing.Generated} or {@code javax.annotation.Generated} is {@linkplain
    * GeneratedAnnotations#generatedAnnotation(Elements) available at compile time}.
    *
@@ -55,7 +55,7 @@
   }
 
   /**
-   * Returns {@code @Generated("processorClass"} for the target {@code SourceVersion}.
+   * Returns {@code @Generated("processorClass")} for the target {@code SourceVersion}.
    *
    * <p>Returns {@code javax.annotation.processing.Generated} for JDK 9 and newer, {@code
    * javax.annotation.Generated} for earlier releases, and Optional#empty()} if the annotation is
@@ -68,7 +68,7 @@
   }
 
   /**
-   * Returns {@code @Generated(value = "processorClass", comments = "comments"} for the target
+   * Returns {@code @Generated(value = "processorClass", comments = "comments")} for the target
    * {@code SourceVersion}.
    *
    * <p>Returns {@code javax.annotation.processing.Generated} for JDK 9 and newer, {@code
diff --git a/common/src/main/java/com/google/auto/common/MoreElements.java b/common/src/main/java/com/google/auto/common/MoreElements.java
index 5e8e354..dfbbaee 100644
--- a/common/src/main/java/com/google/auto/common/MoreElements.java
+++ b/common/src/main/java/com/google/auto/common/MoreElements.java
@@ -16,6 +16,7 @@
  */
 package com.google.auto.common;
 
+import static com.google.auto.common.MoreStreams.toImmutableSet;
 import static javax.lang.model.element.ElementKind.PACKAGE;
 import static javax.lang.model.element.Modifier.STATIC;
 
@@ -212,29 +213,80 @@
   }
 
   /**
-   * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose
-   * {@linkplain AnnotationMirror#getAnnotationType() annotation type} has the same canonical name
-   * as that of {@code annotationClass}. This method is a safer alternative to calling
-   * {@link Element#getAnnotation} and checking for {@code null} as it avoids any interaction with
+   * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain
+   * AnnotationMirror#getAnnotationType() annotation type} has the same canonical name as that of
+   * {@code annotationClass}. This method is a safer alternative to calling {@link
+   * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with
    * annotation proxies.
    */
-  public static boolean isAnnotationPresent(Element element,
-      Class<? extends Annotation> annotationClass) {
+  public static boolean isAnnotationPresent(
+      Element element, Class<? extends Annotation> annotationClass) {
     return getAnnotationMirror(element, annotationClass).isPresent();
   }
 
   /**
+   * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain
+   * AnnotationMirror#getAnnotationType() annotation type} has the same fully qualified name as that
+   * of {@code annotation}. This method is a safer alternative to calling {@link
+   * Element#getAnnotation} and checking for {@code null} as it avoids any interaction with
+   * annotation proxies.
+   */
+  public static boolean isAnnotationPresent(Element element, TypeElement annotation) {
+    return getAnnotationMirror(element, annotation).isPresent();
+  }
+
+  /**
+   * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain
+   * AnnotationMirror#getAnnotationType() annotation type} has {@code annotationName} as its
+   * canonical name. This method is a safer alternative to calling {@link Element#getAnnotation} and
+   * checking for {@code null} as it avoids any interaction with annotation proxies.
+   */
+  public static boolean isAnnotationPresent(Element element, String annotationName) {
+    return getAnnotationMirror(element, annotationName).isPresent();
+  }
+
+  /**
    * Returns an {@link AnnotationMirror} for the annotation of type {@code annotationClass} on
    * {@code element}, or {@link Optional#absent()} if no such annotation exists. This method is a
    * safer alternative to calling {@link Element#getAnnotation} as it avoids any interaction with
    * annotation proxies.
    */
-  public static Optional<AnnotationMirror> getAnnotationMirror(Element element,
-      Class<? extends Annotation> annotationClass) {
-    String annotationClassName = annotationClass.getCanonicalName();
+  public static Optional<AnnotationMirror> getAnnotationMirror(
+      Element element, Class<? extends Annotation> annotationClass) {
+    String name = annotationClass.getCanonicalName();
+    if (name == null) {
+      return Optional.absent();
+    }
+    return getAnnotationMirror(element, name);
+  }
+
+  /**
+   * Returns an {@link AnnotationMirror} for the annotation of type {@code annotation} on {@code
+   * element}, or {@link Optional#absent()} if no such annotation exists. This method is a safer
+   * alternative to calling {@link Element#getAnnotation} as it avoids any interaction with
+   * annotation proxies.
+   */
+  public static Optional<AnnotationMirror> getAnnotationMirror(
+      Element element, TypeElement annotation) {
+    for (AnnotationMirror elementAnnotation : element.getAnnotationMirrors()) {
+      if (elementAnnotation.getAnnotationType().asElement().equals(annotation)) {
+        return Optional.of(elementAnnotation);
+      }
+    }
+    return Optional.absent();
+  }
+
+  /**
+   * Returns an {@link AnnotationMirror} for the annotation whose type's canonical name is on {@code
+   * element}, or {@link Optional#absent()} if no such annotation exists. This method is a safer
+   * alternative to calling {@link Element#getAnnotation} as it avoids any interaction with
+   * annotation proxies.
+   */
+  public static Optional<AnnotationMirror> getAnnotationMirror(
+      Element element, String annotationName) {
     for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
       TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement());
-      if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) {
+      if (annotationTypeElement.getQualifiedName().contentEquals(annotationName)) {
         return Optional.of(annotationMirror);
       }
     }
@@ -435,9 +487,9 @@
         }
       }
     }
-    Set<ExecutableElement> methods = new LinkedHashSet<ExecutableElement>(methodMap.values());
-    methods.removeAll(overridden);
-    return ImmutableSet.copyOf(methods);
+    return methodMap.values().stream()
+        .filter(m -> !overridden.contains(m))
+        .collect(toImmutableSet());
   }
 
   // Add to `methods` the static and instance methods from `type`. This means all methods from
diff --git a/common/src/main/java/com/google/auto/common/MoreStreams.java b/common/src/main/java/com/google/auto/common/MoreStreams.java
new file mode 100644
index 0000000..934514a
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/MoreStreams.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.common;
+
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+/**
+ * A utility class that provides Android compatible alternatives to Guava's streaming APIs.
+ *
+ * <p>This is useful when the Android flavor of Guava somehow finds its way onto the processor
+ * classpath.
+ */
+public final class MoreStreams {
+
+  /** Returns a collector for an {@link ImmutableList}. */
+  public static <T> Collector<T, ?, ImmutableList<T>> toImmutableList() {
+    return collectingAndThen(toList(), ImmutableList::copyOf);
+  }
+
+  /** Returns a collector for an {@link ImmutableSet}. */
+  public static <T> Collector<T, ?, ImmutableSet<T>> toImmutableSet() {
+    return collectingAndThen(toList(), ImmutableSet::copyOf);
+  }
+
+  /** Returns a collector for an {@link ImmutableMap}. */
+  public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
+      Function<? super T, K> keyMapper, Function<? super T, V> valueMapper) {
+    return Collectors.mapping(
+        value -> Maps.immutableEntry(keyMapper.apply(value), valueMapper.apply(value)),
+        Collector.of(
+            ImmutableMap::builder,
+            (ImmutableMap.Builder<K, V> builder, Map.Entry<K, V> entry) -> builder.put(entry),
+            (left, right) -> left.putAll(right.build()),
+            ImmutableMap.Builder::build));
+  }
+
+  /** Returns a collector for an {@link ImmutableBiMap}. */
+  public static <T, K, V> Collector<T, ?, ImmutableBiMap<K, V>> toImmutableBiMap(
+      Function<? super T, K> keyMapper, Function<? super T, V> valueMapper) {
+    return Collectors.mapping(
+        value -> Maps.immutableEntry(keyMapper.apply(value), valueMapper.apply(value)),
+        Collector.of(
+            ImmutableBiMap::builder,
+            (ImmutableBiMap.Builder<K, V> builder, Map.Entry<K, V> entry) -> builder.put(entry),
+            (left, right) -> left.putAll(right.build()),
+            ImmutableBiMap.Builder::build));
+  }
+
+  private MoreStreams() {}
+}
diff --git a/common/src/main/java/com/google/auto/common/MoreTypes.java b/common/src/main/java/com/google/auto/common/MoreTypes.java
index e09680b..1a49062 100644
--- a/common/src/main/java/com/google/auto/common/MoreTypes.java
+++ b/common/src/main/java/com/google/auto/common/MoreTypes.java
@@ -25,7 +25,6 @@
 import static javax.lang.model.type.TypeKind.WILDCARD;
 
 import com.google.common.base.Equivalence;
-import com.google.common.base.Objects;
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -55,6 +54,7 @@
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.SimpleTypeVisitor8;
 import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Utilities related to {@link TypeMirror} instances.
@@ -75,6 +75,11 @@
     protected int doHash(TypeMirror t) {
       return MoreTypes.hash(t, ImmutableSet.<Element>of());
     }
+
+    @Override
+    public String toString() {
+      return "MoreTypes.equivalence()";
+    }
   }
 
   /**
@@ -135,13 +140,11 @@
     }
 
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(@Nullable Object o) {
       if (o instanceof ComparedElements) {
         ComparedElements that = (ComparedElements) o;
         int nArguments = aArguments.size();
-        if (!this.a.equals(that.a)
-            || !this.b.equals(that.b)
-            || nArguments != bArguments.size()) {
+        if (!this.a.equals(that.a) || !this.b.equals(that.b) || nArguments != bArguments.size()) {
           // The arguments must be the same size, but we check anyway.
           return false;
         }
@@ -294,7 +297,14 @@
   }
 
   @SuppressWarnings("TypeEquals")
-  private static boolean equal(TypeMirror a, TypeMirror b, Set<ComparedElements> visiting) {
+  private static boolean equal(
+      @Nullable TypeMirror a, @Nullable TypeMirror b, Set<ComparedElements> visiting) {
+    if (a == b) {
+      return true;
+    }
+    if (a == null || b == null) {
+      return false;
+    }
     // TypeMirror.equals is not guaranteed to return true for types that are equal, but we can
     // assume that if it does return true then the types are equal. This check also avoids getting
     // stuck in infinite recursion when Eclipse decrees that the upper bound of the second K in
@@ -302,13 +312,15 @@
     // The javac implementation of ExecutableType, at least in some versions, does not take thrown
     // exceptions into account in its equals implementation, so avoid this optimization for
     // ExecutableType.
-    if (Objects.equal(a, b) && !(a instanceof ExecutableType)) {
+    @SuppressWarnings("TypesEquals")
+    boolean equal = a.equals(b);
+    if (equal && !(a instanceof ExecutableType)) {
       return true;
     }
     EqualVisitorParam p = new EqualVisitorParam();
     p.type = b;
     p.visiting = visiting;
-    return (a == b) || (a != null && b != null && a.accept(EqualVisitor.INSTANCE, p));
+    return a.accept(EqualVisitor.INSTANCE, p);
   }
 
   /**
@@ -318,7 +330,7 @@
    * <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=508222">this bug</a> whereby
    * the Eclipse compiler returns a value for static classes that is not NoType.
    */
-  private static TypeMirror enclosingType(DeclaredType t) {
+  private static @Nullable TypeMirror enclosingType(DeclaredType t) {
     TypeMirror enclosing = t.getEnclosingType();
     if (enclosing.getKind().equals(TypeKind.NONE)
         || t.asElement().getModifiers().contains(Modifier.STATIC)) {
@@ -337,16 +349,14 @@
     Iterator<? extends TypeMirror> aIterator = a.iterator();
     Iterator<? extends TypeMirror> bIterator = b.iterator();
     while (aIterator.hasNext()) {
-      if (!bIterator.hasNext()) {
-        return false;
-      }
+      // We checked that the lists have the same size, so we know that bIterator.hasNext() too.
       TypeMirror nextMirrorA = aIterator.next();
       TypeMirror nextMirrorB = bIterator.next();
       if (!equal(nextMirrorA, nextMirrorB, visiting)) {
         return false;
       }
     }
-    return !aIterator.hasNext();
+    return true;
   }
 
   private static final int HASH_SEED = 17;
@@ -433,7 +443,7 @@
     public Integer visitUnknown(TypeMirror t, Set<Element> visiting) {
       throw new UnsupportedOperationException();
     }
-  };
+  }
 
   private static int hashList(List<? extends TypeMirror> mirrors, Set<Element> visiting) {
     int result = HASH_SEED;
@@ -460,17 +470,17 @@
   }
 
   private static final class ReferencedTypes
-      extends SimpleTypeVisitor8<Void, ImmutableSet.Builder<TypeElement>> {
+      extends SimpleTypeVisitor8<@Nullable Void, ImmutableSet.Builder<TypeElement>> {
     private static final ReferencedTypes INSTANCE = new ReferencedTypes();
 
     @Override
-    public Void visitArray(ArrayType t, ImmutableSet.Builder<TypeElement> p) {
+    public @Nullable Void visitArray(ArrayType t, ImmutableSet.Builder<TypeElement> p) {
       t.getComponentType().accept(this, p);
       return null;
     }
 
     @Override
-    public Void visitDeclared(DeclaredType t, ImmutableSet.Builder<TypeElement> p) {
+    public @Nullable Void visitDeclared(DeclaredType t, ImmutableSet.Builder<TypeElement> p) {
       p.add(MoreElements.asType(t.asElement()));
       for (TypeMirror typeArgument : t.getTypeArguments()) {
         typeArgument.accept(this, p);
@@ -479,14 +489,14 @@
     }
 
     @Override
-    public Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder<TypeElement> p) {
+    public @Nullable Void visitTypeVariable(TypeVariable t, ImmutableSet.Builder<TypeElement> p) {
       t.getLowerBound().accept(this, p);
       t.getUpperBound().accept(this, p);
       return null;
     }
 
     @Override
-    public Void visitWildcard(WildcardType t, ImmutableSet.Builder<TypeElement> p) {
+    public @Nullable Void visitWildcard(WildcardType t, ImmutableSet.Builder<TypeElement> p) {
       TypeMirror extendsBound = t.getExtendsBound();
       if (extendsBound != null) {
         extendsBound.accept(this, p);
@@ -534,7 +544,8 @@
     public Element visitTypeVariable(TypeVariable t, Void p) {
       return t.asElement();
     }
-  };
+  }
+  ;
 
   // TODO(gak): consider removing these two methods as they're pretty trivial now
   public static TypeElement asTypeElement(TypeMirror mirror) {
@@ -833,6 +844,11 @@
     }
 
     @Override
+    public Boolean visitError(ErrorType errorType, Void p) {
+      return false;
+    }
+
+    @Override
     public Boolean visitPrimitive(PrimitiveType type, Void p) {
       switch (type.getKind()) {
         case BOOLEAN:
@@ -873,11 +889,11 @@
    * {@link Optional#absent()} if {@code type} is an interface or {@link Object} or its superclass
    * is {@link Object}.
    */
-  // TODO(user): Remove unused parameter Elements?
-  public static Optional<DeclaredType> nonObjectSuperclass(Types types, Elements elements,
-      DeclaredType type) {
+  // TODO(bcorso): Remove unused parameter Elements?
+  public static Optional<DeclaredType> nonObjectSuperclass(
+      Types types, Elements elements, DeclaredType type) {
     checkNotNull(types);
-    checkNotNull(elements);  // This is no longer used, but here to avoid changing the API.
+    checkNotNull(elements); // This is no longer used, but here to avoid changing the API.
     checkNotNull(type);
 
     TypeMirror superclassType = asTypeElement(type).getSuperclass();
@@ -885,7 +901,7 @@
       return Optional.absent();
     }
 
-    DeclaredType superclass =  asDeclared(superclassType);
+    DeclaredType superclass = asDeclared(superclassType);
     if (isObjectType(superclass)) {
       return Optional.absent();
     }
@@ -912,8 +928,8 @@
    * {@code container} of type {@code Set<String>}, and a variable corresponding to the {@code E e}
    * parameter in the {@code Set.add(E e)} method, this will return a TypeMirror for {@code String}.
    */
-  public static TypeMirror asMemberOf(Types types, DeclaredType container,
-      VariableElement variable) {
+  public static TypeMirror asMemberOf(
+      Types types, DeclaredType container, VariableElement variable) {
     if (variable.getKind().equals(ElementKind.PARAMETER)) {
       ExecutableElement methodOrConstructor =
           MoreElements.asExecutable(variable.getEnclosingElement());
diff --git a/common/src/main/java/com/google/auto/common/Overrides.java b/common/src/main/java/com/google/auto/common/Overrides.java
index 19a4586..775c304 100644
--- a/common/src/main/java/com/google/auto/common/Overrides.java
+++ b/common/src/main/java/com/google/auto/common/Overrides.java
@@ -15,6 +15,8 @@
  */
 package com.google.auto.common;
 
+import static java.util.stream.Collectors.toList;
+
 import com.google.common.base.Preconditions;
 import com.google.common.base.Verify;
 import com.google.common.collect.ImmutableList;
@@ -38,6 +40,7 @@
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.SimpleTypeVisitor8;
 import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Determines if one method overrides another. This class defines two ways of doing that:
@@ -114,12 +117,11 @@
         // can't be overridden.
         return false;
       }
-      TypeElement overriddenType;
       if (!(overridden.getEnclosingElement() instanceof TypeElement)) {
         return false;
         // We don't know how this could happen but we avoid blowing up if it does.
       }
-      overriddenType = MoreElements.asType(overridden.getEnclosingElement());
+      TypeElement overriddenType = MoreElements.asType(overridden.getEnclosingElement());
       // We erase the types before checking subtypes, because the TypeMirror we get for List<E> is
       // not a subtype of the one we get for Collection<E> since the two E instances are not the
       // same. For the purposes of overriding, type parameters in the containing type should not
@@ -141,7 +143,8 @@
           // the enclosing elements rather than the methods themselves for the reason described
           // at the start of the method.
           ExecutableElement inherited = methodFromSuperclasses(in, overridden);
-          return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
+          return inherited != null
+              && !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
         } else if (overriddenType.getKind().isInterface()) {
           // ...overrides from C another method mI declared in interface I. We've already checked
           // the conditions (assuming that the only alternative to mI being abstract or default is
@@ -157,7 +160,8 @@
           // to methodFromSuperclasses above.
           if (overrider.getModifiers().contains(Modifier.ABSTRACT)) {
             ExecutableElement inherited = methodFromSuperinterfaces(in, overridden);
-            return !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
+            return inherited != null
+                && !overridden.getEnclosingElement().equals(inherited.getEnclosingElement());
           } else {
             return true;
           }
@@ -215,6 +219,7 @@
      * implements List<E>}. The parameter types are erased since the purpose of this method is to
      * determine whether two methods are candidates for one to override the other.
      */
+    @Nullable
     ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) {
       if (method.getParameters().isEmpty()) {
         return ImmutableList.of();
@@ -241,6 +246,7 @@
        */
       private final Map<TypeParameterElement, TypeMirror> typeBindings = Maps.newLinkedHashMap();
 
+      @Nullable
       ImmutableList<TypeMirror> erasedParameterTypes(ExecutableElement method, TypeElement in) {
         if (method.getEnclosingElement().equals(in)) {
           ImmutableList.Builder<TypeMirror> params = ImmutableList.builder();
@@ -261,6 +267,10 @@
           TypeElement element = MoreElements.asType(declared.asElement());
           List<? extends TypeMirror> actuals = declared.getTypeArguments();
           List<? extends TypeParameterElement> formals = element.getTypeParameters();
+          if (actuals.isEmpty()) {
+            // Either the formal type arguments are also empty or `declared` is raw.
+            actuals = formals.stream().map(t -> t.getBounds().get(0)).collect(toList());
+          }
           Verify.verify(actuals.size() == formals.size());
           for (int i = 0; i < actuals.size(); i++) {
             typeBindings.put(formals.get(i), actuals.get(i));
@@ -315,7 +325,7 @@
      * or the nearest override in a superclass of the given type, or null if the method is not
      * found in the given type or any of its superclasses.
      */
-    ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) {
+    @Nullable ExecutableElement methodFromSuperclasses(TypeElement in, ExecutableElement method) {
       for (TypeElement t = in; t != null; t = superclass(t)) {
         ExecutableElement tMethod = methodInType(t, method);
         if (tMethod != null) {
@@ -330,6 +340,7 @@
      * itself, or the nearest override in a superinterface of the given type, or null if the method
      * is not found in the given type or any of its transitive superinterfaces.
      */
+    @Nullable
     ExecutableElement methodFromSuperinterfaces(TypeElement in, ExecutableElement method) {
       TypeElement methodContainer = MoreElements.asType(method.getEnclosingElement());
       Preconditions.checkArgument(methodContainer.getKind().isInterface());
@@ -366,7 +377,7 @@
      * Returns the method from within the given type that has the same erased signature as the given
      * method, or null if there is no such method.
      */
-    private ExecutableElement methodInType(TypeElement type, ExecutableElement method) {
+    private @Nullable ExecutableElement methodInType(TypeElement type, ExecutableElement method) {
       int nParams = method.getParameters().size();
       List<TypeMirror> params = erasedParameterTypes(method, type);
       if (params == null) {
@@ -388,7 +399,7 @@
       return null;
     }
 
-    private TypeElement superclass(TypeElement type) {
+    private @Nullable TypeElement superclass(TypeElement type) {
       TypeMirror sup = type.getSuperclass();
       if (sup.getKind() == TypeKind.DECLARED) {
         return MoreElements.asType(typeUtils.asElement(sup));
diff --git a/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java
index 7d508e3..7952eb3 100644
--- a/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java
+++ b/common/src/main/java/com/google/auto/common/SimpleAnnotationMirror.java
@@ -16,8 +16,8 @@
 
 package com.google.auto.common;
 
+import static com.google.auto.common.MoreStreams.toImmutableMap;
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static javax.lang.model.util.ElementFilter.methodsIn;
 
 import com.google.common.base.Joiner;
@@ -32,6 +32,7 @@
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.DeclaredType;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * A simple implementation of the {@link AnnotationMirror} interface.
@@ -65,7 +66,7 @@
         missingMembers.add(memberName);
       }
     }
-    
+
     checkArgument(
         unusedValues.isEmpty(),
         "namedValues has entries for members that are not in %s: %s",
@@ -77,8 +78,7 @@
     this.annotationType = annotationType;
     this.namedValues = ImmutableMap.copyOf(namedValues);
     this.elementValues =
-        methodsIn(annotationType.getEnclosedElements())
-            .stream()
+        methodsIn(annotationType.getEnclosedElements()).stream()
             .collect(toImmutableMap(e -> e, e -> values.get(e.getSimpleName().toString())));
   }
 
@@ -123,7 +123,7 @@
   }
 
   @Override
-  public boolean equals(Object other) {
+  public boolean equals(@Nullable Object other) {
     return other instanceof AnnotationMirror
         && AnnotationMirrors.equivalence().equivalent(this, (AnnotationMirror) other);
   }
diff --git a/common/src/main/java/com/google/auto/common/SuperficialValidation.java b/common/src/main/java/com/google/auto/common/SuperficialValidation.java
index 5ef4dbf..614e262 100644
--- a/common/src/main/java/com/google/auto/common/SuperficialValidation.java
+++ b/common/src/main/java/com/google/auto/common/SuperficialValidation.java
@@ -57,23 +57,27 @@
 
   private static final ElementVisitor<Boolean, Void> ELEMENT_VALIDATING_VISITOR =
       new AbstractElementVisitor8<Boolean, Void>() {
-        @Override public Boolean visitPackage(PackageElement e, Void p) {
+        @Override
+        public Boolean visitPackage(PackageElement e, Void p) {
           // don't validate enclosed elements because it will return types in the package
           return validateAnnotations(e.getAnnotationMirrors());
         }
 
-        @Override public Boolean visitType(TypeElement e, Void p) {
+        @Override
+        public Boolean visitType(TypeElement e, Void p) {
           return isValidBaseElement(e)
               && validateElements(e.getTypeParameters())
               && validateTypes(e.getInterfaces())
               && validateType(e.getSuperclass());
         }
 
-        @Override public Boolean visitVariable(VariableElement e, Void p) {
+        @Override
+        public Boolean visitVariable(VariableElement e, Void p) {
           return isValidBaseElement(e);
         }
 
-        @Override public Boolean visitExecutable(ExecutableElement e, Void p) {
+        @Override
+        public Boolean visitExecutable(ExecutableElement e, Void p) {
           AnnotationValue defaultValue = e.getDefaultValue();
           return isValidBaseElement(e)
               && (defaultValue == null || validateAnnotationValue(defaultValue, e.getReturnType()))
@@ -83,12 +87,13 @@
               && validateElements(e.getParameters());
         }
 
-        @Override public Boolean visitTypeParameter(TypeParameterElement e, Void p) {
-          return isValidBaseElement(e)
-              && validateTypes(e.getBounds());
+        @Override
+        public Boolean visitTypeParameter(TypeParameterElement e, Void p) {
+          return isValidBaseElement(e) && validateTypes(e.getBounds());
         }
 
-        @Override public Boolean visitUnknown(Element e, Void p) {
+        @Override
+        public Boolean visitUnknown(Element e, Void p) {
           // just assume that unknown elements are OK
           return true;
         }
@@ -206,16 +211,19 @@
 
   private static final AnnotationValueVisitor<Boolean, TypeMirror> VALUE_VALIDATING_VISITOR =
       new SimpleAnnotationValueVisitor8<Boolean, TypeMirror>() {
-        @Override protected Boolean defaultAction(Object o, TypeMirror expectedType) {
+        @Override
+        protected Boolean defaultAction(Object o, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(o.getClass(), expectedType);
         }
 
-        @Override public Boolean visitUnknown(AnnotationValue av, TypeMirror expectedType) {
+        @Override
+        public Boolean visitUnknown(AnnotationValue av, TypeMirror expectedType) {
           // just take the default action for the unknown
           return defaultAction(av, expectedType);
         }
 
-        @Override public Boolean visitAnnotation(AnnotationMirror a, TypeMirror expectedType) {
+        @Override
+        public Boolean visitAnnotation(AnnotationMirror a, TypeMirror expectedType) {
           return MoreTypes.equivalence().equivalent(a.getAnnotationType(), expectedType)
               && validateAnnotation(a);
         }
@@ -235,7 +243,8 @@
               && validateElement(enumConstant);
         }
 
-        @Override public Boolean visitType(TypeMirror type, TypeMirror ignored) {
+        @Override
+        public Boolean visitType(TypeMirror type, TypeMirror ignored) {
           // We could check assignability here, but would require a Types instance. Since this
           // isn't really the sort of thing that shows up in a bad AST from upstream compilation
           // we ignore the expected type and just validate the type.  It might be wrong, but
@@ -243,35 +252,43 @@
           return validateType(type);
         }
 
-        @Override public Boolean visitBoolean(boolean b, TypeMirror expectedType) {
+        @Override
+        public Boolean visitBoolean(boolean b, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Boolean.TYPE, expectedType);
         }
 
-        @Override public Boolean visitByte(byte b, TypeMirror expectedType) {
+        @Override
+        public Boolean visitByte(byte b, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Byte.TYPE, expectedType);
         }
 
-        @Override public Boolean visitChar(char c, TypeMirror expectedType) {
+        @Override
+        public Boolean visitChar(char c, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Character.TYPE, expectedType);
         }
 
-        @Override public Boolean visitDouble(double d, TypeMirror expectedType) {
+        @Override
+        public Boolean visitDouble(double d, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Double.TYPE, expectedType);
         }
 
-        @Override public Boolean visitFloat(float f, TypeMirror expectedType) {
+        @Override
+        public Boolean visitFloat(float f, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Float.TYPE, expectedType);
         }
 
-        @Override public Boolean visitInt(int i, TypeMirror expectedType) {
+        @Override
+        public Boolean visitInt(int i, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Integer.TYPE, expectedType);
         }
 
-        @Override public Boolean visitLong(long l, TypeMirror expectedType) {
+        @Override
+        public Boolean visitLong(long l, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Long.TYPE, expectedType);
         }
 
-        @Override public Boolean visitShort(short s, TypeMirror expectedType) {
+        @Override
+        public Boolean visitShort(short s, TypeMirror expectedType) {
           return MoreTypes.isTypeOf(Short.TYPE, expectedType);
         }
       };
diff --git a/common/src/main/java/com/google/auto/common/Visibility.java b/common/src/main/java/com/google/auto/common/Visibility.java
index f82fdd5..36f4ad6 100644
--- a/common/src/main/java/com/google/auto/common/Visibility.java
+++ b/common/src/main/java/com/google/auto/common/Visibility.java
@@ -16,14 +16,15 @@
 package com.google.auto.common;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Comparators.min;
 import static javax.lang.model.element.ElementKind.PACKAGE;
 
 import com.google.common.base.Enums;
-import com.google.common.collect.Ordering;
 import java.util.Set;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.Modifier;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Represents the visibility of a given {@link Element}: {@code public}, {@code protected},
@@ -41,7 +42,7 @@
 
   // TODO(ronshapiro): remove this and reference ElementKind.MODULE directly once we start building
   // with -source 9
-  private static final ElementKind MODULE =
+  private static final @Nullable ElementKind MODULE =
       Enums.getIfPresent(ElementKind.class, "MODULE").orNull();
 
   /**
@@ -76,8 +77,7 @@
     Visibility effectiveVisibility = PUBLIC;
     Element currentElement = element;
     while (currentElement != null) {
-      effectiveVisibility =
-          Ordering.natural().min(effectiveVisibility, ofElement(currentElement));
+      effectiveVisibility = min(effectiveVisibility, ofElement(currentElement));
       currentElement = currentElement.getEnclosingElement();
     }
     return effectiveVisibility;
diff --git a/common/src/main/java/com/google/auto/common/package-info.java b/common/src/main/java/com/google/auto/common/package-info.java
new file mode 100644
index 0000000..22b0c45
--- /dev/null
+++ b/common/src/main/java/com/google/auto/common/package-info.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.common;
+
diff --git a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java
index dfc043a..b1dfe3b 100644
--- a/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java
+++ b/common/src/test/java/com/google/auto/common/AnnotationMirrorsTest.java
@@ -22,12 +22,17 @@
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static org.junit.Assert.fail;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.testing.EquivalenceTester;
+import com.google.common.truth.Correspondence;
 import com.google.testing.compile.CompilationRule;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Map;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.SimpleAnnotationValueVisitor6;
@@ -46,110 +51,134 @@
 
   private Elements elements;
 
-  @Before public void setUp() {
+  @Before
+  public void setUp() {
     this.elements = compilationRule.getElements();
   }
 
   @interface SimpleAnnotation {}
 
-  @SimpleAnnotation class SimplyAnnotated {}
-  @SimpleAnnotation class AlsoSimplyAnnotated {}
+  @SimpleAnnotation
+  static class SimplyAnnotated {}
+
+  @SimpleAnnotation
+  static class AlsoSimplyAnnotated {}
 
   enum SimpleEnum {
-    BLAH, FOO
+    BLAH,
+    FOO
   }
 
   @interface Outer {
     SimpleEnum value();
   }
 
-  @Outer(BLAH) static class TestClassBlah {}
-  @Outer(BLAH) static class TestClassBlah2 {}
-  @Outer(FOO) static class TestClassFoo {}
+  @Outer(BLAH)
+  static class TestClassBlah {}
+
+  @Outer(BLAH)
+  static class TestClassBlah2 {}
+
+  @Outer(FOO)
+  static class TestClassFoo {}
 
   @interface DefaultingOuter {
     SimpleEnum value() default SimpleEnum.BLAH;
   }
 
-  @DefaultingOuter class TestWithDefaultingOuterDefault {}
-  @DefaultingOuter(BLAH) class TestWithDefaultingOuterBlah {}
-  @DefaultingOuter(FOO) class TestWithDefaultingOuterFoo {}
+  @DefaultingOuter
+  static class TestWithDefaultingOuterDefault {}
+
+  @DefaultingOuter(BLAH)
+  static class TestWithDefaultingOuterBlah {}
+
+  @DefaultingOuter(FOO)
+  static class TestWithDefaultingOuterFoo {}
 
   @interface AnnotatedOuter {
     DefaultingOuter value();
   }
 
-  @AnnotatedOuter(@DefaultingOuter) class TestDefaultNestedAnnotated {}
-  @AnnotatedOuter(@DefaultingOuter(BLAH)) class TestBlahNestedAnnotated {}
-  @AnnotatedOuter(@DefaultingOuter(FOO)) class TestFooNestedAnnotated {}
+  @AnnotatedOuter(@DefaultingOuter)
+  static class TestDefaultNestedAnnotated {}
+
+  @AnnotatedOuter(@DefaultingOuter(BLAH))
+  static class TestBlahNestedAnnotated {}
+
+  @AnnotatedOuter(@DefaultingOuter(FOO))
+  static class TestFooNestedAnnotated {}
 
   @interface OuterWithValueArray {
     DefaultingOuter[] value() default {};
   }
 
-  @OuterWithValueArray class TestValueArrayWithDefault {}
-  @OuterWithValueArray({}) class TestValueArrayWithEmpty {}
+  @OuterWithValueArray
+  static class TestValueArrayWithDefault {}
 
-  @OuterWithValueArray({@DefaultingOuter}) class TestValueArrayWithOneDefault {}
-  @OuterWithValueArray(@DefaultingOuter(BLAH)) class TestValueArrayWithOneBlah {}
-  @OuterWithValueArray(@DefaultingOuter(FOO)) class TestValueArrayWithOneFoo {}
+  @OuterWithValueArray({})
+  static class TestValueArrayWithEmpty {}
+
+  @OuterWithValueArray({@DefaultingOuter})
+  static class TestValueArrayWithOneDefault {}
+
+  @OuterWithValueArray(@DefaultingOuter(BLAH))
+  static class TestValueArrayWithOneBlah {}
+
+  @OuterWithValueArray(@DefaultingOuter(FOO))
+  static class TestValueArrayWithOneFoo {}
 
   @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter})
   class TestValueArrayWithFooAndDefaultBlah {}
+
   @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)})
   class TestValueArrayWithFooBlah {}
+
   @OuterWithValueArray({@DefaultingOuter(FOO), @DefaultingOuter(BLAH)})
   class TestValueArrayWithFooBlah2 {} // Different instances than on TestValueArrayWithFooBlah.
+
   @OuterWithValueArray({@DefaultingOuter(BLAH), @DefaultingOuter(FOO)})
   class TestValueArrayWithBlahFoo {}
 
-  @Test public void testEquivalences() {
+  @Test
+  public void testEquivalences() {
     EquivalenceTester<AnnotationMirror> tester =
         EquivalenceTester.of(AnnotationMirrors.equivalence());
 
     tester.addEquivalenceGroup(
-        annotationOn(SimplyAnnotated.class),
-        annotationOn(AlsoSimplyAnnotated.class));
+        annotationOn(SimplyAnnotated.class), annotationOn(AlsoSimplyAnnotated.class));
 
     tester.addEquivalenceGroup(
-        annotationOn(TestClassBlah.class),
-        annotationOn(TestClassBlah2.class));
+        annotationOn(TestClassBlah.class), annotationOn(TestClassBlah2.class));
 
-    tester.addEquivalenceGroup(
-        annotationOn(TestClassFoo.class));
+    tester.addEquivalenceGroup(annotationOn(TestClassFoo.class));
 
     tester.addEquivalenceGroup(
         annotationOn(TestWithDefaultingOuterDefault.class),
         annotationOn(TestWithDefaultingOuterBlah.class));
 
-    tester.addEquivalenceGroup(
-        annotationOn(TestWithDefaultingOuterFoo.class));
+    tester.addEquivalenceGroup(annotationOn(TestWithDefaultingOuterFoo.class));
 
     tester.addEquivalenceGroup(
         annotationOn(TestDefaultNestedAnnotated.class),
         annotationOn(TestBlahNestedAnnotated.class));
 
-    tester.addEquivalenceGroup(
-        annotationOn(TestFooNestedAnnotated.class));
+    tester.addEquivalenceGroup(annotationOn(TestFooNestedAnnotated.class));
 
     tester.addEquivalenceGroup(
-        annotationOn(TestValueArrayWithDefault.class),
-        annotationOn(TestValueArrayWithEmpty.class));
+        annotationOn(TestValueArrayWithDefault.class), annotationOn(TestValueArrayWithEmpty.class));
 
     tester.addEquivalenceGroup(
         annotationOn(TestValueArrayWithOneDefault.class),
         annotationOn(TestValueArrayWithOneBlah.class));
 
-    tester.addEquivalenceGroup(
-        annotationOn(TestValueArrayWithOneFoo.class));
+    tester.addEquivalenceGroup(annotationOn(TestValueArrayWithOneFoo.class));
 
     tester.addEquivalenceGroup(
         annotationOn(TestValueArrayWithFooAndDefaultBlah.class),
         annotationOn(TestValueArrayWithFooBlah.class),
         annotationOn(TestValueArrayWithFooBlah2.class));
 
-    tester.addEquivalenceGroup(
-        annotationOn(TestValueArrayWithBlahFoo.class));
+    tester.addEquivalenceGroup(annotationOn(TestValueArrayWithBlahFoo.class));
 
     tester.test();
   }
@@ -158,44 +187,61 @@
     String value() default "default";
   }
 
-  @Stringy class StringyUnset {}
-  @Stringy("foo") class StringySet {}
+  @Stringy
+  static class StringyUnset {}
 
-  @Test public void testGetDefaultValuesUnset() {
+  @Stringy("foo")
+  static class StringySet {}
+
+  @Test
+  public void testGetDefaultValuesUnset() {
     assertThat(annotationOn(StringyUnset.class).getElementValues()).isEmpty();
-    Iterable<AnnotationValue> values = AnnotationMirrors.getAnnotationValuesWithDefaults(
-        annotationOn(StringyUnset.class)).values();
-    String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6<String, Void>() {
-          @Override public String visitString(String value, Void ignored) {
-            return value;
-          }
-        }, null);
+    Iterable<AnnotationValue> values =
+        AnnotationMirrors.getAnnotationValuesWithDefaults(annotationOn(StringyUnset.class))
+            .values();
+    String value =
+        getOnlyElement(values)
+            .accept(
+                new SimpleAnnotationValueVisitor6<String, Void>() {
+                  @Override
+                  public String visitString(String value, Void ignored) {
+                    return value;
+                  }
+                },
+                null);
     assertThat(value).isEqualTo("default");
   }
 
-  @Test public void testGetDefaultValuesSet() {
-    Iterable<AnnotationValue> values = AnnotationMirrors.getAnnotationValuesWithDefaults(
-        annotationOn(StringySet.class)).values();
-    String value = getOnlyElement(values).accept(new SimpleAnnotationValueVisitor6<String, Void>() {
-          @Override public String visitString(String value, Void ignored) {
-            return value;
-          }
-        }, null);
+  @Test
+  public void testGetDefaultValuesSet() {
+    Iterable<AnnotationValue> values =
+        AnnotationMirrors.getAnnotationValuesWithDefaults(annotationOn(StringySet.class)).values();
+    String value =
+        getOnlyElement(values)
+            .accept(
+                new SimpleAnnotationValueVisitor6<String, Void>() {
+                  @Override
+                  public String visitString(String value, Void ignored) {
+                    return value;
+                  }
+                },
+                null);
     assertThat(value).isEqualTo("foo");
   }
 
-  @Test public void testGetValueEntry() {
+  @Test
+  public void testGetValueEntry() {
     Map.Entry<ExecutableElement, AnnotationValue> elementValue =
-        AnnotationMirrors.getAnnotationElementAndValue(
-            annotationOn(TestClassBlah.class), "value");
+        AnnotationMirrors.getAnnotationElementAndValue(annotationOn(TestClassBlah.class), "value");
     assertThat(elementValue.getKey().getSimpleName().toString()).isEqualTo("value");
     assertThat(elementValue.getValue().getValue()).isInstanceOf(VariableElement.class);
-    AnnotationValue value = AnnotationMirrors.getAnnotationValue(
-        annotationOn(TestClassBlah.class), "value");
+    AnnotationValue value =
+        AnnotationMirrors.getAnnotationValue(annotationOn(TestClassBlah.class), "value");
     assertThat(value.getValue()).isInstanceOf(VariableElement.class);
   }
 
-  @Test public void testGetValueEntryFailure() {
+  @Test
+  public void testGetValueEntryFailure() {
     try {
       AnnotationMirrors.getAnnotationValue(annotationOn(TestClassBlah.class), "a");
     } catch (IllegalArgumentException e) {
@@ -212,4 +258,52 @@
     return getOnlyElement(elements.getTypeElement(clazz.getCanonicalName()).getAnnotationMirrors());
   }
 
+  @Retention(RetentionPolicy.RUNTIME)
+  private @interface AnnotatingAnnotation {}
+
+  @AnnotatingAnnotation
+  @Retention(RetentionPolicy.RUNTIME)
+  private @interface AnnotatedAnnotation1 {}
+
+  @AnnotatingAnnotation
+  @Retention(RetentionPolicy.RUNTIME)
+  private @interface AnnotatedAnnotation2 {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  private @interface NotAnnotatedAnnotation {}
+
+  @AnnotatedAnnotation1
+  @NotAnnotatedAnnotation
+  @AnnotatedAnnotation2
+  private static final class AnnotatedClass {}
+
+  @Test
+  public void getAnnotatedAnnotations() {
+    TypeElement element = elements.getTypeElement(AnnotatedClass.class.getCanonicalName());
+
+    // Test Class API
+    getAnnotatedAnnotationsAsserts(
+        AnnotationMirrors.getAnnotatedAnnotations(element, AnnotatingAnnotation.class));
+
+    // Test String API
+    String annotatingAnnotationName = AnnotatingAnnotation.class.getCanonicalName();
+    getAnnotatedAnnotationsAsserts(
+        AnnotationMirrors.getAnnotatedAnnotations(element, annotatingAnnotationName));
+
+    // Test TypeElement API
+    TypeElement annotatingAnnotationElement = elements.getTypeElement(annotatingAnnotationName);
+    getAnnotatedAnnotationsAsserts(
+        AnnotationMirrors.getAnnotatedAnnotations(element, annotatingAnnotationElement));
+  }
+
+  private void getAnnotatedAnnotationsAsserts(
+      ImmutableSet<? extends AnnotationMirror> annotatedAnnotations) {
+    assertThat(annotatedAnnotations)
+        .comparingElementsUsing(
+            Correspondence.transforming(
+                (AnnotationMirror a) -> MoreTypes.asTypeElement(a.getAnnotationType()), "has type"))
+        .containsExactly(
+            elements.getTypeElement(AnnotatedAnnotation1.class.getCanonicalName()),
+            elements.getTypeElement(AnnotatedAnnotation2.class.getCanonicalName()));
+  }
 }
diff --git a/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java
index 1c816c1..f942652 100644
--- a/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java
+++ b/common/src/test/java/com/google/auto/common/GeneratedAnnotationsTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
 import static org.junit.Assume.assumeTrue;
 
 import com.google.common.collect.ImmutableList;
@@ -31,6 +32,7 @@
 import java.lang.reflect.Method;
 import java.net.URI;
 import java.nio.file.Files;
+import java.util.Objects;
 import java.util.Set;
 import javax.annotation.processing.AbstractProcessor;
 import javax.annotation.processing.RoundEnvironment;
@@ -44,6 +46,7 @@
 import javax.tools.StandardJavaFileManager;
 import javax.tools.StandardLocation;
 import javax.tools.ToolProvider;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -99,12 +102,12 @@
    * Run {@link TestProcessor} in a compilation with the given {@code options}, and prevent the
    * compilation from accessing classes with the qualified names in {@code maskFromClasspath}.
    */
-  private String runProcessor(ImmutableList<String> options, String packageToMask)
+  private String runProcessor(ImmutableList<String> options, @Nullable String packageToMask)
       throws IOException {
     File tempDir = temporaryFolder.newFolder();
     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
     StandardJavaFileManager standardFileManager =
-        compiler.getStandardFileManager(/* diagnostics= */ null, /* locale= */ null, UTF_8);
+        compiler.getStandardFileManager(/* diagnosticListener= */ null, /* locale= */ null, UTF_8);
     standardFileManager.setLocation(StandardLocation.CLASS_OUTPUT, ImmutableList.of(tempDir));
     StandardJavaFileManager proxyFileManager =
         Reflection.newProxy(
@@ -142,18 +145,20 @@
    */
   private static class FileManagerInvocationHandler implements InvocationHandler {
     private final StandardJavaFileManager fileManager;
-    private final String packageToMask;
+    private final @Nullable String packageToMask;
 
-    FileManagerInvocationHandler(StandardJavaFileManager fileManager, String packageToMask) {
+    FileManagerInvocationHandler(
+        StandardJavaFileManager fileManager, @Nullable String packageToMask) {
       this.fileManager = fileManager;
       this.packageToMask = packageToMask;
     }
 
     @Override
-    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+    public Object invoke(Object proxy, Method method, @Nullable Object @Nullable [] args)
+        throws Throwable {
       if (method.getName().equals("list")) {
-        String packageName = (String) args[1];
-        if (packageName.equals(packageToMask)) {
+        String packageName = (String) requireNonNull(args)[1];
+        if (Objects.equals(packageName, packageToMask)) {
           return ImmutableList.of();
         }
       }
@@ -187,8 +192,7 @@
     // An alternative would be to delete this test method. JDK8 always has
     // javax.annotation.Generated so it isn't really meaningful to test it without.
     ImmutableList<String> options = ImmutableList.of("-source", "8", "-target", "8");
-    String generated =
-        runProcessor(options, "javax.annotation");
+    String generated = runProcessor(options, "javax.annotation");
     assertThat(generated).doesNotContain(JAVAX_ANNOTATION_GENERATED);
     assertThat(generated).doesNotContain(JAVAX_ANNOTATION_PROCESSING_GENERATED);
   }
diff --git a/common/src/test/java/com/google/auto/common/MoreElementsTest.java b/common/src/test/java/com/google/auto/common/MoreElementsTest.java
index 95043cf..b98b79b 100644
--- a/common/src/test/java/com/google/auto/common/MoreElementsTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreElementsTest.java
@@ -18,6 +18,7 @@
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
+import static java.util.Objects.requireNonNull;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -58,13 +59,14 @@
   @Rule public CompilationRule compilation = new CompilationRule();
   @Rule public Expect expect = Expect.create();
 
+  private Elements elements;
   private PackageElement javaLangPackageElement;
   private TypeElement objectElement;
   private TypeElement stringElement;
 
   @Before
   public void initializeTestElements() {
-    Elements elements = compilation.getElements();
+    this.elements = compilation.getElements();
     this.javaLangPackageElement = elements.getPackageElement("java.lang");
     this.objectElement = elements.getTypeElement(Object.class.getCanonicalName());
     this.stringElement = elements.getTypeElement(String.class.getCanonicalName());
@@ -80,8 +82,7 @@
 
   @Test
   public void asPackage() {
-    assertThat(MoreElements.asPackage(javaLangPackageElement))
-        .isEqualTo(javaLangPackageElement);
+    assertThat(MoreElements.asPackage(javaLangPackageElement)).isEqualTo(javaLangPackageElement);
   }
 
   @Test
@@ -89,19 +90,20 @@
     try {
       MoreElements.asPackage(stringElement);
       fail();
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
-  @Test public void asTypeElement() {
-    Element typeElement =
-        compilation.getElements().getTypeElement(String.class.getCanonicalName());
+  @Test
+  public void asTypeElement() {
+    Element typeElement = elements.getTypeElement(String.class.getCanonicalName());
     assertTrue(MoreElements.isType(typeElement));
     assertThat(MoreElements.asType(typeElement)).isEqualTo(typeElement);
   }
 
-  @Test public void asTypeElement_notATypeElement() {
-    TypeElement typeElement =
-        compilation.getElements().getTypeElement(String.class.getCanonicalName());
+  @Test
+  public void asTypeElement_notATypeElement() {
+    TypeElement typeElement = elements.getTypeElement(String.class.getCanonicalName());
     for (ExecutableElement e : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
       assertFalse(MoreElements.isType(e));
       try {
@@ -143,7 +145,8 @@
     try {
       MoreElements.asType(javaLangPackageElement);
       fail();
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
   @Test
@@ -158,7 +161,8 @@
     try {
       MoreElements.asVariable(javaLangPackageElement);
       fail();
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
   @Test
@@ -166,8 +170,8 @@
     for (Element methodElement : ElementFilter.methodsIn(stringElement.getEnclosedElements())) {
       assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement);
     }
-    for (Element methodElement
-        : ElementFilter.constructorsIn(stringElement.getEnclosedElements())) {
+    for (Element methodElement :
+        ElementFilter.constructorsIn(stringElement.getEnclosedElements())) {
       assertThat(MoreElements.asExecutable(methodElement)).isEqualTo(methodElement);
     }
   }
@@ -177,7 +181,8 @@
     try {
       MoreElements.asExecutable(javaLangPackageElement);
       fail();
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
   @Retention(RetentionPolicy.RUNTIME)
@@ -190,39 +195,90 @@
   @Test
   public void isAnnotationPresent() {
     TypeElement annotatedAnnotationElement =
-        compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
-    assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class))
-        .isTrue();
-    assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class))
-        .isTrue();
-    assertThat(MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class))
-        .isFalse();
+        elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
+
+    // Test Class API
+    isAnnotationPresentAsserts(
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, Documented.class),
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, InnerAnnotation.class),
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, SuppressWarnings.class));
+
+    // Test String API
+    String documentedName = Documented.class.getCanonicalName();
+    String innerAnnotationName = InnerAnnotation.class.getCanonicalName();
+    String suppressWarningsName = SuppressWarnings.class.getCanonicalName();
+    isAnnotationPresentAsserts(
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, documentedName),
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, innerAnnotationName),
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, suppressWarningsName));
+
+    // Test TypeElement API
+    TypeElement documentedElement = elements.getTypeElement(documentedName);
+    TypeElement innerAnnotationElement = elements.getTypeElement(innerAnnotationName);
+    TypeElement suppressWarningsElement = elements.getTypeElement(suppressWarningsName);
+    isAnnotationPresentAsserts(
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, documentedElement),
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, innerAnnotationElement),
+        MoreElements.isAnnotationPresent(annotatedAnnotationElement, suppressWarningsElement));
+  }
+
+  private void isAnnotationPresentAsserts(
+      boolean isDocumentedPresent,
+      boolean isInnerAnnotationPresent,
+      boolean isSuppressWarningsPresent) {
+    assertThat(isDocumentedPresent).isTrue();
+    assertThat(isInnerAnnotationPresent).isTrue();
+    assertThat(isSuppressWarningsPresent).isFalse();
   }
 
   @Test
   public void getAnnotationMirror() {
     TypeElement element =
-        compilation.getElements().getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
+        elements.getTypeElement(AnnotatedAnnotation.class.getCanonicalName());
 
-    Optional<AnnotationMirror> documented =
-        MoreElements.getAnnotationMirror(element, Documented.class);
-    Optional<AnnotationMirror> innerAnnotation =
-        MoreElements.getAnnotationMirror(element, InnerAnnotation.class);
-    Optional<AnnotationMirror> suppressWarnings =
-        MoreElements.getAnnotationMirror(element, SuppressWarnings.class);
+    // Test Class API
+    getAnnotationMirrorAsserts(
+        MoreElements.getAnnotationMirror(element, Documented.class),
+        MoreElements.getAnnotationMirror(element, InnerAnnotation.class),
+        MoreElements.getAnnotationMirror(element, SuppressWarnings.class));
 
+    // Test String API
+    String documentedName = Documented.class.getCanonicalName();
+    String innerAnnotationName = InnerAnnotation.class.getCanonicalName();
+    String suppressWarningsName = SuppressWarnings.class.getCanonicalName();
+    getAnnotationMirrorAsserts(
+        MoreElements.getAnnotationMirror(element, documentedName),
+        MoreElements.getAnnotationMirror(element, innerAnnotationName),
+        MoreElements.getAnnotationMirror(element, suppressWarningsName));
+
+    // Test TypeElement API
+    TypeElement documentedElement = elements.getTypeElement(documentedName);
+    TypeElement innerAnnotationElement = elements.getTypeElement(innerAnnotationName);
+    TypeElement suppressWarningsElement = elements.getTypeElement(suppressWarningsName);
+    getAnnotationMirrorAsserts(
+        MoreElements.getAnnotationMirror(element, documentedElement),
+        MoreElements.getAnnotationMirror(element, innerAnnotationElement),
+        MoreElements.getAnnotationMirror(element, suppressWarningsElement));
+  }
+
+  private void getAnnotationMirrorAsserts(
+      Optional<AnnotationMirror> documented,
+      Optional<AnnotationMirror> innerAnnotation,
+      Optional<AnnotationMirror> suppressWarnings) {
     expect.that(documented).isPresent();
     expect.that(innerAnnotation).isPresent();
     expect.that(suppressWarnings).isAbsent();
 
     Element annotationElement = documented.get().getAnnotationType().asElement();
     expect.that(MoreElements.isType(annotationElement)).isTrue();
-    expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString())
+    expect
+        .that(MoreElements.asType(annotationElement).getQualifiedName().toString())
         .isEqualTo(Documented.class.getCanonicalName());
 
     annotationElement = innerAnnotation.get().getAnnotationType().asElement();
     expect.that(MoreElements.isType(annotationElement)).isTrue();
-    expect.that(MoreElements.asType(annotationElement).getQualifiedName().toString())
+    expect
+        .that(MoreElements.asType(annotationElement).getQualifiedName().toString())
         .isEqualTo(InnerAnnotation.class.getCanonicalName());
   }
 
@@ -231,6 +287,7 @@
 
     abstract String foo();
 
+    @SuppressWarnings("unused")
     private void privateMethod() {}
   }
 
@@ -259,7 +316,6 @@
 
   @Test
   public void getLocalAndInheritedMethods_Old() {
-    Elements elements = compilation.getElements();
     Types types = compilation.getTypes();
     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
@@ -270,19 +326,20 @@
     Set<ExecutableElement> objectMethods = visibleMethodsFromObject();
     assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
     Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
-    assertThat(nonObjectMethods).containsExactly(
+    assertThat(nonObjectMethods)
+        .containsExactly(
             getMethod(ParentInterface.class, "bar", longMirror),
             getMethod(ParentClass.class, "foo"),
             getMethod(Child.class, "bar"),
             getMethod(Child.class, "baz"),
             getMethod(Child.class, "buh", intMirror),
             getMethod(Child.class, "buh", intMirror, intMirror))
-        .inOrder();;
+        .inOrder();
+    ;
   }
 
   @Test
   public void getLocalAndInheritedMethods() {
-    Elements elements = compilation.getElements();
     Types types = compilation.getTypes();
     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
@@ -293,7 +350,8 @@
     Set<ExecutableElement> objectMethods = visibleMethodsFromObject();
     assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
     Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
-    assertThat(nonObjectMethods).containsExactly(
+    assertThat(nonObjectMethods)
+        .containsExactly(
             getMethod(ParentInterface.class, "bar", longMirror),
             getMethod(ParentClass.class, "foo"),
             getMethod(Child.class, "bar"),
@@ -305,7 +363,6 @@
 
   @Test
   public void getAllMethods() {
-    Elements elements = compilation.getElements();
     Types types = compilation.getTypes();
     TypeMirror intMirror = types.getPrimitiveType(TypeKind.INT);
     TypeMirror longMirror = types.getPrimitiveType(TypeKind.LONG);
@@ -316,7 +373,8 @@
     Set<ExecutableElement> objectMethods = allMethodsFromObject();
     assertThat(childTypeMethods).containsAtLeastElementsIn(objectMethods);
     Set<ExecutableElement> nonObjectMethods = Sets.difference(childTypeMethods, objectMethods);
-    assertThat(nonObjectMethods).containsExactly(
+    assertThat(nonObjectMethods)
+        .containsExactly(
             getMethod(ParentInterface.class, "staticMethod"),
             getMethod(ParentInterface.class, "bar", longMirror),
             getMethod(ParentClass.class, "staticMethod"),
@@ -355,10 +413,9 @@
   // Example from https://github.com/williamlian/daggerbug
   @Test
   public void getLocalAndInheritedMethods_DaggerBug() {
-    Elements elementUtils = compilation.getElements();
-    TypeElement main = elementUtils.getTypeElement(Main.ParentComponent.class.getCanonicalName());
-    Set<ExecutableElement> methods = MoreElements.getLocalAndInheritedMethods(
-        main, compilation.getTypes(), elementUtils);
+    TypeElement main = elements.getTypeElement(Main.ParentComponent.class.getCanonicalName());
+    Set<ExecutableElement> methods =
+        MoreElements.getLocalAndInheritedMethods(main, compilation.getTypes(), elements);
     assertThat(methods).hasSize(1);
     ExecutableElement method = methods.iterator().next();
     assertThat(method.getSimpleName().toString()).isEqualTo("injectable");
@@ -404,7 +461,7 @@
   }
 
   private ExecutableElement getMethod(Class<?> c, String methodName, TypeMirror... parameterTypes) {
-    TypeElement type = compilation.getElements().getTypeElement(c.getCanonicalName());
+    TypeElement type = elements.getTypeElement(c.getCanonicalName());
     Types types = compilation.getTypes();
     ExecutableElement found = null;
     for (ExecutableElement method : ElementFilter.methodsIn(type.getEnclosedElements())) {
@@ -423,7 +480,7 @@
       }
     }
     assertWithMessage(methodName + Arrays.toString(parameterTypes)).that(found).isNotNull();
-    return found;
+    return requireNonNull(found);
   }
 
   private abstract static class AbstractAbstractList extends AbstractList<String> {}
@@ -458,8 +515,6 @@
   // are implemented in AbstractList.
   @Test
   public void getLocalAndInheritedMethods_AbstractList() {
-    Elements elements = compilation.getElements();
-
     TypeElement abstractType =
         elements.getTypeElement(AbstractAbstractList.class.getCanonicalName());
     Set<ExecutableElement> abstractTypeMethods =
diff --git a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
index 05a0a11..7cd7865 100644
--- a/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreTypesIsTypeOfTest.java
@@ -43,13 +43,15 @@
 
   private Elements elements;
 
-  @Before public void setUp() {
+  @Before
+  public void setUp() {
     this.elements = compilationRule.getElements();
   }
 
   private interface TestType {}
 
-  @Test public void isTypeOf_DeclaredType() {
+  @Test
+  public void isTypeOf_declaredType() {
     assertTrue(MoreTypes.isType(typeElementFor(TestType.class).asType()));
     assertWithMessage("mirror represents the TestType")
         .that(MoreTypes.isTypeOf(TestType.class, typeElementFor(TestType.class).asType()))
@@ -63,7 +65,8 @@
     String[] array();
   }
 
-  @Test public void isTypeOf_ArrayType() {
+  @Test
+  public void isTypeOf_arrayType() {
     assertTrue(MoreTypes.isType(typeElementFor(ArrayType.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(ArrayType.class));
     assertWithMessage("array mirror represents an array Class object")
@@ -75,7 +78,8 @@
     boolean method();
   }
 
-  @Test public void isTypeOf_PrimitiveBoolean() {
+  @Test
+  public void isTypeOf_primitiveBoolean() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveBoolean.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveBoolean.class));
     assertWithMessage("mirror of a boolean").that(MoreTypes.isTypeOf(Boolean.TYPE, type)).isTrue();
@@ -85,7 +89,8 @@
     byte method();
   }
 
-  @Test public void isTypeOf_PrimitiveByte() {
+  @Test
+  public void isTypeOf_primitiveByte() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveByte.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveByte.class));
     assertWithMessage("mirror of a byte").that(MoreTypes.isTypeOf(Byte.TYPE, type)).isTrue();
@@ -95,7 +100,8 @@
     char method();
   }
 
-  @Test public void isTypeOf_PrimitiveChar() {
+  @Test
+  public void isTypeOf_primitiveChar() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveChar.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveChar.class));
     assertWithMessage("mirror of a char").that(MoreTypes.isTypeOf(Character.TYPE, type)).isTrue();
@@ -105,7 +111,8 @@
     double method();
   }
 
-  @Test public void isTypeOf_PrimitiveDouble() {
+  @Test
+  public void isTypeOf_primitiveDouble() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveDouble.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveDouble.class));
     assertWithMessage("mirror of a double").that(MoreTypes.isTypeOf(Double.TYPE, type)).isTrue();
@@ -115,7 +122,8 @@
     float method();
   }
 
-  @Test public void isTypeOf_PrimitiveFloat() {
+  @Test
+  public void isTypeOf_primitiveFloat() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveFloat.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveFloat.class));
     assertWithMessage("mirror of a float").that(MoreTypes.isTypeOf(Float.TYPE, type)).isTrue();
@@ -125,7 +133,8 @@
     int method();
   }
 
-  @Test public void isTypeOf_PrimitiveInt() {
+  @Test
+  public void isTypeOf_primitiveInt() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveInt.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveInt.class));
     assertWithMessage("mirror of a int").that(MoreTypes.isTypeOf(Integer.TYPE, type)).isTrue();
@@ -135,7 +144,8 @@
     long method();
   }
 
-  @Test public void isTypeOf_PrimitiveLong() {
+  @Test
+  public void isTypeOf_primitiveLong() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveLong.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveLong.class));
     assertWithMessage("mirror of a long").that(MoreTypes.isTypeOf(Long.TYPE, type)).isTrue();
@@ -145,7 +155,8 @@
     short method();
   }
 
-  @Test public void isTypeOf_PrimitiveShort() {
+  @Test
+  public void isTypeOf_primitiveShort() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveShort.class).asType()));
     TypeMirror type = extractReturnTypeFromHolder(typeElementFor(PrimitiveShort.class));
     assertWithMessage("mirror of a short").that(MoreTypes.isTypeOf(Short.TYPE, type)).isTrue();
@@ -155,7 +166,8 @@
     void method();
   }
 
-  @Test public void isTypeOf_void() {
+  @Test
+  public void isTypeOf_primitiveVoid() {
     assertTrue(MoreTypes.isType(typeElementFor(PrimitiveVoid.class).asType()));
     TypeMirror primitive = extractReturnTypeFromHolder(typeElementFor(PrimitiveVoid.class));
     assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.TYPE, primitive)).isTrue();
@@ -165,21 +177,25 @@
     Void method();
   }
 
-  @Test public void isTypeOf_Void() {
+  @Test
+  public void isTypeOf_declaredVoid() {
     assertTrue(MoreTypes.isType(typeElementFor(DeclaredVoid.class).asType()));
     TypeMirror declared = extractReturnTypeFromHolder(typeElementFor(DeclaredVoid.class));
     assertWithMessage("mirror of a void").that(MoreTypes.isTypeOf(Void.class, declared)).isTrue();
   }
 
-  @Test public void isTypeOf_fail() {
-    assertFalse(MoreTypes.isType(
-        getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType()));
+  @Test
+  public void isTypeOf_fail() {
+    assertFalse(
+        MoreTypes.isType(
+            getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType()));
     TypeMirror method =
         getOnlyElement(typeElementFor(DeclaredVoid.class).getEnclosedElements()).asType();
     try {
       MoreTypes.isTypeOf(String.class, method);
       fail();
-    } catch (IllegalArgumentException expected) {}
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
   // Utility methods for this test.
diff --git a/common/src/test/java/com/google/auto/common/MoreTypesTest.java b/common/src/test/java/com/google/auto/common/MoreTypesTest.java
index 3cd360d..b8e84e0 100644
--- a/common/src/test/java/com/google/auto/common/MoreTypesTest.java
+++ b/common/src/test/java/com/google/auto/common/MoreTypesTest.java
@@ -16,11 +16,12 @@
 package com.google.auto.common;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 import static javax.lang.model.type.TypeKind.NONE;
 import static javax.lang.model.type.TypeKind.VOID;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
@@ -49,6 +50,7 @@
 import javax.lang.model.util.ElementFilter;
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -90,48 +92,51 @@
     DeclaredType containerOfString = types.getDeclaredType(container, stringType);
     TypeMirror containedInObject = types.asMemberOf(containerOfObject, contained);
     TypeMirror containedInString = types.asMemberOf(containerOfString, contained);
-    EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
-        .addEquivalenceGroup(types.getNullType())
-        .addEquivalenceGroup(types.getNoType(NONE))
-        .addEquivalenceGroup(types.getNoType(VOID))
-        .addEquivalenceGroup(objectType)
-        .addEquivalenceGroup(stringType)
-        .addEquivalenceGroup(containedInObject)
-        .addEquivalenceGroup(containedInString)
-        .addEquivalenceGroup(funkyBounds.asType())
-        .addEquivalenceGroup(funkyBounds2.asType())
-        .addEquivalenceGroup(funkierBounds.asType())
-        .addEquivalenceGroup(funkyBoundsVar, funkyBounds2Var)
-        .addEquivalenceGroup(funkierBoundsVar)
-        // Enum<E extends Enum<E>>
-        .addEquivalenceGroup(enumElement.asType())
-        // Map<K, V>
-        .addEquivalenceGroup(mapType)
-        .addEquivalenceGroup(mapOfObjectToObjectType)
-        // Map<?, ?>
-        .addEquivalenceGroup(types.getDeclaredType(mapElement, wildcard, wildcard))
-        // Map
-        .addEquivalenceGroup(types.erasure(mapType), types.erasure(mapOfObjectToObjectType))
-        .addEquivalenceGroup(types.getDeclaredType(mapElement, objectType, stringType))
-        .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, objectType))
-        .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, stringType))
-        .addEquivalenceGroup(setOfSetOfObject)
-        .addEquivalenceGroup(setOfSetOfString)
-        .addEquivalenceGroup(setOfSetOfSetOfObject)
-        .addEquivalenceGroup(setOfSetOfSetOfString)
-        .addEquivalenceGroup(wildcard)
-        // ? extends Object
-        .addEquivalenceGroup(types.getWildcardType(objectType, null))
-        // ? extends String
-        .addEquivalenceGroup(types.getWildcardType(stringType, null))
-        // ? super String
-        .addEquivalenceGroup(types.getWildcardType(null, stringType))
-        // Map<String, Map<String, Set<Object>>>
-        .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType,
-            types.getDeclaredType(mapElement, stringType,
-                types.getDeclaredType(setElement, objectType))))
-        .addEquivalenceGroup(FAKE_ERROR_TYPE)
-        ;
+    EquivalenceTester<TypeMirror> tester =
+        EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
+            .addEquivalenceGroup(types.getNullType())
+            .addEquivalenceGroup(types.getNoType(NONE))
+            .addEquivalenceGroup(types.getNoType(VOID))
+            .addEquivalenceGroup(objectType)
+            .addEquivalenceGroup(stringType)
+            .addEquivalenceGroup(containedInObject)
+            .addEquivalenceGroup(containedInString)
+            .addEquivalenceGroup(funkyBounds.asType())
+            .addEquivalenceGroup(funkyBounds2.asType())
+            .addEquivalenceGroup(funkierBounds.asType())
+            .addEquivalenceGroup(funkyBoundsVar, funkyBounds2Var)
+            .addEquivalenceGroup(funkierBoundsVar)
+            // Enum<E extends Enum<E>>
+            .addEquivalenceGroup(enumElement.asType())
+            // Map<K, V>
+            .addEquivalenceGroup(mapType)
+            .addEquivalenceGroup(mapOfObjectToObjectType)
+            // Map<?, ?>
+            .addEquivalenceGroup(types.getDeclaredType(mapElement, wildcard, wildcard))
+            // Map
+            .addEquivalenceGroup(types.erasure(mapType), types.erasure(mapOfObjectToObjectType))
+            .addEquivalenceGroup(types.getDeclaredType(mapElement, objectType, stringType))
+            .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, objectType))
+            .addEquivalenceGroup(types.getDeclaredType(mapElement, stringType, stringType))
+            .addEquivalenceGroup(setOfSetOfObject)
+            .addEquivalenceGroup(setOfSetOfString)
+            .addEquivalenceGroup(setOfSetOfSetOfObject)
+            .addEquivalenceGroup(setOfSetOfSetOfString)
+            .addEquivalenceGroup(wildcard)
+            // ? extends Object
+            .addEquivalenceGroup(types.getWildcardType(objectType, null))
+            // ? extends String
+            .addEquivalenceGroup(types.getWildcardType(stringType, null))
+            // ? super String
+            .addEquivalenceGroup(types.getWildcardType(null, stringType))
+            // Map<String, Map<String, Set<Object>>>
+            .addEquivalenceGroup(
+                types.getDeclaredType(
+                    mapElement,
+                    stringType,
+                    types.getDeclaredType(
+                        mapElement, stringType, types.getDeclaredType(setElement, objectType))))
+            .addEquivalenceGroup(FAKE_ERROR_TYPE);
 
     for (TypeKind kind : TypeKind.values()) {
       if (kind.isPrimitive()) {
@@ -144,20 +149,18 @@
       }
     }
 
-    ImmutableSet<Class<?>> testClasses = ImmutableSet.of(
-        ExecutableElementsGroupA.class,
-        ExecutableElementsGroupB.class,
-        ExecutableElementsGroupC.class,
-        ExecutableElementsGroupD.class,
-        ExecutableElementsGroupE.class);
+    ImmutableSet<Class<?>> testClasses =
+        ImmutableSet.of(
+            ExecutableElementsGroupA.class,
+            ExecutableElementsGroupB.class,
+            ExecutableElementsGroupC.class,
+            ExecutableElementsGroupD.class,
+            ExecutableElementsGroupE.class);
     for (Class<?> testClass : testClasses) {
-      ImmutableList<TypeMirror> equivalenceGroup = FluentIterable.from(
-          elements.getTypeElement(testClass.getCanonicalName()).getEnclosedElements())
-              .transform(new Function<Element, TypeMirror>() {
-                @Override public TypeMirror apply(Element input) {
-                  return input.asType();
-                }
-              })
+      ImmutableList<TypeMirror> equivalenceGroup =
+          FluentIterable.from(
+                  elements.getTypeElement(testClass.getCanonicalName()).getEnclosedElements())
+              .transform(Element::asType)
               .toList();
       tester.addEquivalenceGroup(equivalenceGroup);
     }
@@ -168,35 +171,45 @@
   @SuppressWarnings("unused")
   private static final class ExecutableElementsGroupA {
     ExecutableElementsGroupA() {}
+
     void a() {}
+
     public static void b() {}
   }
 
   @SuppressWarnings("unused")
   private static final class ExecutableElementsGroupB {
     ExecutableElementsGroupB(String s) {}
+
     void a(String s) {}
+
     public static void b(String s) {}
   }
 
   @SuppressWarnings("unused")
   private static final class ExecutableElementsGroupC {
     ExecutableElementsGroupC() throws Exception {}
+
     void a() throws Exception {}
+
     public static void b() throws Exception {}
   }
 
   @SuppressWarnings("unused")
   private static final class ExecutableElementsGroupD {
     ExecutableElementsGroupD() throws RuntimeException {}
+
     void a() throws RuntimeException {}
+
     public static void b() throws RuntimeException {}
   }
 
   @SuppressWarnings("unused")
   private static final class ExecutableElementsGroupE {
     <T> ExecutableElementsGroupE() {}
+
     <T> void a() {}
+
     public static <T> void b() {}
   }
 
@@ -214,53 +227,44 @@
   @SuppressWarnings("unused")
   private static final class FunkierBounds<T extends Number & Comparable<T> & Cloneable> {}
 
-  @Test public void testReferencedTypes() {
+  @Test
+  public void testReferencedTypes() {
     Elements elements = compilationRule.getElements();
-    TypeElement testDataElement = elements
-        .getTypeElement(ReferencedTypesTestData.class.getCanonicalName());
+    TypeElement testDataElement =
+        elements.getTypeElement(ReferencedTypesTestData.class.getCanonicalName());
     ImmutableMap<String, VariableElement> fieldIndex =
         FluentIterable.from(ElementFilter.fieldsIn(testDataElement.getEnclosedElements()))
-            .uniqueIndex(new Function<VariableElement, String>() {
-              @Override public String apply(VariableElement input) {
-                return input.getSimpleName().toString();
-              }
-            });
+            .uniqueIndex(input -> input.getSimpleName().toString());
 
-    TypeElement objectElement =
-        elements.getTypeElement(Object.class.getCanonicalName());
-    TypeElement stringElement =
-        elements.getTypeElement(String.class.getCanonicalName());
-    TypeElement integerElement =
-        elements.getTypeElement(Integer.class.getCanonicalName());
-    TypeElement setElement =
-        elements.getTypeElement(Set.class.getCanonicalName());
-    TypeElement mapElement =
-        elements.getTypeElement(Map.class.getCanonicalName());
+    TypeElement objectElement = elements.getTypeElement(Object.class.getCanonicalName());
+    TypeElement stringElement = elements.getTypeElement(String.class.getCanonicalName());
+    TypeElement integerElement = elements.getTypeElement(Integer.class.getCanonicalName());
+    TypeElement setElement = elements.getTypeElement(Set.class.getCanonicalName());
+    TypeElement mapElement = elements.getTypeElement(Map.class.getCanonicalName());
     TypeElement charSequenceElement =
         elements.getTypeElement(CharSequence.class.getCanonicalName());
 
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f1").asType()))
-        .containsExactly(objectElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f2").asType()))
-        .containsExactly(setElement, stringElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f3").asType()))
+    assertThat(referencedTypes(fieldIndex, "f1")).containsExactly(objectElement);
+    assertThat(referencedTypes(fieldIndex, "f2")).containsExactly(setElement, stringElement);
+    assertThat(referencedTypes(fieldIndex, "f3"))
         .containsExactly(mapElement, stringElement, objectElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f4").asType()))
-        .containsExactly(integerElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f5").asType()))
-        .containsExactly(setElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f6").asType()))
-        .containsExactly(setElement, charSequenceElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f7").asType()))
+    assertThat(referencedTypes(fieldIndex, "f4")).containsExactly(integerElement);
+    assertThat(referencedTypes(fieldIndex, "f5")).containsExactly(setElement);
+    assertThat(referencedTypes(fieldIndex, "f6")).containsExactly(setElement, charSequenceElement);
+    assertThat(referencedTypes(fieldIndex, "f7"))
         .containsExactly(mapElement, stringElement, setElement, charSequenceElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f8").asType()))
-        .containsExactly(stringElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f9").asType()))
-        .containsExactly(stringElement);
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f10").asType())).isEmpty();
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f11").asType())).isEmpty();
-    assertThat(MoreTypes.referencedTypes(fieldIndex.get("f12").asType()))
-        .containsExactly(setElement, stringElement);
+    assertThat(referencedTypes(fieldIndex, "f8")).containsExactly(stringElement);
+    assertThat(referencedTypes(fieldIndex, "f9")).containsExactly(stringElement);
+    assertThat(referencedTypes(fieldIndex, "f10")).isEmpty();
+    assertThat(referencedTypes(fieldIndex, "f11")).isEmpty();
+    assertThat(referencedTypes(fieldIndex, "f12")).containsExactly(setElement, stringElement);
+  }
+
+  private static ImmutableSet<TypeElement> referencedTypes(
+      ImmutableMap<String, VariableElement> fieldIndex, String fieldName) {
+    VariableElement field = fieldIndex.get(fieldName);
+    requireNonNull(field, fieldName);
+    return MoreTypes.referencedTypes(field.asType());
   }
 
   @SuppressWarnings("unused") // types used in compiler tests
@@ -280,20 +284,23 @@
   }
 
   private static class Parent<T> {}
+
   private static class ChildA extends Parent<Number> {}
+
   private static class ChildB extends Parent<String> {}
+
   private static class GenericChild<T> extends Parent<T> {}
+
   private interface InterfaceType {}
 
   @Test
   public void asElement_throws() {
-    TypeMirror javaDotLang =
-        compilationRule.getElements().getPackageElement("java.lang").asType();
+    TypeMirror javaDotLang = compilationRule.getElements().getPackageElement("java.lang").asType();
     try {
       MoreTypes.asElement(javaDotLang);
       fail();
-    } catch (IllegalArgumentException expected) {}
-
+    } catch (IllegalArgumentException expected) {
+    }
   }
 
   @Test
@@ -301,8 +308,9 @@
     Elements elements = compilationRule.getElements();
     TypeElement stringElement = elements.getTypeElement("java.lang.String");
     assertThat(MoreTypes.asElement(stringElement.asType())).isEqualTo(stringElement);
-    TypeParameterElement setParameterElement = Iterables.getOnlyElement(
-        compilationRule.getElements().getTypeElement("java.util.Set").getTypeParameters());
+    TypeParameterElement setParameterElement =
+        Iterables.getOnlyElement(
+            compilationRule.getElements().getTypeElement("java.util.Set").getTypeParameters());
     assertThat(MoreTypes.asElement(setParameterElement.asType())).isEqualTo(setParameterElement);
     // we don't test error types because those are very hard to get predictably
   }
@@ -320,8 +328,7 @@
     TypeElement genericChild = elements.getTypeElement(GenericChild.class.getCanonicalName());
     TypeMirror genericChildOfNumber = types.getDeclaredType(genericChild, numberType);
     TypeMirror genericChildOfInteger = types.getDeclaredType(genericChild, integerType);
-    TypeMirror objectType =
-        elements.getTypeElement(Object.class.getCanonicalName()).asType();
+    TypeMirror objectType = elements.getTypeElement(Object.class.getCanonicalName()).asType();
     TypeMirror interfaceType =
         elements.getTypeElement(InterfaceType.class.getCanonicalName()).asType();
 
@@ -343,18 +350,20 @@
     Optional<DeclaredType> parentOfGenericChildOfInteger =
         MoreTypes.nonObjectSuperclass(types, elements, (DeclaredType) genericChildOfInteger);
 
-    EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
-          .addEquivalenceGroup(parentOfChildA.get(),
-              types.getDeclaredType(parent, numberType),
-              parentOfGenericChildOfNumber.get())
-          .addEquivalenceGroup(parentOfChildB.get(), types.getDeclaredType(parent, stringType))
-          .addEquivalenceGroup(parentOfGenericChild.get(), parent.asType())
-          .addEquivalenceGroup(parentOfGenericChildOfInteger.get(),
-              types.getDeclaredType(parent, integerType));
+    EquivalenceTester<TypeMirror> tester =
+        EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
+            .addEquivalenceGroup(
+                parentOfChildA.get(),
+                types.getDeclaredType(parent, numberType),
+                parentOfGenericChildOfNumber.get())
+            .addEquivalenceGroup(parentOfChildB.get(), types.getDeclaredType(parent, stringType))
+            .addEquivalenceGroup(parentOfGenericChild.get(), parent.asType())
+            .addEquivalenceGroup(
+                parentOfGenericChildOfInteger.get(), types.getDeclaredType(parent, integerType));
 
     tester.test();
   }
-  
+
   @Test
   public void testAsMemberOf_variableElement() {
     Types types = compilationRule.getTypes();
@@ -364,11 +373,13 @@
     TypeMirror integerType = elements.getTypeElement(Integer.class.getCanonicalName()).asType();
 
     TypeElement paramsElement = elements.getTypeElement(Params.class.getCanonicalName());
-    VariableElement tParam = Iterables.getOnlyElement(Iterables.getOnlyElement(
-        ElementFilter.methodsIn(paramsElement.getEnclosedElements())).getParameters());
+    VariableElement tParam =
+        Iterables.getOnlyElement(
+            Iterables.getOnlyElement(ElementFilter.methodsIn(paramsElement.getEnclosedElements()))
+                .getParameters());
     VariableElement tField =
-        Iterables.getOnlyElement(ElementFilter.fieldsIn(paramsElement.getEnclosedElements())); 
-    
+        Iterables.getOnlyElement(ElementFilter.fieldsIn(paramsElement.getEnclosedElements()));
+
     DeclaredType numberParams =
         (DeclaredType) elements.getTypeElement(NumberParams.class.getCanonicalName()).asType();
     DeclaredType stringParams =
@@ -376,7 +387,7 @@
     TypeElement genericParams = elements.getTypeElement(GenericParams.class.getCanonicalName());
     DeclaredType genericParamsOfNumber = types.getDeclaredType(genericParams, numberType);
     DeclaredType genericParamsOfInteger = types.getDeclaredType(genericParams, integerType);
-    
+
     TypeMirror fieldOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tField);
     TypeMirror paramOfNumberParams = MoreTypes.asMemberOf(types, numberParams, tParam);
     TypeMirror fieldOfStringParams = MoreTypes.asMemberOf(types, stringParams, tField);
@@ -388,62 +399,76 @@
     TypeMirror paramOfGenericOfInteger =
         MoreTypes.asMemberOf(types, genericParamsOfInteger, tParam);
 
-    EquivalenceTester<TypeMirror> tester = EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
-        .addEquivalenceGroup(fieldOfNumberParams, paramOfNumberParams, fieldOfGenericOfNumber,
-            paramOfGenericOfNumber, numberType)
-        .addEquivalenceGroup(fieldOfStringParams, paramOfStringParams, stringType)
-        .addEquivalenceGroup(fieldOfGenericOfInteger, paramOfGenericOfInteger, integerType);
+    EquivalenceTester<TypeMirror> tester =
+        EquivalenceTester.<TypeMirror>of(MoreTypes.equivalence())
+            .addEquivalenceGroup(
+                fieldOfNumberParams,
+                paramOfNumberParams,
+                fieldOfGenericOfNumber,
+                paramOfGenericOfNumber,
+                numberType)
+            .addEquivalenceGroup(fieldOfStringParams, paramOfStringParams, stringType)
+            .addEquivalenceGroup(fieldOfGenericOfInteger, paramOfGenericOfInteger, integerType);
     tester.test();
   }
-  
+
   private static class Params<T> {
-    @SuppressWarnings("unused") T t;
-    @SuppressWarnings("unused") void add(T t) {}
+    @SuppressWarnings("unused")
+    T t;
+
+    @SuppressWarnings("unused")
+    void add(T t) {}
   }
+
   private static class NumberParams extends Params<Number> {}
+
   private static class StringParams extends Params<String> {}
+
   private static class GenericParams<T> extends Params<T> {}
 
-  private static final ErrorType FAKE_ERROR_TYPE = new ErrorType() {
-    @Override
-    public TypeKind getKind() {
-      return TypeKind.ERROR;
-    }
+  private static final ErrorType FAKE_ERROR_TYPE =
+      new ErrorType() {
+        @Override
+        public TypeKind getKind() {
+          return TypeKind.ERROR;
+        }
 
-    @Override
-    public <R, P> R accept(TypeVisitor<R, P> v, P p) {
-      return v.visitError(this, p);
-    }
+        @Override
+        public <R, P> R accept(TypeVisitor<R, P> v, P p) {
+          return v.visitError(this, p);
+        }
 
-    @Override
-    public List<? extends TypeMirror> getTypeArguments() {
-      return ImmutableList.of();
-    }
+        @Override
+        public ImmutableList<? extends TypeMirror> getTypeArguments() {
+          return ImmutableList.of();
+        }
 
-    @Override
-    public TypeMirror getEnclosingType() {
-      return null;
-    }
+        @Override
+        public @Nullable TypeMirror getEnclosingType() {
+          return null;
+        }
 
-    @Override
-    public Element asElement() {
-      return null;
-    }
+        @Override
+        public @Nullable Element asElement() {
+          return null;
+        }
 
-    // JDK8 Compatibility:
+        @Override
+        public <A extends Annotation> A @Nullable [] getAnnotationsByType(Class<A> annotationType) {
+          return null;
+        }
 
-    public <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType) {
-      return null;
-    }
+        @Override
+        public <A extends Annotation> @Nullable A getAnnotation(Class<A> annotationType) {
+          return null;
+        }
 
-    public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
-      return null;
-    }
-
-    public List<? extends AnnotationMirror> getAnnotationMirrors() {
-      return null;
-    }
-  };
+        @Override
+        @SuppressWarnings("MutableMethodReturnType")
+        public List<? extends AnnotationMirror> getAnnotationMirrors() {
+          return ImmutableList.of();
+        }
+      };
 
   @Test
   public void testIsConversionFromObjectUnchecked_yes() {
@@ -471,6 +496,21 @@
     }
   }
 
+  @Test
+  public void testIsTypeOf() {
+    Types types = compilationRule.getTypes();
+    PrimitiveType intType = types.getPrimitiveType(TypeKind.INT);
+    TypeMirror integerType = types.boxedClass(intType).asType();
+    WildcardType wildcardType = types.getWildcardType(null, null);
+    expect.that(MoreTypes.isTypeOf(int.class, intType)).isTrue();
+    expect.that(MoreTypes.isTypeOf(Integer.class, integerType)).isTrue();
+    expect.that(MoreTypes.isTypeOf(Integer.class, intType)).isFalse();
+    expect.that(MoreTypes.isTypeOf(int.class, integerType)).isFalse();
+    expect.that(MoreTypes.isTypeOf(Integer.class, FAKE_ERROR_TYPE)).isFalse();
+    assertThrows(
+        IllegalArgumentException.class, () -> MoreTypes.isTypeOf(Integer.class, wildcardType));
+  }
+
   // The type of every field here is such that casting to it provokes an "unchecked" warning.
   @SuppressWarnings("unused")
   private static class Unchecked<T> {
diff --git a/common/src/test/java/com/google/auto/common/OverridesTest.java b/common/src/test/java/com/google/auto/common/OverridesTest.java
index afb7976..c5ccc5f 100644
--- a/common/src/test/java/com/google/auto/common/OverridesTest.java
+++ b/common/src/test/java/com/google/auto/common/OverridesTest.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
 import static javax.lang.model.util.ElementFilter.methodsIn;
 
 import com.google.common.base.Converter;
@@ -57,6 +58,7 @@
 import javax.tools.StandardJavaFileManager;
 import javax.tools.StandardLocation;
 import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -103,6 +105,7 @@
 
     abstract void initUtils(OverridesTest test);
   }
+
   private final CompilerType compilerType;
 
   private Types typeUtils;
@@ -126,12 +129,15 @@
   static class TypesForInheritance {
     interface One {
       void m();
+
       void m(String x);
+
       void n();
     }
 
     interface Two {
       void m();
+
       void m(int x);
     }
 
@@ -142,28 +148,50 @@
     static class ChildOfParent extends Parent {}
 
     static class ChildOfOne implements One {
-      @Override public void m() {}
-      @Override public void m(String x) {}
-      @Override public void n() {}
+      @Override
+      public void m() {}
+
+      @Override
+      public void m(String x) {}
+
+      @Override
+      public void n() {}
     }
 
     static class ChildOfOneAndTwo implements One, Two {
-      @Override public void m() {}
-      @Override public void m(String x) {}
-      @Override public void m(int x) {}
-      @Override public void n() {}
+      @Override
+      public void m() {}
+
+      @Override
+      public void m(String x) {}
+
+      @Override
+      public void m(int x) {}
+
+      @Override
+      public void n() {}
     }
 
     static class ChildOfParentAndOne extends Parent implements One {
-      @Override public void m() {}
-      @Override public void m(String x) {}
-      @Override public void n() {}
+      @Override
+      public void m() {}
+
+      @Override
+      public void m(String x) {}
+
+      @Override
+      public void n() {}
     }
 
     static class ChildOfParentAndOneAndTwo extends Parent implements One, Two {
-      @Override public void m(String x) {}
-      @Override public void m(int x) {}
-      @Override public void n() {}
+      @Override
+      public void m(String x) {}
+
+      @Override
+      public void m(int x) {}
+
+      @Override
+      public void n() {}
     }
 
     abstract static class AbstractChildOfOne implements One {}
@@ -194,14 +222,20 @@
 
     abstract static class BindingDeclaration implements HasKey {
       abstract Optional<Element> bindingElement();
+
       abstract Optional<TypeElement> contributingModule();
     }
 
-    abstract static class MultibindingDeclaration
-        extends BindingDeclaration implements HasBindingType, HasContributionType {
-      @Override public abstract Key key();
-      @Override public abstract ContributionType contributionType();
-      @Override public abstract BindingType bindingType();
+    abstract static class MultibindingDeclaration extends BindingDeclaration
+        implements HasBindingType, HasContributionType {
+      @Override
+      public abstract Key key();
+
+      @Override
+      public abstract ContributionType contributionType();
+
+      @Override
+      public abstract BindingType bindingType();
     }
   }
 
@@ -221,16 +255,26 @@
   }
 
   static class TypesForGenerics {
-    interface XCollection<E> {
+    interface GCollection<E> {
       boolean add(E x);
     }
 
-    interface XList<E> extends XCollection<E> {
-      @Override public boolean add(E x);
+    interface GList<E> extends GCollection<E> {
+      @Override
+      boolean add(E x);
     }
 
-    static class StringList implements XList<String> {
-      @Override public boolean add(String x) {
+    static class StringList implements GList<String> {
+      @Override
+      public boolean add(String x) {
+        return false;
+      }
+    }
+
+    @SuppressWarnings("rawtypes")
+    static class RawList implements GList {
+      @Override
+      public boolean add(Object x) {
         return false;
       }
     }
@@ -243,7 +287,8 @@
     }
 
     static class RawChildOfRaw extends RawParent {
-      @Override void frob(List x) {}
+      @Override
+      void frob(List x) {}
     }
 
     static class NonRawParent {
@@ -251,7 +296,8 @@
     }
 
     static class RawChildOfNonRaw extends NonRawParent {
-      @Override void frob(List x) {}
+      @Override
+      void frob(List x) {}
     }
   }
 
@@ -291,8 +337,9 @@
   // since the two Es are not the same.
   @Test
   public void overridesDiamond() {
-    checkOverridesInSet(ImmutableSet.<Class<?>>of(
-        Collection.class, List.class, AbstractCollection.class, AbstractList.class));
+    checkOverridesInSet(
+        ImmutableSet.<Class<?>>of(
+            Collection.class, List.class, AbstractCollection.class, AbstractList.class));
   }
 
   private void checkOverridesInContainedClasses(Class<?> container) {
@@ -324,10 +371,13 @@
             expect
                 .withMessage(
                     "%s.%s overrides %s.%s in %s: javac says %s, we say %s",
-                    overrider.getEnclosingElement(), overrider,
-                    overridden.getEnclosingElement(), overridden,
+                    overrider.getEnclosingElement(),
+                    overrider,
+                    overridden.getEnclosingElement(),
+                    overridden,
                     in,
-                    javacSays, weSay)
+                    javacSays,
+                    weSay)
                 .fail();
           }
         }
@@ -355,7 +405,7 @@
       }
     }
     assertThat(found).isNotNull();
-    return found;
+    return requireNonNull(found);
   }
 
   // These skeletal parallels to the real collection classes ensure that the test is independent
@@ -375,8 +425,8 @@
     }
   }
 
-  private abstract static class XAbstractList<E>
-      extends XAbstractCollection<E> implements XList<E> {
+  private abstract static class XAbstractList<E> extends XAbstractCollection<E>
+      implements XList<E> {
     @Override
     public boolean add(E e) {
       return true;
@@ -440,7 +490,7 @@
       extends Converter<String, Range<T>> {
     @Override
     protected String doBackward(Range<T> b) {
-      return null;
+      return "";
     }
   }
 
@@ -470,9 +520,8 @@
         explicitOverrides.methodFromSuperclasses(xAbstractStringList, add);
     assertThat(addInAbstractStringList).isNull();
 
-    ExecutableElement addInStringList =
-        explicitOverrides.methodFromSuperclasses(xStringList, add);
-    assertThat(addInStringList.getEnclosingElement()).isEqualTo(xAbstractList);
+    ExecutableElement addInStringList = explicitOverrides.methodFromSuperclasses(xStringList, add);
+    assertThat(requireNonNull(addInStringList).getEnclosingElement()).isEqualTo(xAbstractList);
   }
 
   @Test
@@ -487,20 +536,21 @@
 
     ExecutableElement addInAbstractStringList =
         explicitOverrides.methodFromSuperinterfaces(xAbstractStringList, add);
-    assertThat(addInAbstractStringList.getEnclosingElement()).isEqualTo(xCollection);
+    assertThat(requireNonNull(addInAbstractStringList).getEnclosingElement())
+        .isEqualTo(xCollection);
 
     ExecutableElement addInNumberList =
         explicitOverrides.methodFromSuperinterfaces(xNumberList, add);
-    assertThat(addInNumberList.getEnclosingElement()).isEqualTo(xAbstractList);
+    assertThat(requireNonNull(addInNumberList).getEnclosingElement()).isEqualTo(xAbstractList);
 
-    ExecutableElement addInList =
-        explicitOverrides.methodFromSuperinterfaces(xList, add);
-    assertThat(addInList.getEnclosingElement()).isEqualTo(xCollection);
+    ExecutableElement addInList = explicitOverrides.methodFromSuperinterfaces(xList, add);
+    assertThat(requireNonNull(addInList).getEnclosingElement()).isEqualTo(xCollection);
   }
 
-  private void assertTypeListsEqual(List<TypeMirror> actual, List<TypeMirror> expected) {
-    assertThat(actual.size()).isEqualTo(expected.size());
-    for (int i = 0; i < actual.size(); i++) {
+  private void assertTypeListsEqual(@Nullable List<TypeMirror> actual, List<TypeMirror> expected) {
+   requireNonNull(actual);
+   assertThat(actual).hasSize(expected.size());
+   for (int i = 0; i < actual.size(); i++) {
       assertThat(typeUtils.isSameType(actual.get(i), expected.get(i))).isTrue();
     }
   }
@@ -552,10 +602,11 @@
       // it hard for ecj to find the boot class path. Elsewhere it is unnecessary but harmless.
       File rtJar = new File(StandardSystemProperty.JAVA_HOME.value() + "/lib/rt.jar");
       if (rtJar.exists()) {
-        List<File> bootClassPath = ImmutableList.<File>builder()
-            .add(rtJar)
-            .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH))
-            .build();
+        List<File> bootClassPath =
+            ImmutableList.<File>builder()
+                .add(rtJar)
+                .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH))
+                .build();
         fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
       }
       Iterable<? extends JavaFileObject> sources = fileManager.getJavaFileObjects(dummySourceFile);
@@ -583,8 +634,7 @@
     }
 
     @Override
-    public boolean process(
-        Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
       if (roundEnv.processingOver()) {
         ecjCompilation.elements = processingEnv.getElementUtils();
         ecjCompilation.types = processingEnv.getTypeUtils();
@@ -643,24 +693,24 @@
 
   private static final TypeVisitor<String, Void> ERASED_STRING_TYPE_VISITOR =
       new SimpleTypeVisitor6<String, Void>() {
-    @Override
-    protected String defaultAction(TypeMirror e, Void p) {
-      return e.toString();
-    }
+        @Override
+        protected String defaultAction(TypeMirror e, Void p) {
+          return e.toString();
+        }
 
-    @Override
-    public String visitArray(ArrayType t, Void p) {
-      return visit(t.getComponentType()) + "[]";
-    }
+        @Override
+        public String visitArray(ArrayType t, Void p) {
+          return visit(t.getComponentType()) + "[]";
+        }
 
-    @Override
-    public String visitDeclared(DeclaredType t, Void p) {
-      return MoreElements.asType(t.asElement()).getQualifiedName().toString();
-    }
+        @Override
+        public String visitDeclared(DeclaredType t, Void p) {
+          return MoreElements.asType(t.asElement()).getQualifiedName().toString();
+        }
 
-    @Override
-    public String visitTypeVariable(TypeVariable t, Void p) {
-      return visit(t.getUpperBound());
-    }
-  };
+        @Override
+        public String visitTypeVariable(TypeVariable t, Void p) {
+          return visit(t.getUpperBound());
+        }
+      };
 }
diff --git a/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java
index d73e1b6..0bad83d 100644
--- a/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java
+++ b/common/src/test/java/com/google/auto/common/SimpleAnnotationMirrorTest.java
@@ -46,6 +46,7 @@
 
   @interface MultipleValues {
     int value1();
+
     int value2();
   }
 
diff --git a/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java
index 4fc61b5..ea85365 100644
--- a/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java
+++ b/common/src/test/java/com/google/auto/common/SimpleTypeAnnotationValueTest.java
@@ -28,6 +28,7 @@
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.SimpleAnnotationValueVisitor8;
 import javax.lang.model.util.Types;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -70,18 +71,21 @@
 
   @Test
   public void visitorMethod() {
-    SimpleTypeAnnotationValue.of(objectType).accept(new SimpleAnnotationValueVisitor8<Void, Void>(){
-      @Override
-      public Void visitType(TypeMirror typeMirror, Void aVoid) {
-        // do nothing, expected case
-        return null;
-      }
+    SimpleTypeAnnotationValue.of(objectType)
+        .accept(
+            new SimpleAnnotationValueVisitor8<@Nullable Void, @Nullable Void>() {
+              @Override
+              public @Nullable Void visitType(TypeMirror typeMirror, @Nullable Void aVoid) {
+                // do nothing, expected case
+                return null;
+              }
 
-      @Override
-      protected Void defaultAction(Object o, Void aVoid) {
-        throw new AssertionError();
-      }
-    }, null);
+              @Override
+              protected @Nullable Void defaultAction(Object o, @Nullable Void aVoid) {
+                throw new AssertionError();
+              }
+            },
+            null);
   }
 
   @Test
diff --git a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
index 15e54ff..c9bcf77 100644
--- a/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
+++ b/common/src/test/java/com/google/auto/common/SuperficialValidationTest.java
@@ -35,231 +35,263 @@
 public class SuperficialValidationTest {
   @Test
   public void missingReturnType() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "abstract class TestClass {",
-        "  abstract MissingType blah();",
-        "}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass",
+            "package test;",
+            "",
+            "abstract class TestClass {",
+            "  abstract MissingType blah();",
+            "}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void missingGenericReturnType() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "abstract class TestClass {",
-        "  abstract MissingType<?> blah();",
-        "}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass",
+            "package test;",
+            "",
+            "abstract class TestClass {",
+            "  abstract MissingType<?> blah();",
+            "}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void missingReturnTypeTypeParameter() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "import java.util.Map;",
-        "import java.util.Set;",
-        "",
-        "abstract class TestClass {",
-        "  abstract Map<Set<?>, MissingType<?>> blah();",
-        "}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass",
+            "package test;",
+            "",
+            "import java.util.Map;",
+            "import java.util.Set;",
+            "",
+            "abstract class TestClass {",
+            "  abstract Map<Set<?>, MissingType<?>> blah();",
+            "}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void missingTypeParameter() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "class TestClass<T extends MissingType> {}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass", //
+            "package test;",
+            "",
+            "class TestClass<T extends MissingType> {}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void missingParameterType() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "abstract class TestClass {",
-        "  abstract void foo(MissingType x);",
-        "}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass",
+            "package test;",
+            "",
+            "abstract class TestClass {",
+            "  abstract void foo(MissingType x);",
+            "}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void missingAnnotation() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "@MissingAnnotation",
-        "class TestClass {}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass", //
+            "package test;",
+            "",
+            "@MissingAnnotation",
+            "class TestClass {}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void handlesRecursiveTypeParams() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "class TestClass<T extends Comparable<T>> {}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass", //
+            "package test;",
+            "",
+            "class TestClass<T extends Comparable<T>> {}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
+              }
+            })
         .compilesWithoutError();
   }
 
   @Test
   public void handlesRecursiveType() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "abstract class TestClass {",
-        "  abstract TestClass foo(TestClass x);",
-        "}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass",
+            "package test;",
+            "",
+            "abstract class TestClass {",
+            "  abstract TestClass foo(TestClass x);",
+            "}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
+              }
+            })
         .compilesWithoutError();
   }
 
   @Test
   public void missingWildcardBound() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "import java.util.Set;",
-        "",
-        "class TestClass {",
-        "  Set<? extends MissingType> extendsTest() {",
-        "    return null;",
-        "  }",
-        "",
-        "  Set<? super MissingType> superTest() {",
-        "    return null;",
-        "  }",
-        "}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass",
+            "package test;",
+            "",
+            "import java.util.Set;",
+            "",
+            "class TestClass {",
+            "  Set<? extends MissingType> extendsTest() {",
+            "    return null;",
+            "  }",
+            "",
+            "  Set<? super MissingType> superTest() {",
+            "    return null;",
+            "  }",
+            "}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void missingIntersection() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
-        "test.TestClass",
-        "package test;",
-        "",
-        "class TestClass<T extends Number & Missing> {}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.TestClass",
+            "package test;",
+            "",
+            "class TestClass<T extends Number & Missing> {}");
     assertAbout(javaSource())
         .that(javaFileObject)
-        .processedWith(new AssertingProcessor() {
-          @Override void runAssertions() {
-            TypeElement testClassElement =
-                processingEnv.getElementUtils().getTypeElement("test.TestClass");
-            assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
-          }
-        })
+        .processedWith(
+            new AssertingProcessor() {
+              @Override
+              void runAssertions() {
+                TypeElement testClassElement =
+                    processingEnv.getElementUtils().getTypeElement("test.TestClass");
+                assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
+              }
+            })
         .failsToCompile();
   }
 
   @Test
   public void invalidAnnotationValue() {
-    JavaFileObject javaFileObject = JavaFileObjects.forSourceLines("test.Outer",
-        "package test;",
-        "",
-        "final class Outer {",
-        "  @interface TestAnnotation {",
-        "    Class[] classes();",
-        "  }",
-        "",
-        "  @TestAnnotation(classes = Foo)",
-        "  static class TestClass {}",
-        "}");
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "test.Outer",
+            "package test;",
+            "",
+            "final class Outer {",
+            "  @interface TestAnnotation {",
+            "    Class[] classes();",
+            "  }",
+            "",
+            "  @TestAnnotation(classes = Foo)",
+            "  static class TestClass {}",
+            "}");
     assertAbout(javaSource())
         .that(javaFileObject)
         .processedWith(
diff --git a/common/src/test/java/com/google/auto/common/VisibilityTest.java b/common/src/test/java/com/google/auto/common/VisibilityTest.java
index 6a80b7a..fc5e630 100644
--- a/common/src/test/java/com/google/auto/common/VisibilityTest.java
+++ b/common/src/test/java/com/google/auto/common/VisibilityTest.java
@@ -39,9 +39,10 @@
   public void packageVisibility() {
     assertThat(Visibility.ofElement(compilation.getElements().getPackageElement("java.lang")))
         .isEqualTo(PUBLIC);
-    assertThat(Visibility.ofElement(
-        compilation.getElements().getPackageElement("com.google.auto.common")))
-            .isEqualTo(PUBLIC);
+    assertThat(
+            Visibility.ofElement(
+                compilation.getElements().getPackageElement("com.google.auto.common")))
+        .isEqualTo(PUBLIC);
   }
 
   @Test
@@ -61,32 +62,44 @@
   @SuppressWarnings("unused")
   public static class PublicClass {
     public static class NestedPublicClass {}
+
     protected static class NestedProtectedClass {}
+
     static class NestedDefaultClass {}
+
     private static class NestedPrivateClass {}
   }
 
   @SuppressWarnings("unused")
   protected static class ProtectedClass {
     public static class NestedPublicClass {}
+
     protected static class NestedProtectedClass {}
+
     static class NestedDefaultClass {}
+
     private static class NestedPrivateClass {}
   }
 
   @SuppressWarnings("unused")
   static class DefaultClass {
     public static class NestedPublicClass {}
+
     protected static class NestedProtectedClass {}
+
     static class NestedDefaultClass {}
+
     private static class NestedPrivateClass {}
   }
 
   @SuppressWarnings("unused")
   private static class PrivateClass {
     public static class NestedPublicClass {}
+
     protected static class NestedProtectedClass {}
+
     static class NestedDefaultClass {}
+
     private static class NestedPrivateClass {}
   }
 
@@ -94,21 +107,25 @@
   public void classVisibility() {
     assertThat(Visibility.ofElement(compilation.getElements().getTypeElement("java.util.Map")))
         .isEqualTo(PUBLIC);
-    assertThat(Visibility.ofElement(
-        compilation.getElements().getTypeElement("java.util.Map.Entry")))
-            .isEqualTo(PUBLIC);
-    assertThat(Visibility.ofElement(
-        compilation.getElements().getTypeElement(PublicClass.class.getCanonicalName())))
-            .isEqualTo(PUBLIC);
-    assertThat(Visibility.ofElement(
-        compilation.getElements().getTypeElement(ProtectedClass.class.getCanonicalName())))
-            .isEqualTo(PROTECTED);
-    assertThat(Visibility.ofElement(
-        compilation.getElements().getTypeElement(DefaultClass.class.getCanonicalName())))
-            .isEqualTo(DEFAULT);
-    assertThat(Visibility.ofElement(
-        compilation.getElements().getTypeElement(PrivateClass.class.getCanonicalName())))
-            .isEqualTo(PRIVATE);
+    assertThat(
+            Visibility.ofElement(compilation.getElements().getTypeElement("java.util.Map.Entry")))
+        .isEqualTo(PUBLIC);
+    assertThat(
+            Visibility.ofElement(
+                compilation.getElements().getTypeElement(PublicClass.class.getCanonicalName())))
+        .isEqualTo(PUBLIC);
+    assertThat(
+            Visibility.ofElement(
+                compilation.getElements().getTypeElement(ProtectedClass.class.getCanonicalName())))
+        .isEqualTo(PROTECTED);
+    assertThat(
+            Visibility.ofElement(
+                compilation.getElements().getTypeElement(DefaultClass.class.getCanonicalName())))
+        .isEqualTo(DEFAULT);
+    assertThat(
+            Visibility.ofElement(
+                compilation.getElements().getTypeElement(PrivateClass.class.getCanonicalName())))
+        .isEqualTo(PRIVATE);
   }
 
   @Test
@@ -118,14 +135,11 @@
     assertThat(effectiveVisiblityOfClass(DefaultClass.class)).isEqualTo(DEFAULT);
     assertThat(effectiveVisiblityOfClass(PrivateClass.class)).isEqualTo(PRIVATE);
 
-    assertThat(effectiveVisiblityOfClass(PublicClass.NestedPublicClass.class))
-        .isEqualTo(PUBLIC);
+    assertThat(effectiveVisiblityOfClass(PublicClass.NestedPublicClass.class)).isEqualTo(PUBLIC);
     assertThat(effectiveVisiblityOfClass(PublicClass.NestedProtectedClass.class))
         .isEqualTo(PROTECTED);
-    assertThat(effectiveVisiblityOfClass(PublicClass.NestedDefaultClass.class))
-        .isEqualTo(DEFAULT);
-    assertThat(effectiveVisiblityOfClass(PublicClass.NestedPrivateClass.class))
-        .isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(PublicClass.NestedDefaultClass.class)).isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(PublicClass.NestedPrivateClass.class)).isEqualTo(PRIVATE);
 
     assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPublicClass.class))
         .isEqualTo(PROTECTED);
@@ -136,23 +150,17 @@
     assertThat(effectiveVisiblityOfClass(ProtectedClass.NestedPrivateClass.class))
         .isEqualTo(PRIVATE);
 
-    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPublicClass.class))
-        .isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPublicClass.class)).isEqualTo(DEFAULT);
     assertThat(effectiveVisiblityOfClass(DefaultClass.NestedProtectedClass.class))
         .isEqualTo(DEFAULT);
-    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedDefaultClass.class))
-        .isEqualTo(DEFAULT);
-    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPrivateClass.class))
-        .isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedDefaultClass.class)).isEqualTo(DEFAULT);
+    assertThat(effectiveVisiblityOfClass(DefaultClass.NestedPrivateClass.class)).isEqualTo(PRIVATE);
 
-    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPublicClass.class))
-        .isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPublicClass.class)).isEqualTo(PRIVATE);
     assertThat(effectiveVisiblityOfClass(PrivateClass.NestedProtectedClass.class))
         .isEqualTo(PRIVATE);
-    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedDefaultClass.class))
-        .isEqualTo(PRIVATE);
-    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPrivateClass.class))
-        .isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedDefaultClass.class)).isEqualTo(PRIVATE);
+    assertThat(effectiveVisiblityOfClass(PrivateClass.NestedPrivateClass.class)).isEqualTo(PRIVATE);
   }
 
   private Visibility effectiveVisiblityOfClass(Class<?> clazz) {
diff --git a/factory/pom.xml b/factory/pom.xml
index 48bcfef..629223a 100644
--- a/factory/pom.xml
+++ b/factory/pom.xml
@@ -36,9 +36,11 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <auto-service.version>1.0</auto-service.version>
+    <auto-value.version>1.8.1</auto-value.version>
     <java.version>1.8</java.version>
-    <guava.version>28.2-jre</guava.version>
-    <truth.version>1.0.1</truth.version>
+    <guava.version>30.1.1-jre</guava.version>
+    <truth.version>1.1.3</truth.version>
   </properties>
 
   <scm>
@@ -69,43 +71,25 @@
     <dependency>
       <groupId>com.google.auto</groupId>
       <artifactId>auto-common</artifactId>
-      <version>0.10</version>
+      <version>1.1</version>
     </dependency>
     <dependency>
       <groupId>com.google.auto.value</groupId>
       <artifactId>auto-value-annotations</artifactId>
-      <version>1.7</version>
-    </dependency>
-    <dependency>
-      <groupId>com.google.auto.value</groupId>
-      <artifactId>auto-value</artifactId>
-      <version>1.7</version>
-      <scope>provided</scope>
+      <version>${auto-value.version}</version>
     </dependency>
     <dependency>
       <groupId>com.google.auto.service</groupId>
-      <artifactId>auto-service</artifactId>
-      <version>1.0-rc6</version>
-      <scope>provided</scope>
+      <artifactId>auto-service-annotations</artifactId>
+      <version>${auto-service.version}</version>
     </dependency>
     <dependency>
       <groupId>net.ltgt.gradle.incap</groupId>
       <artifactId>incap</artifactId>
-      <version>0.2</version>
+      <version>0.3</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
-      <groupId>net.ltgt.gradle.incap</groupId>
-      <artifactId>incap-processor</artifactId>
-      <version>0.2</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.google.googlejavaformat</groupId>
-      <artifactId>google-java-format</artifactId>
-      <version>1.7</version>
-    </dependency>
-    <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
       <version>${guava.version}</version>
@@ -113,7 +97,7 @@
     <dependency>
       <groupId>com.squareup</groupId>
       <artifactId>javapoet</artifactId>
-      <version>1.12.1</version>
+      <version>1.13.0</version>
     </dependency>
     <dependency>
       <groupId>javax.inject</groupId>
@@ -124,13 +108,13 @@
     <dependency>
       <groupId>com.google.testing.compile</groupId>
       <artifactId>compile-testing</artifactId>
-      <version>0.18</version>
+      <version>0.19</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>4.13</version>
+      <version>4.13.2</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -157,29 +141,54 @@
     <plugins>
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.7.0</version>
+        <version>3.8.1</version>
         <configuration>
           <source>${java.version}</source>
           <target>${java.version}</target>
           <compilerArgument>-Xlint:all</compilerArgument>
           <showWarnings>true</showWarnings>
           <showDeprecation>true</showDeprecation>
+          <annotationProcessorPaths>
+            <path>
+              <groupId>com.google.auto.service</groupId>
+              <artifactId>auto-service</artifactId>
+              <version>${auto-service.version}</version>
+            </path>
+            <path>
+              <groupId>com.google.auto.value</groupId>
+              <artifactId>auto-value</artifactId>
+              <version>${auto-value.version}</version>
+            </path>
+            <path>
+              <groupId>net.ltgt.gradle.incap</groupId>
+              <artifactId>incap-processor</artifactId>
+              <version>0.3</version>
+            </path>
+          </annotationProcessorPaths>
         </configuration>
         <dependencies>
           <dependency>
             <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-java</artifactId>
-            <version>0.9.4</version>
+            <version>1.0.7</version>
           </dependency>
         </dependencies>
       </plugin>
       <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.22.2</version>
+        <configuration>
+          <argLine>${test.jvm.flags}</argLine>
+        </configuration>
+      </plugin>
+      <plugin>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.2.0</version>
       </plugin>
       <plugin>
         <artifactId>maven-invoker-plugin</artifactId>
-        <version>3.0.1</version>
+        <version>3.2.2</version>
         <configuration>
           <addTestClassPath>true</addTestClassPath>
           <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
@@ -205,4 +214,15 @@
       </plugin>
     </plugins>
   </build>
+  <profiles>
+    <profile>
+      <id>open-modules</id>
+      <activation>
+        <jdk>[9,)</jdk>
+      </activation>
+      <properties>
+        <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</test.jvm.flags>
+      </properties>
+    </profile>
+  </profiles>
 </project>
diff --git a/factory/src/it/functional/pom.xml b/factory/src/it/functional/pom.xml
index 56e8f6f..a7a1853 100644
--- a/factory/src/it/functional/pom.xml
+++ b/factory/src/it/functional/pom.xml
@@ -45,7 +45,7 @@
     <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
-      <version>27.0.1-jre</version>
+      <version>29.0-jre</version>
     </dependency>
     <dependency>
       <groupId>com.google.inject</groupId>
@@ -66,7 +66,7 @@
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>4.12</version>
+      <version>4.13.2</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -82,12 +82,12 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.2.0</version>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.7.0</version>
+        <version>3.8.1</version>
         <configuration>
           <source>1.8</source>
           <target>1.8</target>
@@ -99,7 +99,7 @@
           <dependency>
             <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-java</artifactId>
-            <version>0.9.4</version>
+            <version>1.0.5</version>
           </dependency>
         </dependencies>
       </plugin>
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
index 4c4a38a..f32e9f8 100644
--- a/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DaggerModule.java
@@ -16,44 +16,44 @@
 package com.google.auto.factory;
 
 import com.google.auto.factory.otherpackage.OtherPackage;
+import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
 @Module
-final class DaggerModule {
-  @Provides Dependency provideDependency(DependencyImpl impl) {
-    return impl;
-  }
+abstract class DaggerModule {
+  private DaggerModule() {} // no instances
 
-  @Provides
+  @Binds
+  abstract Dependency provideDependency(DependencyImpl impl);
+
+  @Binds
   @Qualifier
-  Dependency provideQualifiedDependency(QualifiedDependencyImpl impl) {
-    return impl;
-  }
+  abstract Dependency provideQualifiedDependency(QualifiedDependencyImpl impl);
 
   @Provides
-  int providePrimitive() {
+  static int providePrimitive() {
     return 1;
   }
 
   @Provides
   @Qualifier
-  int provideQualifiedPrimitive() {
+  static int provideQualifiedPrimitive() {
     return 2;
   }
 
   @Provides
-  Number provideNumber() {
+  static Number provideNumber() {
     return 3;
   }
 
   @Provides
-  ReferencePackage provideReferencePackage(ReferencePackageFactory factory) {
+  static ReferencePackage provideReferencePackage(ReferencePackageFactory factory) {
     return factory.create(17);
   }
 
   @Provides
-  OtherPackage provideOtherPackage() {
+  static OtherPackage provideOtherPackage() {
     return new OtherPackage(null, 23);
   }
 }
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java
index 4c019ea..d94a3cf 100644
--- a/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/DependencyImpl.java
@@ -18,5 +18,6 @@
 import javax.inject.Inject;
 
 public class DependencyImpl implements Dependency {
-  @Inject DependencyImpl() {}
+  @Inject
+  DependencyImpl() {}
 }
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java
index f7c13b4..31954a1 100644
--- a/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GenericFoo.java
@@ -28,10 +28,7 @@
   private final E depE;
 
   <D extends IntAccessor & StringAccessor> GenericFoo(
-      @Provided Provider<A> depA,
-      B depB,
-      D depD,
-      E depE) {
+      @Provided Provider<A> depA, B depB, D depD, E depE) {
     this.depA = depA.get();
     this.depB = depB;
     this.depDIntAccessor = depD;
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java
index 45d4d26..7734665 100644
--- a/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/GuiceModule.java
@@ -18,7 +18,8 @@
 import com.google.inject.AbstractModule;
 
 public class GuiceModule extends AbstractModule {
-  @Override protected void configure() {
+  @Override
+  protected void configure() {
     bind(Dependency.class).to(DependencyImpl.class);
     bind(Dependency.class).annotatedWith(Qualifier.class).to(QualifiedDependencyImpl.class);
     bind(Integer.class).toInstance(1);
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java
old mode 100644
new mode 100755
index 22aff65..4f67c3b
--- a/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java
+++ b/factory/src/it/functional/src/main/java/com/google/auto/factory/ReferencePackage.java
@@ -25,9 +25,7 @@
   private final int random;
 
   @Inject
-  public ReferencePackage(
-      @Provided OtherPackageFactory otherPackageFactory,
-      int random) {
+  ReferencePackage(@Provided OtherPackageFactory otherPackageFactory, int random) {
     this.otherPackageFactory = otherPackageFactory;
     this.random = random;
   }
diff --git a/factory/src/it/functional/src/main/java/com/google/auto/factory/otherpackage/OtherPackage.java b/factory/src/it/functional/src/main/java/com/google/auto/factory/otherpackage/OtherPackage.java
old mode 100644
new mode 100755
diff --git a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
index e141211..3caa5a5 100644
--- a/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
+++ b/factory/src/it/functional/src/test/java/com/google/auto/factory/DependencyInjectionIntegrationTest.java
@@ -19,7 +19,8 @@
 
   private static final IntAndStringAccessor INT_AND_STRING_ACCESSOR = new IntAndStringAccessor() {};
 
-  @Test public void daggerInjectedFactory() {
+  @Test
+  public void daggerInjectedFactory() {
     FooFactory fooFactory = DaggerFactoryComponent.create().factory();
     Foo one = fooFactory.create("A");
     Foo two = fooFactory.create("B");
@@ -46,8 +47,9 @@
         genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1);
     ArrayList<Double> intAndStringAccessorArrayList = new ArrayList<>();
     intAndStringAccessorArrayList.add(4.0);
-    GenericFoo<Number, ArrayList<Double>, Long, DepE> four = genericFooFactory.create(
-        intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2);
+    GenericFoo<Number, ArrayList<Double>, Long, DepE> four =
+        genericFooFactory.create(
+            intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2);
     assertThat(three.getDepA()).isEqualTo(3);
     ImmutableList<Long> unusedLongList = three.getDepB();
     assertThat(three.getDepB()).containsExactly(3L);
@@ -75,7 +77,8 @@
     assertThat(otherPackage.random()).isEqualTo(5);
   }
 
-  @Test public void guiceInjectedFactory() {
+  @Test
+  public void guiceInjectedFactory() {
     FooFactory fooFactory = Guice.createInjector(new GuiceModule()).getInstance(FooFactory.class);
     Foo one = fooFactory.create("A");
     Foo two = fooFactory.create("B");
@@ -103,8 +106,9 @@
         genericFooFactory.create(ImmutableList.of(3L), INT_AND_STRING_ACCESSOR, DepE.VALUE_1);
     ArrayList<Double> intAndStringAccessorArrayList = new ArrayList<>();
     intAndStringAccessorArrayList.add(4.0);
-    GenericFoo<Number, ArrayList<Double>, Long, DepE> four = genericFooFactory.create(
-        intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2);
+    GenericFoo<Number, ArrayList<Double>, Long, DepE> four =
+        genericFooFactory.create(
+            intAndStringAccessorArrayList, INT_AND_STRING_ACCESSOR, DepE.VALUE_2);
     assertThat(three.getDepA()).isEqualTo(3);
     ImmutableList<Long> unusedLongList = three.getDepB();
     assertThat(three.getDepB()).containsExactly(3L);
@@ -124,8 +128,7 @@
   @Test
   public void guiceInjectedPackageSpanningFactory() {
     ReferencePackageFactory referencePackageFactory =
-        Guice.createInjector(new GuiceModule())
-            .getInstance(ReferencePackageFactory.class);
+        Guice.createInjector(new GuiceModule()).getInstance(ReferencePackageFactory.class);
     ReferencePackage referencePackage = referencePackageFactory.create(5);
     OtherPackage otherPackage = referencePackage.otherPackage();
     assertThat(otherPackage.referencePackageFactory()).isNotSameInstanceAs(referencePackageFactory);
diff --git a/factory/src/main/java/com/google/auto/factory/AutoFactory.java b/factory/src/main/java/com/google/auto/factory/AutoFactory.java
index 2ef84cc..3b5d90e 100644
--- a/factory/src/main/java/com/google/auto/factory/AutoFactory.java
+++ b/factory/src/main/java/com/google/auto/factory/AutoFactory.java
@@ -32,7 +32,7 @@
  *
  * @author Gregory Kick
  */
-@Target({ TYPE, CONSTRUCTOR })
+@Target({TYPE, CONSTRUCTOR})
 public @interface AutoFactory {
   /**
    * The <i>simple</i> name of the generated factory; the factory is always generated in the same
@@ -50,7 +50,7 @@
   /**
    * A list of interfaces that the generated factory is required to implement.
    */
-  Class<?>[] implementing() default { };
+  Class<?>[] implementing() default {};
 
   /**
    * The type that the generated factory is require to extend.
diff --git a/factory/src/main/java/com/google/auto/factory/Provided.java b/factory/src/main/java/com/google/auto/factory/Provided.java
index e81e4aa..226a16f 100644
--- a/factory/src/main/java/com/google/auto/factory/Provided.java
+++ b/factory/src/main/java/com/google/auto/factory/Provided.java
@@ -26,4 +26,4 @@
  * @author Gregory Kick
  */
 @Target(PARAMETER)
-public @interface Provided { }
+public @interface Provided {}
diff --git a/factory/src/main/java/com/google/auto/factory/package-info.java b/factory/src/main/java/com/google/auto/factory/package-info.java
new file mode 100644
index 0000000..ea1ddd8
--- /dev/null
+++ b/factory/src/main/java/com/google/auto/factory/package-info.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.factory;
+
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java
index b767c47..53d38a4 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AnnotationValues.java
@@ -32,24 +32,29 @@
   static boolean asBoolean(AnnotationValue value) {
     return value.accept(
         new SimpleAnnotationValueVisitor6<Boolean, Void>() {
-          @Override protected Boolean defaultAction(Object o, Void p) {
+          @Override
+          protected Boolean defaultAction(Object o, Void p) {
             throw new IllegalArgumentException();
           }
 
-          @Override public Boolean visitBoolean(boolean b, Void p) {
+          @Override
+          public Boolean visitBoolean(boolean b, Void p) {
             return b;
           }
-        }, null);
+        },
+        null);
   }
 
   static TypeElement asType(AnnotationValue value) {
     return value.accept(
         new SimpleAnnotationValueVisitor6<TypeElement, Void>() {
-          @Override protected TypeElement defaultAction(Object o, Void p) {
+          @Override
+          protected TypeElement defaultAction(Object o, Void p) {
             throw new IllegalArgumentException();
           }
 
-          @Override public TypeElement visitType(TypeMirror t, Void p) {
+          @Override
+          public TypeElement visitType(TypeMirror t, Void p) {
             return t.accept(
                 new SimpleTypeVisitor6<TypeElement, Void>() {
                   @Override
@@ -59,12 +64,14 @@
 
                   @Override
                   public TypeElement visitDeclared(DeclaredType t, Void p) {
-                    return Iterables.getOnlyElement(ElementFilter.typesIn(
-                        ImmutableList.of(t.asElement())));
+                    return Iterables.getOnlyElement(
+                        ElementFilter.typesIn(ImmutableList.of(t.asElement())));
                   }
-                }, null);
+                },
+                null);
           }
-        }, null);
+        },
+        null);
   }
 
   static ImmutableList<? extends AnnotationValue> asList(AnnotationValue value) {
@@ -80,6 +87,7 @@
               List<? extends AnnotationValue> vals, Void p) {
             return ImmutableList.copyOf(vals);
           }
-        }, null);
+        },
+        null);
   }
 }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
index e2da6ea..889d8e4 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java
@@ -21,13 +21,13 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Iterables.getOnlyElement;
+import static java.util.Objects.requireNonNull;
 import static javax.lang.model.element.ElementKind.PACKAGE;
 import static javax.lang.model.util.ElementFilter.typesIn;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
@@ -36,6 +36,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import javax.annotation.processing.Messager;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.AnnotationMirror;
@@ -54,12 +55,19 @@
 @AutoValue
 abstract class AutoFactoryDeclaration {
   abstract TypeElement targetType();
+
   abstract Element target();
+
   abstract Optional<String> className();
+
   abstract TypeElement extendingType();
+
   abstract ImmutableSet<TypeElement> implementingTypes();
+
   abstract boolean allowSubclasses();
+
   abstract AnnotationMirror mirror();
+
   abstract ImmutableMap<String, AnnotationValue> valuesMap();
 
   PackageAndClass getFactoryName() {
@@ -97,52 +105,70 @@
     Optional<AutoFactoryDeclaration> createIfValid(Element element) {
       checkNotNull(element);
       AnnotationMirror mirror = Mirrors.getAnnotationMirror(element, AutoFactory.class).get();
-      checkArgument(Mirrors.getQualifiedName(mirror.getAnnotationType()).
-          contentEquals(AutoFactory.class.getName()));
+      checkArgument(
+          Mirrors.getQualifiedName(mirror.getAnnotationType())
+              .contentEquals(AutoFactory.class.getName()));
       Map<String, AnnotationValue> values =
           Mirrors.simplifyAnnotationValueMap(elements.getElementValuesWithDefaults(mirror));
       checkState(values.size() == 4);
 
-      // className value is a string, so we can just call toString
-      AnnotationValue classNameValue = values.get("className");
+      // className value is a string, so we can just call toString. We know values.get("className")
+      // is non-null because @AutoFactory has an annotation element of that name.
+      AnnotationValue classNameValue = requireNonNull(values.get("className"));
       String className = classNameValue.getValue().toString();
       if (!className.isEmpty() && !isValidIdentifier(className)) {
-        messager.printMessage(ERROR,
+        messager.printMessage(
+            ERROR,
             String.format("\"%s\" is not a valid Java identifier", className),
-            element, mirror, classNameValue);
-        return Optional.absent();
+            element,
+            mirror,
+            classNameValue);
+        return Optional.empty();
       }
 
       AnnotationValue extendingValue = checkNotNull(values.get("extending"));
       TypeElement extendingType = AnnotationValues.asType(extendingValue);
       if (extendingType == null) {
-        messager.printMessage(ERROR, "Unable to find the type: "
-            + extendingValue.getValue().toString(),
-                element, mirror, extendingValue);
-        return Optional.absent();
+        messager.printMessage(
+            ERROR,
+            "Unable to find the type: " + extendingValue.getValue(),
+            element,
+            mirror,
+            extendingValue);
+        return Optional.empty();
       } else if (!isValidSupertypeForClass(extendingType)) {
-        messager.printMessage(ERROR,
-            String.format("%s is not a valid supertype for a factory. "
-                + "Supertypes must be non-final classes.",
-                    extendingType.getQualifiedName()),
-            element, mirror, extendingValue);
-        return Optional.absent();
+        messager.printMessage(
+            ERROR,
+            String.format(
+                "%s is not a valid supertype for a factory. "
+                    + "Supertypes must be non-final classes.",
+                extendingType.getQualifiedName()),
+            element,
+            mirror,
+            extendingValue);
+        return Optional.empty();
       }
       ImmutableList<ExecutableElement> noParameterConstructors =
           FluentIterable.from(ElementFilter.constructorsIn(extendingType.getEnclosedElements()))
-              .filter(new Predicate<ExecutableElement>() {
-                @Override public boolean apply(ExecutableElement constructor) {
-                  return constructor.getParameters().isEmpty();
-                }
-              })
+              .filter(
+                  new Predicate<ExecutableElement>() {
+                    @Override
+                    public boolean apply(ExecutableElement constructor) {
+                      return constructor.getParameters().isEmpty();
+                    }
+                  })
               .toList();
-      if (noParameterConstructors.size() == 0) {
-        messager.printMessage(ERROR,
-            String.format("%s is not a valid supertype for a factory. "
-                + "Factory supertypes must have a no-arg constructor.",
-                    extendingType.getQualifiedName()),
-            element, mirror, extendingValue);
-        return Optional.absent();
+      if (noParameterConstructors.isEmpty()) {
+        messager.printMessage(
+            ERROR,
+            String.format(
+                "%s is not a valid supertype for a factory. "
+                    + "Factory supertypes must have a no-arg constructor.",
+                extendingType.getQualifiedName()),
+            element,
+            mirror,
+            extendingValue);
+        return Optional.empty();
       } else if (noParameterConstructors.size() > 1) {
         throw new IllegalStateException("Multiple constructors with no parameters??");
       }
@@ -161,7 +187,7 @@
           new AutoValue_AutoFactoryDeclaration(
               getAnnotatedType(element),
               element,
-              className.isEmpty() ? Optional.<String>absent() : Optional.of(className),
+              className.isEmpty() ? Optional.empty() : Optional.of(className),
               extendingType,
               implementingTypes,
               allowSubclasses,
diff --git a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
index 5cc1d94..82349f2 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java
@@ -19,7 +19,6 @@
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.factory.Provided;
 import com.google.auto.service.AutoService;
-import com.google.common.base.Optional;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
@@ -30,7 +29,9 @@
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import javax.annotation.processing.AbstractProcessor;
 import javax.annotation.processing.Messager;
@@ -83,8 +84,9 @@
     try {
       doProcess(roundEnv);
     } catch (Throwable e) {
-      messager.printMessage(Kind.ERROR, "Failed to process @AutoFactory annotations:\n"
-          + Throwables.getStackTraceAsString(e));
+      messager.printMessage(
+          Kind.ERROR,
+          "Failed to process @AutoFactory annotations:\n" + Throwables.getStackTraceAsString(e));
     }
     return false;
   }
@@ -127,49 +129,58 @@
         simpleNamesToNames(indexedMethods.keySet());
     FactoryWriter factoryWriter = new FactoryWriter(processingEnv, factoriesBeingCreated);
 
-    indexedMethods.asMap().forEach(
-        (factoryName, methodDescriptors) -> {
-          // The sets of classes that are mentioned in the `extending` and `implementing` elements,
-          // respectively, of the @AutoFactory annotations for this factory.
-          ImmutableSet.Builder<TypeMirror> extending = newTypeSetBuilder();
-          ImmutableSortedSet.Builder<TypeMirror> implementing = newTypeSetBuilder();
-          boolean publicType = false;
-          Boolean allowSubclasses = null;
-          boolean skipCreation = false;
-          for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) {
-            extending.add(methodDescriptor.declaration().extendingType().asType());
-            for (TypeElement implementingType :
-                methodDescriptor.declaration().implementingTypes()) {
-              implementing.add(implementingType.asType());
-            }
-            publicType |= methodDescriptor.publicMethod();
-            if (allowSubclasses == null) {
-              allowSubclasses = methodDescriptor.declaration().allowSubclasses();
-            } else if (!allowSubclasses.equals(methodDescriptor.declaration().allowSubclasses())) {
-              skipCreation = true;
-              messager.printMessage(Kind.ERROR,
-                  "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.",
-                  methodDescriptor.declaration().target(),
-                  methodDescriptor.declaration().mirror(),
-                  methodDescriptor.declaration().valuesMap().get("allowSubclasses"));
-            }
-          }
-          if (!skipCreation) {
-            try {
-              factoryWriter.writeFactory(
-                  FactoryDescriptor.create(
-                      factoryName,
-                      Iterables.getOnlyElement(extending.build()),
-                      implementing.build(),
-                      publicType,
-                      ImmutableSet.copyOf(methodDescriptors),
-                      implementationMethodDescriptors.get(factoryName),
-                      allowSubclasses));
-            } catch (IOException e) {
-              messager.printMessage(Kind.ERROR, "failed: " + e);
-            }
-          }
-        });
+    indexedMethods
+        .asMap()
+        .forEach(
+            (factoryName, methodDescriptors) -> {
+              if (methodDescriptors.isEmpty()) {
+                // This shouldn't happen, but check anyway to avoid an exception for
+                // methodDescriptors.iterator().next() below.
+                return;
+              }
+              // The sets of classes that are mentioned in the `extending` and `implementing`
+              // elements, respectively, of the @AutoFactory annotations for this factory.
+              ImmutableSet.Builder<TypeMirror> extending = newTypeSetBuilder();
+              ImmutableSortedSet.Builder<TypeMirror> implementing = newTypeSetBuilder();
+              boolean publicType = false;
+              Set<Boolean> allowSubclassesSet = new HashSet<>();
+              boolean skipCreation = false;
+              for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) {
+                extending.add(methodDescriptor.declaration().extendingType().asType());
+                for (TypeElement implementingType :
+                    methodDescriptor.declaration().implementingTypes()) {
+                  implementing.add(implementingType.asType());
+                }
+                publicType |= methodDescriptor.publicMethod();
+                allowSubclassesSet.add(methodDescriptor.declaration().allowSubclasses());
+                if (allowSubclassesSet.size() > 1) {
+                  skipCreation = true;
+                  messager.printMessage(
+                      Kind.ERROR,
+                      "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.",
+                      methodDescriptor.declaration().target(),
+                      methodDescriptor.declaration().mirror(),
+                      methodDescriptor.declaration().valuesMap().get("allowSubclasses"));
+                }
+              }
+              // The set can't be empty because we eliminated methodDescriptors.isEmpty() above.
+              boolean allowSubclasses = allowSubclassesSet.iterator().next();
+              if (!skipCreation) {
+                try {
+                  factoryWriter.writeFactory(
+                      FactoryDescriptor.create(
+                          factoryName,
+                          Iterables.getOnlyElement(extending.build()),
+                          implementing.build(),
+                          publicType,
+                          ImmutableSet.copyOf(methodDescriptors),
+                          implementationMethodDescriptors.get(factoryName),
+                          allowSubclasses));
+                } catch (IOException e) {
+                  messager.printMessage(Kind.ERROR, "failed: " + e);
+                }
+              }
+            });
   }
 
   private ImmutableSet<ImplementationMethodDescriptor> implementationMethods(
@@ -180,8 +191,7 @@
         ElementFilter.methodsIn(elements.getAllMembers(supertype))) {
       if (implementationMethod.getModifiers().contains(Modifier.ABSTRACT)) {
         ExecutableType methodType =
-            Elements2.getExecutableElementAsMemberOf(
-                types, implementationMethod, supertype);
+            Elements2.getExecutableElementAsMemberOf(types, implementationMethod, supertype);
         ImmutableSet<Parameter> passedParameters =
             Parameter.forParameterList(
                 implementationMethod.getParameters(), methodType.getParameterTypes(), types);
@@ -192,6 +202,7 @@
                 .publicMethod()
                 .passedParameters(passedParameters)
                 .isVarArgs(implementationMethod.isVarArgs())
+                .exceptions(implementationMethod.getThrownTypes())
                 .build());
       }
     }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Elements2.java b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java
index 30230f7..3663f37 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/Elements2.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/Elements2.java
@@ -22,10 +22,10 @@
 import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.STATIC;
 
+import com.google.auto.common.MoreTypes;
 import com.google.common.collect.ImmutableSet;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.ExecutableType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
@@ -33,7 +33,7 @@
 import javax.lang.model.util.Types;
 
 final class Elements2 {
-  private Elements2() { }
+  private Elements2() {}
 
   static ImmutableSet<ExecutableElement> getConstructors(TypeElement type) {
     checkNotNull(type);
@@ -72,11 +72,11 @@
       throw new IllegalStateException(
           "Expected subTypeElement.asType() to return a class/interface type.");
     }
-    TypeMirror subExecutableTypeMirror = types.asMemberOf(
-        (DeclaredType) subTypeMirror, executableElement);
+    TypeMirror subExecutableTypeMirror =
+        types.asMemberOf(MoreTypes.asDeclared(subTypeMirror), executableElement);
     if (!subExecutableTypeMirror.getKind().equals(TypeKind.EXECUTABLE)) {
       throw new IllegalStateException("Expected subExecutableTypeMirror to be an executable type.");
     }
-    return (ExecutableType) subExecutableTypeMirror;
+    return MoreTypes.asExecutable(subExecutableTypeMirror);
   }
 }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
index 5ed2307..019295f 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java
@@ -17,16 +17,15 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import java.util.Collection;
+import com.google.common.collect.Streams;
 import java.util.HashSet;
-import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.type.TypeMirror;
@@ -47,20 +46,28 @@
       };
 
   abstract PackageAndClass name();
+
   abstract TypeMirror extendingType();
+
   abstract ImmutableSet<TypeMirror> implementingTypes();
+
   abstract boolean publicType();
+
   abstract ImmutableSet<FactoryMethodDescriptor> methodDescriptors();
+
   abstract ImmutableSet<ImplementationMethodDescriptor> implementationMethodDescriptors();
+
   abstract boolean allowSubclasses();
+
   abstract ImmutableMap<Key, ProviderField> providers();
 
   final AutoFactoryDeclaration declaration() {
-    return Iterables.getFirst(methodDescriptors(), null).declaration();
+    // There is always at least one method descriptor.
+    return methodDescriptors().iterator().next().declaration();
   }
 
   private static class UniqueNameSet {
-    private final Set<String> uniqueNames = new HashSet<String>();
+    private final Set<String> uniqueNames = new HashSet<>();
 
     /**
      * Generates a unique name using {@code base}. If {@code base} has not yet been added, it will
@@ -92,33 +99,37 @@
     }
     ImmutableMap.Builder<Key, ProviderField> providersBuilder = ImmutableMap.builder();
     UniqueNameSet uniqueNames = new UniqueNameSet();
-    for (Entry<Key, Collection<Parameter>> entry :
-        parametersForProviders.build().asMap().entrySet()) {
-      Key key = entry.getKey();
-      switch (entry.getValue().size()) {
-        case 0:
-          throw new AssertionError();
-        case 1:
-          Parameter parameter = Iterables.getOnlyElement(entry.getValue());
-          providersBuilder.put(
-              key,
-              ProviderField.create(
-                  uniqueNames.getUniqueName(parameter.name() + "Provider"),
-                  key,
-                  parameter.nullable()));
-          break;
-        default:
-          String providerName =
-              uniqueNames.getUniqueName(
-                  invalidIdentifierCharacters.replaceFrom(key.toString(), '_') + "Provider");
-          Optional<AnnotationMirror> nullable = Optional.absent();
-          for (Parameter param : entry.getValue()) {
-            nullable = nullable.or(param.nullable());
-          }
-          providersBuilder.put(key, ProviderField.create(providerName, key, nullable));
-          break;
-      }
-    }
+    parametersForProviders
+        .build()
+        .asMap()
+        .forEach(
+            (key, parameters) -> {
+              switch (parameters.size()) {
+                case 0:
+                  throw new AssertionError();
+                case 1:
+                  Parameter parameter = Iterables.getOnlyElement(parameters);
+                  providersBuilder.put(
+                      key,
+                      ProviderField.create(
+                          uniqueNames.getUniqueName(parameter.name() + "Provider"),
+                          key,
+                          parameter.nullable()));
+                  break;
+                default:
+                  String providerName =
+                      uniqueNames.getUniqueName(
+                          invalidIdentifierCharacters.replaceFrom(key.toString(), '_')
+                              + "Provider");
+                  Optional<AnnotationMirror> nullable =
+                      parameters.stream()
+                          .map(Parameter::nullable)
+                          .flatMap(Streams::stream)
+                          .findFirst();
+                  providersBuilder.put(key, ProviderField.create(providerName, key, nullable));
+                  break;
+              }
+            });
 
     ImmutableBiMap<FactoryMethodDescriptor, ImplementationMethodDescriptor>
         duplicateMethodDescriptors =
@@ -129,8 +140,8 @@
         getDeduplicatedMethodDescriptors(methodDescriptors, duplicateMethodDescriptors);
 
     ImmutableSet<ImplementationMethodDescriptor> deduplicatedImplementationMethodDescriptors =
-        ImmutableSet.copyOf(
-            Sets.difference(implementationMethodDescriptors, duplicateMethodDescriptors.values()));
+        Sets.difference(implementationMethodDescriptors, duplicateMethodDescriptors.values())
+            .immutableCopy();
 
     return new AutoValue_FactoryDescriptor(
         name,
@@ -191,12 +202,12 @@
           duplicateMethodDescriptors.get(methodDescriptor);
 
       FactoryMethodDescriptor newMethodDescriptor =
-         (duplicateMethodDescriptor != null)
-              ? methodDescriptor
-                  .toBuilder()
+          (duplicateMethodDescriptor != null)
+              ? methodDescriptor.toBuilder()
                   .overridingMethod(true)
                   .publicMethod(duplicateMethodDescriptor.publicMethod())
                   .returnType(duplicateMethodDescriptor.returnType())
+                  .exceptions(duplicateMethodDescriptor.exceptions())
                   .build()
               : methodDescriptor;
       deduplicatedMethodDescriptors.add(newMethodDescriptor);
@@ -213,8 +224,7 @@
    * in the same order.
    */
   private static boolean areDuplicateMethodDescriptors(
-      FactoryMethodDescriptor factory,
-      ImplementationMethodDescriptor implementation) {
+      FactoryMethodDescriptor factory, ImplementationMethodDescriptor implementation) {
 
     if (!factory.name().equals(implementation.name())) {
       return false;
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
index d2331f4..70a21ea 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptorGenerator.java
@@ -18,6 +18,8 @@
 import static com.google.auto.common.MoreElements.isAnnotationPresent;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.partitioningBy;
 import static javax.lang.model.element.Modifier.ABSTRACT;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.tools.Diagnostic.Kind.ERROR;
@@ -26,13 +28,11 @@
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.factory.Provided;
 import com.google.common.base.Function;
-import com.google.common.base.Functions;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimaps;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 import javax.annotation.processing.Messager;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.Element;
@@ -54,9 +54,7 @@
   private final AutoFactoryDeclaration.Factory declarationFactory;
 
   FactoryDescriptorGenerator(
-      Messager messager,
-      Types types,
-      AutoFactoryDeclaration.Factory declarationFactory) {
+      Messager messager, Types types, AutoFactoryDeclaration.Factory declarationFactory) {
     this.messager = messager;
     this.types = types;
     this.declarationFactory = declarationFactory;
@@ -68,70 +66,75 @@
     if (!declaration.isPresent()) {
       return ImmutableSet.of();
     }
-    return element.accept(new ElementKindVisitor6<ImmutableSet<FactoryMethodDescriptor>, Void>() {
-      @Override
-      protected ImmutableSet<FactoryMethodDescriptor> defaultAction(Element e, Void p) {
-        throw new AssertionError("@AutoFactory applied to an impossible element");
-      }
-
-      @Override
-      public ImmutableSet<FactoryMethodDescriptor> visitTypeAsClass(TypeElement type, Void p) {
-        if (type.getModifiers().contains(ABSTRACT)) {
-          // applied to an abstract factory
-          messager.printMessage(ERROR,
-              "Auto-factory doesn't support being applied to abstract classes.", type, mirror);
-          return ImmutableSet.of();
-        } else {
-          // applied to the type to be created
-          ImmutableSet<ExecutableElement> constructors = Elements2.getConstructors(type);
-          if (constructors.isEmpty()) {
-            return generateDescriptorForDefaultConstructor(declaration.get(), type);
-          } else {
-            return FluentIterable.from(constructors)
-                .transform(new Function<ExecutableElement, FactoryMethodDescriptor>() {
-                  @Override public FactoryMethodDescriptor apply(ExecutableElement constructor) {
-                    return generateDescriptorForConstructor(declaration.get(), constructor);
-                  }
-                })
-                .toSet();
+    return element.accept(
+        new ElementKindVisitor6<ImmutableSet<FactoryMethodDescriptor>, Void>() {
+          @Override
+          protected ImmutableSet<FactoryMethodDescriptor> defaultAction(Element e, Void p) {
+            throw new AssertionError("@AutoFactory applied to an impossible element");
           }
-        }
-      }
 
-      @Override
-      public ImmutableSet<FactoryMethodDescriptor> visitTypeAsInterface(TypeElement type, Void p) {
-        // applied to the factory interface
-        messager.printMessage(ERROR,
-            "Auto-factory doesn't support being applied to interfaces.", type, mirror);
-        return ImmutableSet.of();
-      }
+          @Override
+          public ImmutableSet<FactoryMethodDescriptor> visitTypeAsClass(TypeElement type, Void p) {
+            if (type.getModifiers().contains(ABSTRACT)) {
+              // applied to an abstract factory
+              messager.printMessage(
+                  ERROR,
+                  "Auto-factory doesn't support being applied to abstract classes.",
+                  type,
+                  mirror);
+              return ImmutableSet.of();
+            } else {
+              // applied to the type to be created
+              ImmutableSet<ExecutableElement> constructors = Elements2.getConstructors(type);
+              if (constructors.isEmpty()) {
+                return generateDescriptorForDefaultConstructor(declaration.get(), type);
+              } else {
+                return FluentIterable.from(constructors)
+                    .transform(
+                        new Function<ExecutableElement, FactoryMethodDescriptor>() {
+                          @Override
+                          public FactoryMethodDescriptor apply(ExecutableElement constructor) {
+                            return generateDescriptorForConstructor(declaration.get(), constructor);
+                          }
+                        })
+                    .toSet();
+              }
+            }
+          }
 
-      @Override
-      public ImmutableSet<FactoryMethodDescriptor> visitExecutableAsConstructor(ExecutableElement e,
-          Void p) {
-        // applied to a constructor of a type to be created
-        return ImmutableSet.of(generateDescriptorForConstructor(declaration.get(), e));
-      }
-    }, null);
+          @Override
+          public ImmutableSet<FactoryMethodDescriptor> visitTypeAsInterface(
+              TypeElement type, Void p) {
+            // applied to the factory interface
+            messager.printMessage(
+                ERROR, "Auto-factory doesn't support being applied to interfaces.", type, mirror);
+            return ImmutableSet.of();
+          }
+
+          @Override
+          public ImmutableSet<FactoryMethodDescriptor> visitExecutableAsConstructor(
+              ExecutableElement e, Void p) {
+            // applied to a constructor of a type to be created
+            return ImmutableSet.of(generateDescriptorForConstructor(declaration.get(), e));
+          }
+        },
+        null);
   }
 
-  FactoryMethodDescriptor generateDescriptorForConstructor(final AutoFactoryDeclaration declaration,
-      ExecutableElement constructor) {
+  FactoryMethodDescriptor generateDescriptorForConstructor(
+      final AutoFactoryDeclaration declaration, ExecutableElement constructor) {
     checkNotNull(constructor);
     checkArgument(constructor.getKind() == ElementKind.CONSTRUCTOR);
     TypeElement classElement = MoreElements.asType(constructor.getEnclosingElement());
-    ImmutableListMultimap<Boolean, ? extends VariableElement> parameterMap =
-        Multimaps.index(constructor.getParameters(), Functions.forPredicate(
-            new Predicate<VariableElement>() {
-              @Override
-              public boolean apply(VariableElement parameter) {
-                return isAnnotationPresent(parameter, Provided.class);
-              }
-            }));
+    Map<Boolean, List<VariableElement>> parameterMap =
+        constructor.getParameters().stream()
+            .collect(partitioningBy(parameter -> isAnnotationPresent(parameter, Provided.class)));
+    // The map returned by partitioningBy always has entries for both key values but our
+    // null-checker isn't yet smart enough to know that.
     ImmutableSet<Parameter> providedParameters =
-        Parameter.forParameterList(parameterMap.get(true), types);
+        Parameter.forParameterList(requireNonNull(parameterMap.get(true)), types);
     ImmutableSet<Parameter> passedParameters =
-        Parameter.forParameterList(parameterMap.get(false), types);
+        Parameter.forParameterList(requireNonNull(parameterMap.get(false)), types);
     return FactoryMethodDescriptor.builder(declaration)
         .name("create")
         .returnType(classElement.asType())
@@ -140,6 +143,8 @@
         .passedParameters(passedParameters)
         .creationParameters(Parameter.forParameterList(constructor.getParameters(), types))
         .isVarArgs(constructor.isVarArgs())
+        .exceptions(constructor.getThrownTypes())
+        .overridingMethod(false)
         .build();
   }
 
@@ -150,9 +155,12 @@
             .name("create")
             .returnType(type.asType())
             .publicMethod(type.getModifiers().contains(PUBLIC))
-            .passedParameters(ImmutableSet.<Parameter>of())
-            .creationParameters(ImmutableSet.<Parameter>of())
-            .providedParameters(ImmutableSet.<Parameter>of())
+            .providedParameters(ImmutableSet.of())
+            .passedParameters(ImmutableSet.of())
+            .creationParameters(ImmutableSet.of())
+            .isVarArgs(false)
+            .exceptions(ImmutableSet.of())
+            .overridingMethod(false)
             .build());
   }
 }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
index 43e5097..4525957 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryMethodDescriptor.java
@@ -31,45 +31,65 @@
 @AutoValue
 abstract class FactoryMethodDescriptor {
   abstract AutoFactoryDeclaration declaration();
+
   abstract String name();
+
   abstract TypeMirror returnType();
+
   abstract boolean publicMethod();
+
   abstract boolean overridingMethod();
+
   abstract ImmutableSet<Parameter> passedParameters();
+
   abstract ImmutableSet<Parameter> providedParameters();
+
   abstract ImmutableSet<Parameter> creationParameters();
-  abstract Builder toBuilder();
+
   abstract boolean isVarArgs();
 
+  abstract ImmutableSet<TypeMirror> exceptions();
+
+  abstract Builder toBuilder();
+
   final PackageAndClass factoryName() {
     return declaration().getFactoryName();
   }
 
   static Builder builder(AutoFactoryDeclaration declaration) {
-    return new AutoValue_FactoryMethodDescriptor.Builder()
-        .declaration(checkNotNull(declaration))
-        .publicMethod(false)
-        .overridingMethod(false)
-        .isVarArgs(false);
+    return new AutoValue_FactoryMethodDescriptor.Builder().declaration(checkNotNull(declaration));
   }
 
   @AutoValue.Builder
   abstract static class Builder {
     abstract Builder declaration(AutoFactoryDeclaration declaration);
+
     abstract Builder name(String name);
+
     abstract Builder returnType(TypeMirror returnType);
+
     abstract Builder publicMethod(boolean publicMethod);
+
     abstract Builder overridingMethod(boolean overridingMethod);
+
     abstract Builder passedParameters(Iterable<Parameter> passedParameters);
+
     abstract Builder providedParameters(Iterable<Parameter> providedParameters);
+
     abstract Builder creationParameters(Iterable<Parameter> creationParameters);
+
     abstract Builder isVarArgs(boolean isVarargs);
+
+    abstract Builder exceptions(Iterable<? extends TypeMirror> exceptions);
+
     abstract FactoryMethodDescriptor buildImpl();
 
     FactoryMethodDescriptor build() {
       FactoryMethodDescriptor descriptor = buildImpl();
-      checkState(descriptor.creationParameters().equals(
-          Sets.union(descriptor.passedParameters(), descriptor.providedParameters())));
+      checkState(
+          descriptor
+              .creationParameters()
+              .equals(Sets.union(descriptor.passedParameters(), descriptor.providedParameters())));
       return descriptor;
     }
   }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
index 53b99cb..b7f9c3e 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java
@@ -19,19 +19,22 @@
 import static com.squareup.javapoet.MethodSpec.constructorBuilder;
 import static com.squareup.javapoet.MethodSpec.methodBuilder;
 import static com.squareup.javapoet.TypeSpec.classBuilder;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
 import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.PRIVATE;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.lang.model.element.Modifier.STATIC;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.FluentIterable;
+import com.google.auto.common.AnnotationMirrors;
+import com.google.auto.common.AnnotationValues;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
@@ -43,13 +46,19 @@
 import com.squareup.javapoet.TypeSpec;
 import com.squareup.javapoet.TypeVariableName;
 import java.io.IOException;
+import java.lang.annotation.Target;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
 import javax.annotation.processing.Filer;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.inject.Inject;
 import javax.inject.Provider;
 import javax.lang.model.SourceVersion;
 import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.type.TypeVariable;
@@ -71,14 +80,10 @@
     this.factoriesBeingCreated = factoriesBeingCreated;
   }
 
-  private static final Joiner ARGUMENT_JOINER = Joiner.on(", ");
-
-  void writeFactory(FactoryDescriptor descriptor)
-      throws IOException {
+  void writeFactory(FactoryDescriptor descriptor) throws IOException {
     String factoryName = descriptor.name().className();
     TypeSpec.Builder factory =
-        classBuilder(factoryName)
-            .addOriginatingElement(descriptor.declaration().targetType());
+        classBuilder(factoryName).addOriginatingElement(descriptor.declaration().targetType());
     generatedAnnotationSpec(
             elements,
             sourceVersion,
@@ -145,7 +150,7 @@
       ImmutableSet<TypeVariableName> factoryTypeVariables) {
     for (FactoryMethodDescriptor methodDescriptor : descriptor.methodDescriptors()) {
       MethodSpec.Builder method =
-          MethodSpec.methodBuilder(methodDescriptor.name())
+          methodBuilder(methodDescriptor.name())
               .addTypeVariables(getMethodTypeVariables(methodDescriptor, factoryTypeVariables))
               .returns(TypeName.get(methodDescriptor.returnType()))
               .varargs(methodDescriptor.isVarArgs());
@@ -155,6 +160,8 @@
       if (methodDescriptor.publicMethod()) {
         method.addModifiers(PUBLIC);
       }
+      method.addExceptions(
+          methodDescriptor.exceptions().stream().map(TypeName::get).collect(toList()));
       CodeBlock.Builder args = CodeBlock.builder();
       method.addParameters(parameters(methodDescriptor.passedParameters()));
       Iterator<Parameter> parameters = methodDescriptor.creationParameters().iterator();
@@ -168,7 +175,7 @@
             checkNotNull = false;
           }
         } else {
-          ProviderField provider = descriptor.providers().get(parameter.key());
+          ProviderField provider = requireNonNull(descriptor.providers().get(parameter.key()));
           argument = CodeBlock.of(provider.name());
           if (parameter.isProvider()) {
             // Providers are checked for nullness in the Factory's constructor.
@@ -190,8 +197,7 @@
     }
   }
 
-  private void addImplementationMethods(
-      TypeSpec.Builder factory, FactoryDescriptor descriptor) {
+  private void addImplementationMethods(TypeSpec.Builder factory, FactoryDescriptor descriptor) {
     for (ImplementationMethodDescriptor methodDescriptor :
         descriptor.implementationMethodDescriptors()) {
       MethodSpec.Builder implementationMethod =
@@ -202,18 +208,12 @@
       if (methodDescriptor.publicMethod()) {
         implementationMethod.addModifiers(PUBLIC);
       }
+      implementationMethod.addExceptions(
+          methodDescriptor.exceptions().stream().map(TypeName::get).collect(toList()));
       implementationMethod.addParameters(parameters(methodDescriptor.passedParameters()));
       implementationMethod.addStatement(
           "return create($L)",
-          FluentIterable.from(methodDescriptor.passedParameters())
-              .transform(
-                  new Function<Parameter, String>() {
-                    @Override
-                    public String apply(Parameter parameter) {
-                      return parameter.name();
-                    }
-                  })
-              .join(ARGUMENT_JOINER));
+          methodDescriptor.passedParameters().stream().map(Parameter::name).collect(joining(", ")));
       factory.addMethod(implementationMethod.build());
     }
   }
@@ -225,17 +225,43 @@
   private ImmutableList<ParameterSpec> parameters(Iterable<Parameter> parameters) {
     ImmutableList.Builder<ParameterSpec> builder = ImmutableList.builder();
     for (Parameter parameter : parameters) {
-      ParameterSpec.Builder parameterBuilder =
-          ParameterSpec.builder(resolveTypeName(parameter.type().get()), parameter.name());
-      for (AnnotationMirror annotation :
-          Iterables.concat(parameter.nullable().asSet(), parameter.key().qualifier().asSet())) {
-        parameterBuilder.addAnnotation(AnnotationSpec.get(annotation));
-      }
-      builder.add(parameterBuilder.build());
+      TypeName type = resolveTypeName(parameter.type().get());
+      // Remove TYPE_USE annotations, since resolveTypeName will already have included those in
+      // the TypeName it returns.
+      List<AnnotationSpec> annotations =
+          Stream.of(parameter.nullable(), parameter.key().qualifier())
+              .flatMap(Streams::stream)
+              .filter(a -> !isTypeUseAnnotation(a))
+              .map(AnnotationSpec::get)
+              .collect(toList());
+      ParameterSpec parameterSpec =
+          ParameterSpec.builder(type, parameter.name()).addAnnotations(annotations).build();
+      builder.add(parameterSpec);
     }
     return builder.build();
   }
 
+  private static boolean isTypeUseAnnotation(AnnotationMirror mirror) {
+    Element annotationElement = mirror.getAnnotationType().asElement();
+    // This is basically equivalent to:
+    //    Target target = annotationElement.getAnnotation(Target.class);
+    //    return target != null
+    //        && Arrays.asList(annotationElement.getAnnotation(Target.class)).contains(TYPE_USE);
+    // but that might blow up if the annotation is being compiled at the same time and has an
+    // undefined identifier in its @Target values. The rigmarole below avoids that problem.
+    Optional<AnnotationMirror> maybeTargetMirror =
+        Mirrors.getAnnotationMirror(annotationElement, Target.class);
+    return maybeTargetMirror
+        .map(
+            targetMirror ->
+                AnnotationValues.getEnums(
+                        AnnotationMirrors.getAnnotationValue(targetMirror, "value"))
+                    .stream()
+                    .map(VariableElement::getSimpleName)
+                    .anyMatch(name -> name.contentEquals("TYPE_USE")))
+        .orElse(false);
+  }
+
   private static void addCheckNotNullMethod(
       TypeSpec.Builder factory, FactoryDescriptor descriptor) {
     if (shouldGenerateCheckNotNull(descriptor)) {
@@ -290,17 +316,20 @@
    * {@code @AutoFactory class Foo} has a constructor parameter of type {@code BarFactory} and
    * {@code @AutoFactory class Bar} has a constructor parameter of type {@code FooFactory}. We did
    * in fact find instances of this in Google's source base.
+   *
+   * <p>If the type has type annotations then include those in the returned {@link TypeName}.
    */
   private TypeName resolveTypeName(TypeMirror type) {
-    if (type.getKind() != TypeKind.ERROR) {
-      return TypeName.get(type);
+    TypeName typeName = TypeName.get(type);
+    if (type.getKind() == TypeKind.ERROR) {
+      ImmutableSet<PackageAndClass> factoryNames = factoriesBeingCreated.get(type.toString());
+      if (factoryNames.size() == 1) {
+        PackageAndClass packageAndClass = Iterables.getOnlyElement(factoryNames);
+        typeName = ClassName.get(packageAndClass.packageName(), packageAndClass.className());
+      }
     }
-    ImmutableSet<PackageAndClass> factoryNames = factoriesBeingCreated.get(type.toString());
-    if (factoryNames.size() == 1) {
-      PackageAndClass packageAndClass = Iterables.getOnlyElement(factoryNames);
-      return ClassName.get(packageAndClass.packageName(), packageAndClass.className());
-    }
-    return TypeName.get(type);
+    return typeName.annotated(
+        type.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(toList()));
   }
 
   private static ImmutableSet<TypeVariableName> getFactoryTypeVariables(
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java
index 9ddc249..b2705b7 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/ImplementationMethodDescriptor.java
@@ -22,21 +22,25 @@
 @AutoValue
 abstract class ImplementationMethodDescriptor {
   abstract String name();
+
   abstract TypeMirror returnType();
+
   abstract boolean publicMethod();
+
   abstract ImmutableSet<Parameter> passedParameters();
+
   abstract boolean isVarArgs();
 
+  abstract ImmutableSet<TypeMirror> exceptions();
+
   static Builder builder() {
-    return new AutoValue_ImplementationMethodDescriptor.Builder()
-        .publicMethod(true)
-        .isVarArgs(false);
+    return new AutoValue_ImplementationMethodDescriptor.Builder();
   }
 
   @AutoValue.Builder
-  static abstract class Builder {
+  abstract static class Builder {
     abstract Builder name(String name);
-    
+
     abstract Builder returnType(TypeMirror returnTypeElement);
 
     abstract Builder publicMethod(boolean publicMethod);
@@ -49,6 +53,8 @@
 
     abstract Builder isVarArgs(boolean isVarargs);
 
+    abstract Builder exceptions(Iterable<? extends TypeMirror> exceptions);
+
     abstract ImplementationMethodDescriptor build();
   }
 }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Key.java b/factory/src/main/java/com/google/auto/factory/processor/Key.java
index 04bd4f3..728149e 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/Key.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/Key.java
@@ -24,9 +24,8 @@
 import com.google.auto.common.MoreTypes;
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Equivalence;
-import com.google.common.base.Optional;
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.Optional;
 import javax.inject.Qualifier;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.type.TypeMirror;
@@ -65,17 +64,15 @@
    *   <tr><td>{@code int}               <td>{@code Integer}
    * </table>
    */
-  static Key create(
-      TypeMirror type, Iterable<? extends AnnotationMirror> annotations, Types types) {
-    ImmutableSet.Builder<AnnotationMirror> qualifiers = ImmutableSet.builder();
-    for (AnnotationMirror annotation : annotations) {
-      if (isAnnotationPresent(annotation.getAnnotationType().asElement(), Qualifier.class)) {
-        qualifiers.add(annotation);
-      }
-    }
-
+  static Key create(TypeMirror type, Collection<AnnotationMirror> annotations, Types types) {
     // TODO(gak): check for only one qualifier rather than using the first
-    Optional<AnnotationMirror> qualifier = FluentIterable.from(qualifiers.build()).first();
+    Optional<AnnotationMirror> qualifier =
+        annotations.stream()
+            .filter(
+                annotation ->
+                    isAnnotationPresent(
+                        annotation.getAnnotationType().asElement(), Qualifier.class))
+            .findFirst();
 
     TypeMirror keyType =
         isProvider(type)
@@ -97,7 +94,7 @@
   }
 
   @Override
-  public String toString() {
+  public final String toString() {
     String typeQualifiedName = MoreTypes.asTypeElement(type().get()).toString();
     return qualifier().isPresent()
         ? qualifier().get() + "/" + typeQualifiedName
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
index c5b7d8b..313fc9e 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/Mirrors.java
@@ -17,11 +17,11 @@
 
 import com.google.auto.common.MoreTypes;
 import com.google.common.base.Equivalence;
-import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableMap;
 import java.lang.annotation.Annotation;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import javax.inject.Provider;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
@@ -34,20 +34,23 @@
 import javax.lang.model.util.SimpleElementVisitor6;
 
 final class Mirrors {
-  private Mirrors() { }
+  private Mirrors() {}
 
   static Name getQualifiedName(DeclaredType type) {
-    return type.asElement().accept(new SimpleElementVisitor6<Name, Void>() {
-      @Override
-      protected Name defaultAction(Element e, Void p) {
-        throw new AssertionError("DeclaredTypes should be TypeElements");
-      }
+    return type.asElement()
+        .accept(
+            new SimpleElementVisitor6<Name, Void>() {
+              @Override
+              protected Name defaultAction(Element e, Void p) {
+                throw new AssertionError("DeclaredTypes should be TypeElements");
+              }
 
-      @Override
-      public Name visitType(TypeElement e, Void p) {
-        return e.getQualifiedName();
-      }
-    }, null);
+              @Override
+              public Name visitType(TypeElement e, Void p) {
+                return e.getQualifiedName();
+              }
+            },
+            null);
   }
 
   /** {@code true} if {@code type} is a {@link Provider}. */
@@ -62,8 +65,8 @@
   static ImmutableMap<String, AnnotationValue> simplifyAnnotationValueMap(
       Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValueMap) {
     ImmutableMap.Builder<String, AnnotationValue> builder = ImmutableMap.builder();
-    for (Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
-        : annotationValueMap.entrySet()) {
+    for (Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
+        annotationValueMap.entrySet()) {
       builder.put(entry.getKey().getSimpleName().toString(), entry.getValue());
     }
     return builder.build();
@@ -73,15 +76,13 @@
    * Get the {@link AnnotationMirror} for the type {@code annotationType} present on the given
    * {@link Element} if it exists.
    */
-  static Optional<AnnotationMirror> getAnnotationMirror(Element element,
-      Class<? extends Annotation> annotationType) {
+  static Optional<AnnotationMirror> getAnnotationMirror(
+      Element element, Class<? extends Annotation> annotationType) {
     String annotationName = annotationType.getName();
-    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
-      if (getQualifiedName(annotationMirror.getAnnotationType()).contentEquals(annotationName)) {
-        return Optional.of(annotationMirror);
-      }
-    }
-    return Optional.absent();
+    return element.getAnnotationMirrors().stream()
+        .filter(a -> getQualifiedName(a.getAnnotationType()).contentEquals(annotationName))
+        .<AnnotationMirror>map(x -> x) // get rid of wildcard <? extends AnnotationMirror>
+        .findFirst();
   }
 
   /**
@@ -91,9 +92,7 @@
   // TODO(ronshapiro): this is used in AutoFactory and Dagger, consider moving it into auto-common.
   static <T> Optional<Equivalence.Wrapper<T>> wrapOptionalInEquivalence(
       Equivalence<T> equivalence, Optional<T> optional) {
-    return optional.isPresent()
-        ? Optional.of(equivalence.wrap(optional.get()))
-        : Optional.<Equivalence.Wrapper<T>>absent();
+    return optional.map(equivalence::wrap);
   }
 
   /**
@@ -102,8 +101,6 @@
    */
   static <T> Optional<T> unwrapOptionalEquivalence(
       Optional<Equivalence.Wrapper<T>> wrappedOptional) {
-    return wrappedOptional.isPresent()
-        ? Optional.of(wrappedOptional.get().get())
-        : Optional.<T>absent();
+    return wrappedOptional.map(Equivalence.Wrapper::get);
   }
 }
diff --git a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
index 781225a..c5059ec 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/Parameter.java
@@ -18,19 +18,20 @@
 import static com.google.auto.factory.processor.Mirrors.unwrapOptionalEquivalence;
 import static com.google.auto.factory.processor.Mirrors.wrapOptionalInEquivalence;
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.stream.Collectors.toList;
 
 import com.google.auto.common.AnnotationMirrors;
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Equivalence;
-import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Stream;
 import javax.inject.Provider;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.TypeElement;
@@ -63,6 +64,7 @@
   abstract String name();
 
   abstract Key key();
+
   abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper();
 
   Optional<AnnotationMirror> nullable() {
@@ -71,15 +73,12 @@
 
   private static Parameter forVariableElement(
       VariableElement variable, TypeMirror type, Types types) {
-    Optional<AnnotationMirror> nullable = Optional.absent();
-    Iterable<? extends AnnotationMirror> annotations =
-        Iterables.concat(variable.getAnnotationMirrors(), type.getAnnotationMirrors());
-    for (AnnotationMirror annotation : annotations) {
-      if (isNullable(annotation)) {
-        nullable = Optional.of(annotation);
-        break;
-      }
-    }
+    List<AnnotationMirror> annotations =
+        Stream.of(variable.getAnnotationMirrors(), type.getAnnotationMirrors())
+            .flatMap(List::stream)
+            .collect(toList());
+    Optional<AnnotationMirror> nullable =
+        annotations.stream().filter(Parameter::isNullable).findFirst();
 
     Key key = Key.create(type, annotations, types);
     return new AutoValue_Parameter(
@@ -108,7 +107,7 @@
     Set<String> names = Sets.newHashSetWithExpectedSize(variables.size());
     for (int i = 0; i < variables.size(); i++) {
       Parameter parameter = forVariableElement(variables.get(i), variableTypes.get(i), types);
-      checkArgument(names.add(parameter.name()));
+      checkArgument(names.add(parameter.name()), "Duplicate parameter name: %s", parameter.name());
       builder.add(parameter);
     }
     ImmutableSet<Parameter> parameters = builder.build();
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
index fe4c1fd..bd88f83 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/ProvidedChecker.java
@@ -26,6 +26,7 @@
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.util.ElementKindVisitor6;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 final class ProvidedChecker {
   private final Messager messager;
@@ -35,41 +36,54 @@
   }
 
   void checkProvidedParameter(Element element) {
-    checkArgument(isAnnotationPresent(element, Provided.class), "%s not annoated with @Provided",
-        element);
-    element.accept(new ElementKindVisitor6<Void, Void>() {
-      @Override
-      protected Void defaultAction(Element e, Void p) {
-        throw new AssertionError("Provided can only be applied to parameters");
-      }
-
-      @Override
-      public Void visitVariableAsParameter(final VariableElement providedParameter, Void p) {
-        providedParameter.getEnclosingElement().accept(new ElementKindVisitor6<Void, Void>() {
+    checkArgument(
+        isAnnotationPresent(element, Provided.class), "%s not annoated with @Provided", element);
+    element.accept(
+        new ElementKindVisitor6<@Nullable Void, @Nullable Void>() {
           @Override
-          protected Void defaultAction(Element e, Void p) {
-            raiseError(providedParameter, "@%s may only be applied to constructor parameters");
-            return null;
+          protected @Nullable Void defaultAction(Element e, @Nullable Void p) {
+            throw new AssertionError("Provided can only be applied to parameters");
           }
 
           @Override
-          public Void visitExecutableAsConstructor(ExecutableElement constructor, Void p) {
-            if (!(annotatedWithAutoFactory(constructor)
-                || annotatedWithAutoFactory(constructor.getEnclosingElement()))) {
-              raiseError(providedParameter,
-                  "@%s may only be applied to constructors requesting an auto-factory");
-            }
+          public @Nullable Void visitVariableAsParameter(
+              VariableElement providedParameter, @Nullable Void p) {
+            providedParameter
+                .getEnclosingElement()
+                .accept(
+                    new ElementKindVisitor6<@Nullable Void, @Nullable Void>() {
+                      @Override
+                      protected @Nullable Void defaultAction(Element e, @Nullable Void p) {
+                        raiseError(
+                            providedParameter, "@%s may only be applied to constructor parameters");
+                        return null;
+                      }
+
+                      @Override
+                      public @Nullable Void visitExecutableAsConstructor(
+                          ExecutableElement constructor, @Nullable Void p) {
+                        if (!(annotatedWithAutoFactory(constructor)
+                            || annotatedWithAutoFactory(constructor.getEnclosingElement()))) {
+                          raiseError(
+                              providedParameter,
+                              "@%s may only be applied to constructors requesting an auto-factory");
+                        }
+                        return null;
+                      }
+                    },
+                    p);
             return null;
           }
-        }, p);
-        return null;
-      }
-    }, null);
+        },
+        null);
   }
 
   private void raiseError(VariableElement providedParameter, String messageFormat) {
-    messager.printMessage(ERROR, String.format(messageFormat, Provided.class.getSimpleName()),
-        providedParameter, Mirrors.getAnnotationMirror(providedParameter, Provided.class).get());
+    messager.printMessage(
+        ERROR,
+        String.format(messageFormat, Provided.class.getSimpleName()),
+        providedParameter,
+        Mirrors.getAnnotationMirror(providedParameter, Provided.class).get());
   }
 
   private static boolean annotatedWithAutoFactory(Element e) {
diff --git a/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java
index 855c1fe..99847ec 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/ProviderField.java
@@ -21,13 +21,15 @@
 import com.google.auto.common.AnnotationMirrors;
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Equivalence;
-import com.google.common.base.Optional;
+import java.util.Optional;
 import javax.lang.model.element.AnnotationMirror;
 
 @AutoValue
 abstract class ProviderField {
   abstract String name();
+
   abstract Key key();
+
   abstract Optional<Equivalence.Wrapper<AnnotationMirror>> nullableWrapper();
 
   Optional<AnnotationMirror> nullable() {
diff --git a/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java
index 1f89473..4bd546e 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/TypeVariables.java
@@ -38,8 +38,8 @@
     return type.accept(ReferencedTypeVariables.INSTANCE, new HashSet<>());
   }
 
-  private static final class ReferencedTypeVariables extends
-      SimpleTypeVisitor8<ImmutableSet<TypeVariable>, Set<Element>> {
+  private static final class ReferencedTypeVariables
+      extends SimpleTypeVisitor8<ImmutableSet<TypeVariable>, Set<Element>> {
 
     private static final ReferencedTypeVariables INSTANCE = new ReferencedTypeVariables();
 
@@ -53,8 +53,7 @@
     }
 
     @Override
-    public ImmutableSet<TypeVariable> visitDeclared(
-        DeclaredType t, Set<Element> visited) {
+    public ImmutableSet<TypeVariable> visitDeclared(DeclaredType t, Set<Element> visited) {
       if (!visited.add(t.asElement())) {
         return ImmutableSet.of();
       }
@@ -66,8 +65,7 @@
     }
 
     @Override
-    public ImmutableSet<TypeVariable> visitTypeVariable(
-        TypeVariable t, Set<Element> visited) {
+    public ImmutableSet<TypeVariable> visitTypeVariable(TypeVariable t, Set<Element> visited) {
       if (!visited.add(t.asElement())) {
         return ImmutableSet.of();
       }
@@ -79,8 +77,7 @@
     }
 
     @Override
-    public ImmutableSet<TypeVariable> visitUnion(
-        UnionType t, Set<Element> visited) {
+    public ImmutableSet<TypeVariable> visitUnion(UnionType t, Set<Element> visited) {
       ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
       for (TypeMirror unionType : t.getAlternatives()) {
         typeVariables.addAll(unionType.accept(this, visited));
@@ -89,8 +86,7 @@
     }
 
     @Override
-    public ImmutableSet<TypeVariable> visitIntersection(
-        IntersectionType t, Set<Element> visited) {
+    public ImmutableSet<TypeVariable> visitIntersection(IntersectionType t, Set<Element> visited) {
       ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
       for (TypeMirror intersectionType : t.getBounds()) {
         typeVariables.addAll(intersectionType.accept(this, visited));
@@ -99,8 +95,7 @@
     }
 
     @Override
-    public ImmutableSet<TypeVariable> visitWildcard(
-        WildcardType t, Set<Element> visited) {
+    public ImmutableSet<TypeVariable> visitWildcard(WildcardType t, Set<Element> visited) {
       ImmutableSet.Builder<TypeVariable> typeVariables = ImmutableSet.builder();
       TypeMirror extendsBound = t.getExtendsBound();
       if (extendsBound != null) {
diff --git a/factory/src/main/java/com/google/auto/factory/processor/package-info.java b/factory/src/main/java/com/google/auto/factory/processor/package-info.java
index 7339020..4a7c4b1 100644
--- a/factory/src/main/java/com/google/auto/factory/processor/package-info.java
+++ b/factory/src/main/java/com/google/auto/factory/processor/package-info.java
@@ -15,4 +15,5 @@
  * This package contains the annotation processor that implements the
  * {@link com.google.auto.factory.AutoFactory} API.
  */
-package com.google.auto.factory.processor;
\ No newline at end of file
+package com.google.auto.factory.processor;
+
diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java
index 1b23a65..7bef233 100644
--- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java
+++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryDeclarationTest.java
@@ -24,7 +24,8 @@
 
 @RunWith(JUnit4.class)
 public class AutoFactoryDeclarationTest {
-  @Test public void identifiers() {
+  @Test
+  public void identifiers() {
     assertThat(isValidIdentifier("String")).isTrue();
     assertThat(isValidIdentifier("9CantStartWithNumber")).isFalse();
     assertThat(isValidIdentifier("enum")).isFalse();
diff --git a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
index 3088bb2..0df4c9c 100644
--- a/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
+++ b/factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java
@@ -15,15 +15,16 @@
  */
 package com.google.auto.factory.processor;
 
-import static com.google.common.truth.Truth.assertAbout;
-import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
-import static com.google.testing.compile.JavaSourcesSubject.assertThat;
-import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;
+import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
+import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.common.collect.ImmutableSet;
 import com.google.common.io.Resources;
-import com.google.testing.compile.CompilationRule;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
 import com.google.testing.compile.JavaFileObjects;
 import java.io.IOException;
 import java.io.UncheckedIOException;
@@ -31,415 +32,489 @@
 import java.util.List;
 import javax.lang.model.SourceVersion;
 import javax.tools.JavaFileObject;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/**
- * Functional tests for the {@link AutoFactoryProcessor}.
- */
+/** Functional tests for the {@link AutoFactoryProcessor}. */
 @RunWith(JUnit4.class)
 public class AutoFactoryProcessorTest {
+  private final Compiler javac = Compiler.javac().withProcessors(new AutoFactoryProcessor());
 
-  @Rule public final CompilationRule compilationRule = new CompilationRule();
+  @Test
+  public void simpleClass() {
+    Compilation compilation = javac.compile(JavaFileObjects.forResource("good/SimpleClass.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassFactory.java"));
+  }
 
-  @Test public void simpleClass() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/SimpleClass.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassFactory.java"));
+  @Test
+  public void simpleClassWithConstructorThrowsClause() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/SimpleClassThrows.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassThrowsFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassThrowsFactory.java"));
   }
 
   @Test
   public void nestedClasses() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/NestedClasses.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(
-            loadExpectedFile("expected/NestedClasses_SimpleNestedClassFactory.java"),
-            loadExpectedFile("expected/NestedClassCustomNamedFactory.java"));
+    Compilation compilation = javac.compile(JavaFileObjects.forResource("good/NestedClasses.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.NestedClasses_SimpleNestedClassFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/NestedClasses_SimpleNestedClassFactory.java"));
+    assertThat(compilation)
+        .generatedSourceFile("tests.NestedClassCustomNamedFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/NestedClassCustomNamedFactory.java"));
   }
 
-  @Test public void simpleClassNonFinal() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/SimpleClassNonFinal.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassNonFinalFactory.java"));
+  @Test
+  public void simpleClassNonFinal() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/SimpleClassNonFinal.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassNonFinalFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassNonFinalFactory.java"));
   }
 
-  @Test public void publicClass() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/PublicClass.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/PublicClassFactory.java"));
+  @Test
+  public void publicClass() {
+    Compilation compilation = javac.compile(JavaFileObjects.forResource("good/PublicClass.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.PublicClassFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/PublicClassFactory.java"));
   }
 
-  @Test public void simpleClassCustomName() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/SimpleClassCustomName.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/CustomNamedFactory.java"));
+  @Test
+  public void simpleClassCustomName() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/SimpleClassCustomName.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.CustomNamedFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/CustomNamedFactory.java"));
   }
 
-  @Test public void simpleClassMixedDeps() {
-    assertAbout(javaSources())
-        .that(
-            ImmutableSet.of(
-                JavaFileObjects.forResource("good/SimpleClassMixedDeps.java"),
-                JavaFileObjects.forResource("support/AQualifier.java")))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassMixedDepsFactory.java"));
+  @Test
+  public void simpleClassMixedDeps() {
+    Compilation compilation =
+        javac.compile(
+            JavaFileObjects.forResource("good/SimpleClassMixedDeps.java"),
+            JavaFileObjects.forResource("support/AQualifier.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassMixedDepsFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassMixedDepsFactory.java"));
   }
 
-  @Test public void simpleClassPassedDeps() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/SimpleClassPassedDeps.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassPassedDepsFactory.java"));
+  @Test
+  public void simpleClassPassedDeps() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/SimpleClassPassedDeps.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassPassedDepsFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassPassedDepsFactory.java"));
   }
 
-  @Test public void simpleClassProvidedDeps() {
-    assertAbout(javaSources())
-        .that(
-            ImmutableSet.of(
-                JavaFileObjects.forResource("support/AQualifier.java"),
-                JavaFileObjects.forResource("support/BQualifier.java"),
-                JavaFileObjects.forResource("good/SimpleClassProvidedDeps.java")))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassProvidedDepsFactory.java"));
+  @Test
+  public void simpleClassProvidedDeps() {
+    Compilation compilation =
+        javac.compile(
+            JavaFileObjects.forResource("support/AQualifier.java"),
+            JavaFileObjects.forResource("support/BQualifier.java"),
+            JavaFileObjects.forResource("good/SimpleClassProvidedDeps.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassProvidedDepsFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassProvidedDepsFactory.java"));
   }
 
   @Test
   public void simpleClassProvidedProviderDeps() {
-    assertAbout(javaSources())
-        .that(
-            ImmutableSet.of(
-                JavaFileObjects.forResource("support/AQualifier.java"),
-                JavaFileObjects.forResource("support/BQualifier.java"),
-                JavaFileObjects.forResource("good/SimpleClassProvidedProviderDeps.java")))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassProvidedProviderDepsFactory.java"));
+    Compilation compilation =
+        javac.compile(
+            JavaFileObjects.forResource("support/AQualifier.java"),
+            JavaFileObjects.forResource("support/BQualifier.java"),
+            JavaFileObjects.forResource("good/SimpleClassProvidedProviderDeps.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassProvidedProviderDepsFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/SimpleClassProvidedProviderDepsFactory.java"));
   }
 
-  @Test public void constructorAnnotated() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/ConstructorAnnotated.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/ConstructorAnnotatedFactory.java"));
+  @Test
+  public void constructorAnnotated() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotated.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.ConstructorAnnotatedFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/ConstructorAnnotatedFactory.java"));
   }
 
-  @Test public void constructorAnnotatedNonFinal() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/ConstructorAnnotatedNonFinal.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/ConstructorAnnotatedNonFinalFactory.java"));
+  @Test
+  public void constructorWithThrowsClauseAnnotated() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotatedThrows.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.ConstructorAnnotatedThrowsFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/ConstructorAnnotatedThrowsFactory.java"));
   }
 
-  @Test public void simpleClassImplementingMarker() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/SimpleClassImplementingMarker.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassImplementingMarkerFactory.java"));
+  @Test
+  public void constructorAnnotatedNonFinal() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/ConstructorAnnotatedNonFinal.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.ConstructorAnnotatedNonFinalFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/ConstructorAnnotatedNonFinalFactory.java"));
   }
 
-  @Test public void simpleClassImplementingSimpleInterface() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/SimpleClassImplementingSimpleInterface.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(
+  @Test
+  public void simpleClassImplementingMarker() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/SimpleClassImplementingMarker.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassImplementingMarkerFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/SimpleClassImplementingMarkerFactory.java"));
+  }
+
+  @Test
+  public void simpleClassImplementingSimpleInterface() {
+    Compilation compilation =
+        javac.compile(
+            JavaFileObjects.forResource("good/SimpleClassImplementingSimpleInterface.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassImplementingSimpleInterfaceFactory")
+        .hasSourceEquivalentTo(
             loadExpectedFile("expected/SimpleClassImplementingSimpleInterfaceFactory.java"));
   }
 
-  @Test public void mixedDepsImplementingInterfaces() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/MixedDepsImplementingInterfaces.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/MixedDepsImplementingInterfacesFactory.java"));
+  @Test
+  public void mixedDepsImplementingInterfaces() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/MixedDepsImplementingInterfaces.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.MixedDepsImplementingInterfacesFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/MixedDepsImplementingInterfacesFactory.java"));
   }
 
-  @Test public void failsWithMixedFinals() {
+  @Test
+  public void failsWithMixedFinals() {
     JavaFileObject file = JavaFileObjects.forResource("bad/MixedFinals.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining(
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
             "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.")
-            .in(file).onLine(24)
-         .and().withErrorContaining(
+        .inFile(file)
+        .onLine(24);
+    assertThat(compilation)
+        .hadErrorContaining(
             "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.")
-            .in(file).onLine(27);
+        .inFile(file)
+        .onLine(27);
   }
 
-  @Test public void providedButNoAutoFactory() {
+  @Test
+  public void providedButNoAutoFactory() {
     JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedButNoAutoFactory.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining(
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
             "@Provided may only be applied to constructors requesting an auto-factory")
-                .in(file).onLine(21).atColumn(38);
+        .inFile(file)
+        .onLineContaining("@Provided");
   }
 
-  @Test public void providedOnMethodParameter() {
+  @Test
+  public void providedOnMethodParameter() {
     JavaFileObject file = JavaFileObjects.forResource("bad/ProvidedOnMethodParameter.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining(
-            "@Provided may only be applied to constructor parameters")
-                .in(file).onLine(21).atColumn(23);
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("@Provided may only be applied to constructor parameters")
+        .inFile(file)
+        .onLineContaining("@Provided");
   }
 
-  @Test public void invalidCustomName() {
+  @Test
+  public void invalidCustomName() {
     JavaFileObject file = JavaFileObjects.forResource("bad/InvalidCustomName.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining("\"SillyFactory!\" is not a valid Java identifier")
-            .in(file).onLine(20);
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("\"SillyFactory!\" is not a valid Java identifier")
+        .inFile(file)
+        .onLineContaining("SillyFactory!");
   }
 
-  @Test public void factoryExtendingAbstractClass() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/FactoryExtendingAbstractClass.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/FactoryExtendingAbstractClassFactory.java"));
+  @Test
+  public void factoryExtendingAbstractClass() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/FactoryExtendingAbstractClass.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.FactoryExtendingAbstractClassFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/FactoryExtendingAbstractClassFactory.java"));
   }
 
-  @Test public void factoryExtendingAbstractClass_withConstructorParams() {
+  @Test
+  public void factoryWithConstructorThrowsClauseExtendingAbstractClass() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/FactoryExtendingAbstractClassThrows.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.FactoryExtendingAbstractClassThrowsFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/FactoryExtendingAbstractClassThrowsFactory.java"));
+  }
+
+  @Test
+  public void factoryExtendingAbstractClass_withConstructorParams() {
     JavaFileObject file =
-        JavaFileObjects.forResource("good/FactoryExtendingAbstractClassWithConstructorParams.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining(
-            "tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory "
-                + "is not a valid supertype for a factory. "
-                + "Factory supertypes must have a no-arg constructor.")
-                    .in(file).onLine(21);
+        JavaFileObjects.forResource("bad/FactoryExtendingAbstractClassWithConstructorParams.java");
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory is not a"
+                + " valid supertype for a factory. Factory supertypes must have a no-arg"
+                + " constructor.")
+        .inFile(file)
+        .onLineContaining("@AutoFactory");
   }
 
-  @Test public void factoryExtendingAbstractClass_multipleConstructors() {
-    JavaFileObject file = JavaFileObjects.forResource(
-        "good/FactoryExtendingAbstractClassWithMultipleConstructors.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError();
+  @Test
+  public void factoryExtendingAbstractClass_multipleConstructors() {
+    JavaFileObject file =
+        JavaFileObjects.forResource(
+            "good/FactoryExtendingAbstractClassWithMultipleConstructors.java");
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).succeededWithoutWarnings();
   }
 
-  @Test public void factoryExtendingInterface() {
+  @Test
+  public void factoryExtendingInterface() {
     JavaFileObject file = JavaFileObjects.forResource("bad/InterfaceSupertype.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining("java.lang.Runnable is not a valid supertype for a factory. "
-            + "Supertypes must be non-final classes.")
-                .in(file).onLine(20);
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "java.lang.Runnable is not a valid supertype for a factory. Supertypes must be"
+                + " non-final classes.")
+        .inFile(file)
+        .onLineContaining("@AutoFactory");
   }
 
-  @Test public void factoryExtendingEnum() {
+  @Test
+  public void factoryExtendingEnum() {
     JavaFileObject file = JavaFileObjects.forResource("bad/EnumSupertype.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining(
-            "java.util.concurrent.TimeUnit is not a valid supertype for a factory. "
-                + "Supertypes must be non-final classes.")
-                    .in(file).onLine(21);
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "java.util.concurrent.TimeUnit is not a valid supertype for a factory. Supertypes must"
+                + " be non-final classes.")
+        .inFile(file)
+        .onLineContaining("@AutoFactory");
   }
 
-  @Test public void factoryExtendingFinalClass() {
+  @Test
+  public void factoryExtendingFinalClass() {
     JavaFileObject file = JavaFileObjects.forResource("bad/FinalSupertype.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .failsToCompile()
-        .withErrorContaining("java.lang.Boolean is not a valid supertype for a factory. "
-            + "Supertypes must be non-final classes.")
-                .in(file).onLine(20);
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "java.lang.Boolean is not a valid supertype for a factory. Supertypes must be"
+                + " non-final classes.")
+        .inFile(file)
+        .onLineContaining("@AutoFactory");
   }
 
-  @Test public void factoryImplementingGenericInterfaceExtension() {
+  @Test
+  public void factoryImplementingGenericInterfaceExtension() {
     JavaFileObject file =
         JavaFileObjects.forResource("good/FactoryImplementingGenericInterfaceExtension.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.FactoryImplementingGenericInterfaceExtensionFactory")
+        .hasSourceEquivalentTo(
             loadExpectedFile("expected/FactoryImplementingGenericInterfaceExtensionFactory.java"));
   }
 
-  @Test public void multipleFactoriesImpementingInterface() {
+  @Test
+  public void multipleFactoriesImpementingInterface() {
     JavaFileObject file =
         JavaFileObjects.forResource("good/MultipleFactoriesImplementingInterface.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(
-            loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassAFactory.java"),
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.MultipleFactoriesImplementingInterface_ClassAFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassAFactory.java"));
+    assertThat(compilation)
+        .generatedSourceFile("tests.MultipleFactoriesImplementingInterface_ClassBFactory")
+        .hasSourceEquivalentTo(
             loadExpectedFile("expected/MultipleFactoriesImplementingInterface_ClassBFactory.java"));
   }
 
-  @Test public void classUsingQualifierWithArgs() {
-    assertAbout(javaSources())
-        .that(
-            ImmutableSet.of(
-                JavaFileObjects.forResource("support/QualifierWithArgs.java"),
-                JavaFileObjects.forResource("good/ClassUsingQualifierWithArgs.java")))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/ClassUsingQualifierWithArgsFactory.java"));
+  @Test
+  public void classUsingQualifierWithArgs() {
+    Compilation compilation =
+        javac.compile(
+            JavaFileObjects.forResource("support/QualifierWithArgs.java"),
+            JavaFileObjects.forResource("good/ClassUsingQualifierWithArgs.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.ClassUsingQualifierWithArgsFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/ClassUsingQualifierWithArgsFactory.java"));
   }
 
-  @Test public void factoryImplementingInterfaceWhichRedeclaresCreateMethods() {
-    JavaFileObject file =
-        JavaFileObjects.forResource("good/FactoryImplementingCreateMethod.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(
+  @Test
+  public void factoryImplementingInterfaceWhichRedeclaresCreateMethods() {
+    JavaFileObject file = JavaFileObjects.forResource("good/FactoryImplementingCreateMethod.java");
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.FactoryImplementingCreateMethod_ConcreteClassFactory")
+        .hasSourceEquivalentTo(
             loadExpectedFile("expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java"));
   }
 
-  @Test public void nullableParams() {
-    assertAbout(javaSources())
-        .that(
-            ImmutableSet.of(
-                JavaFileObjects.forResource("good/SimpleClassNullableParameters.java"),
-                JavaFileObjects.forResource("support/AQualifier.java"),
-                JavaFileObjects.forResource("support/BQualifier.java")))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassNullableParametersFactory.java"));
+  @Test
+  public void nullableParams() {
+    Compilation compilation =
+        javac.compile(
+            JavaFileObjects.forResource("good/SimpleClassNullableParameters.java"),
+            JavaFileObjects.forResource("support/AQualifier.java"),
+            JavaFileObjects.forResource("support/BQualifier.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassNullableParametersFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/SimpleClassNullableParametersFactory.java"));
   }
 
-  @Test public void customNullableType() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/CustomNullable.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/CustomNullableFactory.java"));
+  @Test
+  public void customNullableType() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/CustomNullable.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.CustomNullableFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/CustomNullableFactory.java"));
   }
 
-  @Test public void checkerFrameworkNullableType() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/CheckerFrameworkNullable.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/CheckerFrameworkNullableFactory.java"));
+  @Test
+  public void checkerFrameworkNullableType() {
+    // TYPE_USE annotations are pretty much unusable with annotation processors on Java 8 because
+    // of bugs that mean they only appear in the javax.lang.model API when the compiler feels like
+    // it. Checking for a java.specification.version that does not start with "1." eliminates 8 and
+    // any earlier version.
+    assume().that(JAVA_SPECIFICATION_VERSION.value()).doesNotMatch("1\\..*");
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/CheckerFrameworkNullable.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.CheckerFrameworkNullableFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/CheckerFrameworkNullableFactory.java"));
   }
 
-  @Test public void multipleProvidedParamsWithSameKey() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/MultipleProvidedParamsSameKey.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/MultipleProvidedParamsSameKeyFactory.java"));
+  @Test
+  public void multipleProvidedParamsWithSameKey() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/MultipleProvidedParamsSameKey.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.MultipleProvidedParamsSameKeyFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/MultipleProvidedParamsSameKeyFactory.java"));
   }
 
-  @Test public void providerArgumentToCreateMethod() {
-    assertAbout(javaSource())
-        .that(JavaFileObjects.forResource("good/ProviderArgumentToCreateMethod.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/ProviderArgumentToCreateMethodFactory.java"));
+  @Test
+  public void providerArgumentToCreateMethod() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/ProviderArgumentToCreateMethod.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.ProviderArgumentToCreateMethodFactory")
+        .hasSourceEquivalentTo(
+            loadExpectedFile("expected/ProviderArgumentToCreateMethodFactory.java"));
   }
 
-  @Test public void multipleFactoriesConflictingParameterNames() {
-    assertThat(
+  @Test
+  public void multipleFactoriesConflictingParameterNames() {
+    Compilation compilation =
+        javac.compile(
             JavaFileObjects.forResource("good/MultipleFactoriesConflictingParameterNames.java"),
-            JavaFileObjects.forResource("support/AQualifier.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(
+            JavaFileObjects.forResource("support/AQualifier.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.MultipleFactoriesConflictingParameterNamesFactory")
+        .hasSourceEquivalentTo(
             loadExpectedFile("expected/MultipleFactoriesConflictingParameterNamesFactory.java"));
   }
 
-  @Test public void factoryVarargs() {
-    assertThat(JavaFileObjects.forResource("good/SimpleClassVarargs.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/SimpleClassVarargsFactory.java"));
+  @Test
+  public void factoryVarargs() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/SimpleClassVarargs.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.SimpleClassVarargsFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/SimpleClassVarargsFactory.java"));
   }
 
-  @Test public void onlyPrimitives() {
-    assertThat(JavaFileObjects.forResource("good/OnlyPrimitives.java"))
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/OnlyPrimitivesFactory.java"));
+  @Test
+  public void onlyPrimitives() {
+    Compilation compilation =
+        javac.compile(JavaFileObjects.forResource("good/OnlyPrimitives.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("tests.OnlyPrimitivesFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/OnlyPrimitivesFactory.java"));
   }
 
   @Test
   public void defaultPackage() {
     JavaFileObject file = JavaFileObjects.forResource("good/DefaultPackage.java");
-    assertAbout(javaSource())
-        .that(file)
-        .processedWith(new AutoFactoryProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(loadExpectedFile("expected/DefaultPackageFactory.java"));
+    Compilation compilation = javac.compile(file);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("DefaultPackageFactory")
+        .hasSourceEquivalentTo(loadExpectedFile("expected/DefaultPackageFactory.java"));
   }
 
   private JavaFileObject loadExpectedFile(String resourceName) {
+    if (isJavaxAnnotationProcessingGeneratedAvailable()) {
+      return JavaFileObjects.forResource(resourceName);
+    }
     try {
       List<String> sourceLines = Resources.readLines(Resources.getResource(resourceName), UTF_8);
-      if (!isJavaxAnnotationProcessingGeneratedAvailable()) {
-        replaceGeneratedImport(sourceLines);
-      }
+      replaceGeneratedImport(sourceLines);
       return JavaFileObjects.forSourceLines(
           resourceName.replace('/', '.').replace(".java", ""), sourceLines);
     } catch (IOException e) {
@@ -457,8 +532,8 @@
     int lastImport = -1;
     for (String line : sourceLines) {
       if (line.startsWith("import ") && !line.startsWith("import static ")) {
-        firstImport = Math.min(firstImport, i);
-        lastImport = Math.max(lastImport, i);
+        firstImport = min(firstImport, i);
+        lastImport = max(lastImport, i);
       }
       i++;
     }
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java b/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java
similarity index 95%
rename from factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
rename to factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java
index 98c5f66..7c7120b 100644
--- a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
+++ b/factory/src/test/resources/bad/FactoryExtendingAbstractClassWithConstructorParams.java
@@ -20,9 +20,9 @@
 
 @AutoFactory(extending = AbstractFactory.class)
 final class FactoryExtendingAbstractClassWithConstructorParams {
-  static abstract class AbstractFactory {
+  abstract static class AbstractFactory {
     protected AbstractFactory(Object obj) {}
-    
+
     abstract FactoryExtendingAbstractClassWithConstructorParams newInstance();
   }
 }
diff --git a/factory/src/test/resources/bad/InvalidCustomName.java b/factory/src/test/resources/bad/InvalidCustomName.java
index 5734ee7..6d0a2f9 100644
--- a/factory/src/test/resources/bad/InvalidCustomName.java
+++ b/factory/src/test/resources/bad/InvalidCustomName.java
@@ -18,4 +18,4 @@
 import com.google.auto.factory.AutoFactory;
 
 @AutoFactory(className = "SillyFactory!")
-final class InvalidCustomName { }
+final class InvalidCustomName {}
diff --git a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
index 79175c7..faa8397 100644
--- a/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
+++ b/factory/src/test/resources/expected/CheckerFrameworkNullableFactory.java
@@ -15,6 +15,7 @@
  */
 package tests;
 
+import java.util.Map;
 import javax.annotation.processing.Generated;
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -22,26 +23,34 @@
 import org.checkerframework.checker.nullness.compatqual.NullableType;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-)
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class CheckerFrameworkNullableFactory {
 
   private final Provider<String> java_lang_StringProvider;
 
+  private final Provider<Map.@NullableType Entry<?, ?>> providedNestedNullableTypeProvider;
+
   @Inject
   CheckerFrameworkNullableFactory(
-      Provider<String> java_lang_StringProvider) {
+      Provider<String> java_lang_StringProvider,
+      Provider<Map.@NullableType Entry<?, ?>> providedNestedNullableTypeProvider) {
     this.java_lang_StringProvider = checkNotNull(java_lang_StringProvider, 1);
+    this.providedNestedNullableTypeProvider = checkNotNull(providedNestedNullableTypeProvider, 2);
   }
 
   CheckerFrameworkNullable create(
-      @NullableDecl String nullableDecl, @NullableType String nullableType) {
+      @NullableDecl String nullableDecl,
+      @NullableType String nullableType,
+      Map.@NullableType Entry<?, ?> nestedNullableType) {
     return new CheckerFrameworkNullable(
         nullableDecl,
         java_lang_StringProvider.get(),
         nullableType,
-        java_lang_StringProvider.get());
+        java_lang_StringProvider.get(),
+        nestedNullableType,
+        providedNestedNullableTypeProvider.get());
   }
 
   private static <T> T checkNotNull(T reference, int argumentIndex) {
diff --git a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
index b5bd89c..8d889ee 100644
--- a/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
+++ b/factory/src/test/resources/expected/ClassUsingQualifierWithArgsFactory.java
@@ -20,14 +20,15 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class ClassUsingQualifierWithArgsFactory {
   private final Provider<String> providedDepAProvider;
 
-  @Inject ClassUsingQualifierWithArgsFactory(
-      @QualifierWithArgs(name="Fred", count=3) Provider<String> providedDepAProvider) {
+  @Inject
+  ClassUsingQualifierWithArgsFactory(
+      @QualifierWithArgs(name = "Fred", count = 3) Provider<String> providedDepAProvider) {
     this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
   }
 
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
index 6e9d242..2234985 100644
--- a/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedFactory.java
@@ -20,13 +20,14 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class ConstructorAnnotatedFactory {
   private final Provider<Object> objProvider;
 
-  @Inject ConstructorAnnotatedFactory(Provider<Object> objProvider) {
+  @Inject
+  ConstructorAnnotatedFactory(Provider<Object> objProvider) {
     this.objProvider = checkNotNull(objProvider, 1);
   }
 
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
index f662642..25ec894 100644
--- a/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedNonFinalFactory.java
@@ -20,13 +20,14 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 class ConstructorAnnotatedNonFinalFactory {
   private final Provider<Object> objProvider;
 
-  @Inject ConstructorAnnotatedNonFinalFactory(Provider<Object> objProvider) {
+  @Inject
+  ConstructorAnnotatedNonFinalFactory(Provider<Object> objProvider) {
     this.objProvider = checkNotNull(objProvider, 1);
   }
 
diff --git a/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java
new file mode 100644
index 0000000..05b30fd
--- /dev/null
+++ b/factory/src/test/resources/expected/ConstructorAnnotatedThrowsFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 tests;
+
+import java.io.IOException;
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+@Generated(
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
+final class ConstructorAnnotatedThrowsFactory {
+  private final Provider<Object> objProvider;
+
+  @Inject
+  ConstructorAnnotatedThrowsFactory(Provider<Object> objProvider) {
+    this.objProvider = checkNotNull(objProvider, 1);
+  }
+
+  ConstructorAnnotatedThrows create() throws IOException, InterruptedException {
+    return new ConstructorAnnotatedThrows();
+  }
+
+  ConstructorAnnotatedThrows create(String s) {
+    return new ConstructorAnnotatedThrows(checkNotNull(s, 1));
+  }
+
+  ConstructorAnnotatedThrows create(int i) throws IOException {
+    return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1), i);
+  }
+
+  ConstructorAnnotatedThrows create(char c) throws InterruptedException {
+    return new ConstructorAnnotatedThrows(checkNotNull(objProvider.get(), 1), c);
+  }
+
+  private static <T> T checkNotNull(T reference, int argumentIndex) {
+    if (reference == null) {
+      throw new NullPointerException(
+          "@AutoFactory method argument is null but is not marked @Nullable. Argument index: "
+              + argumentIndex);
+    }
+    return reference;
+  }
+}
diff --git a/factory/src/test/resources/expected/CustomNamedFactory.java b/factory/src/test/resources/expected/CustomNamedFactory.java
index c388387..512c244 100644
--- a/factory/src/test/resources/expected/CustomNamedFactory.java
+++ b/factory/src/test/resources/expected/CustomNamedFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class CustomNamedFactory {
-  @Inject CustomNamedFactory() {}
+  @Inject
+  CustomNamedFactory() {}
 
   SimpleClassCustomName create() {
     return new SimpleClassCustomName();
diff --git a/factory/src/test/resources/expected/CustomNullableFactory.java b/factory/src/test/resources/expected/CustomNullableFactory.java
index 31bb5bf..c8f2f28 100644
--- a/factory/src/test/resources/expected/CustomNullableFactory.java
+++ b/factory/src/test/resources/expected/CustomNullableFactory.java
@@ -20,9 +20,9 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class CustomNullableFactory {
 
   private final Provider<Object> objectProvider;
diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
index c56afb0..f35b414 100644
--- a/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
+++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassFactory.java
@@ -19,18 +19,20 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class FactoryExtendingAbstractClassFactory
     extends FactoryExtendingAbstractClass.AbstractFactory {
-  @Inject FactoryExtendingAbstractClassFactory() {}
+  @Inject
+  FactoryExtendingAbstractClassFactory() {}
 
   FactoryExtendingAbstractClass create() {
     return new FactoryExtendingAbstractClass();
   }
 
-  @Override public FactoryExtendingAbstractClass newInstance() {
+  @Override
+  public FactoryExtendingAbstractClass newInstance() {
     return create();
   }
 }
diff --git a/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java
new file mode 100644
index 0000000..402f894
--- /dev/null
+++ b/factory/src/test/resources/expected/FactoryExtendingAbstractClassThrowsFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 tests;
+
+import java.io.IOException;
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
+final class FactoryExtendingAbstractClassThrowsFactory
+    extends FactoryExtendingAbstractClassThrows.AbstractFactory {
+  @Inject
+  FactoryExtendingAbstractClassThrowsFactory() {}
+
+  FactoryExtendingAbstractClassThrows create() throws IOException, InterruptedException {
+    return new FactoryExtendingAbstractClassThrows();
+  }
+
+  @Override
+  public FactoryExtendingAbstractClassThrows newInstance() throws Exception {
+    return create();
+  }
+}
diff --git a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
index f23dc9a..2d8a392 100644
--- a/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
+++ b/factory/src/test/resources/expected/FactoryImplementingCreateMethod_ConcreteClassFactory.java
@@ -20,9 +20,9 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class FactoryImplementingCreateMethod_ConcreteClassFactory
     implements FactoryImplementingCreateMethod.FactoryInterfaceWithCreateMethod {
 
@@ -40,7 +40,8 @@
   }
 
   @Override
-  public FactoryImplementingCreateMethod.ConcreteClass create(List<Integer> genericWithDifferentArgumentName) {
+  public FactoryImplementingCreateMethod.ConcreteClass create(
+      List<Integer> genericWithDifferentArgumentName) {
     return new FactoryImplementingCreateMethod.ConcreteClass(
         checkNotNull(genericWithDifferentArgumentName, 1));
   }
diff --git a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
index 3bfc526..9f2bf0a 100644
--- a/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
+++ b/factory/src/test/resources/expected/FactoryImplementingGenericInterfaceExtensionFactory.java
@@ -20,20 +20,23 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class FactoryImplementingGenericInterfaceExtensionFactory
     implements FactoryImplementingGenericInterfaceExtension.MyFactory {
   private final Provider<String> sProvider;
+
   @Inject
   FactoryImplementingGenericInterfaceExtensionFactory(Provider<String> sProvider) {
     this.sProvider = checkNotNull(sProvider, 1);
   }
+
   FactoryImplementingGenericInterfaceExtension create(Integer i) {
     return new FactoryImplementingGenericInterfaceExtension(
         checkNotNull(sProvider.get(), 1), checkNotNull(i, 2));
   }
+
   @Override
   public FactoryImplementingGenericInterfaceExtension make(Integer arg) {
     return create(arg);
diff --git a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
index 19f2c13..ec4089b 100644
--- a/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
+++ b/factory/src/test/resources/expected/MixedDepsImplementingInterfacesFactory.java
@@ -23,15 +23,18 @@
  * @author Gregory Kick
  */
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-)
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class MixedDepsImplementingInterfacesFactory
-    implements MixedDepsImplementingInterfaces.FromInt, MixedDepsImplementingInterfaces.FromObject,
-        MixedDepsImplementingInterfaces.MarkerA, MixedDepsImplementingInterfaces.MarkerB {
+    implements MixedDepsImplementingInterfaces.FromInt,
+        MixedDepsImplementingInterfaces.FromObject,
+        MixedDepsImplementingInterfaces.MarkerA,
+        MixedDepsImplementingInterfaces.MarkerB {
   private final Provider<String> sProvider;
 
-  @Inject MixedDepsImplementingInterfacesFactory(Provider<String> sProvider) {
+  @Inject
+  MixedDepsImplementingInterfacesFactory(Provider<String> sProvider) {
     this.sProvider = checkNotNull(sProvider, 1);
   }
 
@@ -43,11 +46,13 @@
     return new MixedDepsImplementingInterfaces(checkNotNull(o, 1));
   }
 
-  @Override public MixedDepsImplementingInterfaces fromInt(int i) {
+  @Override
+  public MixedDepsImplementingInterfaces fromInt(int i) {
     return create(i);
   }
 
-  @Override public MixedDepsImplementingInterfaces fromObject(Object o) {
+  @Override
+  public MixedDepsImplementingInterfaces fromObject(Object o) {
     return create(o);
   }
 
diff --git a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
index fac6e13..3eaf3af 100644
--- a/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
+++ b/factory/src/test/resources/expected/MultipleFactoriesConflictingParameterNamesFactory.java
@@ -22,7 +22,7 @@
 @Generated(
     value = "com.google.auto.factory.processor.AutoFactoryProcessor",
     comments = "https://github.com/google/auto/tree/master/factory"
-)
+    )
 final class MultipleFactoriesConflictingParameterNamesFactory {
 
   private final Provider<String> stringProvider;
diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
index 5ee2b2f..6fcfb03 100644
--- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
+++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassAFactory.java
@@ -19,9 +19,9 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class MultipleFactoriesImplementingInterface_ClassAFactory
     implements MultipleFactoriesImplementingInterface.Base.Factory {
   @Inject
diff --git a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
index f654068..5664689 100644
--- a/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
+++ b/factory/src/test/resources/expected/MultipleFactoriesImplementingInterface_ClassBFactory.java
@@ -19,9 +19,9 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class MultipleFactoriesImplementingInterface_ClassBFactory
     implements MultipleFactoriesImplementingInterface.Base.Factory {
   @Inject
diff --git a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
index de7bad7..97cc8ac 100644
--- a/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
+++ b/factory/src/test/resources/expected/MultipleProvidedParamsSameKeyFactory.java
@@ -20,9 +20,9 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class MultipleProvidedParamsSameKeyFactory {
   private final Provider<String> java_lang_StringProvider;
 
diff --git a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
index bf6a468..fe7aa1a 100644
--- a/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
+++ b/factory/src/test/resources/expected/NestedClassCustomNamedFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class NestedClassCustomNamedFactory {
-  @Inject NestedClassCustomNamedFactory() {}
+  @Inject
+  NestedClassCustomNamedFactory() {}
 
   NestedClasses.SimpleNestedClassWithCustomFactory create() {
     return new NestedClasses.SimpleNestedClassWithCustomFactory();
diff --git a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
index f982e86..41ecc52 100644
--- a/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
+++ b/factory/src/test/resources/expected/NestedClasses_SimpleNestedClassFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class NestedClasses_SimpleNestedClassFactory {
-  @Inject NestedClasses_SimpleNestedClassFactory() {}
+  @Inject
+  NestedClasses_SimpleNestedClassFactory() {}
 
   NestedClasses.SimpleNestedClass create() {
     return new NestedClasses.SimpleNestedClass();
diff --git a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
index ec60c58..b931a22 100644
--- a/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
+++ b/factory/src/test/resources/expected/OnlyPrimitivesFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class OnlyPrimitivesFactory {
-  @Inject OnlyPrimitivesFactory() {}
+  @Inject
+  OnlyPrimitivesFactory() {}
 
   OnlyPrimitives create(int i, long l) {
     return new OnlyPrimitives(i, l);
diff --git a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
index 4d1a4cf..75a6291 100644
--- a/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
+++ b/factory/src/test/resources/expected/ProviderArgumentToCreateMethodFactory.java
@@ -22,10 +22,11 @@
 @Generated(
     value = "com.google.auto.factory.processor.AutoFactoryProcessor",
     comments = "https://github.com/google/auto/tree/master/factory"
-)
+    )
 final class ProviderArgumentToCreateMethodFactory
-    implements ProviderArgumentToCreateMethod.CustomCreator{
-  @Inject ProviderArgumentToCreateMethodFactory() {}
+    implements ProviderArgumentToCreateMethod.CustomCreator {
+  @Inject
+  ProviderArgumentToCreateMethodFactory() {}
 
   ProviderArgumentToCreateMethod create(Provider<String> stringProvider) {
     return new ProviderArgumentToCreateMethod(checkNotNull(stringProvider, 1));
diff --git a/factory/src/test/resources/expected/PublicClassFactory.java b/factory/src/test/resources/expected/PublicClassFactory.java
index 06671dc..9e5c113 100644
--- a/factory/src/test/resources/expected/PublicClassFactory.java
+++ b/factory/src/test/resources/expected/PublicClassFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 public final class PublicClassFactory {
-  @Inject public PublicClassFactory() {}
+  @Inject
+  public PublicClassFactory() {}
 
   public PublicClass create() {
     return new PublicClass();
diff --git a/factory/src/test/resources/expected/SimpleClassFactory.java b/factory/src/test/resources/expected/SimpleClassFactory.java
index 308d2cd..4741b75 100644
--- a/factory/src/test/resources/expected/SimpleClassFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassFactory {
-  @Inject SimpleClassFactory() {}
+  @Inject
+  SimpleClassFactory() {}
 
   SimpleClass create() {
     return new SimpleClass();
diff --git a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
index 6c611e9..1770138 100644
--- a/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassImplementingMarkerFactory.java
@@ -20,11 +20,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassImplementingMarkerFactory implements RandomAccess {
-  @Inject SimpleClassImplementingMarkerFactory() {}
+  @Inject
+  SimpleClassImplementingMarkerFactory() {}
 
   SimpleClassImplementingMarker create() {
     return new SimpleClassImplementingMarker();
diff --git a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
index 720e7d0..7dd91be 100644
--- a/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassImplementingSimpleInterfaceFactory.java
@@ -19,18 +19,20 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassImplementingSimpleInterfaceFactory
     implements SimpleClassImplementingSimpleInterface.SimpleInterface {
-  @Inject SimpleClassImplementingSimpleInterfaceFactory() {}
+  @Inject
+  SimpleClassImplementingSimpleInterfaceFactory() {}
 
   SimpleClassImplementingSimpleInterface create() {
     return new SimpleClassImplementingSimpleInterface();
   }
 
-  @Override public SimpleClassImplementingSimpleInterface newInstance() {
+  @Override
+  public SimpleClassImplementingSimpleInterface newInstance() {
     return create();
   }
 }
diff --git a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
index ccdea61..b69ea32 100644
--- a/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassMixedDepsFactory.java
@@ -20,14 +20,14 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassMixedDepsFactory {
   private final Provider<String> providedDepAProvider;
 
-  @Inject SimpleClassMixedDepsFactory(
-      @AQualifier Provider<String> providedDepAProvider) {
+  @Inject
+  SimpleClassMixedDepsFactory(@AQualifier Provider<String> providedDepAProvider) {
     this.providedDepAProvider = checkNotNull(providedDepAProvider, 1);
   }
 
diff --git a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
index d323812..5ab9030 100644
--- a/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassNonFinalFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 class SimpleClassNonFinalFactory {
-  @Inject SimpleClassNonFinalFactory() {}
+  @Inject
+  SimpleClassNonFinalFactory() {}
 
   SimpleClassNonFinal create() {
     return new SimpleClassNonFinal();
diff --git a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
index e354038..5b95596 100644
--- a/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassNullableParametersFactory.java
@@ -21,9 +21,9 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassNullableParametersFactory {
   private final Provider<String> providedNullableProvider;
 
diff --git a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
index 3260c36..9cc8a16 100644
--- a/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassPassedDepsFactory.java
@@ -19,11 +19,12 @@
 import javax.inject.Inject;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassPassedDepsFactory {
-  @Inject SimpleClassPassedDepsFactory() {}
+  @Inject
+  SimpleClassPassedDepsFactory() {}
 
   SimpleClassPassedDeps create(String depA, String depB) {
     return new SimpleClassPassedDeps(checkNotNull(depA, 1), checkNotNull(depB, 2));
diff --git a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
index 05d1e5a..52448aa 100644
--- a/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassProvidedDepsFactory.java
@@ -20,9 +20,9 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassProvidedDepsFactory {
   private final Provider<Integer> providedPrimitiveAProvider;
   private final Provider<Integer> providedPrimitiveBProvider;
diff --git a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
index aafdcec..7bf2372 100644
--- a/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassProvidedProviderDepsFactory.java
@@ -20,9 +20,9 @@
 import javax.inject.Provider;
 
 @Generated(
-  value = "com.google.auto.factory.processor.AutoFactoryProcessor",
-  comments = "https://github.com/google/auto/tree/master/factory"
-  )
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
 final class SimpleClassProvidedProviderDepsFactory {
   private final Provider<String> providedDepAProvider;
   private final Provider<String> providedDepBProvider;
diff --git a/factory/src/test/resources/expected/SimpleClassThrowsFactory.java b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java
new file mode 100644
index 0000000..eda503a
--- /dev/null
+++ b/factory/src/test/resources/expected/SimpleClassThrowsFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 tests;
+
+import java.io.IOException;
+import javax.annotation.processing.Generated;
+import javax.inject.Inject;
+
+@Generated(
+    value = "com.google.auto.factory.processor.AutoFactoryProcessor",
+    comments = "https://github.com/google/auto/tree/master/factory"
+    )
+final class SimpleClassThrowsFactory {
+  @Inject
+  SimpleClassThrowsFactory() {}
+
+  SimpleClassThrows create() throws IOException, InterruptedException {
+    return new SimpleClassThrows();
+  }
+}
diff --git a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
index 51c7f46..ac7c4bd 100644
--- a/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
+++ b/factory/src/test/resources/expected/SimpleClassVarargsFactory.java
@@ -21,9 +21,10 @@
 @Generated(
     value = "com.google.auto.factory.processor.AutoFactoryProcessor",
     comments = "https://github.com/google/auto/tree/master/factory"
-)
+    )
 final class SimpleClassVarargsFactory implements SimpleClassVarargs.InterfaceWithVarargs {
-  @Inject SimpleClassVarargsFactory() {}
+  @Inject
+  SimpleClassVarargsFactory() {}
 
   SimpleClassVarargs create(String... args) {
     return new SimpleClassVarargs(checkNotNull(args, 1));
diff --git a/factory/src/test/resources/good/CheckerFrameworkNullable.java b/factory/src/test/resources/good/CheckerFrameworkNullable.java
index 7f2a0fe..8b9cbc2 100644
--- a/factory/src/test/resources/good/CheckerFrameworkNullable.java
+++ b/factory/src/test/resources/good/CheckerFrameworkNullable.java
@@ -17,6 +17,7 @@
 
 import com.google.auto.factory.AutoFactory;
 import com.google.auto.factory.Provided;
+import java.util.Map;
 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
 import org.checkerframework.checker.nullness.compatqual.NullableType;
 
@@ -27,5 +28,7 @@
       @NullableDecl String nullableDecl,
       @Provided @NullableDecl String providedNullableDecl,
       @NullableType String nullableType,
-      @Provided @NullableType String providedNullableType) {}
+      @Provided @NullableType String providedNullableType,
+      Map.@NullableType Entry<?, ?> nestedNullableType,
+      @Provided Map.@NullableType Entry<?, ?> providedNestedNullableType) {}
 }
diff --git a/factory/src/test/resources/good/ConstructorAnnotated.java b/factory/src/test/resources/good/ConstructorAnnotated.java
index fdc02f3..ddb154f 100644
--- a/factory/src/test/resources/good/ConstructorAnnotated.java
+++ b/factory/src/test/resources/good/ConstructorAnnotated.java
@@ -19,9 +19,17 @@
 import com.google.auto.factory.Provided;
 
 final class ConstructorAnnotated {
-  @AutoFactory ConstructorAnnotated() {}
+  @AutoFactory
+  ConstructorAnnotated() {}
+
   ConstructorAnnotated(Object obj) {}
-  @AutoFactory ConstructorAnnotated(String s) {}
-  @AutoFactory ConstructorAnnotated(@Provided Object obj, int i) {}
-  @AutoFactory ConstructorAnnotated(@Provided Object obj, char c) {}
+
+  @AutoFactory
+  ConstructorAnnotated(String s) {}
+
+  @AutoFactory
+  ConstructorAnnotated(@Provided Object obj, int i) {}
+
+  @AutoFactory
+  ConstructorAnnotated(@Provided Object obj, char c) {}
 }
diff --git a/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java
index 5bed1e6..1b10e79 100644
--- a/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java
+++ b/factory/src/test/resources/good/ConstructorAnnotatedNonFinal.java
@@ -19,9 +19,17 @@
 import com.google.auto.factory.Provided;
 
 final class ConstructorAnnotatedNonFinal {
-  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal() {}
+  @AutoFactory(allowSubclasses = true)
+  ConstructorAnnotatedNonFinal() {}
+
   ConstructorAnnotatedNonFinal(Object obj) {}
-  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(String s) {}
-  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, int i) {}
-  @AutoFactory(allowSubclasses = true) ConstructorAnnotatedNonFinal(@Provided Object obj, char c) {}
+
+  @AutoFactory(allowSubclasses = true)
+  ConstructorAnnotatedNonFinal(String s) {}
+
+  @AutoFactory(allowSubclasses = true)
+  ConstructorAnnotatedNonFinal(@Provided Object obj, int i) {}
+
+  @AutoFactory(allowSubclasses = true)
+  ConstructorAnnotatedNonFinal(@Provided Object obj, char c) {}
 }
diff --git a/factory/src/test/resources/good/ConstructorAnnotatedThrows.java b/factory/src/test/resources/good/ConstructorAnnotatedThrows.java
new file mode 100644
index 0000000..58a52d0
--- /dev/null
+++ b/factory/src/test/resources/good/ConstructorAnnotatedThrows.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 tests;
+
+import com.google.auto.factory.AutoFactory;
+import com.google.auto.factory.Provided;
+import java.io.IOException;
+
+final class ConstructorAnnotatedThrows {
+  @AutoFactory
+  ConstructorAnnotatedThrows() throws IOException, InterruptedException {}
+
+  ConstructorAnnotatedThrows(Object obj) {}
+
+  @AutoFactory
+  ConstructorAnnotatedThrows(String s) {}
+
+  @AutoFactory
+  ConstructorAnnotatedThrows(@Provided Object obj, int i) throws IOException {}
+
+  @AutoFactory
+  ConstructorAnnotatedThrows(@Provided Object obj, char c) throws InterruptedException {}
+}
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClass.java b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java
index 5511e99..bd3a4dc 100644
--- a/factory/src/test/resources/good/FactoryExtendingAbstractClass.java
+++ b/factory/src/test/resources/good/FactoryExtendingAbstractClass.java
@@ -20,7 +20,7 @@
 
 @AutoFactory(extending = AbstractFactory.class)
 final class FactoryExtendingAbstractClass {
-  static abstract class AbstractFactory {
+  abstract static class AbstractFactory {
     abstract FactoryExtendingAbstractClass newInstance();
   }
 }
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java
similarity index 63%
copy from factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
copy to factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java
index 98c5f66..2ac43f3 100644
--- a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
+++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassThrows.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Google LLC
+ * Copyright 2020 Google LLC
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,13 +16,14 @@
 package tests;
 
 import com.google.auto.factory.AutoFactory;
-import tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory;
+import java.io.IOException;
+import tests.FactoryExtendingAbstractClassThrows.AbstractFactory;
 
 @AutoFactory(extending = AbstractFactory.class)
-final class FactoryExtendingAbstractClassWithConstructorParams {
-  static abstract class AbstractFactory {
-    protected AbstractFactory(Object obj) {}
-    
-    abstract FactoryExtendingAbstractClassWithConstructorParams newInstance();
+final class FactoryExtendingAbstractClassThrows {
+  FactoryExtendingAbstractClassThrows() throws IOException, InterruptedException {}
+
+  abstract static class AbstractFactory {
+    abstract FactoryExtendingAbstractClassThrows newInstance() throws Exception;
   }
 }
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java
index 43e94ce..20e0b83 100644
--- a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java
+++ b/factory/src/test/resources/good/FactoryExtendingAbstractClassWithMultipleConstructors.java
@@ -20,10 +20,11 @@
 
 @AutoFactory(extending = AbstractFactory.class)
 final class FactoryExtendingAbstractClassWithMultipleConstructors {
-  static abstract class AbstractFactory {
+  abstract static class AbstractFactory {
     protected AbstractFactory(Object obj) {}
+
     protected AbstractFactory() {}
-    
+
     abstract FactoryExtendingAbstractClassWithMultipleConstructors newInstance();
   }
 }
diff --git a/factory/src/test/resources/good/FactoryImplementingCreateMethod.java b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java
index db15eef..d265966 100644
--- a/factory/src/test/resources/good/FactoryImplementingCreateMethod.java
+++ b/factory/src/test/resources/good/FactoryImplementingCreateMethod.java
@@ -26,7 +26,7 @@
     Interface create();
 
     Interface create(int a);
-    
+
     Interface create(List<Integer> generic);
   }
 
diff --git a/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java
index c7435ed..05ee0df 100644
--- a/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java
+++ b/factory/src/test/resources/good/MixedDepsImplementingInterfaces.java
@@ -24,18 +24,18 @@
 final class MixedDepsImplementingInterfaces {
   @AutoFactory(implementing = {FromInt.class, MarkerA.class})
   MixedDepsImplementingInterfaces(@Provided String s, int i) {}
-  
+
   @AutoFactory(implementing = {FromObject.class, MarkerB.class})
   MixedDepsImplementingInterfaces(Object o) {}
 
   interface FromInt {
     MixedDepsImplementingInterfaces fromInt(int i);
   }
-  
+
   interface FromObject {
     MixedDepsImplementingInterfaces fromObject(Object o);
   }
-  
+
   interface MarkerA {}
 
   interface MarkerB {}
diff --git a/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java
index 2eecf1a..f7709ec 100644
--- a/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java
+++ b/factory/src/test/resources/good/MultipleFactoriesImplementingInterface.java
@@ -17,7 +17,7 @@
 
 import com.google.auto.factory.AutoFactory;
 
-class MultipleFactoriesImplementingInterface {  
+class MultipleFactoriesImplementingInterface {
   static interface Base {
     static interface Factory {
       public abstract Base abstractNonDefaultCreate();
@@ -25,8 +25,8 @@
   }
 
   @AutoFactory(implementing = Base.Factory.class)
-  static class ClassA implements Base { }
+  static class ClassA implements Base {}
 
   @AutoFactory(implementing = Base.Factory.class)
   static class ClassB implements Base {}
-}  
+}
diff --git a/factory/src/test/resources/good/SimpleClassImplementingMarker.java b/factory/src/test/resources/good/SimpleClassImplementingMarker.java
index 24e3abc..52a1fd5 100644
--- a/factory/src/test/resources/good/SimpleClassImplementingMarker.java
+++ b/factory/src/test/resources/good/SimpleClassImplementingMarker.java
@@ -1,11 +1,11 @@
 /*
  * Copyright 2013 Google LLC
- * 
+ *
  * 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
@@ -17,5 +17,4 @@
 import java.util.RandomAccess;
 
 @AutoFactory(implementing = RandomAccess.class)
-class SimpleClassImplementingMarker {
-}
+class SimpleClassImplementingMarker {}
diff --git a/factory/src/test/resources/good/SimpleClassProvidedDeps.java b/factory/src/test/resources/good/SimpleClassProvidedDeps.java
index ffcefd2..fa9e0c4 100644
--- a/factory/src/test/resources/good/SimpleClassProvidedDeps.java
+++ b/factory/src/test/resources/good/SimpleClassProvidedDeps.java
@@ -1,11 +1,11 @@
 /*
  * Copyright 2013 Google LLC
- * 
+ *
  * 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
diff --git a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java b/factory/src/test/resources/good/SimpleClassThrows.java
similarity index 60%
copy from factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
copy to factory/src/test/resources/good/SimpleClassThrows.java
index 98c5f66..6715595 100644
--- a/factory/src/test/resources/good/FactoryExtendingAbstractClassWithConstructorParams.java
+++ b/factory/src/test/resources/good/SimpleClassThrows.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013 Google LLC
+ * Copyright 2020 Google LLC
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,13 +16,9 @@
 package tests;
 
 import com.google.auto.factory.AutoFactory;
-import tests.FactoryExtendingAbstractClassWithConstructorParams.AbstractFactory;
+import java.io.IOException;
 
-@AutoFactory(extending = AbstractFactory.class)
-final class FactoryExtendingAbstractClassWithConstructorParams {
-  static abstract class AbstractFactory {
-    protected AbstractFactory(Object obj) {}
-    
-    abstract FactoryExtendingAbstractClassWithConstructorParams newInstance();
-  }
+@AutoFactory
+final class SimpleClassThrows {
+  SimpleClassThrows() throws IOException, InterruptedException {}
 }
diff --git a/factory/src/test/resources/support/QualifierWithArgs.java b/factory/src/test/resources/support/QualifierWithArgs.java
index 81e3f84..89f54eb 100644
--- a/factory/src/test/resources/support/QualifierWithArgs.java
+++ b/factory/src/test/resources/support/QualifierWithArgs.java
@@ -29,5 +29,6 @@
 @Retention(RUNTIME)
 @interface QualifierWithArgs {
   String name();
+
   int count();
 }
diff --git a/service/pom.xml b/service/pom.xml
index a64ca05..e29bdc3 100644
--- a/service/pom.xml
+++ b/service/pom.xml
@@ -37,8 +37,8 @@
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <java.version>1.8</java.version>
-    <guava.version>27.0.1-jre</guava.version>
-    <truth.version>1.0.1</truth.version>
+    <guava.version>30.1.1-jre</guava.version>
+    <truth.version>1.1.3</truth.version>
   </properties>
 
   <scm>
@@ -90,7 +90,7 @@
       <dependency>
         <groupId>com.google.testing.compile</groupId>
         <artifactId>compile-testing</artifactId>
-        <version>0.18</version>
+        <version>0.19</version>
       </dependency>
       <dependency>
         <groupId>com.google.truth</groupId>
@@ -100,7 +100,7 @@
       <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
-        <version>4.12</version>
+        <version>4.13.2</version>
       </dependency>
     </dependencies>
   </dependencyManagement>
@@ -111,7 +111,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
-          <version>3.7.0</version>
+          <version>3.8.1</version>
           <configuration>
             <source>${java.version}</source>
             <target>${java.version}</target>
@@ -123,14 +123,14 @@
             <dependency>
               <groupId>org.codehaus.plexus</groupId>
               <artifactId>plexus-java</artifactId>
-              <version>0.9.4</version>
+              <version>1.0.7</version>
             </dependency>
           </dependencies>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jar-plugin</artifactId>
-          <version>3.0.2</version>
+          <version>3.2.0</version>
         </plugin>
       </plugins>
     </pluginManagement>
diff --git a/service/processor/pom.xml b/service/processor/pom.xml
index 22fc20e..262493a 100644
--- a/service/processor/pom.xml
+++ b/service/processor/pom.xml
@@ -49,7 +49,7 @@
     <dependency>
       <groupId>com.google.auto</groupId>
       <artifactId>auto-common</artifactId>
-      <version>0.10</version>
+      <version>1.1</version>
     </dependency>
     <dependency>
       <groupId>com.google.guava</groupId>
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java b/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
index 3bf42d9..f12299a 100644
--- a/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
+++ b/service/processor/src/main/java/com/google/auto/service/processor/AutoServiceProcessor.java
@@ -17,8 +17,10 @@
 
 import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
 import static com.google.auto.common.MoreElements.getAnnotationMirror;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.common.base.Throwables.getStackTraceAsString;
 
+import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import com.google.auto.service.AutoService;
 import com.google.common.annotations.VisibleForTesting;
@@ -28,8 +30,7 @@
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -57,10 +58,11 @@
  * configuration files described in {@link java.util.ServiceLoader}.
  * <p>
  * Processor Options:<ul>
- *   <li>debug - turns on debug statements</li>
+ *   <li>{@code -Adebug} - turns on debug statements</li>
+ *   <li>{@code -Averify=true} - turns on extra verification</li>
  * </ul>
  */
-@SupportedOptions({ "debug", "verify" })
+@SupportedOptions({"debug", "verify"})
 public class AutoServiceProcessor extends AbstractProcessor {
 
   @VisibleForTesting
@@ -74,7 +76,7 @@
    *   {@code "com.google.apphosting.LocalRpcService" ->
    *   "com.google.apphosting.datastore.LocalDatastoreService"}
    */
-  private Multimap<String, String> providers = HashMultimap.create();
+  private final Multimap<String, String> providers = HashMultimap.create();
 
   @Override
   public ImmutableSet<String> getSupportedAnnotationTypes() {
@@ -104,28 +106,24 @@
   @Override
   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
     try {
-      return processImpl(annotations, roundEnv);
-    } catch (Exception e) {
+      processImpl(annotations, roundEnv);
+    } catch (RuntimeException e) {
       // We don't allow exceptions of any kind to propagate to the compiler
-      StringWriter writer = new StringWriter();
-      e.printStackTrace(new PrintWriter(writer));
-      fatalError(writer.toString());
-      return true;
+      fatalError(getStackTraceAsString(e));
     }
+    return false;
   }
 
-  private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+  private void processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
     if (roundEnv.processingOver()) {
       generateConfigFiles();
     } else {
       processAnnotations(annotations, roundEnv);
     }
-
-    return true;
   }
 
-  private void processAnnotations(Set<? extends TypeElement> annotations,
-      RoundEnvironment roundEnv) {
+  private void processAnnotations(
+      Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
 
     Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
 
@@ -134,7 +132,7 @@
 
     for (Element e : elements) {
       // TODO(gak): check for error trees?
-      TypeElement providerImplementer = (TypeElement) e;
+      TypeElement providerImplementer = MoreElements.asType(e);
       AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
       Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
       if (providerInterfaces.isEmpty()) {
@@ -147,12 +145,14 @@
         log("provider interface: " + providerType.getQualifiedName());
         log("provider implementer: " + providerImplementer.getQualifiedName());
 
-        if (checkImplementer(providerImplementer, providerType)) {
+        if (checkImplementer(providerImplementer, providerType, annotationMirror)) {
           providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
         } else {
-          String message = "ServiceProviders must implement their service provider interface. "
-              + providerImplementer.getQualifiedName() + " does not implement "
-              + providerType.getQualifiedName();
+          String message =
+              "ServiceProviders must implement their service provider interface. "
+                  + providerImplementer.getQualifiedName()
+                  + " does not implement "
+                  + providerType.getQualifiedName();
           error(message, e, annotationMirror);
         }
       }
@@ -172,8 +172,8 @@
           // before we attempt to get the resource in case the behavior
           // of filer.getResource does change to match the spec, but there's
           // no good way to resolve CLASS_OUTPUT without first getting a resource.
-          FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",
-              resourceFile);
+          FileObject existingFile =
+              filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
           log("Looking for existing resource file at " + existingFile.toUri());
           Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
           log("Existing service entries: " + oldServices);
@@ -187,19 +187,18 @@
           log("Resource file did not already exist.");
         }
 
-        Set<String> newServices = new HashSet<String>(providers.get(providerInterface));
-        if (allServices.containsAll(newServices)) {
+        Set<String> newServices = new HashSet<>(providers.get(providerInterface));
+        if (!allServices.addAll(newServices)) {
           log("No new service entries being added.");
-          return;
+          continue;
         }
 
-        allServices.addAll(newServices);
         log("New service file contents: " + allServices);
-        FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "",
-            resourceFile);
-        OutputStream out = fileObject.openOutputStream();
-        ServicesFiles.writeServiceFile(allServices, out);
-        out.close();
+        FileObject fileObject =
+            filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
+        try (OutputStream out = fileObject.openOutputStream()) {
+          ServicesFiles.writeServiceFile(allServices, out);
+        }
         log("Wrote to: " + fileObject.toUri());
       } catch (IOException e) {
         fatalError("Unable to create " + resourceFile + ", " + e);
@@ -209,14 +208,17 @@
   }
 
   /**
-   * Verifies {@link ServiceProvider} constraints on the concrete provider class.
-   * Note that these constraints are enforced at runtime via the ServiceLoader,
-   * we're just checking them at compile time to be extra nice to our users.
+   * Verifies {@link ServiceProvider} constraints on the concrete provider class. Note that these
+   * constraints are enforced at runtime via the ServiceLoader, we're just checking them at compile
+   * time to be extra nice to our users.
    */
-  private boolean checkImplementer(TypeElement providerImplementer, TypeElement providerType) {
+  private boolean checkImplementer(
+      TypeElement providerImplementer,
+      TypeElement providerType,
+      AnnotationMirror annotationMirror) {
 
     String verify = processingEnv.getOptions().get("verify");
-    if (verify == null || !Boolean.valueOf(verify)) {
+    if (verify == null || !Boolean.parseBoolean(verify)) {
       return true;
     }
 
@@ -225,7 +227,37 @@
 
     Types types = processingEnv.getTypeUtils();
 
-    return types.isSubtype(providerImplementer.asType(), providerType.asType());
+    if (types.isSubtype(providerImplementer.asType(), providerType.asType())) {
+      return true;
+    }
+
+    // Maybe the provider has generic type, but the argument to @AutoService can't be generic.
+    // So we allow that with a warning, which can be suppressed with @SuppressWarnings("rawtypes").
+    // See https://github.com/google/auto/issues/870.
+    if (types.isSubtype(providerImplementer.asType(), types.erasure(providerType.asType()))) {
+      if (!rawTypesSuppressed(providerImplementer)) {
+        warning(
+            "Service provider "
+                + providerType
+                + " is generic, so it can't be named exactly by @AutoService."
+                + " If this is OK, add @SuppressWarnings(\"rawtypes\").",
+            providerImplementer,
+            annotationMirror);
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+  private static boolean rawTypesSuppressed(Element element) {
+    for (; element != null; element = element.getEnclosingElement()) {
+      SuppressWarnings suppress = element.getAnnotation(SuppressWarnings.class);
+      if (suppress != null && Arrays.asList(suppress.value()).contains("rawtypes")) {
+        return true;
+      }
+    }
+    return false;
   }
 
   /**
@@ -241,19 +273,20 @@
     Element enclosingElement = element.getEnclosingElement();
 
     if (enclosingElement instanceof PackageElement) {
-      PackageElement pkg = (PackageElement) enclosingElement;
+      PackageElement pkg = MoreElements.asPackage(enclosingElement);
       if (pkg.isUnnamed()) {
         return className;
       }
       return pkg.getQualifiedName() + "." + className;
     }
 
-    TypeElement typeElement = (TypeElement) enclosingElement;
+    TypeElement typeElement = MoreElements.asType(enclosingElement);
     return getBinaryNameImpl(typeElement, typeElement.getSimpleName() + "$" + className);
   }
 
   /**
-   * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code annotationMirror}.
+   * Returns the contents of a {@code Class[]}-typed "value" field in a given {@code
+   * annotationMirror}.
    */
   private ImmutableSet<DeclaredType> getValueFieldOfClasses(AnnotationMirror annotationMirror) {
     return getAnnotationValue(annotationMirror, "value")
@@ -261,16 +294,15 @@
             new SimpleAnnotationValueVisitor8<ImmutableSet<DeclaredType>, Void>() {
               @Override
               public ImmutableSet<DeclaredType> visitType(TypeMirror typeMirror, Void v) {
-                // TODO(ronshapiro): class literals may not always be declared types, i.e. int.class,
-                // int[].class
+                // TODO(ronshapiro): class literals may not always be declared types, i.e.
+                // int.class, int[].class
                 return ImmutableSet.of(MoreTypes.asDeclared(typeMirror));
               }
 
               @Override
               public ImmutableSet<DeclaredType> visitArray(
                   List<? extends AnnotationValue> values, Void v) {
-                return values
-                    .stream()
+                return values.stream()
                     .flatMap(value -> value.accept(this, null).stream())
                     .collect(toImmutableSet());
               }
@@ -284,6 +316,10 @@
     }
   }
 
+  private void warning(String msg, Element element, AnnotationMirror annotation) {
+    processingEnv.getMessager().printMessage(Kind.WARNING, msg, element, annotation);
+  }
+
   private void error(String msg, Element element, AnnotationMirror annotation) {
     processingEnv.getMessager().printMessage(Kind.ERROR, msg, element, annotation);
   }
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
index c61b3ba..75d6cca 100644
--- a/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
+++ b/service/processor/src/main/java/com/google/auto/service/processor/ServicesFiles.java
@@ -35,7 +35,7 @@
 final class ServicesFiles {
   public static final String SERVICES_PATH = "META-INF/services";
 
-  private ServicesFiles() { }
+  private ServicesFiles() {}
 
   /**
    * Returns an absolute path to a service file given the class
@@ -96,4 +96,4 @@
     }
     writer.flush();
   }
-}
\ No newline at end of file
+}
diff --git a/service/processor/src/main/java/com/google/auto/service/processor/package-info.java b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java
index a9f0adb..453c95e 100644
--- a/service/processor/src/main/java/com/google/auto/service/processor/package-info.java
+++ b/service/processor/src/main/java/com/google/auto/service/processor/package-info.java
@@ -15,4 +15,4 @@
  * This package contains the annotation processor that implements the
  * {@link com.google.auto.service.AutoService} API.
  */
-package com.google.auto.service.processor;
\ No newline at end of file
+package com.google.auto.service.processor;
diff --git a/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java b/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
index d3e00a7..3561568 100644
--- a/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
+++ b/service/processor/src/test/java/com/google/auto/service/processor/AutoServiceProcessorTest.java
@@ -16,52 +16,132 @@
 package com.google.auto.service.processor;
 
 import static com.google.auto.service.processor.AutoServiceProcessor.MISSING_SERVICES_ERROR;
-import static com.google.testing.compile.JavaSourcesSubject.assertThat;
+import static com.google.testing.compile.CompilationSubject.assertThat;
 
+import com.google.common.io.Resources;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
 import com.google.testing.compile.JavaFileObjects;
+import javax.tools.StandardLocation;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/**
- * Tests the {@link AutoServiceProcessor}.
- */
+/** Tests the {@link AutoServiceProcessor}. */
 @RunWith(JUnit4.class)
 public class AutoServiceProcessorTest {
   @Test
   public void autoService() {
-      assertThat(
-            JavaFileObjects.forResource("test/SomeService.java"),
-            JavaFileObjects.forResource("test/SomeServiceProvider1.java"),
-            JavaFileObjects.forResource("test/SomeServiceProvider2.java"),
-            JavaFileObjects.forResource("test/Enclosing.java"),
-            JavaFileObjects.forResource("test/AnotherService.java"),
-            JavaFileObjects.forResource("test/AnotherServiceProvider.java"))
-        .processedWith(new AutoServiceProcessor())
-        .compilesWithoutError()
-        .and().generatesFiles(
-            JavaFileObjects.forResource("META-INF/services/test.SomeService"),
-            JavaFileObjects.forResource("META-INF/services/test.AnotherService"));
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(new AutoServiceProcessor())
+            .compile(
+                JavaFileObjects.forResource("test/SomeService.java"),
+                JavaFileObjects.forResource("test/SomeServiceProvider1.java"),
+                JavaFileObjects.forResource("test/SomeServiceProvider2.java"),
+                JavaFileObjects.forResource("test/Enclosing.java"),
+                JavaFileObjects.forResource("test/AnotherService.java"),
+                JavaFileObjects.forResource("test/AnotherServiceProvider.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.SomeService")
+        .hasContents(
+            Resources.asByteSource(Resources.getResource("META-INF/services/test.SomeService")));
+    assertThat(compilation)
+        .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.AnotherService")
+        .hasContents(
+            Resources.asByteSource(Resources.getResource("META-INF/services/test.AnotherService")));
   }
 
   @Test
   public void multiService() {
-    assertThat(
-            JavaFileObjects.forResource("test/SomeService.java"),
-            JavaFileObjects.forResource("test/AnotherService.java"),
-            JavaFileObjects.forResource("test/MultiServiceProvider.java"))
-        .processedWith(new AutoServiceProcessor())
-        .compilesWithoutError()
-        .and().generatesFiles(
-            JavaFileObjects.forResource("META-INF/services/test.SomeServiceMulti"),
-            JavaFileObjects.forResource("META-INF/services/test.AnotherServiceMulti"));
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(new AutoServiceProcessor())
+            .compile(
+                JavaFileObjects.forResource("test/SomeService.java"),
+                JavaFileObjects.forResource("test/AnotherService.java"),
+                JavaFileObjects.forResource("test/MultiServiceProvider.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    // We have @AutoService({SomeService.class, AnotherService.class}) class MultiServiceProvider.
+    // So we expect META-INF/services/test.SomeService with contents that name MultiServiceProvider
+    // and likewise META-INF/services/test.AnotherService.
+    assertThat(compilation)
+        .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.SomeService")
+        .contentsAsUtf8String()
+        .isEqualTo("test.MultiServiceProvider\n");
+    assertThat(compilation)
+        .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.AnotherService")
+        .contentsAsUtf8String()
+        .isEqualTo("test.MultiServiceProvider\n");
   }
 
   @Test
   public void badMultiService() {
-    assertThat(JavaFileObjects.forResource("test/NoServices.java"))
-        .processedWith(new AutoServiceProcessor())
-        .failsToCompile()
-        .withErrorContaining(MISSING_SERVICES_ERROR);
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(new AutoServiceProcessor())
+            .compile(JavaFileObjects.forResource("test/NoServices.java"));
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorContaining(MISSING_SERVICES_ERROR);
+  }
+
+  @Test
+  public void generic() {
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(new AutoServiceProcessor())
+            .compile(
+                JavaFileObjects.forResource("test/GenericService.java"),
+                JavaFileObjects.forResource("test/GenericServiceProvider.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.GenericService")
+        .contentsAsUtf8String()
+        .isEqualTo("test.GenericServiceProvider\n");
+  }
+
+  @Test
+  public void genericWithVerifyOption() {
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(new AutoServiceProcessor())
+            .withOptions("-Averify=true")
+            .compile(
+                JavaFileObjects.forResource("test/GenericService.java"),
+                JavaFileObjects.forResource("test/GenericServiceProvider.java"));
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .hadWarningContaining(
+            "Service provider test.GenericService is generic, so it can't be named exactly by"
+                + " @AutoService. If this is OK, add @SuppressWarnings(\"rawtypes\").");
+  }
+
+  @Test
+  public void genericWithVerifyOptionAndSuppressWarings() {
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(new AutoServiceProcessor())
+            .withOptions("-Averify=true")
+            .compile(
+                JavaFileObjects.forResource("test/GenericService.java"),
+                JavaFileObjects.forResource("test/GenericServiceProviderSuppressWarnings.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
+  public void nestedGenericWithVerifyOptionAndSuppressWarnings() {
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(new AutoServiceProcessor())
+            .withOptions("-Averify=true")
+            .compile(
+                JavaFileObjects.forResource("test/GenericService.java"),
+                JavaFileObjects.forResource("test/EnclosingGeneric.java"));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedFile(StandardLocation.CLASS_OUTPUT, "META-INF/services/test.GenericService")
+        .contentsAsUtf8String()
+        .isEqualTo("test.EnclosingGeneric$GenericServiceProvider\n");
   }
 }
diff --git a/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti b/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti
deleted file mode 100644
index f6ef36a..0000000
--- a/service/processor/src/test/resources/META-INF/services/test.AnotherServiceMulti
+++ /dev/null
@@ -1 +0,0 @@
-test.MultiServiceProvider
diff --git a/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti b/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti
deleted file mode 100644
index f6ef36a..0000000
--- a/service/processor/src/test/resources/META-INF/services/test.SomeServiceMulti
+++ /dev/null
@@ -1 +0,0 @@
-test.MultiServiceProvider
diff --git a/service/processor/src/test/resources/test/AnotherService.java b/service/processor/src/test/resources/test/AnotherService.java
index c096c22..de80f06 100644
--- a/service/processor/src/test/resources/test/AnotherService.java
+++ b/service/processor/src/test/resources/test/AnotherService.java
@@ -15,4 +15,4 @@
  */
 package test;
 
-interface AnotherService { }
\ No newline at end of file
+interface AnotherService {}
diff --git a/service/processor/src/test/resources/test/AnotherServiceProvider.java b/service/processor/src/test/resources/test/AnotherServiceProvider.java
index c5e5c11..2a023e4 100644
--- a/service/processor/src/test/resources/test/AnotherServiceProvider.java
+++ b/service/processor/src/test/resources/test/AnotherServiceProvider.java
@@ -18,4 +18,4 @@
 import com.google.auto.service.AutoService;
 
 @AutoService(AnotherService.class)
-public class AnotherServiceProvider implements AnotherService { }
\ No newline at end of file
+public class AnotherServiceProvider implements AnotherService {}
diff --git a/service/processor/src/test/resources/test/Enclosing.java b/service/processor/src/test/resources/test/Enclosing.java
index 26dd585..24202a5 100644
--- a/service/processor/src/test/resources/test/Enclosing.java
+++ b/service/processor/src/test/resources/test/Enclosing.java
@@ -19,5 +19,5 @@
 
 public class Enclosing {
   @AutoService(SomeService.class)
-  public static class NestedSomeServiceProvider implements SomeService { }
-}
\ No newline at end of file
+  public static class NestedSomeServiceProvider implements SomeService {}
+}
diff --git a/service/processor/src/test/resources/test/EnclosingGeneric.java b/service/processor/src/test/resources/test/EnclosingGeneric.java
new file mode 100644
index 0000000..cddddac
--- /dev/null
+++ b/service/processor/src/test/resources/test/EnclosingGeneric.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 test;
+
+import com.google.auto.service.AutoService;
+
+/** Test for suppressing warnings about raw types on nested {@code @AutoService} classes. */
+@SuppressWarnings("rawtypes")
+public final class EnclosingGeneric {
+  /**
+   * This is technically a raw class reference, but should be suppressed by the
+   * {@code @SuppressWarnings} on the enclosing class.
+   */
+  @AutoService(GenericService.class)
+  public class GenericServiceProvider<T> implements GenericService<T> {}
+
+  private EnclosingGeneric() {}
+}
diff --git a/service/processor/src/test/resources/test/GenericService.java b/service/processor/src/test/resources/test/GenericService.java
new file mode 100644
index 0000000..5ed13ff
--- /dev/null
+++ b/service/processor/src/test/resources/test/GenericService.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 test;
+
+/**
+ * An interface with a type parameter, which by default will produce a warning with
+ * {@code @AutoService} if you compile with {@code -Averify=true}.
+ */
+public interface GenericService<T> {}
diff --git a/service/processor/src/test/resources/test/GenericServiceProvider.java b/service/processor/src/test/resources/test/GenericServiceProvider.java
new file mode 100644
index 0000000..84c5cba
--- /dev/null
+++ b/service/processor/src/test/resources/test/GenericServiceProvider.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 test;
+
+import com.google.auto.service.AutoService;
+
+/**
+ * An implementation of a service with a type parameter, which by default will produce a warning
+ * if you compile with {@code -Averify=true}.
+ */
+@AutoService(GenericService.class)
+public class GenericServiceProvider<T> implements GenericService<T> {}
diff --git a/service/processor/src/test/resources/test/GenericServiceProviderSuppressWarnings.java b/service/processor/src/test/resources/test/GenericServiceProviderSuppressWarnings.java
new file mode 100644
index 0000000..eb285da
--- /dev/null
+++ b/service/processor/src/test/resources/test/GenericServiceProviderSuppressWarnings.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 test;
+
+import com.google.auto.service.AutoService;
+
+/**
+ * An implementation of a service with a type parameter, which will not produce a warning even if
+ * compiled with {@code -Averify=true}, because of the {@code @SuppressWarnings}.
+ */
+@AutoService(GenericService.class)
+@SuppressWarnings("rawtypes")
+public class GenericServiceProviderSuppressWarnings<T> implements GenericService<T> {}
diff --git a/service/processor/src/test/resources/test/SomeService.java b/service/processor/src/test/resources/test/SomeService.java
index d29c409..d81cac4 100644
--- a/service/processor/src/test/resources/test/SomeService.java
+++ b/service/processor/src/test/resources/test/SomeService.java
@@ -15,4 +15,4 @@
  */
 package test;
 
-interface SomeService { }
\ No newline at end of file
+interface SomeService {}
diff --git a/service/processor/src/test/resources/test/SomeServiceProvider1.java b/service/processor/src/test/resources/test/SomeServiceProvider1.java
index 008136b..fc2c843 100644
--- a/service/processor/src/test/resources/test/SomeServiceProvider1.java
+++ b/service/processor/src/test/resources/test/SomeServiceProvider1.java
@@ -18,4 +18,4 @@
 import com.google.auto.service.AutoService;
 
 @AutoService(SomeService.class)
-public class SomeServiceProvider1 implements SomeService { }
\ No newline at end of file
+public class SomeServiceProvider1 implements SomeService {}
diff --git a/service/processor/src/test/resources/test/SomeServiceProvider2.java b/service/processor/src/test/resources/test/SomeServiceProvider2.java
index 5444996..b7097d4 100644
--- a/service/processor/src/test/resources/test/SomeServiceProvider2.java
+++ b/service/processor/src/test/resources/test/SomeServiceProvider2.java
@@ -18,4 +18,4 @@
 import com.google.auto.service.AutoService;
 
 @AutoService(SomeService.class)
-public class SomeServiceProvider2 implements SomeService { }
\ No newline at end of file
+public class SomeServiceProvider2 implements SomeService {}
diff --git a/util/generate-latest-docs.sh b/util/generate-latest-docs.sh
index 8617ba4..88c8e7f 100755
--- a/util/generate-latest-docs.sh
+++ b/util/generate-latest-docs.sh
@@ -1,25 +1,25 @@
-# see http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ for details
+#!/bin/bash
 
-if [ "$TRAVIS_REPO_SLUG" == "google/auto" ] && \
-   [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \
-   [ "$TRAVIS_PULL_REQUEST" == "false" ] && \
-   [ "$TRAVIS_BRANCH" == "master" ]; then
-  echo -e "Publishing javadoc...\n"
-  
-  mvn -f build-pom.xml javadoc:aggregate
-  TARGET="$(pwd)/target"
+# Run by GitHub Actions (see .github/workflows/ci.yml)
 
-  cd $HOME
-  git clone --quiet --branch=gh-pages https://${GH_TOKEN}@github.com/google/auto gh-pages > /dev/null
-  
-  cd gh-pages
-  git config --global user.email "travis@travis-ci.org"
-  git config --global user.name "travis-ci"
-  git rm -rf api/latest 
-  mv ${TARGET}/site/apidocs api/latest
-  git add -A -f api/latest
-  git commit -m "Latest javadoc on successful travis build $TRAVIS_BUILD_NUMBER auto-pushed to gh-pages"
-  git push -fq origin gh-pages > /dev/null
+set -e
 
-  echo -e "Published Javadoc to gh-pages.\n"
-fi
+echo -e "Publishing javadoc...\n"
+
+mvn -f build-pom.xml javadoc:aggregate
+TARGET="$(pwd)/target"
+
+cd $HOME
+git clone --quiet --branch=gh-pages "https://x-access-token:${GITHUB_TOKEN}@github.com/google/auto" gh-pages > /dev/null
+
+cd gh-pages
+git config --global user.name "$GITHUB_ACTOR"
+git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
+git rm -rf api/latest
+mkdir -p api # Just to make mv work if the directory is missing
+mv ${TARGET}/site/apidocs api/latest
+git add -A -f api/latest
+git commit -m "Latest javadoc on successful CI build auto-pushed to gh-pages"
+git push -fq origin gh-pages > /dev/null
+
+echo -e "Published Javadoc to gh-pages.\n"
diff --git a/util/publish-snapshot-on-commit.sh b/util/publish-snapshot-on-commit.sh
index 0b74c6b..0166b05 100755
--- a/util/publish-snapshot-on-commit.sh
+++ b/util/publish-snapshot-on-commit.sh
@@ -1,12 +1,6 @@
-# see https://coderwall.com/p/9b_lfq
+#!/bin/bash
 
-if [ "$TRAVIS_REPO_SLUG" == "google/auto" ] && \
-   [ "$TRAVIS_JDK_VERSION" == "oraclejdk7" ] && \
-   [ "$TRAVIS_PULL_REQUEST" == "false" ] && \
-   [ "$TRAVIS_BRANCH" == "master" ]; then
-  echo -e "Publishing maven snapshot...\n"
+set -e
 
-  mvn -f build-pom.xml clean source:jar deploy --settings="util/settings.xml" -DskipTests=true -Dmaven.javadoc.skip=true
-
-  echo -e "Published maven snapshot"
-fi
+mvn -B dependency:go-offline test clean -U --quiet --fail-never -DskipTests=true -f build-pom.xml
+mvn -B -U source:jar deploy -DskipTests=true -f build-pom.xml
diff --git a/util/settings.xml b/util/settings.xml
deleted file mode 100644
index 91f444b..0000000
--- a/util/settings.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<settings>
-  <servers>
-    <server>
-      <id>sonatype-nexus-snapshots</id>
-      <username>${env.CI_DEPLOY_USERNAME}</username>
-      <password>${env.CI_DEPLOY_PASSWORD}</password>
-    </server>
-  </servers>
-</settings>
diff --git a/value/Android.bp b/value/Android.bp
index 73a7215..2f554aa 100644
--- a/value/Android.bp
+++ b/value/Android.bp
@@ -72,6 +72,23 @@
 }
 
 java_plugin {
+    name: "auto_oneof_plugin",
+    static_libs: [
+        "libauto_value_plugin",
+
+        "auto_android_annotation_stubs",
+        "auto_common",
+        "auto_service_plugin",
+        "auto_value_extension",
+        "escapevelocity",
+        "guava",
+        "javapoet",
+    ],
+    processor_class: "com.google.auto.value.processor.AutoOneOfProcessor",
+    visibility: ["//visibility:public"],
+}
+
+java_plugin {
     name: "auto_annotation_plugin",
     static_libs: [
         "libauto_value_plugin",
diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml
index d6c03de..8bc63ac 100644
--- a/value/annotations/pom.xml
+++ b/value/annotations/pom.xml
@@ -54,6 +54,7 @@
             <include>com/google/auto/value/*</include>
             <include>com/google/auto/value/extension/memoized/*</include>
             <include>com/google/auto/value/extension/serializable/*</include>
+            <include>com/google/auto/value/extension/toprettystring/*</include>
           </includes>
         </configuration>
       </plugin>
diff --git a/value/pom.xml b/value/pom.xml
index dcf2cb1..5405c7c 100644
--- a/value/pom.xml
+++ b/value/pom.xml
@@ -37,8 +37,8 @@
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <java.version>1.8</java.version>
-    <guava.version>28.1-jre</guava.version>
-    <truth.version>1.0.1</truth.version>
+    <guava.version>30.1.1-jre</guava.version>
+    <truth.version>1.1.3</truth.version>
   </properties>
 
   <scm>
@@ -89,7 +89,7 @@
       <dependency>
         <groupId>com.squareup</groupId>
         <artifactId>javapoet</artifactId>
-        <version>1.11.1</version>
+        <version>1.13.0</version>
       </dependency>
 
       <!-- test dependencies -->
@@ -107,7 +107,7 @@
       <dependency>
         <groupId>com.google.testing.compile</groupId>
         <artifactId>compile-testing</artifactId>
-        <version>0.18</version>
+        <version>0.19</version>
       </dependency>
       <dependency>
         <groupId>com.google.truth</groupId>
@@ -122,7 +122,7 @@
       <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
-        <version>4.13</version>
+        <version>4.13.2</version>
       </dependency>
       <dependency>
         <groupId>org.apache.velocity</groupId>
@@ -138,7 +138,7 @@
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
-          <version>3.7.0</version>
+          <version>3.8.1</version>
           <configuration>
             <source>${java.version}</source>
             <target>${java.version}</target>
@@ -150,19 +150,19 @@
             <dependency>
               <groupId>org.codehaus.plexus</groupId>
               <artifactId>plexus-java</artifactId>
-              <version>0.9.4</version>
+              <version>1.0.7</version>
             </dependency>
           </dependencies>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-jar-plugin</artifactId>
-          <version>3.0.2</version>
+          <version>3.2.0</version>
         </plugin>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-invoker-plugin</artifactId>
-          <version>3.0.1</version>
+          <version>3.2.2</version>
           <configuration>
             <addTestClassPath>true</addTestClassPath>
             <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
diff --git a/value/processor/pom.xml b/value/processor/pom.xml
index 5968352..8892c4f 100644
--- a/value/processor/pom.xml
+++ b/value/processor/pom.xml
@@ -40,23 +40,26 @@
     <tag>HEAD</tag>
   </scm>
 
+  <properties>
+    <auto-service.version>1.0</auto-service.version>
+    <errorprone.version>2.7.1</errorprone.version>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>com.google.auto</groupId>
       <artifactId>auto-common</artifactId>
-      <version>0.10</version>
+      <version>1.1</version>
     </dependency>
     <dependency>
       <groupId>com.google.auto.service</groupId>
-      <artifactId>auto-service</artifactId>
-      <version>1.0-rc6</version>
-      <scope>provided</scope>
+      <artifactId>auto-service-annotations</artifactId>
+      <version>${auto-service.version}</version>
     </dependency>
     <dependency>
       <groupId>com.google.errorprone</groupId>
       <artifactId>error_prone_annotations</artifactId>
-      <version>2.3.3</version>
-      <scope>provided</scope>
+      <version>${errorprone.version}</version>
     </dependency>
     <dependency>
       <groupId>com.google.escapevelocity</groupId>
@@ -66,13 +69,7 @@
     <dependency>
       <groupId>net.ltgt.gradle.incap</groupId>
       <artifactId>incap</artifactId>
-      <version>0.2</version>
-    </dependency>
-    <dependency>
-      <groupId>net.ltgt.gradle.incap</groupId>
-      <artifactId>incap-processor</artifactId>
-      <version>0.2</version>
-      <scope>provided</scope>
+      <version>0.3</version>
     </dependency>
     <dependency>
       <groupId>com.google.guava</groupId>
@@ -90,6 +87,12 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>com.google.errorprone</groupId>
+      <artifactId>error_prone_type_annotations</artifactId>
+      <version>${errorprone.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.apache.velocity</groupId>
       <artifactId>velocity</artifactId>
       <scope>test</scope>
@@ -122,7 +125,7 @@
     <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
-       <version>3.1.0</version>
+       <version>3.11.2</version>
        <scope>test</scope>
      </dependency>
   </dependencies>
@@ -149,7 +152,55 @@
             <include>com/google/auto/value/extension/memoized/processor/**/*.java</include>
             <include>com/google/auto/value/extension/serializable/processor/**/*.java</include>
             <include>com/google/auto/value/extension/serializable/serializer/**/*.java</include>
+            <include>com/google/auto/value/extension/toprettystring/processor/**/*.java</include>
           </includes>
+          <compilerArgs>
+            <!-- This is something of a hack to allow tests to pass. Ideally we would build
+                 TestStringSerializerFactory as a separate artifact, to avoid a problem when it
+                 is built at the same time as @AutoValue classes that might end up finding it.
+                 But by allowing a missing class to be ignored, we avoid crashing if there is a
+                 META-INF/services entry for a class that the compiler has not yet generated. -->
+            <arg>-AallowedMissingSerializableExtensionClasses=.*TestStringSerializerFactory</arg>
+          </compilerArgs>
+          <annotationProcessorPaths>
+            <path>
+              <groupId>com.google.auto.service</groupId>
+              <artifactId>auto-service</artifactId>
+              <version>${auto-service.version}</version>
+            </path>
+            <path>
+              <groupId>net.ltgt.gradle.incap</groupId>
+              <artifactId>incap-processor</artifactId>
+              <version>0.3</version>
+            </path>
+          </annotationProcessorPaths>
+        </configuration>
+        <executions>
+          <execution>
+            <id>default-testCompile</id>
+            <configuration>
+              <annotationProcessorPaths>
+                <path>
+                  <groupId>com.google.auto.value</groupId>
+                  <artifactId>auto-value</artifactId>
+                  <version>${project.version}</version>
+                </path>
+                <path>
+                  <groupId>com.google.auto.service</groupId>
+                  <artifactId>auto-service</artifactId>
+                  <version>${auto-service.version}</version>
+                </path>
+              </annotationProcessorPaths>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.22.2</version>
+        <configuration>
+          <argLine>${test.jvm.flags}</argLine>
         </configuration>
       </plugin>
       <plugin>
@@ -217,5 +268,14 @@
         <additionalparam>-Xdoclint:none</additionalparam>
       </properties>
     </profile>
+    <profile>
+      <id>open-modules</id>
+      <activation>
+        <jdk>[9,)</jdk>
+      </activation>
+      <properties>
+        <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</test.jvm.flags>
+      </properties>
+    </profile>
   </profiles>
 </project>
diff --git a/value/src/it/functional/pom.xml b/value/src/it/functional/pom.xml
index 750b9c4..d4ae138 100644
--- a/value/src/it/functional/pom.xml
+++ b/value/src/it/functional/pom.xml
@@ -32,6 +32,7 @@
   <version>1.7.4</version>
   <name>Auto-Value Functional Integration Test</name>
   <properties>
+    <kotlin.version>1.5.21</kotlin.version>
     <exclude.tests>this-matches-nothing</exclude.tests>
   </properties>
   <dependencies>
@@ -48,7 +49,7 @@
     <dependency>
       <groupId>com.google.auto.service</groupId>
       <artifactId>auto-service</artifactId>
-      <version>1.0-rc6</version>
+      <version>1.0</version>
     </dependency>
     <dependency>
       <groupId>com.google.guava</groupId>
@@ -62,7 +63,7 @@
     <dependency>
       <groupId>com.google.gwt</groupId>
       <artifactId>gwt-user</artifactId>
-      <version>2.8.2</version>
+      <version>2.9.0</version>
     </dependency>
     <dependency>
       <groupId>junit</groupId>
@@ -90,21 +91,32 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>dev.gradleplugins</groupId>
+      <artifactId>gradle-test-kit</artifactId>
+      <version>6.8.3</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
-      <version>3.1.0</version>
+      <version>3.11.2</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.eclipse.jdt</groupId>
       <artifactId>ecj</artifactId>
-      <version>3.20.0</version>
+      <version>3.25.0</version>
     </dependency>
     <dependency>
       <groupId>com.google.escapevelocity</groupId>
       <artifactId>escapevelocity</artifactId>
       <version>0.9.1</version>
     </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
   </dependencies>
 
   <build>
@@ -112,17 +124,49 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
-       <version>3.0.2</version>
+        <version>3.2.0</version>
+      </plugin>
+      <plugin>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <artifactId>kotlin-maven-plugin</artifactId>
+        <version>${kotlin.version}</version>
+        <executions>
+          <!--
+          The Kotlin configuration here is a bit unusual. JetBrains recommends
+          <https://kotlinlang.org/docs/maven.html#compile-kotlin-and-java-sources>
+          a fairly invasive reconfiguration of the maven-compiler-plugin (which compiles Java)
+          in order to ensure that the Kotlin compiler can run first. In our case, we have just
+          one Kotlin file that a test in Java accesses. So, even though it is in src/test/java,
+          we compile it in the `compile` goal, which means it is available to the Java test sources
+          when they are compiled in the `default-testCompile` goal.
+
+          Currently if you want to use JDK ≥ 16 then you must set
+          JAVA_TOOL_OPTIONS=__illegal-access=permit
+          except the two underscores should be dashes (which XML comments won't allow us to write).
+          This is a bug that will presumably be fixed in a forthcoming version.
+          -->
+          <execution>
+            <id>compile</id>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/test/java</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+        </executions>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.7.0</version>
+        <version>3.8.1</version>
         <dependencies>
           <dependency>
             <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-java</artifactId>
-            <version>0.9.4</version>
+            <version>1.0.7</version>
           </dependency>
         </dependencies>
         <configuration>
@@ -132,6 +176,7 @@
             <arg>-Xlint:all</arg>
             <arg>-encoding</arg>
             <arg>utf8</arg>
+            <arg>-Acom.google.auto.value.AutoBuilderIsUnstable</arg>
           </compilerArgs>
           <showWarnings>true</showWarnings>
           <showDeprecation>true</showDeprecation>
@@ -143,12 +188,23 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-deploy-plugin</artifactId>
-        <version>2.7</version>
+        <version>2.8.2</version>
         <configuration>
           <!-- Build/test, but don't deploy -->
           <skip>true</skip>
         </configuration>
       </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.22.2</version>
+        <configuration>
+          <argLine>${test.jvm.flags}</argLine>
+          <systemPropertyVariables>
+            <autoValueVersion>${project.version}</autoValueVersion>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
     </plugins>
   </build>
 
@@ -160,12 +216,12 @@
           <plugin>
             <groupId>org.apache.maven.plugins</groupId>
             <artifactId>maven-compiler-plugin</artifactId>
-            <version>3.7.0</version>
+            <version>3.8.1</version>
             <dependencies>
               <dependency>
                 <groupId>org.codehaus.plexus</groupId>
                 <artifactId>plexus-java</artifactId>
-                <version>0.9.4</version>
+                <version>1.0.7</version>
               </dependency>
             </dependencies>
             <configuration>
@@ -195,5 +251,27 @@
         <exclude.tests>**/AutoValueJava8Test.java</exclude.tests>
       </properties>
     </profile>
+    <profile>
+      <id>open-modules</id>
+      <activation>
+        <jdk>[9,)</jdk>
+      </activation>
+      <properties>
+        <test.jvm.flags>--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</test.jvm.flags>
+      </properties>
+    </profile>
+    <profile>
+      <!-- Before JDK 11, parameter names from already-compiled classes are not reliably available
+           to the compiler even when they are present in class files. Since our Kotlin test file
+           obviously has to be compiled separately from the Java test that uses it,
+           AutoBuilderKotlinTest doesn't pass on earlier JDK versions. -->
+      <id>exclude-separate-compilation-parameter-names</id>
+      <activation>
+        <jdk>(,11)</jdk>
+      </activation>
+      <properties>
+        <exclude.tests>**/AutoBuilderKotlinTest.java</exclude.tests>
+      </properties>
+    </profile>
   </profiles>
 </project>
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
index ca1ef6b..a04d41f 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoAnnotationTest.java
@@ -15,20 +15,24 @@
  */
 package com.google.auto.value;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 
 import com.google.auto.value.annotations.Empty;
 import com.google.auto.value.annotations.GwtArrays;
 import com.google.auto.value.annotations.StringValues;
+import com.google.common.base.StandardSystemProperty;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.primitives.Ints;
 import com.google.common.testing.EqualsTester;
+import com.google.common.testing.SerializableTester;
+import java.io.ObjectStreamClass;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -38,6 +42,7 @@
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import org.junit.AssumptionViolatedException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -66,13 +71,41 @@
   }
 
   @Test
+  public void testEqualsParameterAnnotation() throws ReflectiveOperationException {
+    assume()
+        .that(Double.parseDouble(StandardSystemProperty.JAVA_SPECIFICATION_VERSION.value()))
+        .isAtLeast(8.0);
+    Class<? extends Annotation> jspecifyNullable;
+    try {
+      // We write this using .concat in order to hide it from rewriting rules.
+      jspecifyNullable =
+          Class.forName("org".concat(".jspecify.nullness.Nullable")).asSubclass(Annotation.class);
+    } catch (ClassNotFoundException e) {
+      throw new AssumptionViolatedException("No JSpecify @Nullable available", e);
+    }
+    @SuppressWarnings("GetClassOnAnnotation") // yes, I really want the implementation class
+    Class<? extends StringValues> autoAnnotationImpl = newStringValues(new String[0]).getClass();
+    Method equals = autoAnnotationImpl.getDeclaredMethod("equals", Object.class);
+    // The remaining faffing around with reflection is there because we have a Google-internal test
+    // that runs this code with -source 7 -target 7. We're really just doing this:
+    //   assertThat(equals.getAnnotatedParameterTypes()[0].isAnnotationPresent(jspecifyNullable))
+    //      .isTrue();
+    Method getAnnotatedParameterTypes = Method.class.getMethod("getAnnotatedParameterTypes");
+    Object[] annotatedParameterTypes = (Object[]) getAnnotatedParameterTypes.invoke(equals);
+    Method isAnnotationPresent =
+        annotatedParameterTypes[0].getClass().getMethod("isAnnotationPresent", Class.class);
+    assertThat(isAnnotationPresent.invoke(annotatedParameterTypes[0], jspecifyNullable))
+        .isEqualTo(true);
+  }
+
+  @Test
   public void testArraysAreCloned() {
     String[] array = {"Jekyll"};
     StringValues stringValues = newStringValues(array);
     array[0] = "Hyde";
-    assertEquals("Jekyll", stringValues.value()[0]);
+    assertThat(stringValues.value()).asList().containsExactly("Jekyll");
     stringValues.value()[0] = "Hyde";
-    assertEquals("Jekyll", stringValues.value()[0]);
+    assertThat(stringValues.value()[0]).isEqualTo("Jekyll");
   }
 
   @Test
@@ -80,12 +113,12 @@
     String[] strings = {"Jekyll"};
     int[] ints = {2, 3, 5};
     GwtArrays arrays = newGwtArrays(strings, ints);
-    assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings()));
-    assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints()));
+    assertThat(arrays.strings()).asList().containsExactly("Jekyll");
+    assertThat(arrays.ints()).asList().containsExactly(2, 3, 5).inOrder();
     strings[0] = "Hyde";
     ints[0] = -1;
-    assertEquals(ImmutableList.of("Jekyll"), ImmutableList.copyOf(arrays.strings()));
-    assertEquals(ImmutableList.of(2, 3, 5), Ints.asList(arrays.ints()));
+    assertThat(arrays.strings()).asList().containsExactly("Jekyll");
+    assertThat(arrays.ints()).asList().containsExactly(2, 3, 5).inOrder();
   }
 
   @AutoAnnotation
@@ -406,7 +439,39 @@
         .testEquals();
   }
 
+  @Test
+  public void testSerialization() {
+    Annotation[] instances = {EVERYTHING_FROM_AUTO, EVERYTHING_FROM_AUTO_COLLECTIONS};
+    for (Annotation instance : instances) {
+      SerializableTester.reserializeAndAssert(instance);
+    }
+  }
+
+  @Test
+  @SuppressWarnings("GetClassOnAnnotation") // yes, we really do want the implementation classes
+  public void testSerialVersionUid() {
+    Class<? extends Everything> everythingImpl = EVERYTHING_FROM_AUTO.getClass();
+    Class<? extends Everything> everythingFromCollectionsImpl =
+        EVERYTHING_FROM_AUTO_COLLECTIONS.getClass();
+    assertThat(everythingImpl).isNotEqualTo(everythingFromCollectionsImpl);
+    long everythingUid = ObjectStreamClass.lookup(everythingImpl).getSerialVersionUID();
+    long everythingFromCollectionsUid =
+        ObjectStreamClass.lookup(everythingFromCollectionsImpl).getSerialVersionUID();
+    // Two different implementations of the same annotation with the same members being provided
+    // (not defaulted) should have the same serialVersionUID. They won't be serial-compatible, of
+    // course, because their classes are different. So we're really just checking that the
+    // serialVersionUID depends only on the names and types of those members.
+    assertThat(everythingFromCollectionsUid).isEqualTo(everythingUid);
+    Class<? extends StringValues> stringValuesImpl = newStringValues(new String[0]).getClass();
+    long stringValuesUid = ObjectStreamClass.lookup(stringValuesImpl).getSerialVersionUID();
+    // The previous assertion would be vacuously true if every implementation had the same
+    // serialVersionUID, so check that that's not true.
+    assertThat(stringValuesUid).isNotEqualTo(everythingUid);
+  }
+
   public static class IntList extends ArrayList<Integer> {
+    private static final long serialVersionUID = 1L;
+
     IntList(Collection<Integer> c) {
       super(c);
     }
@@ -440,7 +505,7 @@
     IntList intList = new IntList(ImmutableList.of(1, 2, 3));
     IntArray actual = newIntArray(intList);
     IntArray expected = AnnotatedWithIntArray.class.getAnnotation(IntArray.class);
-    assertEquals(expected, actual);
+    assertThat(actual).isEqualTo(expected);
   }
 
   @Test
@@ -461,8 +526,8 @@
             + "@com.google.auto.value.annotations.StringValues([\"foo\", \"bar\"])"
             + "]"
             + ")";
-    assertEquals(expected, EVERYTHING_FROM_AUTO.toString());
-    assertEquals(expected, EVERYTHING_FROM_AUTO_COLLECTIONS.toString());
+    assertThat(EVERYTHING_FROM_AUTO.toString()).isEqualTo(expected);
+    assertThat(EVERYTHING_FROM_AUTO_COLLECTIONS.toString()).isEqualTo(expected);
   }
 
   @Test
@@ -475,7 +540,7 @@
     String expected =
         "@com.google.auto.value.annotations.StringValues("
             + "[\"\", \"\\r\\n\", \"hello, world\", \"Éamonn\", \"\\007\\uffef\"])";
-    assertEquals(expected, instance.toString());
+    assertThat(instance.toString()).isEqualTo(expected);
   }
 
   @Retention(RetentionPolicy.RUNTIME)
@@ -498,7 +563,7 @@
             ImmutableList.<Class<? extends Annotation>>of(AnnotationsAnnotation.class));
     AnnotationsAnnotation fromReflect =
         AnnotatedWithAnnotationsAnnotation.class.getAnnotation(AnnotationsAnnotation.class);
-    assertEquals(fromReflect, generated);
+    assertThat(generated).isEqualTo(fromReflect);
   }
 
   @Retention(RetentionPolicy.RUNTIME)
@@ -520,7 +585,7 @@
         newClassesAnnotation(Arrays.<Class<?>>asList(AnnotationsAnnotation.class));
     ClassesAnnotation fromReflect =
         AnnotatedWithClassesAnnotation.class.getAnnotation(ClassesAnnotation.class);
-    assertEquals(fromReflect, generated);
+    assertThat(generated).isEqualTo(fromReflect);
   }
 
   @Retention(RetentionPolicy.RUNTIME)
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java
new file mode 100644
index 0000000..1dc346c
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderKotlinTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AutoBuilderKotlinTest {
+  @AutoBuilder(ofClass = KotlinData.class)
+  abstract static class KotlinDataBuilder {
+    static KotlinDataBuilder builder() {
+      return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataBuilder();
+    }
+
+    abstract KotlinDataBuilder setInt(int x);
+
+    abstract KotlinDataBuilder setString(String x);
+
+    abstract KotlinData build();
+  }
+
+  @Test
+  public void simpleKotlin() {
+    KotlinData x = KotlinDataBuilder.builder().setInt(23).setString("skidoo").build();
+    assertThat(x.getInt()).isEqualTo(23);
+    assertThat(x.getString()).isEqualTo("skidoo");
+  }
+
+  @AutoBuilder(ofClass = KotlinDataWithNullable.class)
+  abstract static class KotlinDataWithNullableBuilder {
+    static KotlinDataWithNullableBuilder builder() {
+      return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithNullableBuilder();
+    }
+
+    abstract KotlinDataWithNullableBuilder setAnInt(int x);
+
+    abstract KotlinDataWithNullableBuilder setAString(String x);
+
+    abstract KotlinDataWithNullable build();
+  }
+
+  @Test
+  public void kotlinWithNullable() {
+    KotlinDataWithNullable empty = KotlinDataWithNullableBuilder.builder().build();
+    assertThat(empty.getAnInt()).isNull();
+    assertThat(empty.getAString()).isNull();
+
+    KotlinDataWithNullable notEmpty =
+        KotlinDataWithNullableBuilder.builder().setAString("answer").setAnInt(42).build();
+    assertThat(notEmpty.getAString()).isEqualTo("answer");
+    assertThat(notEmpty.getAnInt()).isEqualTo(42);
+  }
+
+  @AutoBuilder(ofClass = KotlinDataWithDefaults.class)
+  abstract static class KotlinDataWithDefaultsBuilder {
+    static KotlinDataWithDefaultsBuilder builder() {
+      return new AutoBuilder_AutoBuilderKotlinTest_KotlinDataWithDefaultsBuilder();
+    }
+
+    abstract KotlinDataWithDefaultsBuilder setAnInt(int x);
+
+    abstract KotlinDataWithDefaultsBuilder setAString(String x);
+
+    abstract KotlinDataWithDefaults build();
+  }
+
+  @Test
+  public void kotlinWithDefaults() {
+    // AutoBuilder doesn't currently try to give the builder the same defaults as the Kotlin class,
+    // but we do at least check that the presence of defaults doesn't throw AutoBuilder off.
+    // When a constructor has default parameters, the Kotlin compiler generates an extra constructor
+    // with two extra parameters: an int bitmask saying which parameters were defaulted, and a
+    // DefaultConstructorMarker parameter to avoid clashing with another constructor that might have
+    // an extra int parameter for some other reason. If AutoBuilder found this constructor it might
+    // be confused, but fortunately the constructor is marked synthetic, and javax.lang.model
+    // doesn't show synthetic elements.
+    KotlinDataWithDefaults x =
+        KotlinDataWithDefaultsBuilder.builder().setAString("answer").setAnInt(42).build();
+    assertThat(x.getAString()).isEqualTo("answer");
+    assertThat(x.getAnInt()).isEqualTo(42);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
new file mode 100644
index 0000000..952edaa
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoBuilderTest.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.time.LocalTime;
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AutoBuilderTest {
+  static class Simple {
+    private final int anInt;
+    private final String aString;
+
+    Simple(int anInt, String aString) {
+      this.anInt = anInt;
+      this.aString = aString;
+    }
+
+    static Simple of(int anInt, String aString) {
+      return new Simple(anInt, aString);
+    }
+
+    @Override
+    public boolean equals(Object x) {
+      if (x instanceof Simple) {
+        Simple that = (Simple) x;
+        return this.anInt == that.anInt && Objects.equals(this.aString, that.aString);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(anInt, aString);
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("anInt", anInt)
+          .add("aString", aString)
+          .toString();
+    }
+
+    static Builder builder() {
+      return new AutoBuilder_AutoBuilderTest_Simple_Builder();
+    }
+
+    @AutoBuilder
+    abstract static class Builder {
+      abstract Builder setAnInt(int x);
+
+      abstract Builder setAString(String x);
+
+      abstract Simple build();
+    }
+  }
+
+  @Test
+  public void simple() {
+    Simple x = Simple.builder().setAnInt(23).setAString("skidoo").build();
+    assertThat(x).isEqualTo(new Simple(23, "skidoo"));
+  }
+
+  @AutoValue
+  abstract static class SimpleAuto {
+    abstract int getFoo();
+
+    abstract String getBar();
+
+    static Builder builder() {
+      return new AutoBuilder_AutoBuilderTest_SimpleAuto_Builder();
+    }
+
+    // There's no particular reason to do this since @AutoValue.Builder works just as well, but
+    // let's check anyway.
+    @AutoBuilder(ofClass = AutoValue_AutoBuilderTest_SimpleAuto.class)
+    abstract static class Builder {
+      abstract Builder setFoo(int x);
+
+      abstract Builder setBar(String x);
+
+      abstract AutoValue_AutoBuilderTest_SimpleAuto build();
+    }
+  }
+
+  @Test
+  public void simpleAuto() {
+    SimpleAuto x = SimpleAuto.builder().setFoo(23).setBar("skidoo").build();
+    assertThat(x.getFoo()).isEqualTo(23);
+    assertThat(x.getBar()).isEqualTo("skidoo");
+  }
+
+  enum Truthiness {
+    FALSY,
+    TRUTHY
+  }
+
+  @interface MyAnnotation {
+    String value();
+
+    int DEFAULT_ID = -1;
+
+    int id() default DEFAULT_ID;
+
+    Truthiness DEFAULT_TRUTHINESS = Truthiness.FALSY;
+
+    Truthiness truthiness() default Truthiness.FALSY;
+  }
+
+  // This method has a parameter for `truthiness`, even though that has a default, but it has no
+  // parameter for `id`, which also has a default.
+  @AutoAnnotation
+  static MyAnnotation myAnnotation(String value, Truthiness truthiness) {
+    return new AutoAnnotation_AutoBuilderTest_myAnnotation(value, truthiness);
+  }
+
+  @AutoBuilder(callMethod = "myAnnotation")
+  interface MyAnnotationBuilder {
+    MyAnnotationBuilder value(String x);
+
+    MyAnnotationBuilder truthiness(Truthiness x);
+
+    MyAnnotation build();
+  }
+
+  static MyAnnotationBuilder myAnnotationBuilder() {
+    return new AutoBuilder_AutoBuilderTest_MyAnnotationBuilder()
+        .truthiness(MyAnnotation.DEFAULT_TRUTHINESS);
+  }
+
+  @Test
+  public void simpleAutoAnnotation() {
+    MyAnnotation annotation1 = myAnnotationBuilder().value("foo").build();
+    assertThat(annotation1.value()).isEqualTo("foo");
+    assertThat(annotation1.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
+    assertThat(annotation1.truthiness()).isEqualTo(MyAnnotation.DEFAULT_TRUTHINESS);
+    MyAnnotation annotation2 =
+        myAnnotationBuilder().value("bar").truthiness(Truthiness.TRUTHY).build();
+    assertThat(annotation2.value()).isEqualTo("bar");
+    assertThat(annotation2.id()).isEqualTo(MyAnnotation.DEFAULT_ID);
+    assertThat(annotation2.truthiness()).isEqualTo(Truthiness.TRUTHY);
+  }
+
+  static class Overload {
+    final int anInt;
+    final String aString;
+    final BigInteger aBigInteger;
+
+    Overload(int anInt, String aString) {
+      this(anInt, aString, BigInteger.ZERO);
+    }
+
+    Overload(int anInt, String aString, BigInteger aBigInteger) {
+      this.anInt = anInt;
+      this.aString = aString;
+      this.aBigInteger = aBigInteger;
+    }
+
+    @Override
+    public boolean equals(Object x) {
+      if (x instanceof Overload) {
+        Overload that = (Overload) x;
+        return this.anInt == that.anInt
+            && Objects.equals(this.aString, that.aString)
+            && Objects.equals(this.aBigInteger, that.aBigInteger);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(anInt, aString, aBigInteger);
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("anInt", anInt)
+          .add("aString", aString)
+          .add("aBigInteger", aBigInteger)
+          .toString();
+    }
+
+    static Builder1 builder1() {
+      return new AutoBuilder_AutoBuilderTest_Overload_Builder1();
+    }
+
+    static Builder2 builder2() {
+      return new AutoBuilder_AutoBuilderTest_Overload_Builder2();
+    }
+
+    @AutoBuilder
+    interface Builder1 {
+      Builder1 setAnInt(int x);
+
+      Builder1 setAString(String x);
+
+      Overload build();
+    }
+
+    @AutoBuilder
+    interface Builder2 {
+      Builder2 setAnInt(int x);
+
+      Builder2 setAString(String x);
+
+      Builder2 setABigInteger(BigInteger x);
+
+      Overload build();
+    }
+  }
+
+  @Test
+  public void overloadedConstructor() {
+    Overload actual1 = Overload.builder1().setAnInt(23).setAString("skidoo").build();
+    Overload expected1 = new Overload(23, "skidoo");
+    assertThat(actual1).isEqualTo(expected1);
+
+    BigInteger big17 = BigInteger.valueOf(17);
+    Overload actual2 =
+        Overload.builder2().setAnInt(17).setAString("17").setABigInteger(big17).build();
+    Overload expected2 = new Overload(17, "17", big17);
+    assertThat(actual2).isEqualTo(expected2);
+  }
+
+  @AutoBuilder(callMethod = "of", ofClass = Simple.class)
+  interface SimpleStaticBuilder {
+    SimpleStaticBuilder anInt(int x);
+
+    SimpleStaticBuilder aString(String x);
+
+    Simple build();
+  }
+
+  static SimpleStaticBuilder simpleStaticBuilder() {
+    return new AutoBuilder_AutoBuilderTest_SimpleStaticBuilder();
+  }
+
+  @Test
+  public void staticMethod() {
+    Simple actual = simpleStaticBuilder().anInt(17).aString("17").build();
+    Simple expected = new Simple(17, "17");
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  // We can't be sure that the java.time package has parameter names, so we use this intermediary.
+  // Otherwise we could just write @AutoBuilder(callMethod = "of", ofClass = LocalTime.class).
+  // It's still interesting to test this as a realistic example.
+  static LocalTime localTimeOf(int hour, int minute, int second, int nanoOfSecond) {
+    return LocalTime.of(hour, minute, second, nanoOfSecond);
+  }
+
+  static LocalTimeBuilder localTimeBuilder() {
+    return new AutoBuilder_AutoBuilderTest_LocalTimeBuilder().nanoOfSecond(0);
+  }
+
+  @AutoBuilder(callMethod = "localTimeOf")
+  interface LocalTimeBuilder {
+    LocalTimeBuilder hour(int hour);
+
+    LocalTimeBuilder minute(int minute);
+
+    LocalTimeBuilder second(int second);
+
+    LocalTimeBuilder nanoOfSecond(int nanoOfSecond);
+
+    LocalTime build();
+  }
+
+  @Test
+  public void staticMethodOfContainingClass() {
+    LocalTime actual = localTimeBuilder().hour(12).minute(34).second(56).build();
+    LocalTime expected = LocalTime.of(12, 34, 56);
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  @Test
+  public void missingRequiredProperty() {
+    // This test is compiled at source level 7 by CompileWithEclipseTest, so we can't use
+    // assertThrows with a lambda.
+    try {
+      localTimeBuilder().hour(12).minute(34).build();
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessageThat().isEqualTo("Missing required properties: second");
+    }
+  }
+
+  static void throwException() throws IOException {
+    throw new IOException("oops");
+  }
+
+  static ThrowExceptionBuilder throwExceptionBuilder() {
+    return new AutoBuilder_AutoBuilderTest_ThrowExceptionBuilder();
+  }
+
+  @AutoBuilder(callMethod = "throwException")
+  interface ThrowExceptionBuilder {
+    void build() throws IOException;
+  }
+
+  @Test
+  public void emptyBuilderThrowsException() {
+    try {
+      throwExceptionBuilder().build();
+      fail();
+    } catch (IOException expected) {
+      assertThat(expected).hasMessageThat().isEqualTo("oops");
+    }
+  }
+
+  static class ListContainer {
+    private final ImmutableList<String> list;
+
+    ListContainer(ImmutableList<String> list) {
+      this.list = checkNotNull(list);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return o instanceof ListContainer && list.equals(((ListContainer) o).list);
+    }
+
+    @Override
+    public int hashCode() {
+      return list.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return list.toString();
+    }
+
+    static Builder builder() {
+      return new AutoBuilder_AutoBuilderTest_ListContainer_Builder();
+    }
+
+    @AutoBuilder
+    interface Builder {
+      Builder setList(Iterable<String> list);
+
+      ImmutableList.Builder<String> listBuilder();
+
+      ListContainer build();
+    }
+  }
+
+  @Test
+  public void propertyBuilder() {
+    ListContainer expected = new ListContainer(ImmutableList.of("one", "two", "three"));
+    ListContainer actual1 =
+        ListContainer.builder().setList(ImmutableList.of("one", "two", "three")).build();
+    assertThat(actual1).isEqualTo(expected);
+
+    ListContainer.Builder builder2 = ListContainer.builder();
+    builder2.listBuilder().add("one", "two", "three");
+    assertThat(builder2.build()).isEqualTo(expected);
+
+    ListContainer.Builder builder3 = ListContainer.builder().setList(ImmutableList.of("one"));
+    builder3.listBuilder().add("two", "three");
+    assertThat(builder3.build()).isEqualTo(expected);
+
+    ListContainer.Builder builder4 = ListContainer.builder();
+    builder4.listBuilder();
+    try {
+      builder4.setList(ImmutableList.of("one", "two", "three"));
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessageThat().isEqualTo("Cannot set list after calling listBuilder()");
+    }
+  }
+
+  static <T> String concatList(ImmutableList<T> list) {
+    // We're avoiding streams for now so we compile this in Java 7 mode in CompileWithEclipseTest.
+    StringBuilder sb = new StringBuilder();
+    for (T element : list) {
+      sb.append(element);
+    }
+    return sb.toString();
+  }
+
+  @AutoBuilder(callMethod = "concatList")
+  interface ConcatListCaller<T> {
+    ImmutableList.Builder<T> listBuilder();
+
+    String call();
+  }
+
+  @Test
+  public void propertyBuilderWithoutSetter() {
+    ConcatListCaller<Integer> caller = new AutoBuilder_AutoBuilderTest_ConcatListCaller<>();
+    caller.listBuilder().add(1, 1, 2, 3, 5, 8);
+    String s = caller.call();
+    assertThat(s).isEqualTo("112358");
+  }
+
+  static <K, V extends Number> Map<K, V> singletonMap(K key, V value) {
+    return Collections.singletonMap(key, value);
+  }
+
+  static <K, V extends Number> SingletonMapBuilder<K, V> singletonMapBuilder() {
+    return new AutoBuilder_AutoBuilderTest_SingletonMapBuilder<>();
+  }
+
+  @AutoBuilder(callMethod = "singletonMap")
+  interface SingletonMapBuilder<K, V extends Number> {
+    SingletonMapBuilder<K, V> key(K key);
+
+    SingletonMapBuilder<K, V> value(V value);
+
+    Map<K, V> build();
+  }
+
+  @Test
+  public void genericStaticMethod() {
+    ImmutableMap<String, Integer> expected = ImmutableMap.of("17", 17);
+    SingletonMapBuilder<String, Integer> builder = singletonMapBuilder();
+    Map<String, Integer> actual = builder.key("17").value(17).build();
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  static class SingletonSet<E> extends AbstractSet<E> {
+    private final E element;
+
+    SingletonSet(E element) {
+      this.element = element;
+    }
+
+    @Override
+    public int size() {
+      return 1;
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+      return new Iterator<E>() {
+        private boolean first = true;
+
+        @Override
+        public boolean hasNext() {
+          return first;
+        }
+
+        @Override
+        public E next() {
+          if (!first) {
+            throw new NoSuchElementException();
+          }
+          first = false;
+          return element;
+        }
+      };
+    }
+  }
+
+  @AutoBuilder(ofClass = SingletonSet.class)
+  interface SingletonSetBuilder<E> {
+    SingletonSetBuilder<E> setElement(E element);
+
+    SingletonSet<E> build();
+  }
+
+  static <E> SingletonSetBuilder<E> singletonSetBuilder() {
+    return new AutoBuilder_AutoBuilderTest_SingletonSetBuilder<>();
+  }
+
+  @Test
+  public void genericClass() {
+    ImmutableSet<String> expected = ImmutableSet.of("foo");
+    SingletonSetBuilder<String> builder = singletonSetBuilder();
+    Set<String> actual = builder.setElement("foo").build();
+    assertThat(actual).isEqualTo(expected);
+  }
+
+  static class TypedSingletonSet<E> extends SingletonSet<E> {
+    private final Class<?> type;
+
+    <T extends E> TypedSingletonSet(T element, Class<T> type) {
+      super(element);
+      this.type = type;
+    }
+
+    @Override
+    public String toString() {
+      return type.getName() + super.toString();
+    }
+  }
+
+  @AutoBuilder(ofClass = TypedSingletonSet.class)
+  interface TypedSingletonSetBuilder<E, T extends E> {
+    TypedSingletonSetBuilder<E, T> setElement(T element);
+
+    TypedSingletonSetBuilder<E, T> setType(Class<T> type);
+
+    TypedSingletonSet<E> build();
+  }
+
+  static <E, T extends E> TypedSingletonSetBuilder<E, T> typedSingletonSetBuilder() {
+    return new AutoBuilder_AutoBuilderTest_TypedSingletonSetBuilder<>();
+  }
+
+  @Test
+  public void genericClassWithGenericConstructor() {
+    TypedSingletonSetBuilder<CharSequence, String> builder = typedSingletonSetBuilder();
+    TypedSingletonSet<CharSequence> set = builder.setElement("foo").setType(String.class).build();
+    assertThat(set.toString()).isEqualTo("java.lang.String[foo]");
+  }
+
+  static <T> ImmutableList<T> pair(T first, T second) {
+    return ImmutableList.of(first, second);
+  }
+
+  @AutoBuilder(callMethod = "pair")
+  interface PairBuilder<T> {
+    PairBuilder<T> setFirst(T x);
+
+    T getFirst();
+
+    PairBuilder<T> setSecond(T x);
+
+    Optional<T> getSecond();
+
+    ImmutableList<T> build();
+  }
+
+  static <T> PairBuilder<T> pairBuilder() {
+    return new AutoBuilder_AutoBuilderTest_PairBuilder<>();
+  }
+
+  @Test
+  public void genericGetters() {
+    PairBuilder<Number> builder = pairBuilder();
+    assertThat(builder.getSecond()).isEmpty();
+    builder.setSecond(2);
+    assertThat(builder.getSecond()).hasValue(2);
+    try {
+      builder.getFirst();
+      fail();
+    } catch (IllegalStateException expected) {
+    }
+    builder.setFirst(1.0);
+    assertThat(builder.getFirst()).isEqualTo(1.0);
+    assertThat(builder.build()).containsExactly(1.0, 2).inOrder();
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java
index 1b58728..ee33740 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoOneOfTest.java
@@ -499,10 +499,7 @@
     ArrayValue string = ArrayValue.ofString("foo");
     ArrayValue ints1 = ArrayValue.ofInts(new int[] {17, 23});
     ArrayValue ints2 = ArrayValue.ofInts(new int[] {17, 23});
-    new EqualsTester()
-        .addEqualityGroup(string)
-        .addEqualityGroup(ints1, ints2)
-        .testEquals();
+    new EqualsTester().addEqualityGroup(string).addEqualityGroup(ints1, ints2).testEquals();
   }
 
   @Retention(RetentionPolicy.RUNTIME)
@@ -560,8 +557,11 @@
 
   @AutoOneOf(MaybeEmpty.Kind.class)
   public abstract static class MaybeEmpty implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     public enum Kind {
-      EMPTY, STRING,
+      EMPTY,
+      STRING,
     }
 
     public abstract Kind getKind();
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
index 10812f8..3f4e9bf 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueJava8Test.java
@@ -15,9 +15,11 @@
  */
 package com.google.auto.value;
 
+import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.Truth8.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assume.assumeTrue;
@@ -53,6 +55,7 @@
 import javax.lang.model.util.ElementFilter;
 import javax.tools.Diagnostic;
 import javax.tools.JavaFileObject;
+import org.junit.AssumptionViolatedException;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -153,6 +156,19 @@
         .isEqualTo("NullableProperties{nullableString=null, randomInt=23}");
   }
 
+  @Test
+  public void testEqualsParameterIsAnnotated() throws NoSuchMethodException {
+    // Sadly we can't rely on JDK 8 to handle type annotations correctly.
+    // Some versions do, some don't. So skip the test unless we are on at least JDK 9.
+    double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value());
+    assume().that(javaVersion).isAtLeast(9.0);
+    Method equals =
+        NullableProperties.create(null, 23).getClass().getMethod("equals", Object.class);
+    AnnotatedType[] parameterTypes = equals.getAnnotatedParameterTypes();
+    assertThat(parameterTypes).hasLength(1);
+    assertThat(parameterTypes[0].getAnnotation(Nullable.class)).isNotNull();
+  }
+
   @AutoAnnotation
   static Nullable nullable() {
     return new AutoAnnotation_AutoValueJava8Test_nullable();
@@ -162,9 +178,7 @@
   public void testNullablePropertyImplementationIsNullable() throws NoSuchMethodException {
     Method method =
         AutoValue_AutoValueJava8Test_NullableProperties.class.getDeclaredMethod("nullableString");
-    assertThat(method.getAnnotatedReturnType().getAnnotations())
-        .asList()
-        .contains(nullable());
+    assertThat(method.getAnnotatedReturnType().getAnnotations()).asList().contains(nullable());
   }
 
   @Test
@@ -198,8 +212,9 @@
 
   @Test
   public void testExcludedNullablePropertyImplementation() throws NoSuchMethodException {
-    Method method = AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class
-        .getDeclaredMethod("nullableString");
+    Method method =
+        AutoValue_AutoValueJava8Test_NullablePropertiesNotCopied.class.getDeclaredMethod(
+            "nullableString");
     assertThat(method.getAnnotatedReturnType().getAnnotations())
         .asList()
         .doesNotContain(nullable());
@@ -536,6 +551,35 @@
     }
   }
 
+  @AutoValue
+  abstract static class NoNullableRef {
+    abstract String foo();
+
+    static NoNullableRef of(String foo) {
+      return new AutoValue_AutoValueJava8Test_NoNullableRef(foo);
+    }
+  }
+
+  // Tests that we generate equals(@Nullable x) using JSpecify @Nullable if that annotation is
+  // available and there is no other @Nullable type annotation mentioned in the @AutoValue class.
+  // If there *are* other @Nullable type annotations, other test methods here will check that they
+  // are used instead.
+  @Test
+  public void testDefaultToJSpecifyNullable() throws ReflectiveOperationException {
+    Class<? extends Annotation> jspecifyNullable;
+    try {
+      // We write this using .concat in order to hide it from rewriting rules.
+      jspecifyNullable =
+          Class.forName("org".concat(".jspecify.nullness.Nullable")).asSubclass(Annotation.class);
+    } catch (ClassNotFoundException e) {
+      throw new AssumptionViolatedException("No JSpecify @Nullable available", e);
+    }
+    Class<? extends NoNullableRef> autoValueImpl = NoNullableRef.of("foo").getClass();
+    Method equals = autoValueImpl.getDeclaredMethod("equals", Object.class);
+    assertThat(equals.getAnnotatedParameterTypes()[0].isAnnotationPresent(jspecifyNullable))
+        .isTrue();
+  }
+
   @Test
   public void testBuilderWithUnprefixedGetter() {
     assumeTrue(javacHandlesTypeAnnotationsCorrectly);
@@ -571,7 +615,7 @@
   public abstract static class BuilderWithPrefixedGetters<T extends Comparable<T>> {
     public abstract ImmutableList<T> getList();
 
-    public abstract T getT();
+    public abstract @Nullable T getT();
 
     @SuppressWarnings("mutable")
     public abstract int @Nullable [] getInts();
@@ -586,7 +630,7 @@
     public abstract static class Builder<T extends Comparable<T>> {
       public abstract Builder<T> setList(ImmutableList<T> list);
 
-      public abstract Builder<T> setT(T t);
+      public abstract Builder<T> setT(@Nullable T t);
 
       public abstract Builder<T> setInts(int[] ints);
 
@@ -761,6 +805,7 @@
     @AutoValue.Builder
     abstract static class Builder {
       abstract Builder maybeJustMaybe(Optional<String> maybe);
+
       abstract OptionalOptional build();
     }
   }
@@ -795,6 +840,7 @@
     @AutoValue.Builder
     abstract static class Builder {
       abstract Builder setPredicate(Predicate<? super Integer> predicate);
+
       abstract OptionalExtends build();
     }
   }
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
index 346bc53..3a7e7bc 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/AutoValueTest.java
@@ -34,6 +34,7 @@
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.ImmutableTable;
+import com.google.common.collect.Ordering;
 import com.google.common.testing.EqualsTester;
 import com.google.common.testing.SerializableTester;
 import java.io.ObjectStreamClass;
@@ -52,6 +53,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -60,6 +62,7 @@
 import java.util.NavigableSet;
 import java.util.NoSuchElementException;
 import java.util.SortedMap;
+import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import javax.annotation.Nullable;
@@ -70,6 +73,7 @@
 
 /** @author emcmanus@google.com (Éamonn McManus) */
 @RunWith(JUnit4.class)
+@SuppressWarnings({"AutoValueImmutableFields", "AutoValueFinalMethods", "TypeNameShadowing"})
 public class AutoValueTest {
   private static boolean omitIdentifiers;
 
@@ -180,12 +184,15 @@
   @AutoValue
   abstract static class StrangeGetters {
     abstract int get1st();
+
     abstract int get_1st(); // by default we'll use _1st where identifiers are needed, so foil that.
 
     @AutoValue.Builder
     abstract static class Builder {
       abstract Builder set1st(int x);
+
       abstract Builder set_1st(int x);
+
       abstract StrangeGetters build();
     }
 
@@ -284,6 +291,8 @@
 
   @AutoValue
   public abstract static class Serialize implements Serializable {
+    private static final long serialVersionUID = 1L;
+
     public abstract int integer();
 
     public abstract String string();
@@ -845,6 +854,8 @@
   @Test
   public void testGenericClassWithHairyBounds() throws Exception {
     class ComparableList<E> extends ArrayList<E> implements Comparable<ComparableList<E>> {
+      private static final long serialVersionUID = 1L;
+
       @Override
       public int compareTo(ComparableList<E> list) {
         throw new UnsupportedOperationException();
@@ -1262,7 +1273,7 @@
   }
 
   @AutoValue
-  public abstract static class ComplexInheritance extends AbstractBase implements A, B {
+  public abstract static class ComplexInheritance extends AbstractBase implements IntfA, IntfB {
     public static ComplexInheritance create(String name) {
       return new AutoValue_AutoValueTest_ComplexInheritance(name);
     }
@@ -1277,9 +1288,9 @@
     }
   }
 
-  interface A extends Base {}
+  interface IntfA extends Base {}
 
-  interface B extends Base {}
+  interface IntfB extends Base {}
 
   interface Base {
     int answer();
@@ -1352,7 +1363,7 @@
   }
 
   @AutoValue
-  public abstract static class InheritTwice implements A, B {
+  public abstract static class InheritTwice implements IntfA, IntfB {
     public static InheritTwice create(int answer) {
       return new AutoValue_AutoValueTest_InheritTwice(answer);
     }
@@ -1664,6 +1675,13 @@
             .build();
     assertThat(suppliedDirectly.optionalString()).hasValue("foo");
     assertThat(suppliedDirectly.optionalInteger()).hasValue(23);
+
+    try {
+      // The parameter is not marked @Nullable so this should fail.
+      OptionalPropertiesWithBuilder.builder().setOptionalString((String) null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
   }
 
   @AutoValue
@@ -1874,6 +1892,40 @@
     assertEquals((Integer) 17, instance3.u());
   }
 
+  public interface ToBuilder<BuilderT> {
+    BuilderT toBuilder();
+  }
+
+  @AutoValue
+  public abstract static class InheritedToBuilder<T, U>
+      implements ToBuilder<InheritedToBuilder.Builder<T, U>> {
+
+    public abstract T t();
+
+    public abstract U u();
+
+    public static <T, U> Builder<T, U> builder() {
+      return new AutoValue_AutoValueTest_InheritedToBuilder.Builder<T, U>();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder<T, U> {
+      public abstract Builder<T, U> setT(T t);
+
+      public abstract Builder<T, U> setU(U u);
+
+      public abstract InheritedToBuilder<T, U> build();
+    }
+  }
+
+  @Test
+  public void testInheritedToBuilder() {
+    InheritedToBuilder<Integer, String> x =
+        InheritedToBuilder.<Integer, String>builder().setT(17).setU("wibble").build();
+    InheritedToBuilder<Integer, String> y = x.toBuilder().setT(23).build();
+    assertThat(y.u()).isEqualTo("wibble");
+  }
+
   @AutoValue
   public abstract static class BuilderWithSet<T extends Comparable<T>> {
     public abstract List<T> list();
@@ -2115,6 +2167,37 @@
   }
 
   @AutoValue
+  public abstract static class BuilderWithPrefixedGettersAndUnprefixedSetters {
+    public abstract String getOAuth();
+
+    public abstract String getOBrien();
+
+    public static Builder builder() {
+      return new AutoValue_AutoValueTest_BuilderWithPrefixedGettersAndUnprefixedSetters.Builder();
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+      public abstract Builder oAuth(String x);
+
+      public abstract Builder OBrien(String x);
+
+      public abstract BuilderWithPrefixedGettersAndUnprefixedSetters build();
+    }
+  }
+
+  @Test
+  public void testBuilderWithPrefixedGetterAndUnprefixedSetter() {
+    BuilderWithPrefixedGettersAndUnprefixedSetters x =
+        BuilderWithPrefixedGettersAndUnprefixedSetters.builder()
+            .oAuth("OAuth")
+            .OBrien("Flann")
+            .build();
+    assertThat(x.getOAuth()).isEqualTo("OAuth");
+    assertThat(x.getOBrien()).isEqualTo("Flann");
+  }
+
+  @AutoValue
   public abstract static class BuilderWithPropertyBuilders<FooT extends Comparable<FooT>> {
     public abstract ImmutableList<FooT> getFoos();
 
@@ -2223,6 +2306,7 @@
     @AutoValue.Builder
     abstract static class Builder {
       abstract ImmutableList.Builder<String> listBuilder();
+
       abstract PropertyBuilderInheritsType build();
     }
   }
@@ -2820,6 +2904,8 @@
   }
 
   public static class MyMap<K, V> extends HashMap<K, V> {
+    private static final long serialVersionUID = 1L;
+
     public MyMap() {}
 
     public MyMap(Map<K, V> map) {
@@ -2828,6 +2914,8 @@
   }
 
   public static class MyMapBuilder<K, V> extends LinkedHashMap<K, V> {
+    private static final long serialVersionUID = 1L;
+
     public MyMapBuilder() {}
 
     public MyMapBuilder(Map<K, V> map) {
@@ -2873,6 +2961,8 @@
   }
 
   public static class MyStringMap<V> extends MyMap<String, V> {
+    private static final long serialVersionUID = 1L;
+
     public MyStringMap() {}
 
     public MyStringMap(Map<String, V> map) {
@@ -2885,6 +2975,8 @@
   }
 
   public static class MyStringMapBuilder<V> extends MyMapBuilder<String, V> {
+    private static final long serialVersionUID = 1L;
+
     public MyStringMapBuilder() {}
 
     public MyStringMapBuilder(Map<String, V> map) {
@@ -3258,6 +3350,7 @@
     @AutoValue.Builder
     abstract static class Builder {
       abstract Builder setMetrics(ImmutableSet<? extends Number> metrics);
+
       abstract GenericExtends build();
     }
   }
@@ -3282,6 +3375,7 @@
     @AutoValue.Builder
     abstract static class Builder {
       abstract Builder setList(List<String> list);
+
       abstract Child build();
     }
   }
@@ -3325,14 +3419,18 @@
   @SuppressWarnings("ClassCanBeStatic")
   static class OuterWithTypeParam<T extends Number> {
     class InnerWithTypeParam<U> {}
+
     class InnerWithoutTypeParam {}
+
     static class Nested {}
   }
 
   @AutoValue
   abstract static class Nesty {
     abstract OuterWithTypeParam<Double>.InnerWithTypeParam<String> innerWithTypeParam();
+
     abstract OuterWithTypeParam<Double>.InnerWithoutTypeParam innerWithoutTypeParam();
+
     abstract OuterWithTypeParam.Nested nested();
 
     static Builder builder() {
@@ -3343,8 +3441,11 @@
     abstract static class Builder {
       abstract Builder setInnerWithTypeParam(
           OuterWithTypeParam<Double>.InnerWithTypeParam<String> x);
+
       abstract Builder setInnerWithoutTypeParam(OuterWithTypeParam<Double>.InnerWithoutTypeParam x);
+
       abstract Builder setNested(OuterWithTypeParam.Nested x);
+
       abstract Nesty build();
     }
   }
@@ -3353,11 +3454,12 @@
   public void outerWithTypeParam() throws ReflectiveOperationException {
     @SuppressWarnings("UseDiamond") // Currently we compile this with -source 6 in the Eclipse test.
     OuterWithTypeParam<Double> outer = new OuterWithTypeParam<Double>();
-    Nesty nesty = Nesty.builder()
-        .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>())
-        .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam())
-        .setNested(new OuterWithTypeParam.Nested())
-        .build();
+    Nesty nesty =
+        Nesty.builder()
+            .setInnerWithTypeParam(outer.new InnerWithTypeParam<String>())
+            .setInnerWithoutTypeParam(outer.new InnerWithoutTypeParam())
+            .setNested(new OuterWithTypeParam.Nested())
+            .build();
     Type originalReturnType =
         Nesty.class.getDeclaredMethod("innerWithTypeParam").getGenericReturnType();
     Type generatedReturnType =
@@ -3383,6 +3485,7 @@
     @MyAnnotation("thing")
     abstract static class Builder {
       abstract Builder setFoo(String x);
+
       abstract BuilderAnnotationsNotCopied build();
     }
   }
@@ -3407,6 +3510,7 @@
     @MyAnnotation("thing")
     abstract static class Builder {
       abstract Builder setFoo(String x);
+
       abstract BuilderAnnotationsCopied build();
     }
   }
@@ -3417,4 +3521,106 @@
     assertThat(builder.getClass().getAnnotations()).asList().containsExactly(myAnnotation("thing"));
     assertThat(builder.setFoo("foo").build().foo()).isEqualTo("foo");
   }
+
+  @AutoValue
+  @AutoValue.CopyAnnotations
+  @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types
+  abstract static class DataWithSortedCollectionBuilders<K, V> {
+    abstract ImmutableSortedMap<K, V> anImmutableSortedMap();
+
+    abstract ImmutableSortedSet<V> anImmutableSortedSet();
+
+    abstract ImmutableSortedMap<Integer, V> nonGenericImmutableSortedMap();
+
+    abstract ImmutableSortedSet rawImmutableSortedSet();
+
+    abstract DataWithSortedCollectionBuilders.Builder<K, V> toBuilder();
+
+    static <K, V> DataWithSortedCollectionBuilders.Builder<K, V> builder() {
+      return new AutoValue_AutoValueTest_DataWithSortedCollectionBuilders.Builder<K, V>();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder<K, V> {
+      abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedMap(
+          SortedMap<K, V> anImmutableSortedMap);
+
+      abstract ImmutableSortedMap.Builder<K, V> anImmutableSortedMapBuilder(
+          Comparator<K> keyComparator);
+
+      abstract DataWithSortedCollectionBuilders.Builder<K, V> anImmutableSortedSet(
+          SortedSet<V> anImmutableSortedSet);
+
+      abstract ImmutableSortedSet.Builder<V> anImmutableSortedSetBuilder(Comparator<V> comparator);
+
+      abstract ImmutableSortedMap.Builder<Integer, V> nonGenericImmutableSortedMapBuilder(
+          Comparator<Integer> keyComparator);
+
+      abstract ImmutableSortedSet.Builder rawImmutableSortedSetBuilder(Comparator comparator);
+
+      abstract DataWithSortedCollectionBuilders<K, V> build();
+    }
+  }
+
+  @Test
+  @SuppressWarnings({"rawtypes", "unchecked"}) // deliberately checking handling of raw types
+  public void shouldGenerateBuildersWithComparators() {
+    Comparator<String> stringComparator =
+        new Comparator<String>() {
+          @Override
+          public int compare(String left, String right) {
+            return left.compareTo(right);
+          }
+        };
+
+    Comparator<Integer> intComparator =
+        new Comparator<Integer>() {
+          @Override
+          public int compare(Integer o1, Integer o2) {
+            return o1 - o2;
+          }
+        };
+
+    Comparator comparator =
+        new Comparator() {
+          @Override
+          public int compare(Object left, Object right) {
+            return String.valueOf(left).compareTo(String.valueOf(right));
+          }
+        };
+
+    AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> builder =
+        AutoValueTest.DataWithSortedCollectionBuilders.builder();
+
+    builder
+        .anImmutableSortedMapBuilder(stringComparator)
+        .put("Charlie", 1)
+        .put("Alfa", 2)
+        .put("Bravo", 3);
+    builder.anImmutableSortedSetBuilder(intComparator).add(1, 5, 9, 3);
+    builder.nonGenericImmutableSortedMapBuilder(intComparator).put(9, 99).put(1, 11).put(3, 33);
+    builder.rawImmutableSortedSetBuilder(comparator).add("Bravo", "Charlie", "Alfa");
+
+    AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> data = builder.build();
+
+    AutoValueTest.DataWithSortedCollectionBuilders.Builder<String, Integer> copiedBuilder =
+        data.toBuilder();
+    AutoValueTest.DataWithSortedCollectionBuilders<String, Integer> copiedData =
+        copiedBuilder.build();
+
+    assertThat(data.anImmutableSortedMap().keySet())
+        .containsExactly("Alfa", "Bravo", "Charlie")
+        .inOrder();
+    assertThat(data.anImmutableSortedSet()).containsExactly(1, 3, 5, 9).inOrder();
+    assertThat(data.nonGenericImmutableSortedMap().keySet()).containsExactly(1, 3, 9).inOrder();
+    assertThat(data.rawImmutableSortedSet()).containsExactly("Alfa", "Bravo", "Charlie").inOrder();
+
+    assertThat(copiedData).isEqualTo(data);
+
+    try {
+      builder.anImmutableSortedMapBuilder(Ordering.from(stringComparator).reverse());
+      fail("Calling property builder method a second time should have failed");
+    } catch (IllegalStateException expected) {
+    }
+  }
 }
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
index 1518827..ca10fb4 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/CompileWithEclipseTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import com.google.auto.value.processor.AutoAnnotationProcessor;
+import com.google.auto.value.processor.AutoBuilderProcessor;
 import com.google.auto.value.processor.AutoOneOfProcessor;
 import com.google.auto.value.processor.AutoValueProcessor;
 import com.google.common.collect.ImmutableList;
@@ -58,25 +59,28 @@
   @BeforeClass
   public static void setSourceRoot() {
     assertWithMessage("basedir property must be set - test must be run from Maven")
-        .that(SOURCE_ROOT).isNotNull();
+        .that(SOURCE_ROOT)
+        .isNotNull();
   }
 
   public @Rule TemporaryFolder tmp = new TemporaryFolder();
 
   private static final ImmutableSet<String> IGNORED_TEST_FILES =
-      ImmutableSet.of("AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java");
+      ImmutableSet.of(
+          "AutoValueNotEclipseTest.java", "CompileWithEclipseTest.java", "GradleTest.java");
 
   private static final Predicate<File> JAVA_FILE =
       f -> f.getName().endsWith(".java") && !IGNORED_TEST_FILES.contains(f.getName());
 
   private static final Predicate<File> JAVA8_TEST =
-      f -> f.getName().equals("AutoValueJava8Test.java")
-          || f.getName().equals("AutoOneOfJava8Test.java")
-          || f.getName().equals("EmptyExtension.java");
+      f ->
+          f.getName().equals("AutoValueJava8Test.java")
+              || f.getName().equals("AutoOneOfJava8Test.java")
+              || f.getName().equals("EmptyExtension.java");
 
   @Test
-  public void compileWithEclipseJava6() throws Exception {
-    compileWithEclipse("6", JAVA_FILE.and(JAVA8_TEST.negate()));
+  public void compileWithEclipseJava7() throws Exception {
+    compileWithEclipse("7", JAVA_FILE.and(JAVA8_TEST.negate()));
   }
 
   @Test
@@ -103,17 +107,28 @@
     // fileManager.getLocation(SYSTEM_MODULES).
     File rtJar = new File(JAVA_HOME.value() + "/lib/rt.jar");
     if (rtJar.exists()) {
-      List<File> bootClassPath = ImmutableList.<File>builder()
-          .add(rtJar)
-          .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH))
-          .build();
+      List<File> bootClassPath =
+          ImmutableList.<File>builder()
+              .add(rtJar)
+              .addAll(fileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH))
+              .build();
       fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootClassPath);
     }
     Iterable<? extends JavaFileObject> sourceFileObjects =
         fileManager.getJavaFileObjectsFromFiles(sources);
     String outputDir = tmp.getRoot().toString();
     ImmutableList<String> options =
-        ImmutableList.of("-d", outputDir, "-s", outputDir, "-source", version, "-target", version);
+        ImmutableList.of(
+            "-d",
+            outputDir,
+            "-s",
+            outputDir,
+            "-source",
+            version,
+            "-target",
+            version,
+            "-warn:-warningToken,-intfAnnotation",
+            "-Acom.google.auto.value.AutoBuilderIsUnstable");
     JavaCompiler.CompilationTask task =
         compiler.getTask(null, fileManager, null, options, null, sourceFileObjects);
     // Explicitly supply an empty list of extensions for AutoValueProcessor, because otherwise this
@@ -121,7 +136,10 @@
     AutoValueProcessor autoValueProcessor = new AutoValueProcessor(ImmutableList.of());
     ImmutableList<? extends Processor> processors =
         ImmutableList.of(
-            autoValueProcessor, new AutoOneOfProcessor(), new AutoAnnotationProcessor());
+            autoValueProcessor,
+            new AutoOneOfProcessor(),
+            new AutoAnnotationProcessor(),
+            new AutoBuilderProcessor());
     task.setProcessors(processors);
     assertWithMessage("Compilation should succeed").that(task.call()).isTrue();
   }
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java b/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java
new file mode 100644
index 0000000..f4eb538
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/GradleTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import org.gradle.testkit.runner.BuildResult;
+import org.gradle.testkit.runner.GradleRunner;
+import org.gradle.util.GradleVersion;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class GradleTest {
+  @Rule public TemporaryFolder fakeProject = new TemporaryFolder();
+
+  private static final String BUILD_GRADLE_TEXT =
+      String.join(
+          "\n",
+          "plugins {",
+          "  id 'java-library'",
+          "}",
+          "repositories {",
+          "  maven { url = uri('${localRepository}') }",
+          "}",
+          "dependencies {",
+          "  compileOnlyApi     "
+              + " 'com.google.auto.value:auto-value-annotations:${autoValueVersion}'",
+          "  annotationProcessor 'com.google.auto.value:auto-value:${autoValueVersion}'",
+          "}");
+
+  private static final String FOO_TEXT =
+      String.join(
+          "\n",
+          "package com.example;",
+          "",
+          "import com.google.auto.value.AutoValue;",
+          "",
+          "@AutoValue",
+          "abstract class Foo {",
+          "  abstract String bar();",
+          "",
+          "  static Foo of(String bar) {",
+          "    return new AutoValue_Foo(bar);",
+          "  }",
+          "}");
+
+  private static final Optional<File> GRADLE_INSTALLATION = getGradleInstallation();
+
+  @Test
+  public void basic() throws IOException {
+    String autoValueVersion = System.getProperty("autoValueVersion");
+    assertThat(autoValueVersion).isNotNull();
+    String localRepository = System.getProperty("localRepository");
+    assertThat(localRepository).isNotNull();
+
+    // Set up the fake Gradle project.
+    String buildGradleText = expandSystemProperties(BUILD_GRADLE_TEXT);
+    writeFile(fakeProject.newFile("build.gradle").toPath(), buildGradleText);
+    Path srcDir = fakeProject.newFolder("src", "main", "java", "com", "example").toPath();
+    writeFile(srcDir.resolve("Foo.java"), FOO_TEXT);
+
+    // Build it the first time.
+    BuildResult result1 = buildFakeProject();
+    assertThat(result1.getOutput())
+        .contains(
+            "Full recompilation is required because no incremental change information is"
+                + " available");
+    Path output =
+        fakeProject
+            .getRoot()
+            .toPath()
+            .resolve("build/classes/java/main/com/example/AutoValue_Foo.class");
+    assertThat(Files.exists(output)).isTrue();
+
+    // Add a source file to the project.
+    String barText = FOO_TEXT.replace("Foo", "Bar");
+    Path barFile = srcDir.resolve("Bar.java");
+    writeFile(barFile, barText);
+
+    // Build it a second time.
+    BuildResult result2 = buildFakeProject();
+    assertThat(result2.getOutput()).doesNotContain("Full recompilation is required");
+
+    // Remove the second source file and build a third time. If incremental annotation processing
+    // is not working, this will produce a message like this:
+    //   Full recompilation is required because com.google.auto.value.processor.AutoValueProcessor
+    //   is not incremental
+    Files.delete(barFile);
+    BuildResult result3 = buildFakeProject();
+    assertThat(result3.getOutput()).doesNotContain("Full recompilation is required");
+  }
+
+  private BuildResult buildFakeProject() throws IOException {
+    GradleRunner runner =
+        GradleRunner.create()
+            .withProjectDir(fakeProject.getRoot())
+            .withArguments("--info", "compileJava");
+    if (GRADLE_INSTALLATION.isPresent()) {
+      runner.withGradleInstallation(GRADLE_INSTALLATION.get());
+    } else {
+      runner.withGradleVersion(GradleVersion.current().getVersion());
+    }
+    return runner.build();
+  }
+
+  private static Optional<File> getGradleInstallation() {
+    String gradleHome = System.getenv("GRADLE_HOME");
+    if (gradleHome != null) {
+      File gradleHomeFile = new File(gradleHome);
+      if (gradleHomeFile.isDirectory()) {
+        return Optional.of(new File(gradleHome));
+      }
+    }
+    try {
+      Path gradleExecutable = Paths.get("/usr/bin/gradle");
+      Path gradleLink = gradleExecutable.resolveSibling(Files.readSymbolicLink(gradleExecutable));
+      if (!gradleLink.endsWith("bin/gradle")) {
+        return Optional.empty();
+      }
+      Path installationPath = gradleLink.getParent().getParent();
+      if (!Files.isDirectory(installationPath)) {
+        return Optional.empty();
+      }
+      Optional<Path> coreJar;
+      Pattern corePattern = Pattern.compile("gradle-core-([0-9]+)\\..*\\.jar");
+      try (Stream<Path> files = Files.walk(installationPath.resolve("lib"))) {
+        coreJar =
+            files
+                .filter(
+                    p -> {
+                      Matcher matcher = corePattern.matcher(p.getFileName().toString());
+                      if (matcher.matches()) {
+                        int version = Integer.parseInt(matcher.group(1));
+                        if (version >= 5) {
+                          return true;
+                        }
+                      }
+                      return false;
+                    })
+                .findFirst();
+      }
+      return coreJar.map(unused -> installationPath.toFile());
+    } catch (IOException e) {
+      return Optional.empty();
+    }
+  }
+
+  private static String expandSystemProperties(String s) {
+    for (String name : System.getProperties().stringPropertyNames()) {
+      String value = System.getProperty(name);
+      s = s.replace("${" + name + "}", value);
+    }
+    return s;
+  }
+
+  private static void writeFile(Path file, String text) throws IOException {
+    Files.write(file, ImmutableList.of(text), UTF_8);
+  }
+}
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt b/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt
new file mode 100644
index 0000000..f331889
--- /dev/null
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/KotlinData.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value
+
+data class KotlinData(val int: Int, val string: String)
+
+data class KotlinDataWithNullable(val anInt: Int?, val aString: String?)
+
+data class KotlinDataWithDefaults(val anInt: Int = 23, val aString: String = "skidoo")
diff --git a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java
index 0476906..e6f7abf 100644
--- a/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java
+++ b/value/src/it/functional/src/test/java/com/google/auto/value/gwt/EmptyExtension.java
@@ -99,8 +99,7 @@
     if (typeParameters.isEmpty()) {
       return "";
     }
-    return typeParameters
-        .stream()
+    return typeParameters.stream()
         .map(e -> e.getSimpleName().toString())
         .collect(joining(", ", "<", ">"));
   }
diff --git a/value/src/it/gwtserializer/pom.xml b/value/src/it/gwtserializer/pom.xml
index eea8952..42cc2fe 100644
--- a/value/src/it/gwtserializer/pom.xml
+++ b/value/src/it/gwtserializer/pom.xml
@@ -36,7 +36,7 @@
       <dependency>
         <groupId>com.google.gwt</groupId>
         <artifactId>gwt</artifactId>
-        <version>2.8.2</version>
+        <version>2.9.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -94,17 +94,17 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.7.0</version>
+        <version>3.8.1</version>
         <dependencies>
           <dependency>
             <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-java</artifactId>
-            <version>0.9.4</version>
+            <version>1.0.7</version>
           </dependency>
         </dependencies>
         <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
+          <source>1.8</source>
+          <target>1.8</target>
           <compilerArgument>-Xlint:all</compilerArgument>
           <showWarnings>true</showWarnings>
           <showDeprecation>true</showDeprecation>
@@ -120,7 +120,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-resources-plugin</artifactId>
-        <version>3.0.2</version>
+        <version>3.2.0</version>
         <executions>
           <execution>
             <!-- postpone resources:testResources until after compiler:testCompile to get generated sources -->
@@ -132,7 +132,7 @@
       <plugin>
         <groupId>net.ltgt.gwt.maven</groupId>
         <artifactId>gwt-maven-plugin</artifactId>
-        <version>1.0-rc-6</version>
+        <version>1.0.0</version>
         <executions>
           <execution>
             <goals>
@@ -144,7 +144,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-deploy-plugin</artifactId>
-        <version>2.7</version>
+        <version>2.8.2</version>
         <configuration>
           <!-- Build/test, but don't deploy -->
           <skip>true</skip>
diff --git a/value/src/main/java/com/google/auto/value/AutoBuilder.java b/value/src/main/java/com/google/auto/value/AutoBuilder.java
new file mode 100644
index 0000000..b970900
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/AutoBuilder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specifies that the annotated interface or abstract class should be implemented as a builder.
+ * This is still unstable; uses outside Google may break.
+ *
+ * <p>A simple example:
+ *
+ * <pre>
+ *
+ *   {@code @}AutoBuilder(ofClass = Person.class)
+ *   abstract class PersonBuilder {
+ *     static PersonBuilder builder() {
+ *       return new AutoBuilder_PersonBuilder();
+ *     }
+ *
+ *     abstract PersonBuilder setName(String name);
+ *     abstract PersonBuilder setId(int id);
+ *     abstract Person build();
+ *   }</pre>
+ *
+ * @see <a
+ * href="https://github.com/google/auto/blob/master/value/userguide/autobuilder.md">AutoBuilder
+ * User's Guide</a>
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+public @interface AutoBuilder {
+  /**
+   * The static method from {@link #ofClass} to call when the build-method of the builder is called.
+   * By default this is empty, meaning that a constructor rather than a static method should be
+   * called. There can be more than one method with the given name, or more than one constructor, in
+   * which case the one to call is the one whose parameter names and types correspond to the
+   * abstract methods of the class or interface with the {@code @AutoBuilder} annotation.
+   */
+  String callMethod() default "";
+
+  /**
+   * The class or interface containing the constructor or static method that the generated builder
+   * will eventually call. By default this is the class or interface that <i>contains</i> the class
+   * or interface with the {@code @AutoBuilder} annotation.
+   */
+  Class<?> ofClass() default Void.class;
+}
diff --git a/value/src/main/java/com/google/auto/value/AutoValue.java b/value/src/main/java/com/google/auto/value/AutoValue.java
index 45a677c..d7541f6 100644
--- a/value/src/main/java/com/google/auto/value/AutoValue.java
+++ b/value/src/main/java/com/google/auto/value/AutoValue.java
@@ -98,7 +98,7 @@
    * <p>If you want to copy annotations from your {@literal @}AutoValue-annotated class's methods to
    * the generated fields in the AutoValue_... implementation, annotate your method
    * with {@literal @}AutoValue.CopyAnnotations. For example, if Example.java is:<pre>
-
+   *
    *   {@code @}Immutable
    *   {@code @}AutoValue
    *   abstract class Example {
diff --git a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
index 2c8a3fb..343645a 100644
--- a/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
@@ -121,12 +121,10 @@
      * example, if you have...
      *
      * <pre>
-     *   interface Parent<T> {
+     *   {@code interface Parent<T>} {
      *     T bar();
      *   }
-     *
-     *  {@code @AutoValue abstract class Foo implements Parent<String> {...}}
-     * </pre>
+     *  {@code @AutoValue abstract class Foo implements Parent<String> {...}}</pre>
      *
      * ...then the type of the {@code bar} property in {@code Foo} is actually {@code String}, but
      * the {@code ExecutableElement} will be the the method in {@code Parent}, whose return type is
@@ -418,11 +416,11 @@
    * Context#abstractMethods()}.
    *
    * <p>For example, Android's {@code Parcelable} interface includes a <a
-   * href="http://developer.android.com/reference/android/os/Parcelable.html#writeToParcel(android.os.Parcel,
-   * int)">method</a> {@code void writeToParcel(Parcel, int)}. Normally AutoValue would not know
-   * what to do with that abstract method. But an {@code AutoValueExtension} that understands {@code
-   * Parcelable} can provide a useful implementation and return the {@code writeToParcel} method
-   * here. That will prevent a warning about the method from AutoValue.
+   * href="http://developer.android.com/reference/android/os/Parcelable.html#writeToParcel(android.os.Parcel,int)">method</a>
+   * {@code void writeToParcel(Parcel, int)}. Normally AutoValue would not know what to do with that
+   * abstract method. But an {@code AutoValueExtension} that understands {@code Parcelable} can
+   * provide a useful implementation and return the {@code writeToParcel} method here. That will
+   * prevent a warning about the method from AutoValue.
    *
    * @param context the Context of the code generation for this class.
    */
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
index 0ca46bd..acbe1c0 100644
--- a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizeExtension.java
@@ -19,18 +19,19 @@
 import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
 import static com.google.auto.common.MoreElements.getPackage;
 import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
 import static com.google.auto.value.extension.memoized.processor.ClassNames.MEMOIZED_NAME;
 import static com.google.auto.value.extension.memoized.processor.MemoizedValidator.getAnnotationMirror;
 import static com.google.common.base.Predicates.equalTo;
 import static com.google.common.base.Predicates.not;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.collect.Sets.union;
 import static com.squareup.javapoet.MethodSpec.constructorBuilder;
 import static com.squareup.javapoet.MethodSpec.methodBuilder;
 import static com.squareup.javapoet.TypeSpec.classBuilder;
+import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
 import static javax.lang.model.element.Modifier.ABSTRACT;
@@ -50,7 +51,6 @@
 import com.google.auto.service.AutoService;
 import com.google.auto.value.extension.AutoValueExtension;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.errorprone.annotations.FormatMethod;
@@ -65,9 +65,7 @@
 import com.squareup.javapoet.TypeSpec;
 import com.squareup.javapoet.TypeVariableName;
 import java.lang.annotation.Inherited;
-import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import javax.annotation.processing.Messager;
@@ -80,7 +78,6 @@
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.QualifiedNameable;
 import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.TypeParameterElement;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.Elements;
 import javax.lang.model.util.Types;
@@ -126,13 +123,9 @@
   }
 
   private static ImmutableSet<ExecutableElement> memoizedMethods(Context context) {
-    ImmutableSet.Builder<ExecutableElement> memoizedMethods = ImmutableSet.builder();
-    for (ExecutableElement method : methodsIn(context.autoValueClass().getEnclosedElements())) {
-      if (getAnnotationMirror(method, MEMOIZED_NAME).isPresent()) {
-        memoizedMethods.add(method);
-      }
-    }
-    return memoizedMethods.build();
+    return methodsIn(context.autoValueClass().getEnclosedElements()).stream()
+        .filter(m -> getAnnotationMirror(m, MEMOIZED_NAME).isPresent())
+        .collect(toImmutableSet());
   }
 
   static final class Generator {
@@ -164,7 +157,7 @@
           classBuilder(className)
               .superclass(superType())
               .addAnnotations(copiedClassAnnotations(context.autoValueClass()))
-              .addTypeVariables(typeVariableNames())
+              .addTypeVariables(annotatedTypeVariableNames())
               .addModifiers(isFinal ? FINAL : ABSTRACT)
               .addMethod(constructor());
       generatedAnnotationSpec(elements, sourceVersion, MemoizeExtension.class)
@@ -183,6 +176,7 @@
       return JavaFile.builder(context.packageName(), generated.build()).build().toString();
     }
 
+    // LINT.IfChange
     private TypeName superType() {
       ClassName superType = ClassName.get(context.packageName(), classToExtend);
       ImmutableList<TypeVariableName> typeVariableNames = typeVariableNames();
@@ -193,23 +187,31 @@
     }
 
     private ImmutableList<TypeVariableName> typeVariableNames() {
-      ImmutableList.Builder<TypeVariableName> typeVariableNamesBuilder = ImmutableList.builder();
-      for (TypeParameterElement typeParameter : context.autoValueClass().getTypeParameters()) {
-        typeVariableNamesBuilder.add(TypeVariableName.get(typeParameter));
-      }
-      return typeVariableNamesBuilder.build();
+      return context.autoValueClass().getTypeParameters().stream()
+          .map(TypeVariableName::get)
+          .collect(toImmutableList());
+    }
+
+    private ImmutableList<TypeVariableName> annotatedTypeVariableNames() {
+      return context.autoValueClass().getTypeParameters().stream()
+          .map(
+              p ->
+                  TypeVariableName.get(p)
+                      .annotated(
+                          p.getAnnotationMirrors().stream()
+                              .map(AnnotationSpec::get)
+                              .collect(toImmutableList())))
+          .collect(toImmutableList());
     }
 
     private MethodSpec constructor() {
       MethodSpec.Builder constructor = constructorBuilder();
-      for (Map.Entry<String, TypeMirror> property : context.propertyTypes().entrySet()) {
-        constructor.addParameter(annotatedType(property.getValue()), property.getKey() + "$");
-      }
-      List<String> namesWithDollars = new ArrayList<String>();
-      for (String property : context.properties().keySet()) {
-        namesWithDollars.add(property + "$");
-      }
-      constructor.addStatement("super($L)", Joiner.on(", ").join(namesWithDollars));
+      context
+          .propertyTypes()
+          .forEach((name, type) -> constructor.addParameter(annotatedType(type), name + "$"));
+      String superParams =
+          context.properties().keySet().stream().map(n -> n + "$").collect(joining(", "));
+      constructor.addStatement("super($L)", superParams);
       return constructor.build();
     }
 
@@ -250,6 +252,7 @@
           .build();
     }
 
+    // LINT.IfChange
     /**
      * True if the given class name is in the com.google.auto.value package or a subpackage. False
      * if the class name contains {@code Test}, since many AutoValue tests under
@@ -478,15 +481,15 @@
         return elements.overrides(method, objectMethod(methodName), context.autoValueClass());
       }
 
-      private ExecutableElement objectMethod(final String methodName) {
+      private ExecutableElement objectMethod(String methodName) {
         TypeElement object = elements.getTypeElement(Object.class.getName());
-        for (ExecutableElement method : methodsIn(object.getEnclosedElements())) {
-          if (method.getSimpleName().contentEquals(methodName)) {
-            return method;
-          }
-        }
-        throw new IllegalArgumentException(
-            String.format("No method in Object named \"%s\"", methodName));
+        return methodsIn(object.getEnclosedElements()).stream()
+            .filter(m -> m.getSimpleName().contentEquals(methodName))
+            .findFirst()
+            .orElseThrow(
+                () ->
+                    new IllegalArgumentException(
+                        String.format("No method in Object named \"%s\"", methodName)));
       }
 
       private boolean pullDownMethodAnnotation(AnnotationMirror annotation) {
@@ -594,9 +597,7 @@
   /** Translate a {@link TypeMirror} into a {@link TypeName}, including type annotations. */
   private static TypeName annotatedType(TypeMirror type) {
     List<AnnotationSpec> annotations =
-        type.getAnnotationMirrors().stream()
-            .map(AnnotationSpec::get)
-            .collect(toList());
+        type.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(toList());
     return TypeName.get(type).annotated(annotations);
   }
 }
diff --git a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java
index 5a77050..d250fbc 100644
--- a/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java
+++ b/value/src/main/java/com/google/auto/value/extension/memoized/processor/MemoizedValidator.java
@@ -37,8 +37,8 @@
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
 
 /**
- * An annotation {@link Processor} that reports errors for {@link Memoized @Memoized} methods that
- * are not inside {@code AutoValue}-annotated classes.
+ * An annotation {@link Processor} that reports errors for {@code @Memoized} methods that are not
+ * inside {@code AutoValue}-annotated classes.
  */
 @AutoService(Processor.class)
 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
@@ -66,9 +66,7 @@
   }
 
   private static boolean isAutoValue(Element element) {
-    return element
-        .getAnnotationMirrors()
-        .stream()
+    return element.getAnnotationMirrors().stream()
         .map(annotation -> MoreTypes.asTypeElement(annotation.getAnnotationType()))
         .anyMatch(type -> type.getQualifiedName().contentEquals("com.google.auto.value.AutoValue"));
   }
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/index.md b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md
similarity index 99%
rename from value/src/main/java/com/google/auto/value/extension/serializable/userguide/index.md
rename to value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md
index e766183..0282404 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/index.md
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/index.md
@@ -4,7 +4,6 @@
 An [`AutoValue`] extension that enables `@AutoValue` classes with
 un-serializable properties to be serializable.
 
-
 ## Usage
 
 To use the [`SerializableAutoValueExtension`] with your `AutoValue` class, the
@@ -100,4 +99,3 @@
 [`SerializableAutoValue`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java
 [`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
 [SerializerExtensions]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md
-
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md
similarity index 99%
rename from value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md
rename to value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md
index edfa3ca..aaec7c0 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/userguide/serializer-extension.md
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/g3doc/serializer-extension.md
@@ -243,4 +243,3 @@
 [`SerializableAutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/extension/SerializableAutoValueExtension.java
 [`SerializerExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java
 [`Serializer`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java
-
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
index d3265d0..5143d8b 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java
@@ -15,9 +15,9 @@
  */
 package com.google.auto.value.extension.serializable.processor;
 
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
 import static com.google.auto.value.extension.serializable.processor.ClassNames.SERIALIZABLE_AUTO_VALUE_NAME;
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.collect.ImmutableMap.toImmutableMap;
 import static java.util.stream.Collectors.joining;
 
 import com.google.auto.common.GeneratedAnnotationSpecs;
@@ -59,6 +59,7 @@
  *   <li>The AutoValue class must implement {@link Serializable}.
  *   <li>Unserializable fields in the AutoValue class must be supported by a {@link
  *       com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension}.
+ * </ul>
  */
 @AutoService(AutoValueExtension.class)
 public final class SerializableAutoValueExtension extends AutoValueExtension {
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java
index 12984b0..e81b586 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java
@@ -21,6 +21,8 @@
 import com.google.auto.value.processor.SimpleServiceLoader;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
+import java.util.Optional;
+import java.util.regex.Pattern;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.tools.Diagnostic;
 
@@ -40,10 +42,18 @@
 
   private static ImmutableList<SerializerExtension> loadExtensions(
       ProcessingEnvironment processingEnv) {
+    // The below is a workaround for a test-building bug. We don't expect to support it indefinitely
+    // so don't depend on it.
+    String allowedMissingClasses =
+        processingEnv.getOptions().get("allowedMissingSerializableExtensionClasses");
+    Optional<Pattern> allowedMissingClassesPattern =
+        Optional.ofNullable(allowedMissingClasses).map(Pattern::compile);
     try {
       return ImmutableList.copyOf(
           SimpleServiceLoader.load(
-              SerializerExtension.class, SerializerFactoryLoader.class.getClassLoader()));
+              SerializerExtension.class,
+              SerializerFactoryLoader.class.getClassLoader(),
+              allowedMissingClassesPattern));
     } catch (Throwable t) {
       processingEnv
           .getMessager()
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java
index 7ff4f19..a390add 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java
@@ -55,17 +55,22 @@
       return Optional.empty();
     }
 
-    return Optional.of(new ImmutableListSerializer(containedTypeSerializer, processingEnv));
+    return Optional.of(
+        new ImmutableListSerializer(containedTypeSerializer, factory, processingEnv));
   }
 
   private static class ImmutableListSerializer implements Serializer {
 
     private final Serializer containedTypeSerializer;
+    private final SerializerFactory factory;
     private final ProcessingEnvironment processingEnv;
 
     ImmutableListSerializer(
-        Serializer containedTypeSerializer, ProcessingEnvironment processingEnv) {
+        Serializer containedTypeSerializer,
+        SerializerFactory factory,
+        ProcessingEnvironment processingEnv) {
       this.containedTypeSerializer = containedTypeSerializer;
+      this.factory = factory;
       this.processingEnv = processingEnv;
     }
 
@@ -81,7 +86,7 @@
 
     @Override
     public CodeBlock toProxy(CodeBlock expression) {
-      CodeBlock element = CodeBlock.of("value$$");
+      CodeBlock element = factory.newIdentifier("value");
       return CodeBlock.of(
           "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())",
           expression,
@@ -93,7 +98,7 @@
 
     @Override
     public CodeBlock fromProxy(CodeBlock expression) {
-      CodeBlock element = CodeBlock.of("value$$");
+      CodeBlock element = factory.newIdentifier("value");
       return CodeBlock.of(
           "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())",
           expression,
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java
index 9d571e3..8d67e10 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java
@@ -60,7 +60,7 @@
 
     return Optional.of(
         new ImmutableMapSerializer(
-            keyType, valueType, keyTypeSerializer, valueTypeSerializer, processingEnv));
+            keyType, valueType, keyTypeSerializer, valueTypeSerializer, factory, processingEnv));
   }
 
   private static class ImmutableMapSerializer implements Serializer {
@@ -71,6 +71,7 @@
     private final TypeMirror valueProxyType;
     private final Serializer keyTypeSerializer;
     private final Serializer valueTypeSerializer;
+    private final SerializerFactory factory;
     private final ProcessingEnvironment processingEnv;
 
     ImmutableMapSerializer(
@@ -78,6 +79,7 @@
         TypeMirror valueType,
         Serializer keyTypeSerializer,
         Serializer valueTypeSerializer,
+        SerializerFactory factory,
         ProcessingEnvironment processingEnv) {
       this.keyType = keyType;
       this.valueType = valueType;
@@ -85,6 +87,7 @@
       this.valueProxyType = valueTypeSerializer.proxyFieldType();
       this.keyTypeSerializer = keyTypeSerializer;
       this.valueTypeSerializer = valueTypeSerializer;
+      this.factory = factory;
       this.processingEnv = processingEnv;
     }
 
@@ -117,13 +120,15 @@
           generateValueMapFunction(valueProxyType, valueType, valueTypeSerializer::fromProxy));
     }
 
-    private static CodeBlock generateKeyMapFunction(
+    private CodeBlock generateKeyMapFunction(
         TypeMirror originalType,
         TypeMirror transformedType,
         Function<CodeBlock, CodeBlock> proxyMap) {
-      CodeBlock element = CodeBlock.of("element$$");
+      CodeBlock element = factory.newIdentifier("element");
+      CodeBlock value = factory.newIdentifier("value");
       return CodeBlock.of(
-          "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getKey())",
+          "$1L -> $2T.<$3T, $4T>wrapper($5L -> $6L).apply($1L.getKey())",
+          value,
           FunctionWithExceptions.class,
           originalType,
           transformedType,
@@ -131,13 +136,15 @@
           proxyMap.apply(element));
     }
 
-    private static CodeBlock generateValueMapFunction(
+    private CodeBlock generateValueMapFunction(
         TypeMirror originalType,
         TypeMirror transformedType,
         Function<CodeBlock, CodeBlock> proxyMap) {
-      CodeBlock element = CodeBlock.of("element$$");
+      CodeBlock element = factory.newIdentifier("element");
+      CodeBlock value = factory.newIdentifier("value");
       return CodeBlock.of(
-          "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getValue())",
+          "$1L -> $2T.<$3T, $4T>wrapper($5L -> $6L).apply($1L.getValue())",
+          value,
           FunctionWithExceptions.class,
           originalType,
           transformedType,
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java
index 57741f9..7c55289 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java
@@ -19,7 +19,9 @@
 import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension;
 import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
 import com.google.common.collect.ImmutableList;
+import com.squareup.javapoet.CodeBlock;
 import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.type.TypeMirror;
 
@@ -28,6 +30,7 @@
 
   private final ImmutableList<SerializerExtension> extensions;
   private final ProcessingEnvironment env;
+  private final AtomicInteger idCount = new AtomicInteger();
 
   public SerializerFactoryImpl(
       ImmutableList<SerializerExtension> extensions, ProcessingEnvironment env) {
@@ -45,4 +48,9 @@
     }
     return IdentitySerializerFactory.getSerializer(typeMirror);
   }
+
+  @Override
+  public CodeBlock newIdentifier(String prefix) {
+    return CodeBlock.of("$L$$$L", prefix, idCount.incrementAndGet());
+  }
 }
diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java
index d05c88b..2b54225 100644
--- a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java
+++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java
@@ -15,6 +15,7 @@
  */
 package com.google.auto.value.extension.serializable.serializer.interfaces;
 
+import com.squareup.javapoet.CodeBlock;
 import javax.lang.model.type.TypeMirror;
 
 /**
@@ -26,4 +27,16 @@
 
   /** Returns a {@link Serializer} for the given {@link TypeMirror}. */
   Serializer getSerializer(TypeMirror type);
+
+  /**
+   * Returns an identifier beginning with the given prefix and that is distinct from any identifier
+   * returned by another call to this method. The returned identifier will contain a {@code $},
+   * which should also mean it is distinct from identifiers in user code that are in scope.
+   *
+   * <p>The default implementation of this method throws {@link UnsupportedOperationException} for
+   * compatibility reasons.
+   */
+  default CodeBlock newIdentifier(String prefix) {
+    throw new UnsupportedOperationException();
+  }
 }
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
new file mode 100644
index 0000000..cd2762a
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Target;
+import java.util.Collection;
+
+/**
+ * Annotates instance methods that return an easy-to-read {@link String} representing the instance.
+ * When the method is {@code abstract} and enclosed in an {@link com.google.auto.value.AutoValue}
+ * class, an implementation of the method will be automatically generated.
+ *
+ * <p>When generating an implementation of an {@code @ToPrettyString} method, each property of the
+ * {@code @AutoValue} type is individually printed in an easy-to-read format. If the type of the
+ * property itself has a {@code @ToPrettyString} method, that method will be called in assistance of
+ * computing the pretty string. Non-{@code @AutoValue} classes can contribute a pretty string
+ * representation by annotating a method with {@code @ToPrettyString}.
+ *
+ * <p>{@link Collection} and {@link Collection}-like types have special representations in generated
+ * pretty strings.
+ *
+ * <p>If no {@code @ToPrettyString} method is found on a type and the type is not one with a built
+ * in rendering, the {@link Object#toString()} value will be used instead.
+ *
+ * <p>{@code @ToPrettyString} is valid on overridden {@code toString()} and other methods alike.
+ *
+ * <h3>Example</h3>
+ *
+ * <pre>
+ *   {@code @AutoValue}
+ *   abstract class Pretty {
+ *     abstract {@code List<String>} property();
+ *
+ *     {@code @ToPrettyString}
+ *     abstract String toPrettyString();
+ *   }
+ *
+ *   System.out.println(new AutoValue_Pretty(List.of("abc", "def", "has\nnewline)).toPrettyString())
+ *   // Pretty{
+ *   //   property = [
+ *   //     abc,
+ *   //     def,
+ *   //     has
+ *   //     newline,
+ *   //   ]
+ *   // }
+ *   }</pre>
+ */
+@Documented
+@Target(METHOD)
+public @interface ToPrettyString {}
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/Annotations.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/Annotations.java
new file mode 100644
index 0000000..255d6c9
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/Annotations.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring.processor;
+
+import static com.google.auto.value.extension.toprettystring.processor.ClassNames.TO_PRETTY_STRING_NAME;
+
+import com.google.auto.common.MoreTypes;
+import java.util.Optional;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+
+/** Extension methods for working with {@link AnnotationMirror}. */
+final class Annotations {
+  static Optional<AnnotationMirror> getAnnotationMirror(Element element, String annotationName) {
+    for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
+      TypeElement annotationElement = MoreTypes.asTypeElement(annotation.getAnnotationType());
+      if (annotationElement.getQualifiedName().contentEquals(annotationName)) {
+        return Optional.of(annotation);
+      }
+    }
+    return Optional.empty();
+  }
+
+  static Optional<AnnotationMirror> toPrettyStringAnnotation(Element element) {
+    return getAnnotationMirror(element, TO_PRETTY_STRING_NAME);
+  }
+
+  private Annotations() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ClassNames.java
new file mode 100644
index 0000000..0cfd577
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ClassNames.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring.processor;
+
+/** Names of classes that are referenced in the processor/extension. */
+final class ClassNames {
+  static final String TO_PRETTY_STRING_NAME =
+      "com.google.auto.value.extension.toprettystring.ToPrettyString";
+
+  private ClassNames() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java
new file mode 100644
index 0000000..e2381f7
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ExtensionClassTypeSpecBuilder.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring.processor;
+
+import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
+import static com.google.auto.common.GeneratedAnnotationSpecs.generatedAnnotationSpec;
+import static com.google.auto.common.MoreElements.getPackage;
+import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.auto.value.extension.toprettystring.processor.Annotations.getAnnotationMirror;
+import static com.google.common.collect.Sets.union;
+import static com.squareup.javapoet.MethodSpec.constructorBuilder;
+import static com.squareup.javapoet.TypeSpec.classBuilder;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static javax.lang.model.element.Modifier.ABSTRACT;
+import static javax.lang.model.element.Modifier.FINAL;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.common.Visibility;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.extension.AutoValueExtension.Context;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.ParameterizedTypeName;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.TypeVariableName;
+import java.lang.annotation.Inherited;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+/**
+ * A factory for {@link TypeSpec}s used in {@link AutoValueExtension} implementations.
+ *
+ * <p>This is copied from {@link
+ * com.google.auto.value.extension.memoized.processor.MemoizeExtension} until we find a better
+ * location to consolidate the code.
+ */
+final class ExtensionClassTypeSpecBuilder {
+  private static final String AUTO_VALUE_PACKAGE_NAME = "com.google.auto.value.";
+  private static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue";
+  private static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations";
+
+  private final Context context;
+  private final String className;
+  private final String classToExtend;
+  private final boolean isFinal;
+  private final Types types;
+  private final Elements elements;
+  private final SourceVersion sourceVersion;
+
+  private ExtensionClassTypeSpecBuilder(
+      Context context, String className, String classToExtend, boolean isFinal) {
+    this.context = context;
+    this.className = className;
+    this.classToExtend = classToExtend;
+    this.isFinal = isFinal;
+    this.types = context.processingEnvironment().getTypeUtils();
+    this.elements = context.processingEnvironment().getElementUtils();
+    this.sourceVersion = context.processingEnvironment().getSourceVersion();
+  }
+
+  static TypeSpec.Builder extensionClassTypeSpecBuilder(
+      Context context, String className, String classToExtend, boolean isFinal) {
+    return new ExtensionClassTypeSpecBuilder(context, className, classToExtend, isFinal)
+        .extensionClassBuilder();
+  }
+
+  TypeSpec.Builder extensionClassBuilder() {
+    TypeSpec.Builder builder =
+        classBuilder(className)
+            .superclass(superType())
+            .addAnnotations(copiedClassAnnotations(context.autoValueClass()))
+            .addTypeVariables(annotatedTypeVariableNames())
+            .addModifiers(isFinal ? FINAL : ABSTRACT)
+            .addMethod(constructor());
+    generatedAnnotationSpec(elements, sourceVersion, ToPrettyStringExtension.class)
+        .ifPresent(builder::addAnnotation);
+    return builder;
+  }
+
+  private TypeName superType() {
+    ClassName superType = ClassName.get(context.packageName(), classToExtend);
+    ImmutableList<TypeVariableName> typeVariableNames = typeVariableNames();
+
+    return typeVariableNames.isEmpty()
+        ? superType
+        : ParameterizedTypeName.get(superType, typeVariableNames.toArray(new TypeName[] {}));
+  }
+
+  private ImmutableList<TypeVariableName> typeVariableNames() {
+    return context.autoValueClass().getTypeParameters().stream()
+        .map(TypeVariableName::get)
+        .collect(toImmutableList());
+  }
+
+  private ImmutableList<TypeVariableName> annotatedTypeVariableNames() {
+    return context.autoValueClass().getTypeParameters().stream()
+        .map(
+            p ->
+                TypeVariableName.get(p)
+                    .annotated(
+                        p.getAnnotationMirrors().stream()
+                            .map(AnnotationSpec::get)
+                            .collect(toImmutableList())))
+        .collect(toImmutableList());
+  }
+
+  private MethodSpec constructor() {
+    MethodSpec.Builder constructor = constructorBuilder();
+    context
+        .propertyTypes()
+        .forEach((name, type) -> constructor.addParameter(annotatedType(type), name + "$"));
+    String superParams =
+        context.properties().keySet().stream().map(n -> n + "$").collect(joining(", "));
+    constructor.addStatement("super($L)", superParams);
+    return constructor.build();
+  }
+
+  /**
+   * True if the given class name is in the com.google.auto.value package or a subpackage. False if
+   * the class name contains {@code Test}, since many AutoValue tests under com.google.auto.value
+   * define their own annotations.
+   */
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private boolean isInAutoValuePackage(String className) {
+    return className.startsWith(AUTO_VALUE_PACKAGE_NAME) && !className.contains("Test");
+  }
+
+  /**
+   * Returns the fully-qualified name of an annotation-mirror, e.g.
+   * "com.google.auto.value.AutoValue".
+   */
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private static String getAnnotationFqName(AnnotationMirror annotation) {
+    return ((QualifiedNameable) annotation.getAnnotationType().asElement())
+        .getQualifiedName()
+        .toString();
+  }
+
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private boolean annotationVisibleFrom(AnnotationMirror annotation, Element from) {
+    Element annotationElement = annotation.getAnnotationType().asElement();
+    Visibility visibility = Visibility.effectiveVisibilityOfElement(annotationElement);
+    switch (visibility) {
+      case PUBLIC:
+        return true;
+      case PROTECTED:
+        // If the annotation is protected, it must be inside another class, call it C. If our
+        // @AutoValue class is Foo then, for the annotation to be visible, either Foo must be in
+        // the same package as C or Foo must be a subclass of C. If the annotation is visible from
+        // Foo then it is also visible from our generated subclass AutoValue_Foo.
+        // The protected case only applies to method annotations. An annotation on the
+        // AutoValue_Foo class itself can't be protected, even if AutoValue_Foo ultimately
+        // inherits from the class that defines the annotation. The JLS says "Access is permitted
+        // only within the body of a subclass":
+        // https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.6.2.1
+        // AutoValue_Foo is a top-level class, so an annotation on it cannot be in the body of a
+        // subclass of anything.
+        return getPackage(annotationElement).equals(getPackage(from))
+            || types.isSubtype(from.asType(), annotationElement.getEnclosingElement().asType());
+      case DEFAULT:
+        return getPackage(annotationElement).equals(getPackage(from));
+      default:
+        return false;
+    }
+  }
+
+  /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private ImmutableList<AnnotationMirror> annotationsToCopy(
+      Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
+    ImmutableList.Builder<AnnotationMirror> result = ImmutableList.builder();
+    for (AnnotationMirror annotation : typeOrMethod.getAnnotationMirrors()) {
+      String annotationFqName = getAnnotationFqName(annotation);
+      // To be included, the annotation should not be in com.google.auto.value,
+      // and it should not be in the excludedAnnotations set.
+      if (!isInAutoValuePackage(annotationFqName)
+          && !excludedAnnotations.contains(annotationFqName)
+          && annotationVisibleFrom(annotation, autoValueType)) {
+        result.add(annotation);
+      }
+    }
+
+    return result.build();
+  }
+
+  /** Implements the semantics of {@code AutoValue.CopyAnnotations}; see its javadoc. */
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private ImmutableList<AnnotationSpec> copyAnnotations(
+      Element autoValueType, Element typeOrMethod, Set<String> excludedAnnotations) {
+    ImmutableList<AnnotationMirror> annotationsToCopy =
+        annotationsToCopy(autoValueType, typeOrMethod, excludedAnnotations);
+    return annotationsToCopy.stream().map(AnnotationSpec::get).collect(toImmutableList());
+  }
+
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private static boolean hasAnnotationMirror(Element element, String annotationName) {
+    return getAnnotationMirror(element, annotationName).isPresent();
+  }
+
+  /**
+   * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
+   * {@code TypeMirror} where each type is an annotation type.
+   */
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private ImmutableSet<TypeMirror> getExcludedAnnotationTypes(Element element) {
+    Optional<AnnotationMirror> maybeAnnotation =
+        getAnnotationMirror(element, COPY_ANNOTATIONS_NAME);
+    if (!maybeAnnotation.isPresent()) {
+      return ImmutableSet.of();
+    }
+
+    @SuppressWarnings("unchecked")
+    List<AnnotationValue> excludedClasses =
+        (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue();
+    return excludedClasses.stream()
+        .map(
+            annotationValue ->
+                MoreTypes.equivalence().wrap((TypeMirror) annotationValue.getValue()))
+        // TODO(b/122509249): Move TypeMirrorSet to common package instead of doing this.
+        .distinct()
+        .map(Wrapper::get)
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Returns the contents of the {@code AutoValue.CopyAnnotations.exclude} element, as a set of
+   * strings that are fully-qualified class names.
+   */
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private Set<String> getExcludedAnnotationClassNames(Element element) {
+    return getExcludedAnnotationTypes(element).stream()
+        .map(MoreTypes::asTypeElement)
+        .map(typeElement -> typeElement.getQualifiedName().toString())
+        .collect(toSet());
+  }
+
+  // TODO(b/122509249): Move code copied from com.google.auto.value.processor to auto-common.
+  private static Set<String> getAnnotationsMarkedWithInherited(Element element) {
+    return element.getAnnotationMirrors().stream()
+        .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
+        .map(ExtensionClassTypeSpecBuilder::getAnnotationFqName)
+        .collect(toSet());
+  }
+
+  private ImmutableList<AnnotationSpec> copiedClassAnnotations(TypeElement type) {
+    // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
+    if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) {
+      Set<String> excludedAnnotations =
+          union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type));
+
+      return copyAnnotations(type, type, excludedAnnotations);
+    } else {
+      return ImmutableList.of();
+    }
+  }
+
+  /** Translate a {@link TypeMirror} into a {@link TypeName}, including type annotations. */
+  private static TypeName annotatedType(TypeMirror type) {
+    List<AnnotationSpec> annotations =
+        type.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(toList());
+
+    return TypeName.get(type).annotated(annotations);
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java
new file mode 100644
index 0000000..134fcac
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringExtension.java
@@ -0,0 +1,562 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring.processor;
+
+import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreTypes.asTypeElement;
+import static com.google.auto.value.extension.toprettystring.processor.ExtensionClassTypeSpecBuilder.extensionClassTypeSpecBuilder;
+import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethod;
+import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethods;
+import static com.google.common.collect.Iterables.getLast;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.collect.Sets.intersection;
+import static com.squareup.javapoet.MethodSpec.methodBuilder;
+import static javax.lang.model.element.Modifier.FINAL;
+import static javax.lang.model.element.Modifier.PRIVATE;
+import static javax.lang.model.element.Modifier.PROTECTED;
+import static javax.lang.model.element.Modifier.PUBLIC;
+import static javax.lang.model.element.Modifier.STATIC;
+
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.extension.AutoValueExtension;
+import com.google.auto.value.extension.toprettystring.processor.ToPrettyStringExtension.PrettyPrintableKind.KindVisitor;
+import com.google.common.base.Equivalence;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import com.squareup.javapoet.CodeBlock;
+import com.squareup.javapoet.JavaFile;
+import com.squareup.javapoet.MethodSpec;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.TypeSpec;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.PrimitiveType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleTypeVisitor8;
+import javax.lang.model.util.Types;
+
+/**
+ * Generates implementations of {@link
+ * com.google.auto.value.extension.toprettystring.ToPrettyString} annotated methods in {@link
+ * com.google.auto.value.AutoValue} types.
+ */
+@AutoService(AutoValueExtension.class)
+public final class ToPrettyStringExtension extends AutoValueExtension {
+  private static final ImmutableSet<Modifier> INHERITED_VISIBILITY_MODIFIERS =
+      ImmutableSet.of(PUBLIC, PROTECTED);
+  private static final String INDENT = "  ";
+  private static final String INDENT_METHOD_NAME = "$indent";
+  private static final CodeBlock KEY_VALUE_SEPARATOR = CodeBlock.of("$S", ": ");
+
+  @Override
+  public String generateClass(
+      Context context, String className, String classToExtend, boolean isFinal) {
+    TypeSpec type =
+        extensionClassTypeSpecBuilder(context, className, classToExtend, isFinal)
+            .addMethods(toPrettyStringMethodSpecs(context))
+            .build();
+    return JavaFile.builder(context.packageName(), type)
+        .skipJavaLangImports(true)
+        .build()
+        .toString();
+  }
+
+  private ImmutableList<MethodSpec> toPrettyStringMethodSpecs(Context context) {
+    ExecutableElement toPrettyStringMethod = getOnlyElement(toPrettyStringMethods(context));
+    MethodSpec.Builder method =
+        methodBuilder(toPrettyStringMethod.getSimpleName().toString())
+            .addAnnotation(Override.class)
+            .returns(ClassName.get(String.class))
+            .addModifiers(FINAL)
+            .addModifiers(
+                intersection(toPrettyStringMethod.getModifiers(), INHERITED_VISIBILITY_MODIFIERS));
+
+    method.addCode("return $S", context.autoValueClass().getSimpleName() + " {");
+    ToPrettyStringImplementation implementation = ToPrettyStringImplementation.create(context);
+    method.addCode(implementation.toStringCodeBlock.build());
+
+    if (!context.properties().isEmpty()) {
+      method.addCode(" + $S", "\n");
+    }
+    method.addCode(" + $S;\n", "}");
+
+    return ImmutableList.<MethodSpec>builder()
+        .add(method.build())
+        .addAll(implementation.delegateMethods.values())
+        .add(indentMethod())
+        .build();
+  }
+
+  private static MethodSpec indentMethod() {
+    return methodBuilder(INDENT_METHOD_NAME)
+        .addModifiers(PRIVATE, STATIC)
+        .returns(ClassName.get(String.class))
+        .addParameter(TypeName.INT, "level")
+        .addStatement("$1T builder = new $1T()", StringBuilder.class)
+        .beginControlFlow("for (int i = 0; i < level; i++)")
+        .addStatement("builder.append($S)", INDENT)
+        .endControlFlow()
+        .addStatement("return builder.toString()")
+        .build();
+  }
+
+  private static class ToPrettyStringImplementation {
+    private final Types types;
+    private final Elements elements;
+
+    private final CodeBlock.Builder toStringCodeBlock = CodeBlock.builder();
+    private final Map<Equivalence.Wrapper<TypeMirror>, MethodSpec> delegateMethods =
+        new LinkedHashMap<>();
+    private final Set<String> methodNames = new HashSet<>();
+
+    private ToPrettyStringImplementation(Context context) {
+      this.types = context.processingEnvironment().getTypeUtils();
+      this.elements = context.processingEnvironment().getElementUtils();
+      // do not submit: what about "inherited" static methods?
+      getLocalAndInheritedMethods(context.autoValueClass(), types, elements)
+          .forEach(method -> methodNames.add(method.getSimpleName().toString()));
+    }
+
+    static ToPrettyStringImplementation create(Context context) {
+      ToPrettyStringImplementation implemention = new ToPrettyStringImplementation(context);
+      context
+          .propertyTypes()
+          .forEach(
+              (propertyName, type) -> {
+                String methodName =
+                    context.properties().get(propertyName).getSimpleName().toString();
+                implemention.toStringCodeBlock.add(
+                    "\n + $S + $L + $S",
+                    String.format("\n%s%s = ", INDENT, propertyName),
+                    implemention.format(CodeBlock.of("$N()", methodName), CodeBlock.of("1"), type),
+                    ",");
+              });
+      return implemention;
+    }
+
+    /**
+     * Returns {@code propertyAccess} formatted for use within the {@link
+     * com.google.auto.value.extension.toprettystring.ToPrettyString} implementation.
+     *
+     * <p>If a helper method is necessary for formatting, a {@link MethodSpec} will be added to
+     * {@link #delegateMethods}.
+     *
+     * @param propertyAccess a reference to the variable that should be formatted.
+     * @param indentAccess a reference to an {@code int} representing how many indent levels should
+     *     be used for this property.
+     * @param type the type of the {@code propertyAccess}.
+     */
+    private CodeBlock format(CodeBlock propertyAccess, CodeBlock indentAccess, TypeMirror type) {
+      PrettyPrintableKind printableKind = type.accept(new KindVisitor(types, elements), null);
+      DelegateMethod delegateMethod = new DelegateMethod(propertyAccess, indentAccess);
+      switch (printableKind) {
+        case PRIMITIVE:
+          return propertyAccess;
+        case REGULAR_OBJECT:
+          return delegateMethod
+              .methodName("format")
+              .invocation(
+                  elements.getTypeElement("java.lang.Object").asType(), () -> reindent("toString"));
+        case HAS_TO_PRETTY_STRING_METHOD:
+          ExecutableElement method =
+              toPrettyStringMethod(asTypeElement(type), types, elements).get();
+          return delegateMethod.invocation(type, () -> reindent(method.getSimpleName()));
+        case ARRAY:
+          TypeMirror componentType = MoreTypes.asArray(type).getComponentType();
+          return delegateMethod.invocation(type, () -> forEachLoopMethodBody(componentType));
+        case COLLECTION:
+          TypeMirror elementType =
+              getOnlyElement(resolvedTypeParameters(type, "java.util.Collection"));
+          return delegateMethod.invocation(
+              collectionOf(elementType), () -> forEachLoopMethodBody(elementType));
+        case IMMUTABLE_PRIMITIVE_ARRAY:
+          return delegateMethod.invocation(type, this::forLoopMethodBody);
+        case OPTIONAL:
+        case GUAVA_OPTIONAL:
+          TypeMirror optionalType = getOnlyElement(MoreTypes.asDeclared(type).getTypeArguments());
+          return delegateMethod.invocation(
+              type, () -> optionalMethodBody(optionalType, printableKind));
+        case MAP:
+          return formatMap(type, delegateMethod);
+        case MULTIMAP:
+          return formatMultimap(type, delegateMethod);
+      }
+      throw new AssertionError(printableKind);
+    }
+
+    private CodeBlock formatMap(TypeMirror type, DelegateMethod delegateMethod) {
+      ImmutableList<TypeMirror> typeParameters = resolvedTypeParameters(type, "java.util.Map");
+      TypeMirror keyType = typeParameters.get(0);
+      TypeMirror valueType = typeParameters.get(1);
+      return delegateMethod.invocation(
+          mapOf(keyType, valueType), () -> mapMethodBody(keyType, valueType));
+    }
+
+    private CodeBlock formatMultimap(TypeMirror type, DelegateMethod delegateMethod) {
+      ImmutableList<TypeMirror> typeParameters =
+          resolvedTypeParameters(type, "com.google.common.collect.Multimap");
+      TypeMirror keyType = typeParameters.get(0);
+      TypeMirror valueType = typeParameters.get(1);
+      return delegateMethod.invocation(
+          multimapOf(keyType, valueType),
+          () -> multimapMethodBody(keyType, collectionOf(valueType)));
+    }
+
+    /**
+     * Parameter object to simplify the branches of {@link #format(CodeBlock, CodeBlock,
+     * TypeMirror)} that call a delegate method.
+     */
+    private class DelegateMethod {
+
+      private final CodeBlock propertyAccess;
+      private final CodeBlock indentAccess;
+      private Optional<String> methodName = Optional.empty();
+
+      DelegateMethod(CodeBlock propertyAccess, CodeBlock indentAccess) {
+        this.propertyAccess = propertyAccess;
+        this.indentAccess = indentAccess;
+      }
+
+      DelegateMethod methodName(String methodName) {
+        this.methodName = Optional.of(methodName);
+        return this;
+      }
+
+      CodeBlock invocation(TypeMirror parameterType, Supplier<CodeBlock> methodBody) {
+        Equivalence.Wrapper<TypeMirror> key = MoreTypes.equivalence().wrap(parameterType);
+        // This doesn't use putIfAbsent because the methodBody supplier could recursively create
+        // new delegate methods. Map.putIfAbsent doesn't support reentrant calls.
+        if (!delegateMethods.containsKey(key)) {
+          delegateMethods.put(
+              key,
+              createMethod(
+                  methodName.orElseGet(() -> newDelegateMethodName(parameterType)),
+                  parameterType,
+                  methodBody));
+        }
+        return CodeBlock.of(
+            "$N($L, $L)", delegateMethods.get(key).name, propertyAccess, indentAccess);
+      }
+
+      private String newDelegateMethodName(TypeMirror type) {
+        String prefix = "format" + nameForType(type);
+        String methodName = prefix;
+        for (int i = 2; !methodNames.add(methodName); i++) {
+          methodName = prefix + i;
+        }
+        return methodName;
+      }
+
+      private MethodSpec createMethod(
+          String methodName, TypeMirror type, Supplier<CodeBlock> methodBody) {
+        return methodBuilder(methodName)
+            .addModifiers(PRIVATE, STATIC)
+            .returns(ClassName.get(String.class))
+            .addParameter(TypeName.get(type), "value")
+            .addParameter(TypeName.INT, "indentLevel")
+            .beginControlFlow("if (value == null)")
+            .addStatement("return $S", "null")
+            .endControlFlow()
+            .addCode(methodBody.get())
+            .build();
+      }
+    }
+
+    private CodeBlock reindent(CharSequence methodName) {
+      return CodeBlock.builder()
+          .addStatement(
+              "return value.$1N().replace($2S, $2S + $3N(indentLevel))",
+              methodName,
+              "\n",
+              INDENT_METHOD_NAME)
+          .build();
+    }
+
+    private CodeBlock forEachLoopMethodBody(TypeMirror elementType) {
+      return loopMethodBody(
+          "[",
+          "]",
+          CodeBlock.of("for ($T element : value)", elementType),
+          format(CodeBlock.of("element"), CodeBlock.of("indentLevel + 1"), elementType));
+    }
+
+    private CodeBlock forLoopMethodBody() {
+      return loopMethodBody(
+          "[",
+          "]",
+          CodeBlock.of("for (int i = 0; i < value.length(); i++)"),
+          CodeBlock.of("value.get(i)"));
+    }
+
+    private CodeBlock mapMethodBody(TypeMirror keyType, TypeMirror valueType) {
+      return forEachMapEntryMethodBody(keyType, valueType, "value");
+    }
+
+    private CodeBlock multimapMethodBody(TypeMirror keyType, TypeMirror valueType) {
+      return forEachMapEntryMethodBody(keyType, valueType, "value.asMap()");
+    }
+
+    private CodeBlock forEachMapEntryMethodBody(
+        TypeMirror keyType, TypeMirror valueType, String propertyAccess) {
+      CodeBlock entryType = CodeBlock.of("$T<$T, $T>", Map.Entry.class, keyType, valueType);
+      return loopMethodBody(
+          "{",
+          "}",
+          CodeBlock.of("for ($L entry : $L.entrySet())", entryType, propertyAccess),
+          format(CodeBlock.of("entry.getKey()"), CodeBlock.of("indentLevel + 1"), keyType),
+          KEY_VALUE_SEPARATOR,
+          format(CodeBlock.of("entry.getValue()"), CodeBlock.of("indentLevel + 1"), valueType));
+    }
+
+    private CodeBlock loopMethodBody(
+        String openSymbol,
+        String closeSymbol,
+        CodeBlock loopDeclaration,
+        CodeBlock... appendedValues) {
+      ImmutableList<CodeBlock> allAppendedValues =
+          ImmutableList.<CodeBlock>builder()
+              .add(CodeBlock.of("$S", "\n"))
+              .add(CodeBlock.of("$N(indentLevel + 1)", INDENT_METHOD_NAME))
+              .add(appendedValues)
+              .add(CodeBlock.of("$S", ","))
+              .build();
+      return CodeBlock.builder()
+          .addStatement("$1T builder = new $1T().append($2S)", StringBuilder.class, openSymbol)
+          .addStatement("boolean hasElements = false")
+          .beginControlFlow("$L", loopDeclaration)
+          .addStatement(
+              "builder$L",
+              allAppendedValues.stream()
+                  .map(value -> CodeBlock.of(".append($L)", value))
+                  .collect(CodeBlock.joining("")))
+          .addStatement("hasElements = true")
+          .endControlFlow()
+          .beginControlFlow("if (hasElements)")
+          .addStatement("builder.append($S).append($N(indentLevel))", "\n", INDENT_METHOD_NAME)
+          .endControlFlow()
+          .addStatement("return builder.append($S).toString()", closeSymbol)
+          .build();
+    }
+
+    private CodeBlock optionalMethodBody(
+        TypeMirror optionalType, PrettyPrintableKind printableKind) {
+      return CodeBlock.builder()
+          .addStatement(
+              "return (value.isPresent() ? $L : $S)",
+              format(CodeBlock.of("value.get()"), CodeBlock.of("indentLevel"), optionalType),
+              printableKind.equals(PrettyPrintableKind.OPTIONAL) ? "<empty>" : "<absent>")
+          .build();
+    }
+
+    private ImmutableList<TypeMirror> resolvedTypeParameters(
+        TypeMirror propertyType, String interfaceName) {
+      return elements.getTypeElement(interfaceName).getTypeParameters().stream()
+          .map(p -> types.asMemberOf(MoreTypes.asDeclared(propertyType), p))
+          .collect(toImmutableList());
+    }
+
+    private DeclaredType collectionOf(TypeMirror elementType) {
+      return types.getDeclaredType(elements.getTypeElement("java.util.Collection"), elementType);
+    }
+
+    private DeclaredType mapOf(TypeMirror keyType, TypeMirror valueType) {
+      return types.getDeclaredType(elements.getTypeElement("java.util.Map"), keyType, valueType);
+    }
+
+    private DeclaredType multimapOf(TypeMirror keyType, TypeMirror valueType) {
+      return types.getDeclaredType(
+          elements.getTypeElement("com.google.common.collect.Multimap"), keyType, valueType);
+    }
+
+    /** Returns a valid Java identifier for method or variable of type {@code type}. */
+    private String nameForType(TypeMirror type) {
+      return type.accept(
+          new SimpleTypeVisitor8<String, Void>() {
+            @Override
+            public String visitDeclared(DeclaredType type, Void v) {
+              String simpleName = simpleNameForType(type);
+              if (type.getTypeArguments().isEmpty()) {
+                return simpleName;
+              }
+              ImmutableList<String> typeArgumentNames =
+                  type.getTypeArguments().stream()
+                      .map(t -> simpleNameForType(t))
+                      .collect(toImmutableList());
+              if (isMapOrMultimap(type) && typeArgumentNames.size() == 2) {
+                return String.format(
+                    "%sOf%sTo%s", simpleName, typeArgumentNames.get(0), typeArgumentNames.get(1));
+              }
+
+              List<String> parts = new ArrayList<>();
+              parts.add(simpleName);
+              parts.add("Of");
+              parts.addAll(typeArgumentNames.subList(0, typeArgumentNames.size() - 1));
+              if (typeArgumentNames.size() > 1) {
+                parts.add("And");
+              }
+              parts.add(getLast(typeArgumentNames));
+              return String.join("", parts);
+            }
+
+            @Override
+            protected String defaultAction(TypeMirror type, Void v) {
+              return simpleNameForType(type);
+            }
+          },
+          null);
+    }
+
+    boolean isMapOrMultimap(TypeMirror type) {
+      TypeMirror mapType = elements.getTypeElement("java.util.Map").asType();
+      if (types.isAssignable(type, types.erasure(mapType))) {
+        return true;
+      }
+      TypeElement multimapElement = elements.getTypeElement("com.google.common.collect.Multimap");
+      return multimapElement != null
+          && types.isAssignable(type, types.erasure(multimapElement.asType()));
+    }
+
+    private String simpleNameForType(TypeMirror type) {
+      return type.accept(
+          new SimpleTypeVisitor8<String, Void>() {
+            @Override
+            public String visitPrimitive(PrimitiveType primitiveType, Void v) {
+              return types.boxedClass(primitiveType).getSimpleName().toString();
+            }
+
+            @Override
+            public String visitArray(ArrayType arrayType, Void v) {
+              return arrayType.getComponentType().accept(this, null) + "Array";
+            }
+
+            @Override
+            public String visitDeclared(DeclaredType declaredType, Void v) {
+              return declaredType.asElement().getSimpleName().toString();
+            }
+
+            @Override
+            protected String defaultAction(TypeMirror typeMirror, Void v) {
+              throw new AssertionError(typeMirror);
+            }
+          },
+          null);
+    }
+  }
+
+  enum PrettyPrintableKind {
+    HAS_TO_PRETTY_STRING_METHOD,
+    REGULAR_OBJECT,
+    PRIMITIVE,
+    COLLECTION,
+    ARRAY,
+    IMMUTABLE_PRIMITIVE_ARRAY,
+    OPTIONAL,
+    GUAVA_OPTIONAL,
+    MAP,
+    MULTIMAP,
+    ;
+
+    private static final ImmutableMap<String, PrettyPrintableKind> KINDS_BY_EXACT_TYPE =
+        ImmutableMap.of(
+            "java.util.Optional", OPTIONAL,
+            "com.google.common.base.Optional", GUAVA_OPTIONAL,
+            "com.google.common.primitives.ImmutableIntArray", IMMUTABLE_PRIMITIVE_ARRAY,
+            "com.google.common.primitives.ImmutableLongArray", IMMUTABLE_PRIMITIVE_ARRAY,
+            "com.google.common.primitives.ImmutableDoubleArray", IMMUTABLE_PRIMITIVE_ARRAY);
+
+    private static final ImmutableMap<String, PrettyPrintableKind> KINDS_BY_SUPERTYPE =
+        ImmutableMap.of(
+            "java.util.Collection", COLLECTION,
+            "java.util.Map", MAP,
+            "com.google.common.collect.Multimap", MULTIMAP);
+
+    static class KindVisitor extends SimpleTypeVisitor8<PrettyPrintableKind, Void> {
+      private final Elements elements;
+      private final Types types;
+
+      KindVisitor(Types types, Elements elements) {
+        this.types = types;
+        this.elements = elements;
+      }
+
+      @Override
+      public PrettyPrintableKind visitPrimitive(PrimitiveType primitiveType, Void v) {
+        return PRIMITIVE;
+      }
+
+      @Override
+      public PrettyPrintableKind visitArray(ArrayType arrayType, Void v) {
+        return ARRAY;
+      }
+
+      @Override
+      public PrettyPrintableKind visitDeclared(DeclaredType declaredType, Void v) {
+        TypeElement typeElement = asTypeElement(declaredType);
+        if (toPrettyStringMethod(typeElement, types, elements).isPresent()) {
+          return HAS_TO_PRETTY_STRING_METHOD;
+        }
+        PrettyPrintableKind byExactType =
+            KINDS_BY_EXACT_TYPE.get(typeElement.getQualifiedName().toString());
+        if (byExactType != null) {
+          return byExactType;
+        }
+
+        for (Map.Entry<String, PrettyPrintableKind> entry : KINDS_BY_SUPERTYPE.entrySet()) {
+          TypeElement supertypeElement = elements.getTypeElement(entry.getKey());
+          if (supertypeElement != null
+              && types.isAssignable(declaredType, types.erasure(supertypeElement.asType()))) {
+            return entry.getValue();
+          }
+        }
+
+        return REGULAR_OBJECT;
+      }
+    }
+  }
+
+  @Override
+  public boolean applicable(Context context) {
+    return toPrettyStringMethods(context).size() == 1;
+  }
+
+  @Override
+  public ImmutableSet<ExecutableElement> consumeMethods(Context context) {
+    return toPrettyStringMethods(context);
+  }
+
+  @Override
+  public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) {
+    return IncrementalExtensionType.ISOLATING;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java
new file mode 100644
index 0000000..041a16d
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringMethods.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring.processor;
+
+import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.auto.value.extension.toprettystring.processor.Annotations.toPrettyStringAnnotation;
+import static com.google.common.collect.MoreCollectors.toOptional;
+
+import com.google.auto.value.extension.AutoValueExtension.Context;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Optional;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+
+final class ToPrettyStringMethods {
+  /**
+   * Returns the {@link com.google.auto.value.extension.toprettystring.ToPrettyString} annotated
+   * methods for an {@code @AutoValue} type.
+   */
+  static ImmutableSet<ExecutableElement> toPrettyStringMethods(Context context) {
+    return context.abstractMethods().stream()
+        .filter(method -> toPrettyStringAnnotation(method).isPresent())
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Returns the {@link com.google.auto.value.extension.toprettystring.ToPrettyString} annotated
+   * method for a type.
+   */
+  static ImmutableList<ExecutableElement> toPrettyStringMethods(
+      TypeElement element, Types types, Elements elements) {
+    return getLocalAndInheritedMethods(element, types, elements).stream()
+        .filter(method -> toPrettyStringAnnotation(method).isPresent())
+        .collect(toImmutableList());
+  }
+
+  /**
+   * Returns the {@link com.google.auto.value.extension.toprettystring.ToPrettyString} annotated
+   * method for a type.
+   */
+  static Optional<ExecutableElement> toPrettyStringMethod(
+      TypeElement element, Types types, Elements elements) {
+    return toPrettyStringMethods(element, types, elements).stream().collect(toOptional());
+  }
+
+  private ToPrettyStringMethods() {}
+}
diff --git a/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringValidator.java b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringValidator.java
new file mode 100644
index 0000000..b77a54d
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/extension/toprettystring/processor/ToPrettyStringValidator.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring.processor;
+
+import static com.google.auto.value.extension.toprettystring.processor.ClassNames.TO_PRETTY_STRING_NAME;
+import static com.google.auto.value.extension.toprettystring.processor.ToPrettyStringMethods.toPrettyStringMethods;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static javax.lang.model.element.Modifier.STATIC;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+import static javax.tools.Diagnostic.Kind.ERROR;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.service.AutoService;
+import com.google.common.collect.ImmutableList;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * An annotation processor that validates {@link
+ * com.google.auto.value.extension.toprettystring.ToPrettyString} usage.
+ */
+@AutoService(Processor.class)
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
+@SupportedAnnotationTypes(TO_PRETTY_STRING_NAME)
+public final class ToPrettyStringValidator extends AbstractProcessor {
+  @Override
+  public boolean process(
+      Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
+    Types types = processingEnv.getTypeUtils();
+    Elements elements = processingEnv.getElementUtils();
+    TypeElement toPrettyString = elements.getTypeElement(TO_PRETTY_STRING_NAME);
+
+    Set<ExecutableElement> annotatedMethods =
+        methodsIn(roundEnvironment.getElementsAnnotatedWith(toPrettyString));
+    for (ExecutableElement method : annotatedMethods) {
+      validateMethod(method, elements);
+    }
+
+    validateSingleToPrettyStringMethod(annotatedMethods, types, elements);
+
+    return false;
+  }
+
+  private void validateMethod(ExecutableElement method, Elements elements) {
+    ErrorReporter errorReporter = new ErrorReporter(method, processingEnv.getMessager());
+    if (method.getModifiers().contains(STATIC)) {
+      errorReporter.reportError("@ToPrettyString methods must be instance methods");
+    }
+
+    TypeMirror stringType = elements.getTypeElement("java.lang.String").asType();
+    if (!MoreTypes.equivalence().equivalent(method.getReturnType(), stringType)) {
+      errorReporter.reportError("@ToPrettyString methods must return String");
+    }
+
+    if (!method.getParameters().isEmpty()) {
+      errorReporter.reportError("@ToPrettyString methods cannot have parameters");
+    }
+  }
+
+  private void validateSingleToPrettyStringMethod(
+      Set<ExecutableElement> annotatedMethods, Types types, Elements elements) {
+    Set<TypeElement> enclosingTypes =
+        annotatedMethods.stream()
+            .map(Element::getEnclosingElement)
+            .map(MoreElements::asType)
+            .collect(toCollection(LinkedHashSet::new));
+    for (TypeElement enclosingType : enclosingTypes) {
+      ImmutableList<ExecutableElement> methods =
+          toPrettyStringMethods(enclosingType, types, elements);
+      if (methods.size() > 1) {
+        processingEnv
+            .getMessager()
+            .printMessage(
+                ERROR,
+                String.format(
+                    "%s has multiple @ToPrettyString methods:%s",
+                    enclosingType.getQualifiedName(), formatMethodList(methods)),
+                enclosingType);
+      }
+    }
+  }
+
+  private String formatMethodList(ImmutableList<ExecutableElement> methods) {
+    return methods.stream().map(this::formatMethodInList).collect(joining());
+  }
+
+  private String formatMethodInList(ExecutableElement method) {
+    return String.format(
+        "\n  - %s.%s()",
+        MoreElements.asType(method.getEnclosingElement()).getQualifiedName(),
+        method.getSimpleName());
+  }
+
+  @Override
+  public SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+
+  private static final class ErrorReporter {
+    private final ExecutableElement method;
+    private final Messager messager;
+
+    ErrorReporter(ExecutableElement method, Messager messager) {
+      this.method = method;
+      this.messager = messager;
+    }
+
+    void reportError(String error) {
+      messager.printMessage(ERROR, error, method);
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
index d3cd9bd..3acf933 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationProcessor.java
@@ -17,14 +17,20 @@
 
 import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
 import static com.google.auto.value.processor.ClassNames.AUTO_ANNOTATION_NAME;
+import static com.google.common.collect.Maps.immutableEntry;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.joining;
 
 import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
 import com.google.auto.common.SuperficialValidation;
 import com.google.auto.service.AutoService;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.hash.Hashing;
 import com.google.common.primitives.Primitives;
 import com.google.errorprone.annotations.FormatMethod;
 import java.io.IOException;
@@ -41,6 +47,7 @@
 import javax.annotation.processing.RoundEnvironment;
 import javax.annotation.processing.SupportedAnnotationTypes;
 import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.AnnotationValue;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
@@ -74,11 +81,30 @@
 public class AutoAnnotationProcessor extends AbstractProcessor {
   public AutoAnnotationProcessor() {}
 
+  private Elements elementUtils;
+  private Types typeUtils;
+  private Nullables nullables;
+  private TypeMirror javaLangObject;
+
   @Override
   public SourceVersion getSupportedSourceVersion() {
     return SourceVersion.latestSupported();
   }
 
+  @Override
+  public ImmutableSet<String> getSupportedOptions() {
+    return ImmutableSet.of(Nullables.NULLABLE_OPTION);
+  }
+
+  @Override
+  public synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+    this.elementUtils = processingEnv.getElementUtils();
+    this.typeUtils = processingEnv.getTypeUtils();
+    this.nullables = new Nullables(processingEnv);
+    this.javaLangObject = elementUtils.getTypeElement("java.lang.Object").asType();
+  }
+
   /**
    * Issue a compilation error. This method does not throw an exception, since we want to continue
    * processing and perhaps report other errors.
@@ -99,26 +125,10 @@
     return new AbortProcessingException();
   }
 
-  private Elements elementUtils;
-  private Types typeUtils;
-
   @Override
   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-    elementUtils = processingEnv.getElementUtils();
-    typeUtils = processingEnv.getTypeUtils();
-    boolean claimed =
-        (annotations.size() == 1
-            && annotations
-                .iterator()
-                .next()
-                .getQualifiedName()
-                .contentEquals(AUTO_ANNOTATION_NAME));
-    if (claimed) {
-      process(roundEnv);
-      return true;
-    } else {
-      return false;
-    }
+    process(roundEnv);
+    return false;
   }
 
   private void process(RoundEnvironment roundEnv) {
@@ -152,7 +162,7 @@
     Set<Class<?>> wrapperTypesUsedInCollections = wrapperTypesUsedInCollections(method);
 
     ImmutableMap<String, ExecutableElement> memberMethods = getMemberMethods(annotationElement);
-    TypeElement methodClass = (TypeElement) method.getEnclosingElement();
+    TypeElement methodClass = MoreElements.asType(method.getEnclosingElement());
     String pkg = TypeSimplifier.packageNameOf(methodClass);
 
     ImmutableMap<String, AnnotationValue> defaultValues = getDefaultValues(annotationElement);
@@ -169,9 +179,11 @@
     vars.generated = getGeneratedTypeName();
     vars.members = members;
     vars.params = parameters;
+    vars.equalsParameterType = equalsParameterType();
     vars.pkg = pkg;
     vars.wrapperTypesUsedInCollections = wrapperTypesUsedInCollections;
     vars.gwtCompatible = isGwtCompatible(annotationElement);
+    vars.serialVersionUID = computeSerialVersionUid(members, parameters);
     ImmutableMap<String, Integer> invariableHashes = invariableHashes(members, parameters.keySet());
     vars.invariableHashSum = 0;
     for (int h : invariableHashes.values()) {
@@ -191,6 +203,18 @@
         .orElse("");
   }
 
+  private String equalsParameterType() {
+    // Unlike AutoValue, we don't currently try to guess a @Nullable based on the methods in your
+    // class. It's the default one or nothing.
+    ImmutableList<AnnotationMirror> equalsParameterAnnotations =
+        nullables
+            .appropriateNullableGivenMethods(ImmutableSet.of())
+            .map(ImmutableList::of)
+            .orElse(ImmutableList.of());
+    return TypeEncoder.encodeWithAnnotations(
+        javaLangObject, equalsParameterAnnotations, ImmutableSet.of());
+  }
+
   /**
    * Returns the hashCode of the given AnnotationValue, if that hashCode is guaranteed to be always
    * the same. The hashCode of a String or primitive type never changes. The hashCode of a Class or
@@ -246,7 +270,7 @@
 
   private boolean methodsAreOverloaded(List<ExecutableElement> methods) {
     boolean overloaded = false;
-    Set<String> classNames = new HashSet<String>();
+    Set<String> classNames = new HashSet<>();
     for (ExecutableElement method : methods) {
       String qualifiedClassName =
           fullyQualifiedName(
@@ -261,10 +285,10 @@
   }
 
   private String generatedClassName(ExecutableElement method) {
-    TypeElement type = (TypeElement) method.getEnclosingElement();
+    TypeElement type = MoreElements.asType(method.getEnclosingElement());
     String name = type.getSimpleName().toString();
     while (type.getEnclosingElement() instanceof TypeElement) {
-      type = (TypeElement) type.getEnclosingElement();
+      type = MoreElements.asType(type.getEnclosingElement());
       name = type.getSimpleName() + "_" + name;
     }
     return "AutoAnnotation_" + name + "_" + method.getSimpleName();
@@ -275,7 +299,7 @@
     if (returnTypeMirror.getKind() == TypeKind.DECLARED) {
       Element returnTypeElement = typeUtils.asElement(method.getReturnType());
       if (returnTypeElement.getKind() == ElementKind.ANNOTATION_TYPE) {
-        return (TypeElement) returnTypeElement;
+        return MoreElements.asType(returnTypeElement);
       }
     }
     throw abortWithError(
@@ -399,7 +423,7 @@
     if (memberType.getKind() != TypeKind.ARRAY) {
       return false;
     }
-    TypeMirror arrayElementType = ((ArrayType) memberType).getComponentType();
+    TypeMirror arrayElementType = MoreTypes.asArray(memberType).getComponentType();
     TypeMirror wrappedArrayElementType =
         arrayElementType.getKind().isPrimitive()
             ? typeUtils.boxedClass((PrimitiveType) arrayElementType).asType()
@@ -416,7 +440,7 @@
    * like {@code List<Integer>}. This is needed because we will emit a helper method for each such
    * type, for example to convert {@code Collection<Integer>} into {@code int[]}.
    */
-  private Set<Class<?>> wrapperTypesUsedInCollections(ExecutableElement method) {
+  private ImmutableSet<Class<?>> wrapperTypesUsedInCollections(ExecutableElement method) {
     TypeElement javaUtilCollection = elementUtils.getTypeElement(Collection.class.getName());
     ImmutableSet.Builder<Class<?>> usedInCollections = ImmutableSet.builder();
     for (Class<?> wrapper : Primitives.allWrapperTypes()) {
@@ -437,9 +461,7 @@
   }
 
   private static boolean isGwtCompatible(TypeElement annotationElement) {
-    return annotationElement
-        .getAnnotationMirrors()
-        .stream()
+    return annotationElement.getAnnotationMirrors().stream()
         .map(mirror -> mirror.getAnnotationType().asElement())
         .anyMatch(element -> element.getSimpleName().contentEquals("GwtCompatible"));
   }
@@ -448,6 +470,43 @@
     return pkg.isEmpty() ? cls : pkg + "." + cls;
   }
 
+  /**
+   * We compute a {@code serialVersionUID} for the generated class based on the names and types of
+   * the annotation members that the {@code @AutoAnnotation} method defines. These are exactly the
+   * names and types of the instance fields in the generated class. So in the common case where the
+   * annotation acquires a new member with a default value, if the {@code @AutoAnnotation} method is
+   * not changed then the generated class will acquire an implementation of the new member method
+   * which just returns the default value. The {@code serialVersionUID} will not change, which makes
+   * sense because the instance fields haven't changed, and instances that were serialized before
+   * the new member was added should deserialize fine. On the other hand, if you then add a
+   * parameter to the {@code @AutoAnnotation} method for the new member, the implementation class
+   * will acquire a new instance field, and we will compute a different {@code serialVersionUID}.
+   * That's because an instance serialized before that change would not have a value for the new
+   * instance field, which would end up zero or null. Users don't expect annotation methods to
+   * return null so that would be bad.
+   *
+   * <p>We could instead add a {@code readObject(ObjectInputStream)} method that would check that
+   * all of the instance fields are really present in the deserialized instance, and perhaps
+   * replace them with their default values from the annotation if not. That seems a lot more
+   * complicated than is justified, though, especially since the instance fields are final and
+   * would have to be set in the deserialized object through reflection.
+   */
+  private static long computeSerialVersionUid(
+      ImmutableMap<String, Member> members, ImmutableMap<String, Parameter> parameters) {
+    // TypeMirror.toString() isn't fully specified so it could potentially differ between
+    // implementations. Our member.getType() string comes from TypeEncoder and is predictable, but
+    // it includes `...` markers around fully-qualified type names, which are used to handle
+    // imports. So we remove those markers below.
+    String namesAndTypesString =
+        members.entrySet().stream()
+            .filter(e -> parameters.containsKey(e.getKey()))
+            .map(e -> immutableEntry(e.getKey(), e.getValue().getType().replace("`", "")))
+            .sorted(comparing(Map.Entry::getKey))
+            .map(e -> e.getKey() + ":" + e.getValue())
+            .collect(joining(";"));
+    return Hashing.murmur3_128().hashUnencodedChars(namesAndTypesString).asLong();
+  }
+
   private void writeSourceFile(String className, String text, TypeElement originatingType) {
     try {
       JavaFileObject sourceFile =
@@ -491,7 +550,7 @@
 
     public String getComponentType() {
       Preconditions.checkState(getTypeMirror().getKind() == TypeKind.ARRAY);
-      ArrayType arrayType = (ArrayType) getTypeMirror();
+      ArrayType arrayType = MoreTypes.asArray(getTypeMirror());
       return TypeEncoder.encode(arrayType.getComponentType());
     }
 
@@ -513,12 +572,12 @@
       if (getTypeMirror().getKind() != TypeKind.ARRAY) {
         return false;
       }
-      TypeMirror componentType = ((ArrayType) getTypeMirror()).getComponentType();
+      TypeMirror componentType = MoreTypes.asArray(getTypeMirror()).getComponentType();
       if (componentType.getKind() != TypeKind.DECLARED) {
         return false;
       }
-      DeclaredType declared = (DeclaredType) componentType;
-      if (!((TypeElement) processingEnv.getTypeUtils().asElement(componentType))
+      DeclaredType declared = MoreTypes.asDeclared(componentType);
+      if (!MoreElements.asType(processingEnv.getTypeUtils().asElement(componentType))
           .getQualifiedName()
           .contentEquals("java.lang.Class")) {
         return false;
@@ -530,7 +589,7 @@
       if (parameter.getKind() != TypeKind.WILDCARD) {
         return true; // for Class<Foo>
       }
-      WildcardType wildcard = (WildcardType) parameter;
+      WildcardType wildcard = MoreTypes.asWildcard(parameter);
       // In theory, we should check if getExtendsBound() != Object, since '?' is equivalent to
       // '? extends Object', but, experimentally, neither javac or ecj will sets getExtendsBound()
       // to 'Object', so there isn't a point in checking.
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java
index 4db59c7..11bc896 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoAnnotationTemplateVars.java
@@ -35,6 +35,12 @@
    */
   Map<String, AutoAnnotationProcessor.Parameter> params;
 
+  /**
+   * A string representing the parameter type declaration of the equals(Object) method, including
+   * any annotations.
+   */
+  String equalsParameterType;
+
   /** The encoded form of the {@code Generated} class, or empty if it is not available. */
   String generated;
 
@@ -74,6 +80,12 @@
   /** The sum of the hash code contributions from the members in {@link #invariableHashes}. */
   Integer invariableHashSum;
 
+  /**
+   * A computed {@code serialVersionUID} based on the names and types of the {@code @AutoAnnotation}
+   * method parameters.
+   */
+  Long serialVersionUID;
+
   private static final Template TEMPLATE = parsedTemplateForResource("autoannotation.vm");
 
   @Override
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
new file mode 100644
index 0000000..b6a578f
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderProcessor.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
+import static com.google.auto.common.MoreElements.getPackage;
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.auto.value.processor.AutoValueProcessor.OMIT_IDENTIFIERS_OPTION;
+import static com.google.auto.value.processor.ClassNames.AUTO_BUILDER_NAME;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toMap;
+import static javax.lang.model.util.ElementFilter.constructorsIn;
+import static javax.lang.model.util.ElementFilter.methodsIn;
+
+import com.google.auto.common.AnnotationMirrors;
+import com.google.auto.common.AnnotationValues;
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.auto.common.Visibility;
+import com.google.auto.service.AutoService;
+import com.google.auto.value.processor.BuilderSpec.PropertyGetter;
+import com.google.auto.value.processor.MissingTypes.MissingTypeException;
+import com.google.common.base.Ascii;
+import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Stream;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
+import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
+
+/**
+ * Javac annotation processor (compiler plugin) for builders; user code never references this class.
+ *
+ * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a>
+ * @author Éamonn McManus
+ */
+@AutoService(Processor.class)
+@SupportedAnnotationTypes(AUTO_BUILDER_NAME)
+@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
+public class AutoBuilderProcessor extends AutoValueishProcessor {
+  private static final String ALLOW_OPTION = "com.google.auto.value.AutoBuilderIsUnstable";
+
+  public AutoBuilderProcessor() {
+    super(AUTO_BUILDER_NAME);
+  }
+
+  @Override
+  public Set<String> getSupportedOptions() {
+    return ImmutableSet.of(OMIT_IDENTIFIERS_OPTION, ALLOW_OPTION);
+  }
+
+  private TypeMirror javaLangVoid;
+
+  @Override
+  public synchronized void init(ProcessingEnvironment processingEnv) {
+    super.init(processingEnv);
+    javaLangVoid = elementUtils().getTypeElement("java.lang.Void").asType();
+  }
+
+  @Override
+  void processType(TypeElement autoBuilderType) {
+    if (!processingEnv.getOptions().containsKey(ALLOW_OPTION)) {
+      errorReporter()
+          .abortWithError(
+              autoBuilderType,
+              "Compile with -A%s to enable this UNSUPPORTED AND UNSTABLE prototype",
+              ALLOW_OPTION);
+    }
+    if (autoBuilderType.getKind() != ElementKind.CLASS
+        && autoBuilderType.getKind() != ElementKind.INTERFACE) {
+      errorReporter()
+          .abortWithError(
+              autoBuilderType,
+              "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces");
+    }
+    checkModifiersIfNested(autoBuilderType);
+    // The annotation is guaranteed to be present by the contract of Processor#process
+    AnnotationMirror autoBuilderAnnotation =
+        getAnnotationMirror(autoBuilderType, AUTO_BUILDER_NAME).get();
+    TypeElement ofClass = getOfClass(autoBuilderType, autoBuilderAnnotation);
+    checkModifiersIfNested(ofClass, autoBuilderType, "AutoBuilder ofClass");
+    String callMethod = findCallMethodValue(autoBuilderAnnotation);
+    ImmutableSet<ExecutableElement> methods =
+        abstractMethodsIn(
+            getLocalAndInheritedMethods(autoBuilderType, typeUtils(), elementUtils()));
+    ExecutableElement executable = findExecutable(ofClass, callMethod, autoBuilderType, methods);
+    BuilderSpec builderSpec = new BuilderSpec(ofClass, processingEnv, errorReporter());
+    BuilderSpec.Builder builder = builderSpec.new Builder(autoBuilderType);
+    TypeMirror builtType = builtType(executable);
+    Optional<BuilderMethodClassifier<VariableElement>> maybeClassifier =
+        BuilderMethodClassifierForAutoBuilder.classify(
+            methods, errorReporter(), processingEnv, executable, builtType, autoBuilderType);
+    if (!maybeClassifier.isPresent()) {
+      // We've already output one or more error messages.
+      return;
+    }
+    BuilderMethodClassifier<VariableElement> classifier = maybeClassifier.get();
+    Map<String, String> propertyToGetterName =
+        Maps.transformValues(classifier.builderGetters(), PropertyGetter::getName);
+    AutoBuilderTemplateVars vars = new AutoBuilderTemplateVars();
+    vars.props = propertySet(executable, propertyToGetterName);
+    builder.defineVars(vars, classifier);
+    vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
+    String generatedClassName = generatedClassName(autoBuilderType, "AutoBuilder_");
+    vars.builderName = TypeSimplifier.simpleNameOf(generatedClassName);
+    vars.builtType = TypeEncoder.encode(builtType);
+    vars.build = build(executable);
+    vars.types = typeUtils();
+    vars.toBuilderConstructor = false;
+    vars.toBuilderMethods = ImmutableList.of();
+    defineSharedVarsForType(autoBuilderType, ImmutableSet.of(), vars);
+    String text = vars.toText();
+    text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoBuilderType.asType());
+    text = Reformatter.fixup(text);
+    writeSourceFile(generatedClassName, text, autoBuilderType);
+  }
+
+  private ImmutableSet<Property> propertySet(
+      ExecutableElement executable, Map<String, String> propertyToGetterName) {
+    // Fix any parameter names that are reserved words in Java. Java source code can't have
+    // such parameter names, but Kotlin code might, for example.
+    Map<VariableElement, String> identifiers =
+        executable.getParameters().stream()
+            .collect(toMap(v -> v, v -> v.getSimpleName().toString()));
+    fixReservedIdentifiers(identifiers);
+    return executable.getParameters().stream()
+        .map(
+            v ->
+                newProperty(
+                    v, identifiers.get(v), propertyToGetterName.get(v.getSimpleName().toString())))
+        .collect(toImmutableSet());
+  }
+
+  private Property newProperty(VariableElement var, String identifier, String getterName) {
+    String name = var.getSimpleName().toString();
+    TypeMirror type = var.asType();
+    Optional<String> nullableAnnotation = nullableAnnotationFor(var, var.asType());
+    return new Property(
+        name, identifier, TypeEncoder.encode(type), type, nullableAnnotation, getterName);
+  }
+
+  private ExecutableElement findExecutable(
+      TypeElement ofClass,
+      String callMethod,
+      TypeElement autoBuilderType,
+      ImmutableSet<ExecutableElement> methods) {
+    List<ExecutableElement> executables =
+        findRelevantExecutables(ofClass, callMethod, autoBuilderType);
+    String description =
+        callMethod.isEmpty() ? "constructor" : "static method named \"" + callMethod + "\"";
+    switch (executables.size()) {
+      case 0:
+        throw errorReporter()
+            .abortWithError(
+                autoBuilderType,
+                "[AutoBuilderNoVisible] No visible %s for %s",
+                description,
+                ofClass);
+      case 1:
+        return executables.get(0);
+      default:
+        return matchingExecutable(autoBuilderType, executables, methods, description);
+    }
+  }
+
+  private ImmutableList<ExecutableElement> findRelevantExecutables(
+      TypeElement ofClass, String callMethod, TypeElement autoBuilderType) {
+    List<? extends Element> elements = ofClass.getEnclosedElements();
+    Stream<ExecutableElement> relevantExecutables =
+        callMethod.isEmpty()
+            ? constructorsIn(elements).stream()
+            : methodsIn(elements).stream()
+                .filter(m -> m.getSimpleName().contentEquals(callMethod))
+                .filter(m -> m.getModifiers().contains(Modifier.STATIC));
+    return relevantExecutables
+        .filter(c -> visibleFrom(c, getPackage(autoBuilderType)))
+        .collect(toImmutableList());
+  }
+
+  private ExecutableElement matchingExecutable(
+      TypeElement autoBuilderType,
+      List<ExecutableElement> executables,
+      ImmutableSet<ExecutableElement> methods,
+      String description) {
+    // There's more than one visible executable (constructor or method). We try to find the one that
+    // corresponds to the methods in the @AutoBuilder interface. This is a bit approximate. We're
+    // basically just looking for an executable where all the parameter names correspond to setters
+    // or property builders in the interface. We might find out after choosing one that it is wrong
+    // for whatever reason (types don't match, spurious methods, etc). But it is likely that if the
+    // names are all accounted for in the methods, and if there's no other matching executable with
+    // more parameters, then this is indeed the one we want. If we later get errors when we try to
+    // analyze the interface in detail, those are probably legitimate errors and not because we
+    // picked the wrong executable.
+    ImmutableList<ExecutableElement> matches =
+        executables.stream().filter(x -> executableMatches(x, methods)).collect(toImmutableList());
+    switch (matches.size()) {
+      case 0:
+        throw errorReporter()
+            .abortWithError(
+                autoBuilderType,
+                "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of"
+                    + " any %s:\n%s",
+                description,
+                executableListString(executables));
+      case 1:
+        return matches.get(0);
+      default:
+        // More than one match, let's see if we can find the best one.
+    }
+    int max = matches.stream().mapToInt(c -> c.getParameters().size()).max().getAsInt();
+    ImmutableList<ExecutableElement> maxMatches =
+        matches.stream().filter(c -> c.getParameters().size() == max).collect(toImmutableList());
+    if (maxMatches.size() > 1) {
+      throw errorReporter()
+          .abortWithError(
+              autoBuilderType,
+              "[AutoBuilderAmbiguous] Property names correspond to more than one %s:\n%s",
+              description,
+              executableListString(maxMatches));
+    }
+    return maxMatches.get(0);
+  }
+
+  private String executableListString(List<ExecutableElement> executables) {
+    return executables.stream()
+        .map(AutoBuilderProcessor::executableString)
+        .collect(joining("\n  ", "  ", ""));
+  }
+
+  static String executableString(ExecutableElement executable) {
+    Element nameSource =
+        executable.getKind() == ElementKind.CONSTRUCTOR
+            ? executable.getEnclosingElement()
+            : executable;
+    return nameSource.getSimpleName()
+        + executable.getParameters().stream()
+            .map(v -> v.asType() + " " + v.getSimpleName())
+            .collect(joining(", ", "(", ")"));
+  }
+
+  private boolean executableMatches(
+      ExecutableElement executable, ImmutableSet<ExecutableElement> methods) {
+    // Start with the complete set of parameter names and remove them one by one as we find
+    // corresponding methods. We ignore case, under the assumption that it is unlikely that a case
+    // difference is going to allow a candidate to match when another one is better.
+    // A parameter named foo could be matched by methods like this:
+    //    X foo(Y)
+    //    X setFoo(Y)
+    //    X fooBuilder()
+    //    X fooBuilder(Y)
+    // There are further constraints, including on the types X and Y, that will later be imposed by
+    // BuilderMethodClassifier, but here we just require that there be at least one method with
+    // one of these shapes for foo.
+    NavigableSet<String> parameterNames =
+        executable.getParameters().stream()
+            .map(v -> v.getSimpleName().toString())
+            .collect(toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)));
+    for (ExecutableElement method : methods) {
+      String name = method.getSimpleName().toString();
+      if (name.endsWith("Builder")) {
+        String property = name.substring(0, name.length() - "Builder".length());
+        parameterNames.remove(property);
+      }
+      if (method.getParameters().size() == 1) {
+        parameterNames.remove(name);
+        if (name.startsWith("set")) {
+          parameterNames.remove(name.substring(3));
+        }
+      }
+      if (parameterNames.isEmpty()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private boolean visibleFrom(Element element, PackageElement fromPackage) {
+    Visibility visibility = Visibility.effectiveVisibilityOfElement(element);
+    switch (visibility) {
+      case PUBLIC:
+        return true;
+      case PROTECTED:
+        // We care about whether the constructor is visible from the generated class. The generated
+        // class is never going to be a subclass of the class containing the constructor, so
+        // protected and default access are equivalent.
+      case DEFAULT:
+        return getPackage(element).equals(fromPackage);
+      default:
+        return false;
+    }
+  }
+
+  private TypeMirror builtType(ExecutableElement executable) {
+    switch (executable.getKind()) {
+      case CONSTRUCTOR:
+        return executable.getEnclosingElement().asType();
+      case METHOD:
+        return executable.getReturnType();
+      default:
+        throw new VerifyException("Unexpected executable kind " + executable.getKind());
+    }
+  }
+
+  private String build(ExecutableElement executable) {
+    TypeElement enclosing = MoreElements.asType(executable.getEnclosingElement());
+    String type = TypeEncoder.encodeRaw(enclosing.asType());
+    switch (executable.getKind()) {
+      case CONSTRUCTOR:
+        boolean generic = !enclosing.getTypeParameters().isEmpty();
+        String typeParams = generic ? "<>" : "";
+        return "new " + type + typeParams;
+      case METHOD:
+        return type + "." + executable.getSimpleName();
+      default:
+        throw new VerifyException("Unexpected executable kind " + executable.getKind());
+    }
+  }
+
+  private static final ElementKind ELEMENT_KIND_RECORD = elementKindRecord();
+
+  private static ElementKind elementKindRecord() {
+    try {
+      Field record = ElementKind.class.getField("RECORD");
+      return (ElementKind) record.get(null);
+    } catch (ReflectiveOperationException e) {
+      // OK: we must be on a JDK version that predates this.
+      return null;
+    }
+  }
+
+  private TypeElement getOfClass(
+      TypeElement autoBuilderType, AnnotationMirror autoBuilderAnnotation) {
+    TypeElement ofClassValue = findOfClassValue(autoBuilderAnnotation);
+    boolean isDefault = typeUtils().isSameType(ofClassValue.asType(), javaLangVoid);
+    if (!isDefault) {
+      return ofClassValue;
+    }
+    Element enclosing = autoBuilderType.getEnclosingElement();
+    ElementKind enclosingKind = enclosing.getKind();
+    if (enclosing.getKind() != ElementKind.CLASS && enclosingKind != ELEMENT_KIND_RECORD) {
+      errorReporter()
+          .abortWithError(
+              autoBuilderType,
+              "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it"
+                  + " must be nested inside the class to be built; actually nested inside %s %s.",
+              Ascii.toLowerCase(enclosingKind.name()),
+              enclosing);
+    }
+    return MoreElements.asType(enclosing);
+  }
+
+  private TypeElement findOfClassValue(AnnotationMirror autoBuilderAnnotation) {
+    AnnotationValue ofClassValue =
+        AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "ofClass");
+    Object value = ofClassValue.getValue();
+    if (value instanceof TypeMirror) {
+      TypeMirror ofClassType = (TypeMirror) value;
+      switch (ofClassType.getKind()) {
+        case DECLARED:
+          return MoreTypes.asTypeElement(ofClassType);
+        case ERROR:
+          throw new MissingTypeException(MoreTypes.asError(ofClassType));
+        default:
+          break;
+      }
+    }
+    throw new MissingTypeException(null);
+  }
+
+  private String findCallMethodValue(AnnotationMirror autoBuilderAnnotation) {
+    AnnotationValue callMethodValue =
+        AnnotationMirrors.getAnnotationValue(autoBuilderAnnotation, "callMethod");
+    return AnnotationValues.getString(callMethodValue);
+  }
+
+  @Override
+  Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
+    // TODO(b/183005059): implement
+    return Optional.empty();
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoBuilderTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoBuilderTemplateVars.java
new file mode 100644
index 0000000..8c08a48
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoBuilderTemplateVars.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import com.google.escapevelocity.Template;
+
+class AutoBuilderTemplateVars extends AutoValueOrBuilderTemplateVars {
+  private static final Template TEMPLATE = parsedTemplateForResource("autobuilder.vm");
+
+  @Override
+  Template parsedTemplate() {
+    return TEMPLATE;
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
index d73c1c2..711b138 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfProcessor.java
@@ -58,7 +58,7 @@
 @AutoService(Processor.class)
 @SupportedAnnotationTypes(AUTO_ONE_OF_NAME)
 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
-public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor {
+public class AutoOneOfProcessor extends AutoValueishProcessor {
   public AutoOneOfProcessor() {
     super(AUTO_ONE_OF_NAME);
   }
@@ -69,6 +69,11 @@
   }
 
   @Override
+  public ImmutableSet<String> getSupportedOptions() {
+    return ImmutableSet.of(Nullables.NULLABLE_OPTION);
+  }
+
+  @Override
   void processType(TypeElement autoOneOfType) {
     if (autoOneOfType.getKind() != ElementKind.CLASS) {
       errorReporter()
@@ -123,19 +128,9 @@
   }
 
   private DeclaredType mirrorForKindType(TypeElement autoOneOfType) {
-    Optional<AnnotationMirror> oneOfAnnotation =
-        getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME);
-    if (!oneOfAnnotation.isPresent()) {
-      // This shouldn't happen unless the compilation environment is buggy,
-      // but it has happened in the past and can crash the compiler.
-      errorReporter()
-          .abortWithError(
-              autoOneOfType,
-              "[AutoOneOfCompilerBug] annotation processor for @AutoOneOf was invoked with a type"
-                  + " that does not have that annotation; this is probably a compiler bug");
-    }
-    AnnotationValue kindValue =
-        AnnotationMirrors.getAnnotationValue(oneOfAnnotation.get(), "value");
+    // The annotation is guaranteed to be present by the contract of Processor#process
+    AnnotationMirror oneOfAnnotation = getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME).get();
+    AnnotationValue kindValue = AnnotationMirrors.getAnnotationValue(oneOfAnnotation, "value");
     Object value = kindValue.getValue();
     if (value instanceof TypeMirror) {
       TypeMirror kindType = (TypeMirror) value;
@@ -162,9 +157,7 @@
     Map<String, String> transformedPropertyNames =
         propertyNames.stream().collect(toMap(this::transformName, s -> s));
     Map<String, Element> transformedEnumConstants =
-        kindElement
-            .getEnclosedElements()
-            .stream()
+        kindElement.getEnclosedElements().stream()
             .filter(e -> e.getKind().equals(ElementKind.ENUM_CONSTANT))
             .collect(toMap(e -> transformName(e.getSimpleName().toString()), e -> e));
 
@@ -215,8 +208,7 @@
       TypeMirror kindMirror,
       ImmutableSet<ExecutableElement> abstractMethods) {
     Set<ExecutableElement> kindGetters =
-        abstractMethods
-            .stream()
+        abstractMethods.stream()
             .filter(e -> sameType(kindMirror, e.getReturnType()))
             .filter(e -> e.getParameters().isEmpty())
             .collect(toSet());
@@ -273,14 +265,15 @@
       AutoOneOfTemplateVars vars,
       ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
       ExecutableElement kindGetter) {
-    vars.props = propertySet(
-        propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of());
+    vars.props =
+        propertySet(
+            propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of());
     vars.kindGetter = kindGetter.getSimpleName().toString();
     vars.kindType = TypeEncoder.encode(kindGetter.getReturnType());
     TypeElement javaIoSerializable = elementUtils().getTypeElement("java.io.Serializable");
     vars.serializable =
-        javaIoSerializable != null  // just in case
-        && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType());
+        javaIoSerializable != null // just in case
+            && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType());
   }
 
   @Override
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java
index 1e15771..06b24e2 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoOneOfTemplateVars.java
@@ -25,7 +25,7 @@
  * @author emcmanus@google.com (Éamonn McManus)
  */
 @SuppressWarnings("unused") // the fields in this class are only read via reflection
-class AutoOneOfTemplateVars extends AutoValueOrOneOfTemplateVars {
+class AutoOneOfTemplateVars extends AutoValueishTemplateVars {
   /**
    * The properties defined by the parent class's abstract methods. The elements of this set are in
    * the same order as the original abstract method declarations in the AutoOneOf class.
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
index 62dfeb3..ac6c8ec 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueBuilderProcessor.java
@@ -15,7 +15,7 @@
  */
 package com.google.auto.value.processor;
 
-import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror;
+import static com.google.auto.value.processor.AutoValueishProcessor.hasAnnotationMirror;
 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME;
 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
 
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
new file mode 100644
index 0000000..86cf497
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueOrBuilderTemplateVars.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2012 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import com.google.auto.value.processor.AutoValueishProcessor.Property;
+import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.util.Types;
+
+/**
+ * Variables to substitute into the autovalue.vm or builder.vm template.
+ *
+ * @author emcmanus@google.com (Éamonn McManus)
+ */
+@SuppressWarnings("unused") // the fields in this class are only read via reflection
+abstract class AutoValueOrBuilderTemplateVars extends AutoValueishTemplateVars {
+  /**
+   * The properties defined by the parent class's abstract methods. The elements of this set are in
+   * the same order as the original abstract method declarations in the AutoValue class.
+   */
+  ImmutableSet<Property> props;
+
+  /**
+   * The simple name of the generated builder, or empty if there is no builder. This is just
+   * {@code Builder} for AutoValue, since it is nested inside the {@code AutoValue_Foo} class. But
+   * it is {@code AutoBuilder_Foo} for AutoBuilder.
+   */
+  String builderName = "";
+
+  /**
+   * The name of the builder type as it should appear in source code, or empty if there is no
+   * builder type. If class {@code Address} contains {@code @AutoValue.Builder} class Builder then
+   * this will typically be {@code "Address.Builder"}.
+   */
+  String builderTypeName = "";
+
+  /**
+   * The formal generic signature of the {@code AutoValue.Builder} class. This is empty, or contains
+   * type variables with optional bounds, for example {@code <K, V extends K>}.
+   */
+  String builderFormalTypes = "";
+
+  /**
+   * The generic signature used by the generated builder subclass for its superclass reference. This
+   * is empty, or contains only type variables with no bounds, for example {@code <K, V>}.
+   */
+  String builderActualTypes = "";
+
+  /** True if the builder being implemented is an interface, false if it is an abstract class. */
+  Boolean builderIsInterface = false;
+
+  /**
+   * The full spelling of any annotations to add to the generated builder subclass, or an empty list
+   * if there are none. A non-empty value might look something like {@code
+   * @`java.lang.SuppressWarnings`("Immutable")}. The {@code ``} marks are explained in
+   * {@link TypeEncoder}.
+   */
+  ImmutableList<String> builderAnnotations = ImmutableList.of();
+
+  /** The builder's build method, often {@code "build"}. */
+  Optional<SimpleMethod> buildMethod = Optional.empty();
+
+  /** The type that will be built by the {@code build()} method of a builder. */
+  String builtType;
+
+  /**
+   * The constructor or method invocation that the {@code build()} method of a builder should use,
+   * without any parameters. This might be {@code "new Foo"} or {@code "Foo.someMethod"}.
+   */
+  String build;
+
+  /**
+   * A multimap from property names (like foo) to the corresponding setters. The same property may
+   * be set by more than one setter. For example, an ImmutableList might be set by {@code
+   * setFoo(ImmutableList<String>)} and {@code setFoo(String[])}.
+   */
+  ImmutableMultimap<String, BuilderSpec.PropertySetter> builderSetters = ImmutableMultimap.of();
+
+  /**
+   * A map from property names to information about the associated property builder. A property
+   * called foo (defined by a method foo() or getFoo()) can have a property builder called
+   * fooBuilder(). The type of foo must be a type that has an associated builder following certain
+   * conventions. Guava immutable types such as ImmutableList follow those conventions, as do many
+   * {@code @AutoValue} types.
+   */
+  ImmutableMap<String, PropertyBuilder> builderPropertyBuilders = ImmutableMap.of();
+
+  /**
+   * Properties that are required to be set. A property must be set explicitly except in the
+   * following cases:
+   *
+   * <ul>
+   *   <li>it is {@code @Nullable} (in which case it defaults to null);
+   *   <li>it is {@code Optional} (in which case it defaults to empty);
+   *   <li>it has a property-builder method (in which case it defaults to empty).
+   * </ul>
+   */
+  ImmutableSet<Property> builderRequiredProperties = ImmutableSet.of();
+
+  /**
+   * A map from property names to information about the associated property getter. A property
+   * called foo (defined by a method foo() or getFoo()) can have a property getter method with the
+   * same name (foo() or getFoo()) and either the same return type or an Optional (or OptionalInt,
+   * etc) wrapping it.
+   */
+  ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters = ImmutableMap.of();
+
+  /**
+   * True if the generated builder should have a second constructor with a parameter of the built
+   * class. The constructor produces a new builder that starts off with the values from the
+   * parameter.
+   */
+  Boolean toBuilderConstructor;
+
+  /**
+   * Any {@code toBuilder()} methods, that is methods that return the builder type. AutoBuilder does
+   * not currently support this, but it's included in these shared variables to simplify the
+   * template.
+   */
+  ImmutableList<SimpleMethod> toBuilderMethods;
+
+  /**
+   * Whether to include identifiers in strings in the generated code. If false, exception messages
+   * will not mention properties by name, and {@code toString()} will include neither property names
+   * nor the name of the {@code @AutoValue} class.
+   */
+  Boolean identifiers;
+
+  /**
+   * True if the generated class should be final (there are no extensions that will generate
+   * subclasses)
+   */
+  Boolean isFinal = false;
+
+  /** The type utilities returned by {@link ProcessingEnvironment#getTypeUtils()}. */
+  Types types;
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
index aafffd7..ab7da92 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java
@@ -16,11 +16,12 @@
 package com.google.auto.value.processor;
 
 import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
+import static com.google.auto.common.MoreStreams.toImmutableList;
 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
 import static com.google.common.collect.Sets.difference;
 import static com.google.common.collect.Sets.intersection;
+import static java.util.Comparator.naturalOrder;
 import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toList;
 
 import com.google.auto.service.AutoService;
 import com.google.auto.value.extension.AutoValueExtension;
@@ -32,11 +33,9 @@
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -64,8 +63,8 @@
 @AutoService(Processor.class)
 @SupportedAnnotationTypes(AUTO_VALUE_NAME)
 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
-public class AutoValueProcessor extends AutoValueOrOneOfProcessor {
-  private static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers";
+public class AutoValueProcessor extends AutoValueishProcessor {
+  static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers";
 
   // We moved MemoizeExtension to a different package, which had an unexpected effect:
   // now if an old version of AutoValue is in the class path, ServiceLoader can pick up both the
@@ -100,10 +99,9 @@
 
   @VisibleForTesting
   static ImmutableList<AutoValueExtension> extensionsFromLoader(ClassLoader loader) {
-    return ImmutableList.copyOf(
-        Iterables.filter(
-            SimpleServiceLoader.load(AutoValueExtension.class, loader),
-            ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION)));
+    return SimpleServiceLoader.load(AutoValueExtension.class, loader).stream()
+        .filter(ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION))
+        .collect(toImmutableList());
   }
 
   @Override
@@ -131,14 +129,17 @@
   }
 
   @Override
-  public Set<String> getSupportedOptions() {
+  public ImmutableSet<String> getSupportedOptions() {
     ImmutableSet.Builder<String> builder = ImmutableSet.builder();
     AutoValueExtension.IncrementalExtensionType incrementalType =
         extensions.stream()
             .map(e -> e.incrementalType(processingEnv))
-            .min(Comparator.naturalOrder())
+            .min(naturalOrder())
             .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING);
-    builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType));
+    builder
+        .add(OMIT_IDENTIFIERS_OPTION)
+        .add(Nullables.NULLABLE_OPTION)
+        .addAll(optionsFor(incrementalType));
     for (AutoValueExtension extension : extensions) {
       builder.addAll(extension.getSupportedOptions());
     }
@@ -164,15 +165,6 @@
 
   @Override
   void processType(TypeElement type) {
-    if (!hasAnnotationMirror(type, AUTO_VALUE_NAME)) {
-      // This shouldn't happen unless the compilation environment is buggy,
-      // but it has happened in the past and can crash the compiler.
-      errorReporter()
-          .abortWithError(
-              type,
-              "[AutoValueCompilerBug] annotation processor for @AutoValue was invoked with a type"
-                  + " that does not have that annotation; this is probably a compiler bug");
-    }
     if (type.getKind() != ElementKind.CLASS) {
       errorReporter()
           .abortWithError(type, "[AutoValueNotClass] @AutoValue only applies to classes");
@@ -216,7 +208,7 @@
     Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder();
     ImmutableSet<ExecutableElement> toBuilderMethods;
     if (builder.isPresent()) {
-      toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), abstractMethods);
+      toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), type, abstractMethods);
     } else {
       toBuilderMethods = ImmutableSet.of();
     }
@@ -226,8 +218,9 @@
     ImmutableMap<String, ExecutableElement> properties =
         propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
 
-    ExtensionContext context = new ExtensionContext(
-        processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods);
+    ExtensionContext context =
+        new ExtensionContext(
+            processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods);
     ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context);
     ImmutableSet<ExecutableElement> consumedMethods =
         methodsConsumedByExtensions(
@@ -240,21 +233,23 @@
       propertyMethodsAndTypes =
           propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type);
       properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
-      context = new ExtensionContext(
-          processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods);
+      context =
+          new ExtensionContext(
+              processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods);
     }
 
     ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
     boolean extensionsPresent = !applicableExtensions.isEmpty();
     validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent);
 
-    String finalSubclass = generatedSubclassName(type, 0);
+    String finalSubclass = TypeSimplifier.simpleNameOf(generatedSubclassName(type, 0));
     AutoValueTemplateVars vars = new AutoValueTemplateVars();
-    vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass);
     vars.types = processingEnv.getTypeUtils();
     vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
     defineSharedVarsForType(type, methods, vars);
     defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder);
+    vars.builtType = vars.origClass + vars.actualTypes;
+    vars.build = "new " + finalSubclass + vars.actualTypes;
 
     // If we've encountered problems then we might end up invoking extensions with inconsistent
     // state. Anyway we probably don't want to generate code which is likely to provoke further
@@ -276,7 +271,7 @@
     text = Reformatter.fixup(text);
     writeSourceFile(subclass, text, type);
     GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type);
-    gwtSerialization.maybeWriteGwtSerializer(vars);
+    gwtSerialization.maybeWriteGwtSerializer(vars, finalSubclass);
   }
 
   // Invokes each of the given extensions to generate its subclass, and returns the number of
@@ -431,22 +426,22 @@
       ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
       Optional<BuilderSpec.Builder> maybeBuilder) {
     ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet();
-    // We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons.
     vars.toBuilderMethods =
-        ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList()));
+        toBuilderMethods.stream().map(SimpleMethod::new).collect(toImmutableList());
+    vars.toBuilderConstructor = !vars.toBuilderMethods.isEmpty();
     ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields =
         propertyFieldAnnotationMap(type, propertyMethods);
     ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods =
         propertyMethodAnnotationMap(type, propertyMethods);
     vars.props =
         propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods);
-    vars.serialVersionUID = getSerialVersionUID(type);
     // Check for @AutoValue.Builder and add appropriate variables if it is present.
     maybeBuilder.ifPresent(
         builder -> {
           ImmutableBiMap<ExecutableElement, String> methodToPropertyName =
               propertyNameToMethodMap(propertyMethods).inverse();
-          builder.defineVars(vars, methodToPropertyName);
+          builder.defineVarsForAutoValue(vars, methodToPropertyName);
+          vars.builderName = "Builder";
           vars.builderAnnotations = copiedClassAnnotations(builder.builderType());
         });
   }
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
index 8f855bd..e53b92e 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueTemplateVars.java
@@ -15,15 +15,7 @@
  */
 package com.google.auto.value.processor;
 
-import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.ImmutableSet;
 import com.google.escapevelocity.Template;
-import java.util.Optional;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.util.Types;
 
 /**
  * The variables to substitute into the autovalue.vm template.
@@ -31,22 +23,7 @@
  * @author emcmanus@google.com (Éamonn McManus)
  */
 @SuppressWarnings("unused") // the fields in this class are only read via reflection
-class AutoValueTemplateVars extends AutoValueOrOneOfTemplateVars {
-  /**
-   * The properties defined by the parent class's abstract methods. The elements of this set are in
-   * the same order as the original abstract method declarations in the AutoValue class.
-   */
-  ImmutableSet<AutoValueOrOneOfProcessor.Property> props;
-
-  /**
-   * Whether to include identifiers in strings in the generated code. If false, exception messages
-   * will not mention properties by name, and {@code toString()} will include neither property names
-   * nor the name of the {@code @AutoValue} class.
-   */
-  Boolean identifiers;
-
-  /** The type utilities returned by {@link ProcessingEnvironment#getTypeUtils()}. */
-  Types types;
+class AutoValueTemplateVars extends AutoValueOrBuilderTemplateVars {
 
   /**
    * The encoding of the {@code @GwtCompatible} annotation to add to this class, or an empty string
@@ -56,100 +33,14 @@
    */
   String gwtCompatibleAnnotation;
 
-  /** The text of the serialVersionUID constant, or empty if there is none. */
-  String serialVersionUID;
-
   /** The simple name of the generated subclass. */
   String subclass;
   /**
-   * The simple name of the final generated subclass. For {@code @AutoValue public static class Foo
-   * {}} this should always be "AutoValue_Foo".
-   */
-  String finalSubclass;
-
-  /**
-   * True if the generated class should be final (there are no extensions that will generate
-   * subclasses)
-   */
-  Boolean isFinal = false;
-
-  /**
    * The modifiers (for example {@code final} or {@code abstract}) for the generated subclass,
    * followed by a space if they are not empty.
    */
   String modifiers;
 
-  /**
-   * The name of the builder type as it should appear in source code, or empty if there is no
-   * builder type. If class {@code Address} contains {@code @AutoValue.Builder} class Builder then
-   * this will typically be {@code "Address.Builder"}.
-   */
-  String builderTypeName = "";
-
-  /**
-   * The formal generic signature of the {@code AutoValue.Builder} class. This is empty, or contains
-   * type variables with optional bounds, for example {@code <K, V extends K>}.
-   */
-  String builderFormalTypes = "";
-  /**
-   * The generic signature used by the generated builder subclass for its superclass reference. This
-   * is empty, or contains only type variables with no bounds, for example {@code <K, V>}.
-   */
-  String builderActualTypes = "";
-
-  /** True if the builder being implemented is an interface, false if it is an abstract class. */
-  Boolean builderIsInterface = false;
-
-  /**
-   * The full spelling of any annotations to add to the generated builder subclass, or an empty list
-   * if there are none. A non-empty value might look something like {@code
-   * @`java.lang.SuppressWarnings`("Immutable")}. The {@code ``} marks are explained in
-   * {@link TypeEncoder}.
-   */
-  ImmutableList<String> builderAnnotations = ImmutableList.of();
-
-  /** The builder's build method, often {@code "build"}. */
-  Optional<SimpleMethod> buildMethod = Optional.empty();
-
-  /**
-   * A multimap from property names (like foo) to the corresponding setters. The same property may
-   * be set by more than one setter. For example, an ImmutableList might be set by {@code
-   * setFoo(ImmutableList<String>)} and {@code setFoo(String[])}.
-   */
-  ImmutableMultimap<String, BuilderSpec.PropertySetter> builderSetters = ImmutableMultimap.of();
-
-  /**
-   * A map from property names to information about the associated property builder. A property
-   * called foo (defined by a method foo() or getFoo()) can have a property builder called
-   * fooBuilder(). The type of foo must be a type that has an associated builder following certain
-   * conventions. Guava immutable types such as ImmutableList follow those conventions, as do many
-   * {@code @AutoValue} types.
-   */
-  ImmutableMap<String, PropertyBuilder> builderPropertyBuilders = ImmutableMap.of();
-
-  /**
-   * Properties that are required to be set. A property must be set explicitly except in the
-   * following cases:
-   *
-   * <ul>
-   *   <li>it is {@code @Nullable} (in which case it defaults to null);
-   *   <li>it is {@code Optional} (in which case it defaults to empty);
-   *   <li>it has a property-builder method (in which case it defaults to empty).
-   * </ul>
-   */
-  ImmutableSet<AutoValueProcessor.Property> builderRequiredProperties = ImmutableSet.of();
-
-  /**
-   * A map from property names to information about the associated property getter. A property
-   * called foo (defined by a method foo() or getFoo()) can have a property getter method with the
-   * same name (foo() or getFoo()) and either the same return type or an Optional (or OptionalInt,
-   * etc) wrapping it.
-   */
-  ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters = ImmutableMap.of();
-
-  /** Any {@code toBuilder()} methods, that is methods that return the builder type. */
-  ImmutableList<SimpleMethod> toBuilderMethods;
-
   private static final Template TEMPLATE = parsedTemplateForResource("autovalue.vm");
 
   @Override
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
similarity index 87%
rename from value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java
rename to value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
index 36a82bd..93f2f79 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfProcessor.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishProcessor.java
@@ -19,6 +19,8 @@
 import static com.google.auto.common.GeneratedAnnotations.generatedAnnotation;
 import static com.google.auto.common.MoreElements.getPackage;
 import static com.google.auto.common.MoreElements.isAnnotationPresent;
+import static com.google.auto.common.MoreStreams.toImmutableList;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_PACKAGE_NAME;
 import static com.google.auto.value.processor.ClassNames.COPY_ANNOTATIONS_NAME;
 import static com.google.common.collect.Iterables.getOnlyElement;
@@ -56,6 +58,7 @@
 import java.util.OptionalInt;
 import java.util.Set;
 import java.util.function.Predicate;
+import java.util.stream.IntStream;
 import javax.annotation.processing.AbstractProcessor;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.annotation.processing.RoundEnvironment;
@@ -82,11 +85,12 @@
 import javax.tools.JavaFileObject;
 
 /**
- * Shared code between AutoValueProcessor and AutoOneOfProcessor.
+ * Shared code between {@link AutoValueProcessor}, {@link AutoOneOfProcessor}, and {@link
+ * AutoBuilderProcessor}.
  *
  * @author emcmanus@google.com (Éamonn McManus)
  */
-abstract class AutoValueOrOneOfProcessor extends AbstractProcessor {
+abstract class AutoValueishProcessor extends AbstractProcessor {
   private final String annotationClassName;
 
   /**
@@ -96,7 +100,7 @@
    */
   private final List<String> deferredTypeNames = new ArrayList<>();
 
-  AutoValueOrOneOfProcessor(String annotationClassName) {
+  AutoValueishProcessor(String annotationClassName) {
     this.annotationClassName = annotationClassName;
   }
 
@@ -106,11 +110,13 @@
   private String simpleAnnotationName;
 
   private ErrorReporter errorReporter;
+  private Nullables nullables;
 
   @Override
   public synchronized void init(ProcessingEnvironment processingEnv) {
     super.init(processingEnv);
     errorReporter = new ErrorReporter(processingEnv);
+    nullables = new Nullables(processingEnv);
   }
 
   final ErrorReporter errorReporter() {
@@ -149,30 +155,26 @@
   public static class Property {
     private final String name;
     private final String identifier;
-    private final ExecutableElement method;
     private final String type;
-    private final ImmutableList<String> fieldAnnotations;
-    private final ImmutableList<String> methodAnnotations;
+    private final TypeMirror typeMirror;
     private final Optional<String> nullableAnnotation;
     private final Optionalish optional;
+    private final String getter;
 
     Property(
         String name,
         String identifier,
-        ExecutableElement method,
         String type,
-        ImmutableList<String> fieldAnnotations,
-        ImmutableList<String> methodAnnotations,
-        Optional<String> nullableAnnotation) {
+        TypeMirror typeMirror,
+        Optional<String> nullableAnnotation,
+        String getter) {
       this.name = name;
       this.identifier = identifier;
-      this.method = method;
       this.type = type;
-      this.fieldAnnotations = fieldAnnotations;
-      this.methodAnnotations = methodAnnotations;
+      this.typeMirror = typeMirror;
       this.nullableAnnotation = nullableAnnotation;
-      TypeMirror propertyType = method.getReturnType();
-      this.optional = Optionalish.createIfOptional(propertyType);
+      this.optional = Optionalish.createIfOptional(typeMirror);
+      this.getter = getter;
     }
 
     /**
@@ -196,16 +198,8 @@
       return name;
     }
 
-    /**
-     * Returns the name of the getter method for this property as defined by the {@code @AutoValue}
-     * class. For property {@code foo}, this will be {@code foo} or {@code getFoo} or {@code isFoo}.
-     */
-    public String getGetter() {
-      return method.getSimpleName().toString();
-    }
-
     public TypeMirror getTypeMirror() {
-      return method.getReturnType();
+      return typeMirror;
     }
 
     public String getType() {
@@ -213,23 +207,7 @@
     }
 
     public TypeKind getKind() {
-      return method.getReturnType().getKind();
-    }
-
-    /**
-     * Returns the annotations (in string form) that should be applied to the property's field
-     * declaration.
-     */
-    public List<String> getFieldAnnotations() {
-      return fieldAnnotations;
-    }
-
-    /**
-     * Returns the annotations (in string form) that should be applied to the property's method
-     * implementation.
-     */
-    public List<String> getMethodAnnotations() {
-      return methodAnnotations;
+      return typeMirror.getKind();
     }
 
     /**
@@ -257,13 +235,66 @@
       return nullableAnnotation.isPresent();
     }
 
+    /**
+     * Returns the name of the getter method for this property as defined by the {@code @AutoValue}
+     * or {@code @AutoBuilder} class. For property {@code foo}, this will be {@code foo} or {@code
+     * getFoo} or {@code isFoo}. For AutoValue, this will also be the name of a getter method in a
+     * builder; in the case of AutoBuilder it will only be that and may be null.
+     */
+    public String getGetter() {
+      return getter;
+    }
+  }
+
+  /** A {@link Property} that corresponds to an abstract getter method in the source. */
+  public static class GetterProperty extends Property {
+    private final ExecutableElement method;
+    private final ImmutableList<String> fieldAnnotations;
+    private final ImmutableList<String> methodAnnotations;
+
+    GetterProperty(
+        String name,
+        String identifier,
+        ExecutableElement method,
+        String type,
+        ImmutableList<String> fieldAnnotations,
+        ImmutableList<String> methodAnnotations,
+        Optional<String> nullableAnnotation) {
+      super(
+          name,
+          identifier,
+          type,
+          method.getReturnType(),
+          nullableAnnotation,
+          method.getSimpleName().toString());
+      this.method = method;
+      this.fieldAnnotations = fieldAnnotations;
+      this.methodAnnotations = methodAnnotations;
+    }
+
+    /**
+     * Returns the annotations (in string form) that should be applied to the property's field
+     * declaration.
+     */
+    public List<String> getFieldAnnotations() {
+      return fieldAnnotations;
+    }
+
+    /**
+     * Returns the annotations (in string form) that should be applied to the property's method
+     * implementation.
+     */
+    public List<String> getMethodAnnotations() {
+      return methodAnnotations;
+    }
+
     public String getAccess() {
       return SimpleMethod.access(method);
     }
 
     @Override
     public boolean equals(Object obj) {
-      return obj instanceof Property && ((Property) obj).method.equals(method);
+      return obj instanceof GetterProperty && ((GetterProperty) obj).method.equals(method);
     }
 
     @Override
@@ -289,8 +320,7 @@
     }
     simpleAnnotationName = annotationType.getSimpleName().toString();
     List<TypeElement> deferredTypes =
-        deferredTypeNames
-            .stream()
+        deferredTypeNames.stream()
             .map(name -> elementUtils().getTypeElement(name))
             .collect(toList());
     if (roundEnv.processingOver()) {
@@ -383,7 +413,7 @@
         (propertyMethod, returnType) -> {
           String propertyType =
               TypeEncoder.encodeWithAnnotations(
-                  returnType, getExcludedAnnotationTypes(propertyMethod));
+                  returnType, ImmutableList.of(), getExcludedAnnotationTypes(propertyMethod));
           String propertyName = methodToPropertyName.get(propertyMethod);
           String identifier = methodToIdentifier.get(propertyMethod);
           ImmutableList<String> fieldAnnotations =
@@ -393,7 +423,7 @@
           ImmutableList<String> methodAnnotations = annotationStrings(methodAnnotationMirrors);
           Optional<String> nullableAnnotation = nullableAnnotationForMethod(propertyMethod);
           Property p =
-              new Property(
+              new GetterProperty(
                   propertyName,
                   identifier,
                   propertyMethod,
@@ -411,11 +441,9 @@
     return props.build();
   }
 
-  /** Defines the template variables that are shared by AutoValue and AutoOneOf. */
+  /** Defines the template variables that are shared by AutoValue, AutoOneOf, and AutoBuilder. */
   final void defineSharedVarsForType(
-      TypeElement type,
-      ImmutableSet<ExecutableElement> methods,
-      AutoValueOrOneOfTemplateVars vars) {
+      TypeElement type, ImmutableSet<ExecutableElement> methods, AutoValueishTemplateVars vars) {
     vars.pkg = TypeSimplifier.packageNameOf(type);
     vars.origClass = TypeSimplifier.classNameOf(type);
     vars.simpleClassName = TypeSimplifier.simpleNameOf(vars.origClass);
@@ -423,7 +451,7 @@
         generatedAnnotation(elementUtils(), processingEnv.getSourceVersion())
             .map(annotation -> TypeEncoder.encode(annotation.asType()))
             .orElse("");
-    vars.formalTypes = TypeEncoder.formalTypeParametersString(type);
+    vars.formalTypes = TypeEncoder.typeParametersString(type.getTypeParameters());
     vars.actualTypes = TypeSimplifier.actualTypeParametersString(type);
     vars.wildcardTypes = wildcardTypeParametersString(type);
     vars.annotations = copiedClassAnnotations(type);
@@ -432,14 +460,17 @@
     vars.toString = methodsToGenerate.containsKey(ObjectMethod.TO_STRING);
     vars.equals = methodsToGenerate.containsKey(ObjectMethod.EQUALS);
     vars.hashCode = methodsToGenerate.containsKey(ObjectMethod.HASH_CODE);
-    vars.equalsParameterType = equalsParameterType(methodsToGenerate);
+    Optional<AnnotationMirror> nullable = nullables.appropriateNullableGivenMethods(methods);
+    vars.equalsParameterType = equalsParameterType(methodsToGenerate, nullable);
+    vars.serialVersionUID = getSerialVersionUID(type);
   }
 
   /** Returns the spelling to be used in the generated code for the given list of annotations. */
   static ImmutableList<String> annotationStrings(List<? extends AnnotationMirror> annotations) {
     // TODO(b/68008628): use ImmutableList.toImmutableList() when that works.
-    return ImmutableList.copyOf(
-        annotations.stream().map(AnnotationOutput::sourceFormForAnnotation).collect(toList()));
+    return annotations.stream()
+        .map(AnnotationOutput::sourceFormForAnnotation)
+        .collect(toImmutableList());
   }
 
   /**
@@ -454,7 +485,7 @@
   static String generatedClassName(TypeElement type, String prefix) {
     String name = type.getSimpleName().toString();
     while (type.getEnclosingElement() instanceof TypeElement) {
-      type = (TypeElement) type.getEnclosingElement();
+      type = MoreElements.asType(type.getEnclosingElement());
       name = type.getSimpleName() + "_" + name;
     }
     String pkg = TypeSimplifier.packageNameOf(type);
@@ -566,12 +597,9 @@
   }
 
   private static OptionalInt nullableAnnotationIndex(List<? extends AnnotationMirror> annotations) {
-    for (int i = 0; i < annotations.size(); i++) {
-      if (isNullable(annotations.get(i))) {
-        return OptionalInt.of(i);
-      }
-    }
-    return OptionalInt.empty();
+    return IntStream.range(0, annotations.size())
+        .filter(i -> isNullable(annotations.get(i)))
+        .findFirst();
   }
 
   private static boolean isNullable(AnnotationMirror annotation) {
@@ -583,21 +611,19 @@
    * includes {@code isFoo} methods if they return {@code boolean}. This corresponds to JavaBeans
    * conventions.
    */
-  static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) {
-    ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder();
-    for (ExecutableElement method : methods) {
-      String name = method.getSimpleName().toString();
-      // Note that getfoo() (without a capital) is still a getter.
-      boolean get = name.startsWith("get") && !name.equals("get");
-      boolean is =
-          name.startsWith("is")
-              && !name.equals("is")
-              && method.getReturnType().getKind() == TypeKind.BOOLEAN;
-      if (get || is) {
-        getters.add(method);
-      }
-    }
-    return getters.build();
+  static ImmutableSet<ExecutableElement> prefixedGettersIn(Collection<ExecutableElement> methods) {
+    return methods.stream()
+        .filter(AutoValueishProcessor::isPrefixedGetter)
+        .collect(toImmutableSet());
+  }
+
+  static boolean isPrefixedGetter(ExecutableElement method) {
+    String name = method.getSimpleName().toString();
+    // Note that getfoo() (without a capital) is still a getter.
+    return (name.startsWith("get") && !name.equals("get"))
+        || (name.startsWith("is")
+            && !name.equals("is")
+            && method.getReturnType().getKind() == TypeKind.BOOLEAN);
   }
 
   /**
@@ -611,7 +637,7 @@
    * anyway, so the special behaviour is not useful, and of course it behaves poorly with examples
    * like {@code OAuth}.
    */
-  private static String nameWithoutPrefix(String name) {
+  static String nameWithoutPrefix(String name) {
     if (name.startsWith("get")) {
       name = name.substring(3);
     } else {
@@ -622,27 +648,33 @@
   }
 
   /**
-   * Checks that, if the given {@code @AutoValue} or {@code @AutoOneOf} class is nested, it is
-   * static and not private. This check is not necessary for correctness, since the generated code
-   * would not compile if the check fails, but it produces better error messages for the user.
+   * Checks that, if the given {@code @AutoValue}, {@code @AutoOneOf}, or {@code @AutoBuilder} class
+   * is nested, it is static and not private. This check is not necessary for correctness, since the
+   * generated code would not compile if the check fails, but it produces better error messages for
+   * the user.
    */
   final void checkModifiersIfNested(TypeElement type) {
+    checkModifiersIfNested(type, type, simpleAnnotationName);
+  }
+
+  final void checkModifiersIfNested(TypeElement type, TypeElement reportedType, String what) {
     ElementKind enclosingKind = type.getEnclosingElement().getKind();
     if (enclosingKind.isClass() || enclosingKind.isInterface()) {
       if (type.getModifiers().contains(Modifier.PRIVATE)) {
         errorReporter.abortWithError(
-            type, "[AutoValuePrivate] @%s class must not be private", simpleAnnotationName);
+            reportedType, "[%sPrivate] @%s class must not be private", simpleAnnotationName, what);
       } else if (Visibility.effectiveVisibilityOfElement(type).equals(Visibility.PRIVATE)) {
         // The previous case, where the class itself is private, is much commoner so it deserves
         // its own error message, even though it would be caught by the test here too.
         errorReporter.abortWithError(
-            type,
-            "[AutoValueInPrivate] @%s class must not be nested in a private class",
-            simpleAnnotationName);
+            reportedType,
+            "[%sInPrivate] @%s class must not be nested in a private class",
+            simpleAnnotationName,
+            what);
       }
       if (!type.getModifiers().contains(Modifier.STATIC)) {
         errorReporter.abortWithError(
-            type, "[AutoValueInner] Nested @%s class must be static", simpleAnnotationName);
+            reportedType, "[%sInner] Nested @%s class must be static", simpleAnnotationName, what);
       }
     }
     // In principle type.getEnclosingElement() could be an ExecutableElement (for a class
@@ -693,7 +725,7 @@
       ObjectMethod override = objectMethodToOverride(method);
       boolean canGenerate =
           method.getModifiers().contains(Modifier.ABSTRACT)
-              || isJavaLangObject((TypeElement) method.getEnclosingElement());
+              || isJavaLangObject(MoreElements.asType(method.getEnclosingElement()));
       if (!override.equals(ObjectMethod.NONE) && canGenerate) {
         methodsToGenerate.put(override, method);
       }
@@ -705,14 +737,25 @@
    * Returns the encoded parameter type of the {@code equals(Object)} method that is to be
    * generated, or an empty string if the method is not being generated. The parameter type includes
    * any type annotations, for example {@code @Nullable}.
+   *
+   * @param methodsToGenerate the Object methods that are being generated
+   * @param nullable the type of a {@code @Nullable} type annotation that we have found, if any
    */
-  static String equalsParameterType(Map<ObjectMethod, ExecutableElement> methodsToGenerate) {
+  static String equalsParameterType(
+      Map<ObjectMethod, ExecutableElement> methodsToGenerate, Optional<AnnotationMirror> nullable) {
     ExecutableElement equals = methodsToGenerate.get(ObjectMethod.EQUALS);
     if (equals == null) {
       return ""; // this will not be referenced because no equals method will be generated
     }
     TypeMirror parameterType = equals.getParameters().get(0).asType();
-    return TypeEncoder.encodeWithAnnotations(parameterType);
+    // Add @Nullable if we know one and the parameter doesn't already have one.
+    // The @Nullable we add will be a type annotation, but if the parameter already has @Nullable
+    // then that might be a type annotation or an annotation on the parameter.
+    ImmutableList<AnnotationMirror> extraAnnotations =
+        nullable.isPresent() && !nullableAnnotationFor(equals, parameterType).isPresent()
+            ? ImmutableList.of(nullable.get())
+            : ImmutableList.of();
+    return TypeEncoder.encodeWithAnnotations(parameterType, extraAnnotations, ImmutableSet.of());
   }
 
   /**
@@ -751,8 +794,7 @@
    * {@code toString()}.
    */
   ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsIn(
-      Set<ExecutableElement> abstractMethods,
-      TypeElement autoValueOrOneOfType) {
+      Set<ExecutableElement> abstractMethods, TypeElement autoValueOrOneOfType) {
     DeclaredType declaredType = MoreTypes.asDeclared(autoValueOrOneOfType.asType());
     ImmutableSet.Builder<ExecutableElement> properties = ImmutableSet.builder();
     for (ExecutableElement method : abstractMethods) {
@@ -778,7 +820,7 @@
     TypeMirror type = getter.getReturnType();
     if (type.getKind() == TypeKind.ARRAY) {
       TypeMirror componentType = MoreTypes.asArray(type).getComponentType();
-     if (componentType.getKind().isPrimitive()) {
+      if (componentType.getKind().isPrimitive()) {
         warnAboutPrimitiveArrays(autoValueClass, getter);
       } else {
         errorReporter.reportError(
@@ -829,12 +871,13 @@
     @Override
     public Boolean visitArray(List<? extends AnnotationValue> list, Void p) {
       return list.stream().map(AnnotationValue::getValue).anyMatch("mutable"::equals);
-   }
+    }
   }
 
   /**
-   * Returns a string like {@code "1234L"} if {@code type instanceof Serializable} and defines
-   * {@code serialVersionUID = 1234L}; otherwise {@code ""}.
+   * Returns a string like {@code "private static final long serialVersionUID = 1234L"} if {@code
+   * type instanceof Serializable} and defines {@code serialVersionUID = 1234L}; otherwise {@code
+   * ""}.
    */
   final String getSerialVersionUID(TypeElement type) {
     TypeMirror serializable = elementUtils().getTypeElement(Serializable.class.getName()).asType();
@@ -846,7 +889,7 @@
           if (field.getModifiers().containsAll(Arrays.asList(Modifier.STATIC, Modifier.FINAL))
               && field.asType().getKind() == TypeKind.LONG
               && value != null) {
-            return value + "L";
+            return "private static final long serialVersionUID = " + value + "L;";
           } else {
             errorReporter.reportError(
                 field, "serialVersionUID must be a static final long compile-time constant");
@@ -889,7 +932,25 @@
     // Only copy annotations from a class if it has @AutoValue.CopyAnnotations.
     if (hasAnnotationMirror(type, COPY_ANNOTATIONS_NAME)) {
       Set<String> excludedAnnotations =
-          union(getExcludedAnnotationClassNames(type), getAnnotationsMarkedWithInherited(type));
+          ImmutableSet.<String>builder()
+              .addAll(getExcludedAnnotationClassNames(type))
+              .addAll(getAnnotationsMarkedWithInherited(type))
+              //
+              // Kotlin classes have an intrinsic @Metadata annotation generated
+              // onto them by kotlinc. This annotation is specific to the annotated
+              // class and should not be implicitly copied. Doing so can mislead
+              // static analysis or metaprogramming tooling that reads the data
+              // contained in these annotations.
+              //
+              // It may be surprising to see AutoValue classes written in Kotlin
+              // when they could be written as Kotlin data classes, but this can
+              // come up in cases where consumers rely on AutoValue features or
+              // extensions that are not available in data classes.
+              //
+              // See: https://github.com/google/auto/issues/1087
+              //
+              .add(ClassNames.KOTLIN_METADATA_NAME)
+              .build();
 
       return copyAnnotations(type, type, excludedAnnotations);
     } else {
@@ -919,8 +980,7 @@
     @SuppressWarnings("unchecked")
     List<AnnotationValue> excludedClasses =
         (List<AnnotationValue>) getAnnotationValue(maybeAnnotation.get(), "exclude").getValue();
-    return excludedClasses
-        .stream()
+    return excludedClasses.stream()
         .map(annotationValue -> (DeclaredType) annotationValue.getValue())
         .collect(toCollection(TypeMirrorSet::new));
   }
@@ -930,17 +990,14 @@
    * strings that are fully-qualified class names.
    */
   private Set<String> getExcludedAnnotationClassNames(Element element) {
-    return getExcludedAnnotationTypes(element)
-        .stream()
+    return getExcludedAnnotationTypes(element).stream()
         .map(MoreTypes::asTypeElement)
         .map(typeElement -> typeElement.getQualifiedName().toString())
         .collect(toSet());
   }
 
   private static Set<String> getAnnotationsMarkedWithInherited(Element element) {
-    return element
-        .getAnnotationMirrors()
-        .stream()
+    return element.getAnnotationMirrors().stream()
         .filter(a -> isAnnotationPresent(a.getAnnotationType().asElement(), Inherited.class))
         .map(a -> getAnnotationFqName(a))
         .collect(toSet());
@@ -1007,9 +1064,7 @@
     Set<String> returnTypeAnnotations =
         getReturnTypeAnnotations(method, this::annotationAppliesToFields);
     Set<String> nonFieldAnnotations =
-        method
-            .getAnnotationMirrors()
-            .stream()
+        method.getAnnotationMirrors().stream()
             .map(a -> a.getAnnotationType().asElement())
             .map(MoreElements::asType)
             .filter(a -> !annotationAppliesToFields(a))
@@ -1027,10 +1082,7 @@
 
   private Set<String> getReturnTypeAnnotations(
       ExecutableElement method, Predicate<TypeElement> typeFilter) {
-    return method
-        .getReturnType()
-        .getAnnotationMirrors()
-        .stream()
+    return method.getReturnType().getAnnotationMirrors().stream()
         .map(a -> a.getAnnotationType().asElement())
         .map(MoreElements::asType)
         .filter(typeFilter)
diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java b/value/src/main/java/com/google/auto/value/processor/AutoValueishTemplateVars.java
similarity index 87%
rename from value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java
rename to value/src/main/java/com/google/auto/value/processor/AutoValueishTemplateVars.java
index 9fdbea4..9ff2891 100644
--- a/value/src/main/java/com/google/auto/value/processor/AutoValueOrOneOfTemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/AutoValueishTemplateVars.java
@@ -18,12 +18,12 @@
 import com.google.common.collect.ImmutableList;
 
 /**
- * The variables to substitute into the autovalue.vm or autooneof.vm template.
+ * The variables to substitute into the autovalue.vm, autooneof.vm, or builder.vm templates.
  *
  * @author emcmanus@google.com (Éamonn McManus)
  */
 @SuppressWarnings("unused") // the fields in this class are only read via reflection
-abstract class AutoValueOrOneOfTemplateVars extends TemplateVars {
+abstract class AutoValueishTemplateVars extends TemplateVars {
   /** Whether to generate an equals(Object) method. */
   Boolean equals;
 
@@ -81,4 +81,11 @@
    * wildcard, for example {@code <?, ?>}.
    */
   String wildcardTypes;
+
+  /**
+   * The text of the complete serialVersionUID declaration, or empty if there is none. When
+   * non-empty, it will be something like {@code private static final long serialVersionUID =
+   * 123L;}.
+   */
+  String serialVersionUID;
 }
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
index 81751e3..51773e6 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java
@@ -15,8 +15,7 @@
  */
 package com.google.auto.value.processor;
 
-import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor;
-import static com.google.common.collect.Sets.difference;
+import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor;
 
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
@@ -38,7 +37,9 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.stream.Stream;
 import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
@@ -54,19 +55,35 @@
 /**
  * Classifies methods inside builder types, based on their names and parameter and return types.
  *
+ * @param <E> the kind of {@link Element} that the corresponding properties are defined by. This is
+ *     {@link ExecutableElement} for AutoValue, where properties are defined by abstract methods,
+ *     and {@link VariableElement} for AutoBuilder, where they are defined by constructor or method
+ *     parameters.
  * @author Éamonn McManus
  */
-class BuilderMethodClassifier {
+abstract class BuilderMethodClassifier<E extends Element> {
   private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence();
 
   private final ErrorReporter errorReporter;
   private final Types typeUtils;
   private final Elements elementUtils;
-  private final TypeElement autoValueClass;
+  private final TypeMirror builtType;
   private final TypeElement builderType;
-  private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName;
-  private final ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType;
-  private final ImmutableMap<String, ExecutableElement> getterNameToGetter;
+
+  /**
+   * Property types, rewritten to refer to type variables in the builder. For example, suppose you
+   * have {@code @AutoValue abstract class Foo<T>} with a getter {@code abstract T bar()} and a
+   * builder {@code @AutoValue.Builder interface Builder<T>} with a setter {@code abstract
+   * Builder<T> setBar(T t)}. Then the {@code T} of {@code Foo<T>} and the {@code T} of {@code
+   * Foo.Builder<T>} are two separate variables. Originally {@code bar()} returned the {@code T} of
+   * {@code Foo<T>}, but in this map we have rewritten it to be the {@code T} of {@code
+   * Foo.Builder<T>}.
+   *
+   * <p>Importantly, this rewrite <b>loses type annotations</b>, so when those are important we must
+   * be careful to look at the original type as reported by the {@link #originalPropertyType}
+   * method.
+   */
+  private final ImmutableMap<String, TypeMirror> rewrittenPropertyTypes;
 
   private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>();
   private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>();
@@ -79,76 +96,27 @@
 
   private boolean settersPrefixed;
 
-  private BuilderMethodClassifier(
+  BuilderMethodClassifier(
       ErrorReporter errorReporter,
       ProcessingEnvironment processingEnv,
-      TypeElement autoValueClass,
+      TypeMirror builtType,
       TypeElement builderType,
-      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
-      ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType) {
+      ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) {
     this.errorReporter = errorReporter;
     this.typeUtils = processingEnv.getTypeUtils();
     this.elementUtils = processingEnv.getElementUtils();
-    this.autoValueClass = autoValueClass;
+    this.builtType = builtType;
     this.builderType = builderType;
-    this.getterToPropertyName = getterToPropertyName;
-    this.getterToPropertyType = getterToPropertyType;
-    ImmutableMap.Builder<String, ExecutableElement> getterToPropertyNameBuilder =
-        ImmutableMap.builder();
-    for (ExecutableElement getter : getterToPropertyName.keySet()) {
-      getterToPropertyNameBuilder.put(getter.getSimpleName().toString(), getter);
-    }
-    this.getterNameToGetter = getterToPropertyNameBuilder.build();
+    this.rewrittenPropertyTypes = rewrittenPropertyTypes;
     this.eclipseHack = new EclipseHack(processingEnv);
   }
 
   /**
-   * Classifies the given methods from a builder type and its ancestors.
-   *
-   * @param methods the abstract methods in {@code builderType} and its ancestors.
-   * @param errorReporter where to report errors.
-   * @param processingEnv the ProcessingEnvironment for annotation processing.
-   * @param autoValueClass the {@code AutoValue} class containing the builder.
-   * @param builderType the builder class or interface within {@code autoValueClass}.
-   * @param getterToPropertyName a map from getter methods to the properties they get.
-   * @param getterToPropertyType a map from getter methods to their return types. The return types
-   *     here use type parameters from the builder class (if any) rather than from the {@code
-   *     AutoValue} class, even though the getter methods are in the latter.
-   * @param autoValueHasToBuilder true if the containing {@code @AutoValue} class has a {@code
-   *     toBuilder()} method.
-   * @return an {@code Optional} that contains the results of the classification if it was
-   *     successful or nothing if it was not.
-   */
-  static Optional<BuilderMethodClassifier> classify(
-      Iterable<ExecutableElement> methods,
-      ErrorReporter errorReporter,
-      ProcessingEnvironment processingEnv,
-      TypeElement autoValueClass,
-      TypeElement builderType,
-      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
-      ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType,
-      boolean autoValueHasToBuilder) {
-    BuilderMethodClassifier classifier =
-        new BuilderMethodClassifier(
-            errorReporter,
-            processingEnv,
-            autoValueClass,
-            builderType,
-            getterToPropertyName,
-            getterToPropertyType);
-    if (classifier.classifyMethods(methods, autoValueHasToBuilder)) {
-      return Optional.of(classifier);
-    } else {
-      return Optional.empty();
-    }
-  }
-
-  /**
    * Returns a multimap from the name of a property to the methods that set it. If the property is
    * defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code
    * getFoo()} then the name of the property is {@code foo} and there will be an entry in the map
-   * where the key is {@code "foo"} and the value describes a method in the builder called
-   * {@code foo} or {@code setFoo}.
+   * where the key is {@code "foo"} and the value describes a method in the builder called {@code
+   * foo} or {@code setFoo}.
    */
   ImmutableMultimap<String, PropertySetter> propertyNameToSetters() {
     return ImmutableMultimap.copyOf(
@@ -179,8 +147,7 @@
   }
 
   /** Classifies the given methods and sets the state of this object based on what is found. */
-  private boolean classifyMethods(
-      Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) {
+  boolean classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) {
     int startErrorCount = errorReporter.errorCount();
     for (ExecutableElement method : methods) {
       classifyMethod(method);
@@ -198,49 +165,48 @@
     } else {
       errorReporter.reportError(
           propertyNameToUnprefixedSetters.values().iterator().next().getSetter(),
-          "[AutoValueSetNotSet] If any setter methods use the setFoo convention then all must");
+          "[%sSetNotSet] If any setter methods use the setFoo convention then all must",
+          autoWhat());
       return false;
     }
-    getterToPropertyName.forEach(
-        (getter, property) -> {
-          TypeMirror propertyType = getterToPropertyType.get(getter);
-          boolean hasSetter = propertyNameToSetter.containsKey(property);
-          PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property);
-          boolean hasBuilder = propertyBuilder != null;
-          if (hasBuilder) {
-            // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must
-            // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a
-            // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is
-            // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll
-            // method that accepts a Bar argument.
-            boolean canMakeBarBuilder =
-                (propertyBuilder.getBuiltToBuilder() != null
-                    || propertyBuilder.getCopyAll() != null);
-            boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter);
-            if (needToMakeBarBuilder && !canMakeBarBuilder) {
-              errorReporter.reportError(
-                  propertyBuilder.getPropertyBuilderMethod(),
-                  "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no"
-                      + " way to make that type from %2$s: %2$s does not have a non-static"
-                      + " toBuilder() method that returns %1$s, and %1$s does not have a method"
-                      + " addAll or putAll that accepts an argument of type %2$s",
-                  propertyBuilder.getBuilderTypeMirror(),
-                  propertyType);
-            }
-          } else if (!hasSetter) {
-            // We have neither barBuilder() nor setBar(Bar), so we should complain.
-            String setterName = settersPrefixed ? prefixWithSet(property) : property;
-            errorReporter.reportError(
-                builderType,
-                "[AutoValueBuilderMissingMethod] Expected a method with this signature: %s%s"
-                    + " %s(%s), or a %sBuilder() method",
-                builderType,
-                typeParamsString(),
-                setterName,
-                propertyType,
-                property);
-          }
-        });
+    for (String property : rewrittenPropertyTypes.keySet()) {
+      TypeMirror propertyType = rewrittenPropertyTypes.get(property);
+      boolean hasSetter = propertyNameToSetter.containsKey(property);
+      PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property);
+      boolean hasBuilder = propertyBuilder != null;
+      if (hasBuilder) {
+        // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must
+        // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a
+        // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is
+        // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll
+        // method that accepts a Bar argument.
+        boolean canMakeBarBuilder =
+            (propertyBuilder.getBuiltToBuilder() != null || propertyBuilder.getCopyAll() != null);
+        boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter);
+        if (needToMakeBarBuilder && !canMakeBarBuilder) {
+          errorReporter.reportError(
+              propertyBuilder.getPropertyBuilderMethod(),
+              "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no"
+                  + " way to make that type from %2$s: %2$s does not have a non-static"
+                  + " toBuilder() method that returns %1$s, and %1$s does not have a method"
+                  + " addAll or putAll that accepts an argument of type %2$s",
+              propertyBuilder.getBuilderTypeMirror(),
+              propertyType);
+        }
+      } else if (!hasSetter) {
+        // We have neither barBuilder() nor setBar(Bar), so we should complain.
+        String setterName = settersPrefixed ? prefixWithSet(property) : property;
+        errorReporter.reportError(
+            builderType,
+            "[%sBuilderMissingMethod] Expected a method with this signature: %s"
+                + " %s(%s), or a %sBuilder() method",
+            autoWhat(),
+            builderType.asType(),
+            setterName,
+            propertyType,
+            property);
+      }
+    }
     return errorReporter.errorCount() == startErrorCount;
   }
 
@@ -255,7 +221,7 @@
         break;
       default:
         errorReporter.reportError(
-            method, "[AutoValueBuilderArgs] Builder methods must have 0 or 1 parameters");
+            method, "[%sBuilderArgs] Builder methods must have 0 or 1 parameters", autoWhat());
     }
   }
 
@@ -268,26 +234,26 @@
    * ImmutableList<String> foos()} or {@code getFoos()}.
    */
   private void classifyMethodNoArgs(ExecutableElement method) {
-    String methodName = method.getSimpleName().toString();
-    TypeMirror returnType = builderMethodReturnType(method);
-
-    ExecutableElement getter = getterNameToGetter.get(methodName);
-    if (getter != null) {
-      classifyGetter(method, getter);
+    Optional<String> getterProperty = propertyForBuilderGetter(method);
+    if (getterProperty.isPresent()) {
+      classifyGetter(method, getterProperty.get());
       return;
     }
 
+    String methodName = method.getSimpleName().toString();
+    TypeMirror returnType = builderMethodReturnType(method);
+
     if (methodName.endsWith("Builder")) {
       String property = methodName.substring(0, methodName.length() - "Builder".length());
-      if (getterToPropertyName.containsValue(property)) {
+      if (rewrittenPropertyTypes.containsKey(property)) {
         PropertyBuilderClassifier propertyBuilderClassifier =
             new PropertyBuilderClassifier(
                 errorReporter,
                 typeUtils,
                 elementUtils,
                 this,
-                getterToPropertyName,
-                getterToPropertyType,
+                this::propertyIsNullable,
+                rewrittenPropertyTypes,
                 eclipseHack);
         Optional<PropertyBuilder> propertyBuilder =
             propertyBuilderClassifier.makePropertyBuilder(method, property);
@@ -298,22 +264,24 @@
       }
     }
 
-    if (TYPE_EQUIVALENCE.equivalent(returnType, autoValueClass.asType())) {
+    if (TYPE_EQUIVALENCE.equivalent(returnType, builtType)) {
       buildMethods.add(method);
     } else {
       errorReporter.reportError(
           method,
-          "[AutoValueBuilderNoArg] Method without arguments should be a build method returning"
-              + " %1$s%2$s, or a getter method with the same name and type as a getter method of"
-              + " %1$s, or fooBuilder() where foo() or getFoo() is a getter method of %1$s",
-          autoValueClass,
-          typeParamsString());
+          "[%1$sBuilderNoArg] Method without arguments should be a build method returning"
+              + " %2$s, or a getter method with the same name and type as %3$s,"
+              + " or fooBuilder() where %4$s is %3$s",
+          // "where foo() or getFoo() is a method in..." or "where foo is a parameter of..."
+          autoWhat(),
+          builtType,
+          getterMustMatch(),
+          fooBuilderMustMatch());
     }
   }
 
-  private void classifyGetter(ExecutableElement builderGetter, ExecutableElement originalGetter) {
-    String propertyName = getterToPropertyName.get(originalGetter);
-    TypeMirror originalGetterType = getterToPropertyType.get(originalGetter);
+  private void classifyGetter(ExecutableElement builderGetter, String propertyName) {
+    TypeMirror originalGetterType = rewrittenPropertyTypes.get(propertyName);
     TypeMirror builderGetterType = builderMethodReturnType(builderGetter);
     String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType);
     if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) {
@@ -344,51 +312,76 @@
         builderGetter,
         "[AutoValueBuilderReturnType] Method matches a property of %1$s but has return type %2$s"
             + " instead of %3$s or an Optional wrapping of %3$s",
-        autoValueClass,
+        builtType,
         builderGetterType,
         originalGetterType);
   }
 
   /**
-   * Classifies a method given that it has one argument. Currently, a method with one argument can
-   * only be a setter, meaning that it must look like {@code foo(T)} or {@code setFoo(T)}, where the
-   * {@code AutoValue} class has a property called {@code foo} of type {@code T}.
+   * Classifies a method given that it has one argument. A method with one argument can be:
+   *
+   * <ul>
+   *   <li>a setter, meaning that it looks like {@code foo(T)} or {@code setFoo(T)}, where the
+   *       {@code AutoValue} class has a property called {@code foo} of type {@code T};
+   *   <li>a property builder with one argument, meaning it looks like {@code
+   *       ImmutableSortedSet.Builder<V> foosBuilder(Comparator<V>)}, where the {@code AutoValue}
+   *       class has a property called {@code foos} with a type whose builder can be made with an
+   *       argument of the given type.
+   * </ul>
    */
   private void classifyMethodOneArg(ExecutableElement method) {
+    if (classifyPropertyBuilderOneArg(method)) {
+      return;
+    }
     String methodName = method.getSimpleName().toString();
-    Map<String, ExecutableElement> propertyNameToGetter = getterToPropertyName.inverse();
+    ImmutableMap<String, E> propertyElements = propertyElements();
     String propertyName = null;
-    ExecutableElement valueGetter = propertyNameToGetter.get(methodName);
+    E propertyElement = propertyElements.get(methodName);
     Multimap<String, PropertySetter> propertyNameToSetters = null;
-    if (valueGetter != null) {
+    if (propertyElement != null) {
       propertyNameToSetters = propertyNameToUnprefixedSetters;
       propertyName = methodName;
-    } else if (valueGetter == null && methodName.startsWith("set") && methodName.length() > 3) {
+    } else if (methodName.startsWith("set") && methodName.length() > 3) {
       propertyNameToSetters = propertyNameToPrefixedSetters;
       propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3));
-      valueGetter = propertyNameToGetter.get(propertyName);
-      if (valueGetter == null) {
+      propertyElement = propertyElements.get(propertyName);
+      if (propertyElement == null) {
         // If our property is defined by a getter called getOAuth() then it is called "OAuth"
-        // because of Introspector.decapitalize. Therefore we want Introspector.decapitalize to
-        // be used for the setter too, so that you can write setOAuth(x). Meanwhile if the property
-        // is defined by a getter called oAuth() then it is called "oAuth", but you would still
-        // expect to be able to set it using setOAuth(x). Hence the second try using a decapitalize
-        // method without the quirky two-leading-capitals rule.
+        // because of JavaBeans rules. Therefore we want JavaBeans rules to be used for the setter
+        // too, so that you can write setOAuth(x). Meanwhile if the property is defined by a getter
+        // called oAuth() then it is called "oAuth", but you would still expect to be able to set it
+        // using setOAuth(x). Hence the second try using a decapitalize method without the quirky
+        // two-leading-capitals rule.
         propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3));
-        valueGetter = propertyNameToGetter.get(propertyName);
+        propertyElement = propertyElements.get(propertyName);
+      }
+    } else {
+      // We might also have an unprefixed setter, so the getter is called OAuth() or getOAuth() and
+      // the setter is called oAuth(x), where again JavaBeans rules imply that it should be called
+      // OAuth(x). Iterating over the properties here is a bit clunky but this case should be
+      // unusual.
+      propertyNameToSetters = propertyNameToUnprefixedSetters;
+      for (Map.Entry<String, E> entry : propertyElements.entrySet()) {
+        if (methodName.equals(PropertyNames.decapitalizeNormally(entry.getKey()))) {
+          propertyName = entry.getKey();
+          propertyElement = entry.getValue();
+          break;
+        }
       }
     }
-    if (valueGetter == null || propertyNameToSetters == null) {
+    if (propertyElement == null || propertyNameToSetters == null) {
       // The second disjunct isn't needed but convinces control-flow checkers that
       // propertyNameToSetters can't be null when we call put on it below.
       errorReporter.reportError(
           method,
-          "[AutoValueBuilderWhatProp] Method does not correspond to a property of %s",
-          autoValueClass);
+          "[%sBuilderWhatProp] Method %s does not correspond to %s",
+          autoWhat(),
+          methodName,
+          getterMustMatch());
       checkForFailedJavaBean(method);
       return;
     }
-    Optional<Copier> function = getSetterFunction(valueGetter, method);
+    Optional<Copier> function = getSetterFunction(propertyElement, method);
     if (function.isPresent()) {
       DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType());
       ExecutableType methodMirror =
@@ -399,28 +392,44 @@
             propertyName, new PropertySetter(method, parameterType, function.get()));
       } else {
         errorReporter.reportError(
-            method, "Setter methods must return %s%s", builderType, typeParamsString());
+            method,
+            "[%sBuilderRet] Setter methods must return %s",
+            autoWhat(),
+            builderType.asType());
       }
     }
   }
 
-  // A frequent source of problems is where the JavaBeans conventions have been followed for
-  // most but not all getters. Then AutoValue considers that they haven't been followed at all,
-  // so you might have a property called getFoo where you thought it was called just foo, and
-  // you might not understand why your setter called setFoo is rejected (it would have to be called
-  // setGetFoo).
-  private void checkForFailedJavaBean(ExecutableElement rejectedSetter) {
-    ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet();
-    ImmutableSet<ExecutableElement> prefixedGetters =
-        AutoValueProcessor.prefixedGettersIn(allGetters);
-    if (prefixedGetters.size() < allGetters.size()
-        && prefixedGetters.size() >= allGetters.size() / 2) {
-      errorReporter.reportNote(
-          rejectedSetter,
-          "This might be because you are using the getFoo() convention"
-              + " for some but not all methods. These methods don't follow the convention: %s",
-          difference(allGetters, prefixedGetters));
+  /**
+   * Classifies a method given that it has one argument and is a property builder with a parameter,
+   * like {@code ImmutableSortedSet.Builder<String> foosBuilder(Comparator<String>)}.
+   *
+   * @param method A method to classify
+   * @return true if method has been classified successfully
+   */
+  private boolean classifyPropertyBuilderOneArg(ExecutableElement method) {
+    String methodName = method.getSimpleName().toString();
+    if (!methodName.endsWith("Builder")) {
+      return false;
     }
+    String property = methodName.substring(0, methodName.length() - "Builder".length());
+    if (!rewrittenPropertyTypes.containsKey(property)) {
+      return false;
+    }
+    PropertyBuilderClassifier propertyBuilderClassifier =
+        new PropertyBuilderClassifier(
+            errorReporter,
+            typeUtils,
+            elementUtils,
+            this,
+            this::propertyIsNullable,
+            rewrittenPropertyTypes,
+            eclipseHack);
+    Optional<PropertyBuilder> maybePropertyBuilder =
+        propertyBuilderClassifier.makePropertyBuilder(method, property);
+    maybePropertyBuilder.ifPresent(
+        propertyBuilder -> propertyNameToPropertyBuilder.put(property, propertyBuilder));
+    return maybePropertyBuilder.isPresent();
   }
 
   /**
@@ -431,12 +440,12 @@
    * using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned
    * function will be something like {@code s -> "Optional.of(" + s + ")"}.
    */
-  private Optional<Copier> getSetterFunction(
-      ExecutableElement valueGetter, ExecutableElement setter) {
+  private Optional<Copier> getSetterFunction(E propertyElement, ExecutableElement setter) {
     VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters());
     boolean nullableParameter =
         nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent();
-    TypeMirror targetType = getterToPropertyType.get(valueGetter);
+    String property = propertyElements().inverse().get(propertyElement);
+    TypeMirror targetType = rewrittenPropertyTypes.get(property);
     ExecutableType finalSetter =
         MoreTypes.asExecutable(
             typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter));
@@ -448,14 +457,14 @@
         && typeUtils.isAssignable(targetType, parameterType)) {
       if (nullableParameter) {
         boolean nullableProperty =
-            nullableAnnotationFor(valueGetter, valueGetter.getReturnType()).isPresent();
+            nullableAnnotationFor(propertyElement, originalPropertyType(propertyElement))
+                .isPresent();
         if (!nullableProperty) {
           errorReporter.reportError(
               setter,
-              "[AutoValueNullNotNull] Parameter of setter method is @Nullable but property method"
-                  + " %s.%s() is not",
-              autoValueClass,
-              valueGetter.getSimpleName());
+              "[%sNullNotNull] Parameter of setter method is @Nullable but %s is not",
+              autoWhat(),
+              propertyString(propertyElement));
           return Optional.empty();
         }
       }
@@ -465,12 +474,15 @@
     // Parameter type is not equal to property type, but might be convertible with copyOf.
     ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, nullableParameter);
     if (!copyOfMethods.isEmpty()) {
-      return getConvertingSetterFunction(copyOfMethods, valueGetter, setter, parameterType);
+      return getConvertingSetterFunction(copyOfMethods, propertyElement, setter, parameterType);
     }
     errorReporter.reportError(
         setter,
-        "[AutoValueGetVsSet] Parameter type %s of setter method should be %s to match getter %s.%s",
-        parameterType, targetType, autoValueClass, valueGetter.getSimpleName());
+        "[%sGetVsSet] Parameter type %s of setter method should be %s to match %s",
+        autoWhat(),
+        parameterType,
+        targetType,
+        propertyString(propertyElement));
     return Optional.empty();
   }
 
@@ -481,10 +493,11 @@
    */
   private Optional<Copier> getConvertingSetterFunction(
       ImmutableList<ExecutableElement> copyOfMethods,
-      ExecutableElement valueGetter,
+      E propertyElement,
       ExecutableElement setter,
       TypeMirror parameterType) {
-    DeclaredType targetType = MoreTypes.asDeclared(getterToPropertyType.get(valueGetter));
+    String property = propertyElements().inverse().get(propertyElement);
+    DeclaredType targetType = MoreTypes.asDeclared(rewrittenPropertyTypes.get(property));
     for (ExecutableElement copyOfMethod : copyOfMethods) {
       Optional<Copier> function =
           getConvertingSetterFunction(copyOfMethod, targetType, parameterType);
@@ -495,12 +508,12 @@
     String targetTypeSimpleName = targetType.asElement().getSimpleName().toString();
     errorReporter.reportError(
         setter,
-        "[AutoValueGetVsSetOrConvert] Parameter type %s of setter method should be %s to match"
-            + " getter %s.%s, or it should be a type that can be passed to %s.%s to produce %s",
+        "[%sGetVsSetOrConvert] Parameter type %s of setter method should be %s to match %s, or it"
+            + " should be a type that can be passed to %s.%s to produce %s",
+        autoWhat(),
         parameterType,
         targetType,
-        autoValueClass,
-        valueGetter.getSimpleName(),
+        propertyString(propertyElement),
         targetTypeSimpleName,
         copyOfMethods.get(0).getSimpleName(),
         targetType);
@@ -635,7 +648,76 @@
     return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1);
   }
 
-  private String typeParamsString() {
-    return TypeSimplifier.actualTypeParametersString(autoValueClass);
+  /**
+   * True if the given property is nullable, either because its type has a {@code @Nullable} type
+   * annotation, or because its getter method has a {@code @Nullable} method annotation.
+   */
+  private boolean propertyIsNullable(String property) {
+    E propertyElement = propertyElements().get(property);
+    return Stream.of(propertyElement, originalPropertyType(propertyElement))
+        .flatMap(ac -> ac.getAnnotationMirrors().stream())
+        .map(a -> a.getAnnotationType().asElement().getSimpleName())
+        .anyMatch(n -> n.contentEquals("Nullable"));
   }
+
+  /**
+   * Returns a map from property names to the corresponding source program elements. For AutoValue,
+   * these elements are the abstract getter methods in the {@code @AutoValue} class. For
+   * AutoBuilder, they are the parameters of the constructor or method that the generated builder
+   * will call.
+   */
+  abstract ImmutableBiMap<String, E> propertyElements();
+
+  /**
+   * Returns the property type as it appears on the original source program element. This can be
+   * different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to
+   * type variables in the builder rather than in the original class. Also, {@link
+   * #rewrittenPropertyTypes} will not have type annotations even if they were present on the
+   * original element, so {@code originalPropertyType} is the right thing to use for those.
+   */
+  abstract TypeMirror originalPropertyType(E propertyElement);
+
+  /**
+   * A string identifying the given property element, which is a method for AutoValue or a parameter
+   * for AutoBuilder.
+   */
+  abstract String propertyString(E propertyElement);
+
+  /**
+   * Returns the name of the property that the given no-arg builder method queries, if
+   * any. For example, if your {@code @AutoValue} class has a method {@code abstract String
+   * getBar()} then an abstract method in its builder with the same signature will query the {@code
+   * bar} property.
+   */
+  abstract Optional<String> propertyForBuilderGetter(ExecutableElement method);
+
+  /**
+   * Checks for failed JavaBean usage when a method that looks like a setter doesn't actually match
+   * anything, and emits a compiler Note if detected. A frequent source of problems is where the
+   * JavaBeans conventions have been followed for most but not all getters. Then AutoValue considers
+   * that they haven't been followed at all, so you might have a property called getFoo where you
+   * thought it was called just foo, and you might not understand why your setter called setFoo is
+   * rejected (it would have to be called setGetFoo).
+   *
+   * <p>This is not relevant for AutoBuilder, which uses parameter names rather than getters. The
+   * parameter names are unambiguously the same as the property names.
+   */
+  abstract void checkForFailedJavaBean(ExecutableElement rejectedSetter);
+
+  /**
+   * A string describing what sort of Auto this is, {@code "AutoValue"} or {@code "AutoBuilder"}.
+   */
+  abstract String autoWhat();
+
+  /**
+   * A string describing what a builder getter must match: a property method for AutoValue, a
+   * parameter for AutoBuilder.
+   */
+  abstract String getterMustMatch();
+
+  /**
+   * A string describing what a property builder for property {@code foo} must match, {@code foo()
+   * or getFoo()} for AutoValue, {@code foo} for AutoBuilder.
+   */
+  abstract String fooBuilderMustMatch();
 }
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
new file mode 100644
index 0000000..55a3198
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoBuilder.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import static com.google.auto.common.MoreStreams.toImmutableBiMap;
+import static com.google.auto.common.MoreStreams.toImmutableMap;
+
+import com.google.auto.common.MoreElements;
+import com.google.auto.common.MoreTypes;
+import com.google.common.base.Equivalence;
+import com.google.common.base.VerifyException;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.TypeParameterElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.util.Types;
+
+class BuilderMethodClassifierForAutoBuilder extends BuilderMethodClassifier<VariableElement> {
+  private final ExecutableElement executable;
+  private final ImmutableBiMap<VariableElement, String> paramToPropertyName;
+
+  private BuilderMethodClassifierForAutoBuilder(
+      ErrorReporter errorReporter,
+      ProcessingEnvironment processingEnv,
+      ExecutableElement executable,
+      TypeMirror builtType,
+      TypeElement builderType,
+      ImmutableBiMap<VariableElement, String> paramToPropertyName,
+      ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) {
+    super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes);
+    this.executable = executable;
+    this.paramToPropertyName = paramToPropertyName;
+  }
+
+  /**
+   * Classifies the given methods from a builder type and its ancestors.
+   *
+   * @param methods the abstract methods in {@code builderType} and its ancestors.
+   * @param errorReporter where to report errors.
+   * @param processingEnv the ProcessingEnvironment for annotation processing.
+   * @param executable the constructor or static method that AutoBuilder will call.
+   * @param builtType the type to be built.
+   * @param builderType the builder class or interface within {@code ofClass}.
+   * @return an {@code Optional} that contains the results of the classification if it was
+   *     successful or nothing if it was not.
+   */
+  static Optional<BuilderMethodClassifier<VariableElement>> classify(
+      Iterable<ExecutableElement> methods,
+      ErrorReporter errorReporter,
+      ProcessingEnvironment processingEnv,
+      ExecutableElement executable,
+      TypeMirror builtType,
+      TypeElement builderType) {
+    ImmutableBiMap<VariableElement, String> paramToPropertyName =
+        executable.getParameters().stream()
+            .collect(toImmutableBiMap(v -> v, v -> v.getSimpleName().toString()));
+    ImmutableMap<String, TypeMirror> rewrittenPropertyTypes =
+        rewriteParameterTypes(executable, builderType, errorReporter, processingEnv.getTypeUtils());
+    BuilderMethodClassifier<VariableElement> classifier =
+        new BuilderMethodClassifierForAutoBuilder(
+            errorReporter,
+            processingEnv,
+            executable,
+            builtType,
+            builderType,
+            paramToPropertyName,
+            rewrittenPropertyTypes);
+    if (classifier.classifyMethods(methods, false)) {
+      return Optional.of(classifier);
+    } else {
+      return Optional.empty();
+    }
+  }
+
+  // Rewrites the parameter types of the executable so they use the type variables of the builder
+  // where appropriate.
+  //
+  // Suppose we have something like this:
+  //
+  // static <E> Set<E> singletonSet(E elem) {...}
+  //
+  // @AutoBuilder(callMethod = "singletonSet")
+  // interface SingletonSetBuilder<E> {
+  //   SingletonSetBuilder<E> setElem(E elem);
+  //   Set<E> build();
+  // }
+  //
+  // We want to check that the type of the setter `setElem` matches the type of the
+  // parameter it is setting. But in fact it doesn't: the type of the setter is
+  // E-of-SingletonSetBuilder while the type of the parameter is E-of-singletonSet. So we
+  // need to rewrite any type variables mentioned in parameters so that they use the corresponding
+  // types from the builder. We want to return a map where "elem" is mapped to
+  // E-of-SingletonSetBuilder, even though the `elem` that we get from the parameters of
+  // singletonSet is going to be E-of-singletonSet. And we also want that to work if the parameter
+  // is something more complicated, like List<? extends E>.
+  //
+  // For the corresponding situation with AutoValue, we have a way of dodging the problem somewhat.
+  // For an @AutoValue class Foo<E> with a builder Builder<E>, we can craft a DeclaredType
+  // Foo<E> where the E comes from Builder<E>, and we can use Types.asMemberOf to determine the
+  // return types of methods (which are what we want to rewrite in that case). But that doesn't
+  // work here because singletonSet is static and Types.asMemberOf would have no effect on it.
+  //
+  // So instead we take the type of each parameter and feed it through a TypeVisitor that rewrites
+  // type variables, rewriting from E-of-singletonSet to E-of-SingletonSetBuilder. Then we can use
+  // Types.isSameType or Types.isAssignable and it will work as we expect.
+  //
+  // In principle a similar situation arises with the return type Set<E> of singletonSet versus
+  // the return type Set<E> of SingletonSetBuilder.build(). But in fact we only use
+  // MoreTypes.equivalence to compare those, and that returns true for distinct type variables if
+  // they have the same name and bounds.
+  private static ImmutableMap<String, TypeMirror> rewriteParameterTypes(
+      ExecutableElement executable,
+      TypeElement builderType,
+      ErrorReporter errorReporter,
+      Types typeUtils) {
+    ImmutableList<TypeParameterElement> executableTypeParams = executableTypeParams(executable);
+    List<? extends TypeParameterElement> builderTypeParams = builderType.getTypeParameters();
+    if (!BuilderSpec.sameTypeParameters(executableTypeParams, builderTypeParams)) {
+      errorReporter.abortWithError(
+          builderType,
+          "[AutoBuilderTypeParams] Builder type parameters %s must match type parameters %s of %s",
+          TypeEncoder.typeParametersString(builderTypeParams),
+          TypeEncoder.typeParametersString(executableTypeParams),
+          AutoBuilderProcessor.executableString(executable));
+    }
+    if (executableTypeParams.isEmpty()) {
+      // Optimization for a common case. No point in doing all that type visiting if we have no
+      // variables to substitute.
+      return executable.getParameters().stream()
+          .collect(toImmutableMap(v -> v.getSimpleName().toString(), Element::asType));
+    }
+    Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables = new LinkedHashMap<>();
+    for (int i = 0; i < executableTypeParams.size(); i++) {
+      TypeVariable from = MoreTypes.asTypeVariable(executableTypeParams.get(i).asType());
+      TypeVariable to = MoreTypes.asTypeVariable(builderTypeParams.get(i).asType());
+      typeVariables.put(MoreTypes.equivalence().wrap(from), to);
+    }
+    Function<TypeVariable, TypeMirror> substitute =
+        v -> typeVariables.get(MoreTypes.equivalence().wrap(v));
+    return executable.getParameters().stream()
+        .collect(
+            toImmutableMap(
+                v -> v.getSimpleName().toString(),
+                v -> TypeVariables.substituteTypeVariables(v.asType(), substitute, typeUtils)));
+  }
+
+  private static ImmutableList<TypeParameterElement> executableTypeParams(
+      ExecutableElement executable) {
+    switch (executable.getKind()) {
+      case CONSTRUCTOR:
+        // A constructor can have its own type parameters, in addition to any that its containing
+        // class has. That's pretty unusual, but we allow it, requiring the builder to have type
+        // parameters that are the concatenation of the class's and the constructor's.
+        TypeElement container = MoreElements.asType(executable.getEnclosingElement());
+        return ImmutableList.<TypeParameterElement>builder()
+            .addAll(container.getTypeParameters())
+            .addAll(executable.getTypeParameters())
+            .build();
+      case METHOD:
+        return ImmutableList.copyOf(executable.getTypeParameters());
+      default:
+        throw new VerifyException("Unexpected executable kind " + executable.getKind());
+    }
+  }
+
+  @Override
+  Optional<String> propertyForBuilderGetter(ExecutableElement method) {
+    String methodName = method.getSimpleName().toString();
+    if (paramToPropertyName.containsValue(methodName)) {
+      return Optional.of(methodName);
+    }
+    if (AutoValueishProcessor.isPrefixedGetter(method)) {
+      int prefixLength = methodName.startsWith("get") ? 3 : 2; // "get" or "is"
+      String unprefixed = methodName.substring(prefixLength);
+      String propertyName = PropertyNames.decapitalizeLikeJavaBeans(unprefixed);
+      if (paramToPropertyName.containsValue(propertyName)) {
+        return Optional.of(propertyName);
+      }
+      propertyName = PropertyNames.decapitalizeNormally(unprefixed);
+      if (paramToPropertyName.containsValue(propertyName)) {
+        return Optional.of(propertyName);
+      }
+    }
+    return Optional.empty();
+  }
+
+  @Override
+  void checkForFailedJavaBean(ExecutableElement rejectedSetter) {}
+
+  @Override
+  ImmutableBiMap<String, VariableElement> propertyElements() {
+    return paramToPropertyName.inverse();
+  }
+
+  @Override
+  TypeMirror originalPropertyType(VariableElement propertyElement) {
+    return propertyElement.asType();
+  }
+
+  @Override
+  String propertyString(VariableElement propertyElement) {
+    return "parameter \""
+        + propertyElement.getSimpleName()
+        + "\" of "
+        + AutoBuilderProcessor.executableString(executable);
+  }
+
+  @Override
+  String autoWhat() {
+    return "AutoBuilder";
+  }
+
+  @Override
+  String getterMustMatch() {
+    return "a parameter of " + AutoBuilderProcessor.executableString(executable);
+  }
+
+  @Override
+  String fooBuilderMustMatch() {
+    return "foo";
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
new file mode 100644
index 0000000..dde449b
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifierForAutoValue.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import static com.google.common.collect.Sets.difference;
+
+import com.google.auto.common.MoreElements;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import java.util.Optional;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+
+class BuilderMethodClassifierForAutoValue extends BuilderMethodClassifier<ExecutableElement> {
+  private final ErrorReporter errorReporter;
+  private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName;
+  private final ImmutableMap<String, ExecutableElement> getterNameToGetter;
+  private final TypeMirror builtType;
+
+  private BuilderMethodClassifierForAutoValue(
+      ErrorReporter errorReporter,
+      ProcessingEnvironment processingEnv,
+      TypeMirror builtType,
+      TypeElement builderType,
+      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+      ImmutableMap<String, TypeMirror> rewrittenPropertyTypes) {
+    super(errorReporter, processingEnv, builtType, builderType, rewrittenPropertyTypes);
+    this.errorReporter = errorReporter;
+    this.getterToPropertyName = getterToPropertyName;
+    this.getterNameToGetter =
+        Maps.uniqueIndex(getterToPropertyName.keySet(), m -> m.getSimpleName().toString());
+    this.builtType = builtType;
+  }
+
+  /**
+   * Classifies the given methods from a builder type and its ancestors.
+   *
+   * @param methods the abstract methods in {@code builderType} and its ancestors.
+   * @param errorReporter where to report errors.
+   * @param processingEnv the {@link ProcessingEnvironment} for annotation processing.
+   * @param autoValueClass the {@code AutoValue} class containing the builder.
+   * @param builderType the builder class or interface within {@code autoValueClass}.
+   * @param getterToPropertyName a map from getter methods to the properties they get.
+   * @param rewrittenPropertyTypes a map from property names to types. The types here use type
+   *     parameters from the builder class (if any) rather than from the {@code AutoValue} class,
+   *     even though the getter methods are in the latter.
+   * @param autoValueHasToBuilder true if the containing {@code @AutoValue} class has a {@code
+   *     toBuilder()} method.
+   * @return an {@code Optional} that contains the results of the classification if it was
+   *     successful or nothing if it was not.
+   */
+  static Optional<BuilderMethodClassifier<ExecutableElement>> classify(
+      Iterable<ExecutableElement> methods,
+      ErrorReporter errorReporter,
+      ProcessingEnvironment processingEnv,
+      TypeElement autoValueClass,
+      TypeElement builderType,
+      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
+      ImmutableMap<String, TypeMirror> rewrittenPropertyTypes,
+      boolean autoValueHasToBuilder) {
+    BuilderMethodClassifier<ExecutableElement> classifier =
+        new BuilderMethodClassifierForAutoValue(
+            errorReporter,
+            processingEnv,
+            autoValueClass.asType(),
+            builderType,
+            getterToPropertyName,
+            rewrittenPropertyTypes);
+    if (classifier.classifyMethods(methods, autoValueHasToBuilder)) {
+      return Optional.of(classifier);
+    } else {
+      return Optional.empty();
+    }
+  }
+
+  @Override
+  TypeMirror originalPropertyType(ExecutableElement propertyElement) {
+    return propertyElement.getReturnType();
+  }
+
+  @Override
+  String propertyString(ExecutableElement propertyElement) {
+    TypeElement type = MoreElements.asType(propertyElement.getEnclosingElement());
+    return "property method "
+        + type.getQualifiedName()
+        + "."
+        + propertyElement.getSimpleName()
+        + "()";
+  }
+
+  @Override
+  ImmutableBiMap<String, ExecutableElement> propertyElements() {
+    return getterToPropertyName.inverse();
+  }
+
+  @Override
+  Optional<String> propertyForBuilderGetter(ExecutableElement method) {
+    String methodName = method.getSimpleName().toString();
+    return Optional.ofNullable(getterNameToGetter.get(methodName)).map(getterToPropertyName::get);
+  }
+
+  @Override
+  void checkForFailedJavaBean(ExecutableElement rejectedSetter) {
+    ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet();
+    ImmutableSet<ExecutableElement> prefixedGetters =
+        AutoValueProcessor.prefixedGettersIn(allGetters);
+    if (prefixedGetters.size() < allGetters.size()
+        && prefixedGetters.size() >= allGetters.size() / 2) {
+      errorReporter.reportNote(
+          rejectedSetter,
+          "This might be because you are using the getFoo() convention"
+              + " for some but not all methods. These methods don't follow the convention: %s",
+          difference(allGetters, prefixedGetters));
+    }
+  }
+
+  @Override
+  String autoWhat() {
+    return "AutoValue";
+  }
+
+  @Override
+  String getterMustMatch() {
+    return "a property method of " + builtType;
+  }
+
+  @Override
+  String fooBuilderMustMatch() {
+    return "foo() or getFoo()";
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
index 7e5b17c..9f45d17 100644
--- a/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
+++ b/value/src/main/java/com/google/auto/value/processor/BuilderSpec.java
@@ -16,8 +16,9 @@
 package com.google.auto.value.processor;
 
 import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
-import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.hasAnnotationMirror;
-import static com.google.auto.value.processor.AutoValueOrOneOfProcessor.nullableAnnotationFor;
+import static com.google.auto.common.MoreStreams.toImmutableSet;
+import static com.google.auto.value.processor.AutoValueishProcessor.hasAnnotationMirror;
+import static com.google.auto.value.processor.AutoValueishProcessor.nullableAnnotationFor;
 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_BUILDER_NAME;
 import static com.google.common.collect.Sets.immutableEnumSet;
 import static java.util.stream.Collectors.toList;
@@ -25,16 +26,16 @@
 import static javax.lang.model.util.ElementFilter.methodsIn;
 import static javax.lang.model.util.ElementFilter.typesIn;
 
+import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import com.google.auto.value.extension.AutoValueExtension;
-import com.google.auto.value.processor.AutoValueOrOneOfProcessor.Property;
+import com.google.auto.value.processor.AutoValueishProcessor.Property;
 import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -49,6 +50,7 @@
 import javax.lang.model.element.TypeParameterElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.ExecutableType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.Types;
@@ -117,7 +119,7 @@
     private final TypeElement builderTypeElement;
     private ImmutableSet<ExecutableElement> toBuilderMethods;
     private ExecutableElement buildMethod;
-    private BuilderMethodClassifier classifier;
+    private BuilderMethodClassifier<?> classifier;
 
     Builder(TypeElement builderTypeElement) {
       this.builderTypeElement = builderTypeElement;
@@ -142,14 +144,23 @@
 
     @Override
     public Optional<ExecutableElement> buildMethod() {
-      return methodsIn(builderTypeElement.getEnclosedElements()).stream()
+      Types typeUtils = processingEnv.getTypeUtils();
+      DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderTypeElement.asType());
+      return MoreElements.getLocalAndInheritedMethods(
+              builderTypeElement, typeUtils, processingEnv.getElementUtils())
+          .stream()
           .filter(
               m ->
                   m.getSimpleName().contentEquals("build")
                       && !m.getModifiers().contains(Modifier.PRIVATE)
                       && !m.getModifiers().contains(Modifier.STATIC)
-                      && m.getParameters().isEmpty()
-                      && erasedTypeIs(m.getReturnType(), autoValueClass))
+                      && m.getParameters().isEmpty())
+          .filter(
+              m -> {
+                ExecutableType methodMirror =
+                    MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, m));
+                return erasedTypeIs(methodMirror.getReturnType(), autoValueClass);
+              })
           .findFirst();
     }
 
@@ -186,7 +197,8 @@
      * Finds any methods in the set that return the builder type. If the builder has type parameters
      * {@code <A, B>}, then the return type of the method must be {@code Builder<A, B>} with the
      * same parameter names. We enforce elsewhere that the names and bounds of the builder
-     * parameters must be the same as those of the @AutoValue class. Here's a correct example:
+     * parameters must be the same as those of the {@code @AutoValue} class. Here's a correct
+     * example:
      *
      * <pre>
      * {@code @AutoValue abstract class Foo<A extends Number, B> {
@@ -201,18 +213,25 @@
      * <p>We currently impose that there cannot be more than one such method.
      */
     ImmutableSet<ExecutableElement> toBuilderMethods(
-        Types typeUtils, Set<ExecutableElement> abstractMethods) {
+        Types typeUtils, TypeElement autoValueType, Set<ExecutableElement> abstractMethods) {
 
       List<String> builderTypeParamNames =
           builderTypeElement.getTypeParameters().stream()
               .map(e -> e.getSimpleName().toString())
               .collect(toList());
 
+      DeclaredType autoValueTypeMirror = MoreTypes.asDeclared(autoValueType.asType());
       ImmutableSet.Builder<ExecutableElement> methods = ImmutableSet.builder();
       for (ExecutableElement method : abstractMethods) {
-        if (builderTypeElement.equals(typeUtils.asElement(method.getReturnType()))) {
+        if (!method.getParameters().isEmpty()) {
+          continue;
+        }
+        ExecutableType methodMirror =
+            MoreTypes.asExecutable(typeUtils.asMemberOf(autoValueTypeMirror, method));
+        TypeMirror returnTypeMirror = methodMirror.getReturnType();
+        if (builderTypeElement.equals(typeUtils.asElement(returnTypeMirror))) {
           methods.add(method);
-          DeclaredType returnType = MoreTypes.asDeclared(method.getReturnType());
+          DeclaredType returnType = MoreTypes.asDeclared(returnTypeMirror);
           List<String> typeArguments =
               returnType.getTypeArguments().stream()
                   .filter(t -> t.getKind().equals(TypeKind.TYPEVAR))
@@ -237,11 +256,12 @@
       return builderMethods;
     }
 
-    void defineVars(
-        AutoValueTemplateVars vars,
+    void defineVarsForAutoValue(
+        AutoValueOrBuilderTemplateVars vars,
         ImmutableBiMap<ExecutableElement, String> getterToPropertyName) {
-      Iterable<ExecutableElement> builderMethods = abstractMethods(builderTypeElement);
-      boolean autoValueHasToBuilder = !toBuilderMethods.isEmpty();
+      Iterable<ExecutableElement> builderMethods =
+          abstractMethods(builderTypeElement, processingEnv);
+      boolean autoValueHasToBuilder = toBuilderMethods != null && !toBuilderMethods.isEmpty();
       ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType =
           TypeVariables.rewriteReturnTypes(
               processingEnv.getElementUtils(),
@@ -249,32 +269,40 @@
               getterToPropertyName.keySet(),
               autoValueClass,
               builderTypeElement);
-      Optional<BuilderMethodClassifier> optionalClassifier =
-          BuilderMethodClassifier.classify(
+      ImmutableMap.Builder<String, TypeMirror> rewrittenPropertyTypes = ImmutableMap.builder();
+      getterToPropertyType.forEach(
+          (getter, type) -> rewrittenPropertyTypes.put(getterToPropertyName.get(getter), type));
+      Optional<BuilderMethodClassifier<ExecutableElement>> optionalClassifier =
+          BuilderMethodClassifierForAutoValue.classify(
               builderMethods,
               errorReporter,
               processingEnv,
               autoValueClass,
               builderTypeElement,
               getterToPropertyName,
-              getterToPropertyType,
+              rewrittenPropertyTypes.build(),
               autoValueHasToBuilder);
       if (!optionalClassifier.isPresent()) {
         return;
       }
       for (ExecutableElement method : methodsIn(builderTypeElement.getEnclosedElements())) {
         if (method.getSimpleName().contentEquals("builder")
-                && method.getModifiers().contains(Modifier.STATIC)
-                && method.getAnnotationMirrors().isEmpty()) {
-          // For now we ignore methods with annotations, because for example we do want to allow
-          // Jackson's @JsonCreator.
+            && method.getModifiers().contains(Modifier.STATIC)
+            && method.getAnnotationMirrors().isEmpty()
+            && !(vars instanceof AutoBuilderTemplateVars)) {
+          // For now we don't warn for methods with annotations, because for example we do want to
+          // allow Jackson's @JsonCreator. We also don't warn if this is an @AutoBuilder.
           errorReporter.reportWarning(
               method,
               "[AutoValueBuilderInBuilder] Static builder() method should be in the containing"
                   + " class");
         }
       }
-      this.classifier = optionalClassifier.get();
+      defineVars(vars, optionalClassifier.get());
+    }
+
+    void defineVars(AutoValueOrBuilderTemplateVars vars, BuilderMethodClassifier<?> classifier) {
+      this.classifier = classifier;
       Set<ExecutableElement> buildMethods = classifier.buildMethods();
       if (buildMethods.size() != 1) {
         Set<? extends Element> errorElements =
@@ -282,8 +310,8 @@
         for (Element buildMethod : errorElements) {
           errorReporter.reportError(
               buildMethod,
-              "[AutoValueBuilderBuild] Builder must have a single no-argument method returning"
-                  + " %s%s",
+              "[AutoValueBuilderBuild] Builder must have a single no-argument method, typically"
+                  + " called build(), that returns %s%s",
               autoValueClass,
               typeParamsString());
         }
@@ -292,7 +320,8 @@
       this.buildMethod = Iterables.getOnlyElement(buildMethods);
       vars.builderIsInterface = builderTypeElement.getKind() == ElementKind.INTERFACE;
       vars.builderTypeName = TypeSimplifier.classNameOf(builderTypeElement);
-      vars.builderFormalTypes = TypeEncoder.formalTypeParametersString(builderTypeElement);
+      vars.builderFormalTypes =
+          TypeEncoder.typeParametersString(builderTypeElement.getTypeParameters());
       vars.builderActualTypes = TypeSimplifier.actualTypeParametersString(builderTypeElement);
       vars.buildMethod = Optional.of(new SimpleMethod(buildMethod));
       vars.builderGetters = classifier.builderGetters();
@@ -301,15 +330,12 @@
       vars.builderPropertyBuilders =
           ImmutableMap.copyOf(classifier.propertyNameToPropertyBuilder());
 
-      Set<Property> required = new LinkedHashSet<>(vars.props);
-      for (Property property : vars.props) {
-        if (property.isNullable()
-            || property.getOptional() != null
-            || vars.builderPropertyBuilders.containsKey(property.getName())) {
-          required.remove(property);
-        }
-      }
-      vars.builderRequiredProperties = ImmutableSet.copyOf(required);
+      vars.builderRequiredProperties =
+          vars.props.stream()
+              .filter(p -> !p.isNullable())
+              .filter(p -> p.getOptional() == null)
+              .filter(p -> !vars.builderPropertyBuilders.containsKey(p.getName()))
+              .collect(toImmutableSet());
     }
   }
 
@@ -325,6 +351,7 @@
    * five) then {@code Optional<T>} can be the corresponding boxed type.
    */
   public static class PropertyGetter {
+    private final String name;
     private final String access;
     private final String type;
     private final Optionalish optional;
@@ -342,11 +369,17 @@
      *     {@code Optional<T>} rather than {@code T}, as explained above.
      */
     PropertyGetter(ExecutableElement method, String type, Optionalish optional) {
+      this.name = method.getSimpleName().toString();
       this.access = SimpleMethod.access(method);
       this.type = type;
       this.optional = optional;
     }
 
+    // Not accessed from templates so doesn't have to be public.
+    String getName() {
+      return name;
+    }
+
     public String getAccess() {
       return access;
     }
@@ -361,8 +394,8 @@
   }
 
   /**
-   * Specifies how to copy a parameter value into the target type. This might be the identity, or
-   * it might be something like {@code ImmutableList.of(...)} or {@code Optional.ofNullable(...)}.
+   * Specifies how to copy a parameter value into the target type. This might be the identity, or it
+   * might be something like {@code ImmutableList.of(...)} or {@code Optional.ofNullable(...)}.
    */
   static class Copier {
     static final Copier IDENTITY = acceptingNull(x -> x);
@@ -452,7 +485,7 @@
       return nullableAnnotation;
     }
 
-    public String copy(AutoValueProcessor.Property property) {
+    public String copy(Property property) {
       String copy = copier.copy.apply(property.toString());
       if (property.isNullable() && !copier.acceptsNull) {
         copy = String.format("(%s == null ? null : %s)", property, copy);
@@ -487,19 +520,24 @@
   }
 
   private static boolean sameTypeParameters(TypeElement a, TypeElement b) {
-    int nTypeParameters = a.getTypeParameters().size();
-    if (nTypeParameters != b.getTypeParameters().size()) {
+    return sameTypeParameters(a.getTypeParameters(), b.getTypeParameters());
+  }
+
+  static boolean sameTypeParameters(
+      List<? extends TypeParameterElement> aParams, List<? extends TypeParameterElement> bParams) {
+    int nTypeParameters = aParams.size();
+    if (nTypeParameters != bParams.size()) {
       return false;
     }
     for (int i = 0; i < nTypeParameters; i++) {
-      TypeParameterElement aParam = a.getTypeParameters().get(i);
-      TypeParameterElement bParam = b.getTypeParameters().get(i);
+      TypeParameterElement aParam = aParams.get(i);
+      TypeParameterElement bParam = bParams.get(i);
       if (!aParam.getSimpleName().equals(bParam.getSimpleName())) {
         return false;
       }
-      Set<TypeMirror> autoValueBounds = new TypeMirrorSet(aParam.getBounds());
-      Set<TypeMirror> builderBounds = new TypeMirrorSet(bParam.getBounds());
-      if (!autoValueBounds.equals(builderBounds)) {
+      Set<TypeMirror> aBounds = new TypeMirrorSet(aParam.getBounds());
+      Set<TypeMirror> bBounds = new TypeMirrorSet(bParam.getBounds());
+      if (!aBounds.equals(bBounds)) {
         return false;
       }
     }
@@ -512,7 +550,8 @@
    * then this method will throw an exception that will cause us to defer processing of the current
    * class until a later annotation-processing round.
    */
-  private ImmutableSet<ExecutableElement> abstractMethods(TypeElement typeElement) {
+  static ImmutableSet<ExecutableElement> abstractMethods(
+      TypeElement typeElement, ProcessingEnvironment processingEnv) {
     Set<ExecutableElement> methods =
         getLocalAndInheritedMethods(
             typeElement, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
diff --git a/value/src/main/java/com/google/auto/value/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/processor/ClassNames.java
index d23cfd2..76c5071 100644
--- a/value/src/main/java/com/google/auto/value/processor/ClassNames.java
+++ b/value/src/main/java/com/google/auto/value/processor/ClassNames.java
@@ -28,5 +28,7 @@
   static final String AUTO_ONE_OF_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoOneOf";
   static final String AUTO_VALUE_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoValue";
   static final String AUTO_VALUE_BUILDER_NAME = AUTO_VALUE_NAME + ".Builder";
+  static final String AUTO_BUILDER_NAME = AUTO_VALUE_PACKAGE_NAME + "AutoBuilder";
   static final String COPY_ANNOTATIONS_NAME = AUTO_VALUE_NAME + ".CopyAnnotations";
+  static final String KOTLIN_METADATA_NAME = "kotlin.Metadata";
 }
diff --git a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
index e2a3d83..3ec9a0e 100644
--- a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
+++ b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java
@@ -81,9 +81,12 @@
    * @param e the element to which it pertains
    * @param format the format string for the text of the error
    * @param args arguments for the format string
+   * @return This method does not return, but is declared with an exception return type so you
+   *     can write {@code throw abortWithError(...)} to tell the compiler that.
+   * @throws AbortProcessingException always
    */
   @FormatMethod
-  void abortWithError(Element e, String format, Object... args) {
+  AbortProcessingException abortWithError(Element e, String format, Object... args) {
     reportError(e, format, args);
     throw new AbortProcessingException();
   }
diff --git a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
index 7220754..fae4e09 100644
--- a/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
+++ b/value/src/main/java/com/google/auto/value/processor/GwtCompatibility.java
@@ -62,9 +62,7 @@
         annotationArguments = "";
       } else {
         annotationArguments =
-            getElementValues(annotation)
-                .entrySet()
-                .stream()
+            getElementValues(annotation).entrySet().stream()
                 .map(e -> e.getKey().getSimpleName() + " = " + e.getValue())
                 .collect(joining(", ", "(", ")"));
       }
diff --git a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
index cf928d5..30ad092 100644
--- a/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
+++ b/value/src/main/java/com/google/auto/value/processor/GwtSerialization.java
@@ -18,6 +18,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.toList;
 
+import com.google.auto.value.processor.AutoValueishProcessor.GetterProperty;
 import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multimap;
@@ -80,12 +81,14 @@
    * com.example.AutoValue_Foo_CustomFieldSerializer.
    *
    * @param autoVars the template variables defined for this type.
+   * @param finalSubclass the simple name of the AutoValue class being generated, AutoValue_Foo
+   *     in the example.
    */
-  void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars) {
+  void maybeWriteGwtSerializer(AutoValueTemplateVars autoVars, String finalSubclass) {
     if (shouldWriteGwtSerializer()) {
       GwtTemplateVars vars = new GwtTemplateVars();
       vars.pkg = autoVars.pkg;
-      vars.subclass = autoVars.finalSubclass;
+      vars.subclass = finalSubclass;
       vars.formalTypes = autoVars.formalTypes;
       vars.actualTypes = autoVars.actualTypes;
       vars.useBuilder = !autoVars.builderTypeName.isEmpty();
@@ -95,7 +98,8 @@
       String className =
           (vars.pkg.isEmpty() ? "" : vars.pkg + ".") + vars.subclass + "_CustomFieldSerializer";
       vars.serializerClass = TypeSimplifier.simpleNameOf(className);
-      vars.props = autoVars.props.stream().map(Property::new).collect(toList());
+      vars.props =
+          autoVars.props.stream().map(p -> new Property((GetterProperty) p)).collect(toList());
       vars.classHashString = computeClassHash(autoVars.props, vars.pkg);
       String text = vars.toText();
       text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType());
@@ -104,10 +108,10 @@
   }
 
   public static class Property {
-    private final AutoValueProcessor.Property property;
+    private final GetterProperty property;
     private final boolean isCastingUnchecked;
 
-    Property(AutoValueProcessor.Property property) {
+    Property(GetterProperty property) {
       this.property = property;
       this.isCastingUnchecked = TypeSimplifier.isCastingUnchecked(property.getTypeMirror());
     }
@@ -242,14 +246,14 @@
           .printMessage(
               Diagnostic.Kind.WARNING, "Could not write generated class " + className + ": " + e);
       // A warning rather than an error for the reason explained in
-      // AutoValueOrOneOfProcessor.writeSourceFile.
+      // AutoValueishProcessor.writeSourceFile.
     }
   }
 
   // Compute a hash that is guaranteed to change if the names, types, or order of the fields
   // change. We use TypeEncoder so that we can get a defined string for types, since
   // TypeMirror.toString() isn't guaranteed to remain the same.
-  private String computeClassHash(Iterable<AutoValueProcessor.Property> props, String pkg) {
+  private String computeClassHash(Iterable<AutoValueishProcessor.Property> props, String pkg) {
     CRC32 crc = new CRC32();
     String encodedType = TypeEncoder.encode(type.asType()) + ":";
     String decodedType = TypeEncoder.decode(encodedType, processingEnv, "", null);
@@ -259,7 +263,7 @@
       decodedType = pkg + "." + decodedType;
     }
     crc.update(decodedType.getBytes(UTF_8));
-    for (AutoValueProcessor.Property prop : props) {
+    for (AutoValueishProcessor.Property prop : props) {
       String encodedProp = prop + ":" + TypeEncoder.encode(prop.getTypeMirror()) + ";";
       String decodedProp = TypeEncoder.decode(encodedProp, processingEnv, pkg, null);
       crc.update(decodedProp.getBytes(UTF_8));
diff --git a/value/src/main/java/com/google/auto/value/processor/Nullables.java b/value/src/main/java/com/google/auto/value/processor/Nullables.java
new file mode 100644
index 0000000..c07656f
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/Nullables.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import static java.util.stream.Collectors.toList;
+
+import com.google.auto.common.MoreTypes;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.IntersectionType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+import javax.lang.model.type.WildcardType;
+import javax.lang.model.util.SimpleTypeVisitor8;
+
+class Nullables {
+  /**
+   * If set to a non-empty string, defines which {@code @Nullable} type annotation should be used by
+   * default. If set to an empty string, does not insert {@code @Nullable} unless it is referenced
+   * in the {@code @AutoValue} methods. If unset, defaults to {@value #DEFAULT_NULLABLE}.
+   */
+  static final String NULLABLE_OPTION = "com.google.auto.value.NullableTypeAnnotation";
+
+  // We write this using .concat in order to hide it from rewriting rules.
+  private static final String DEFAULT_NULLABLE = "org".concat(".jspecify.nullness.Nullable");
+
+  private final Optional<AnnotationMirror> defaultNullable;
+
+  Nullables(ProcessingEnvironment processingEnv) {
+    // -Afoo without `=` sets "foo" to null in the getOptions() map.
+    String nullableOption =
+        Strings.nullToEmpty(
+            processingEnv.getOptions().getOrDefault(NULLABLE_OPTION, DEFAULT_NULLABLE));
+    this.defaultNullable =
+        (!nullableOption.isEmpty()
+                && processingEnv.getSourceVersion().ordinal() >= SourceVersion.RELEASE_8.ordinal())
+            ? Optional.ofNullable(processingEnv.getElementUtils().getTypeElement(nullableOption))
+                .map(t -> annotationMirrorOf(MoreTypes.asDeclared(t.asType())))
+            : Optional.empty();
+  }
+
+  private static AnnotationMirror annotationMirrorOf(DeclaredType annotationType) {
+    return new AnnotationMirror() {
+      @Override
+      public DeclaredType getAnnotationType() {
+        return annotationType;
+      }
+
+      @Override
+      public ImmutableMap<? extends ExecutableElement, ? extends AnnotationValue>
+          getElementValues() {
+        return ImmutableMap.of();
+      }
+    };
+  }
+
+  /**
+   * Returns the type of a {@code @Nullable} type-annotation, if one is found anywhere in the
+   * signatures of the given methods.
+   */
+  @VisibleForTesting
+  static Optional<AnnotationMirror> nullableMentionedInMethods(
+      Collection<ExecutableElement> methods) {
+    return methods.stream()
+        .flatMap(
+            method ->
+                Stream.concat(
+                    Stream.of(method.getReturnType()),
+                    method.getParameters().stream().map(Element::asType)))
+        .map(Nullables::nullableIn)
+        .filter(Optional::isPresent)
+        .findFirst()
+        .orElse(Optional.empty());
+  }
+
+  /**
+   * Returns the type of an appropriate {@code @Nullable} type-annotation, given a set of methods
+   * that are known to be in the same compilation as the code being generated. If one of those
+   * methods contains an appropriate {@code @Nullable} annotation on a parameter or return type,
+   * this method will return that. Otherwise, if the <a href="http://jspecify.org">JSpecify</a>
+   * {@code @Nullable} is available, this method will return it. Otherwise, this method will return
+   * an empty {@code Optional}.
+   */
+  Optional<AnnotationMirror> appropriateNullableGivenMethods(
+      Collection<ExecutableElement> methods) {
+    return nullableMentionedInMethods(methods).map(Optional::of).orElse(defaultNullable);
+  }
+
+  private static Optional<AnnotationMirror> nullableIn(TypeMirror type) {
+    return new NullableFinder().visit(type);
+  }
+
+  private static Optional<AnnotationMirror> nullableIn(
+      List<? extends AnnotationMirror> annotations) {
+    return annotations.stream()
+        .filter(a -> a.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable"))
+        .map(a -> (AnnotationMirror) a) // get rid of the pesky wildcard
+        .findFirst();
+  }
+
+  private static class NullableFinder extends SimpleTypeVisitor8<Optional<AnnotationMirror>, Void> {
+    private final TypeMirrorSet visiting = new TypeMirrorSet();
+
+    NullableFinder() {
+      super(Optional.empty());
+    }
+
+    // Primitives can't be @Nullable so we don't check that.
+
+    @Override
+    public Optional<AnnotationMirror> visitDeclared(DeclaredType t, Void unused) {
+      if (!visiting.add(t)) {
+        return Optional.empty();
+      }
+      return nullableIn(t.getAnnotationMirrors())
+          .map(Optional::of)
+          .orElseGet(() -> visitAll(t.getTypeArguments()));
+    }
+
+    @Override
+    public Optional<AnnotationMirror> visitTypeVariable(TypeVariable t, Void unused) {
+      if (!visiting.add(t)) {
+        return Optional.empty();
+      }
+      return nullableIn(t.getAnnotationMirrors())
+          .map(Optional::of)
+          .orElseGet(() -> visitAll(ImmutableList.of(t.getUpperBound(), t.getLowerBound())));
+    }
+
+    @Override
+    public Optional<AnnotationMirror> visitArray(ArrayType t, Void unused) {
+      return nullableIn(t.getAnnotationMirrors())
+          .map(Optional::of)
+          .orElseGet(() -> visit(t.getComponentType()));
+    }
+
+    @Override
+    public Optional<AnnotationMirror> visitWildcard(WildcardType t, Void unused) {
+      return nullableIn(t.getAnnotationMirrors())
+          .map(Optional::of)
+          .orElseGet(
+              () ->
+                  visitAll(
+                      Stream.of(t.getExtendsBound(), t.getSuperBound())
+                          .filter(Objects::nonNull)
+                          .collect(toList())));
+    }
+
+    @Override
+    public Optional<AnnotationMirror> visitIntersection(IntersectionType t, Void unused) {
+      return nullableIn(t.getAnnotationMirrors())
+          .map(Optional::of)
+          .orElseGet(() -> visitAll(t.getBounds()));
+    }
+
+    private Optional<AnnotationMirror> visitAll(List<? extends TypeMirror> types) {
+      return types.stream()
+          .map(this::visit)
+          .filter(Optional::isPresent)
+          .findFirst()
+          .orElse(Optional.empty());
+    }
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
index 2565cdd..5d0168e 100644
--- a/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/PropertyBuilderClassifier.java
@@ -15,17 +15,19 @@
  */
 package com.google.auto.value.processor;
 
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toMap;
+
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
-import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
-import javax.lang.model.element.AnnotationMirror;
+import java.util.function.Function;
+import java.util.function.Predicate;
 import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
@@ -51,25 +53,25 @@
   private final ErrorReporter errorReporter;
   private final Types typeUtils;
   private final Elements elementUtils;
-  private final BuilderMethodClassifier builderMethodClassifier;
-  private final ImmutableBiMap<ExecutableElement, String> getterToPropertyName;
-  private final ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType;
+  private final BuilderMethodClassifier<?> builderMethodClassifier;
+  private final Predicate<String> propertyIsNullable;
+  private final ImmutableMap<String, TypeMirror> propertyTypes;
   private final EclipseHack eclipseHack;
 
   PropertyBuilderClassifier(
       ErrorReporter errorReporter,
       Types typeUtils,
       Elements elementUtils,
-      BuilderMethodClassifier builderMethodClassifier,
-      ImmutableBiMap<ExecutableElement, String> getterToPropertyName,
-      ImmutableMap<ExecutableElement, TypeMirror> getterToPropertyType,
+      BuilderMethodClassifier<?> builderMethodClassifier,
+      Predicate<String> propertyIsNullable,
+      ImmutableMap<String, TypeMirror> propertyTypes,
       EclipseHack eclipseHack) {
     this.errorReporter = errorReporter;
     this.typeUtils = typeUtils;
     this.elementUtils = elementUtils;
     this.builderMethodClassifier = builderMethodClassifier;
-    this.getterToPropertyName = getterToPropertyName;
-    this.getterToPropertyType = getterToPropertyType;
+    this.propertyIsNullable = propertyIsNullable;
+    this.propertyTypes = propertyTypes;
     this.eclipseHack = eclipseHack;
   }
 
@@ -115,6 +117,14 @@
       return propertyBuilderMethod;
     }
 
+    /** The property builder method parameters, for example {@code Comparator<T> comparator} */
+    public String getPropertyBuilderMethodParameters() {
+      return propertyBuilderMethod.getParameters().stream()
+          .map(
+              parameter -> TypeEncoder.encode(parameter.asType()) + " " + parameter.getSimpleName())
+          .collect(joining(", "));
+    }
+
     public String getAccess() {
       return SimpleMethod.access(propertyBuilderMethod);
     }
@@ -206,8 +216,7 @@
     TypeElement barBuilderTypeElement = MoreTypes.asTypeElement(barBuilderTypeMirror);
     Map<String, ExecutableElement> barBuilderNoArgMethods = noArgMethodsOf(barBuilderTypeElement);
 
-    ExecutableElement barGetter = getterToPropertyName.inverse().get(property);
-    TypeMirror barTypeMirror = getterToPropertyType.get(barGetter);
+    TypeMirror barTypeMirror = propertyTypes.get(property);
     if (barTypeMirror.getKind() != TypeKind.DECLARED) {
       errorReporter.reportError(
           method,
@@ -216,10 +225,10 @@
           property);
       return Optional.empty();
     }
-    if (isNullable(barGetter)) {
+    if (propertyIsNullable.test(property)) {
       errorReporter.reportError(
-          barGetter,
-          "[AutoValueNullBuilder] Property %s has a property builder so it cannot be @Nullable",
+          method,
+          "[AutoValueNullBuilder] Property %s is @Nullable so it cannot have a property builder",
           property);
     }
     TypeElement barTypeElement = MoreTypes.asTypeElement(barTypeMirror);
@@ -251,8 +260,13 @@
       return Optional.empty();
     }
 
-    Optional<ExecutableElement> maybeBuilderMaker =
-        builderMaker(barNoArgMethods, barBuilderTypeElement);
+    Optional<ExecutableElement> maybeBuilderMaker;
+    if (method.getParameters().isEmpty()) {
+      maybeBuilderMaker = noArgBuilderMaker(barNoArgMethods, barBuilderTypeElement);
+    } else {
+      Map<String, ExecutableElement> barOneArgMethods = oneArgumentMethodsOf(barTypeElement);
+      maybeBuilderMaker = oneArgBuilderMaker(barOneArgMethods, barBuilderTypeElement);
+    }
     if (!maybeBuilderMaker.isPresent()) {
       errorReporter.reportError(
           method,
@@ -268,10 +282,14 @@
 
     String barBuilderType = TypeEncoder.encodeWithAnnotations(barBuilderTypeMirror);
     String rawBarType = TypeEncoder.encodeRaw(barTypeMirror);
+    String arguments =
+        method.getParameters().isEmpty()
+            ? "()"
+            : "(" + method.getParameters().get(0).getSimpleName() + ")";
     String initializer =
         (builderMaker.getKind() == ElementKind.CONSTRUCTOR)
-            ? "new " + barBuilderType + "()"
-            : rawBarType + "." + builderMaker.getSimpleName() + "()";
+            ? "new " + barBuilderType + arguments
+            : rawBarType + "." + builderMaker.getSimpleName() + arguments;
     String builtToBuilder = null;
     String copyAll = null;
     ExecutableElement toBuilder = barNoArgMethods.get("toBuilder");
@@ -324,41 +342,75 @@
   private static final ImmutableSet<String> BUILDER_METHOD_NAMES =
       ImmutableSet.of("naturalOrder", "builder", "newBuilder");
 
-  // (2) `BarBuilder must have a public no-arg constructor, or `Bar` must have a visible static
-  //      method `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`.
-  private Optional<ExecutableElement> builderMaker(
+  // (2) `BarBuilder` must have a public no-arg constructor, or `Bar` must have a visible static
+  //      method `naturalOrder(), `builder()`, or `newBuilder()` that returns `BarBuilder`; or,
+  //      if we have a foosBuilder(T) method, then `BarBuilder` must have a public constructor with
+  //      a single parameter assignable from T, or a visible static `builder(T)` method.
+  private Optional<ExecutableElement> noArgBuilderMaker(
       Map<String, ExecutableElement> barNoArgMethods, TypeElement barBuilderTypeElement) {
-    for (String builderMethodName : BUILDER_METHOD_NAMES) {
-      ExecutableElement method = barNoArgMethods.get(builderMethodName);
-      if (method != null
-          && method.getModifiers().contains(Modifier.STATIC)
-          && typeUtils.isSameType(
-              typeUtils.erasure(method.getReturnType()),
-              typeUtils.erasure(barBuilderTypeElement.asType()))) {
-        // TODO(emcmanus): check visibility. We don't want to require public for @AutoValue
-        // builders. By not checking visibility we risk accepting something as a builder maker
-        // and then failing when the generated code tries to call Bar.builder(). But the risk
-        // seems small.
-        return Optional.of(method);
-      }
+    return builderMaker(BUILDER_METHOD_NAMES, barNoArgMethods, barBuilderTypeElement, 0);
+  }
+
+  private static final ImmutableSet<String> ONE_ARGUMENT_BUILDER_METHOD_NAMES =
+      ImmutableSet.of("builder");
+
+  private Optional<ExecutableElement> oneArgBuilderMaker(
+      Map<String, ExecutableElement> barOneArgMethods, TypeElement barBuilderTypeElement) {
+
+    return builderMaker(
+        ONE_ARGUMENT_BUILDER_METHOD_NAMES, barOneArgMethods, barBuilderTypeElement, 1);
+  }
+
+  private Optional<ExecutableElement> builderMaker(
+      ImmutableSet<String> methodNamesToCheck,
+      Map<String, ExecutableElement> methods,
+      TypeElement barBuilderTypeElement,
+      int argumentCount) {
+    Optional<ExecutableElement> maybeMethod =
+        methodNamesToCheck.stream()
+            .map(methods::get)
+            .filter(Objects::nonNull)
+            .filter(method -> method.getModifiers().contains(Modifier.STATIC))
+            .filter(
+                method ->
+                    typeUtils.isSameType(
+                        typeUtils.erasure(method.getReturnType()),
+                        typeUtils.erasure(barBuilderTypeElement.asType())))
+            .findFirst();
+
+    if (maybeMethod.isPresent()) {
+      // TODO(emcmanus): check visibility. We don't want to require public for @AutoValue
+      // builders. By not checking visibility we risk accepting something as a builder maker
+      // and then failing when the generated code tries to call Bar.builder(). But the risk
+      // seems small.
+      return maybeMethod;
     }
-    return ElementFilter.constructorsIn(barBuilderTypeElement.getEnclosedElements())
-        .stream()
-        .filter(c -> c.getParameters().isEmpty())
+
+    return ElementFilter.constructorsIn(barBuilderTypeElement.getEnclosedElements()).stream()
+        .filter(c -> c.getParameters().size() == argumentCount)
         .filter(c -> c.getModifiers().contains(Modifier.PUBLIC))
         .findFirst();
   }
 
   private Map<String, ExecutableElement> noArgMethodsOf(TypeElement type) {
-    // Can't easily use ImmutableMap here because getAllMembers could return more than one method
-    // with the same name.
-    Map<String, ExecutableElement> methods = new LinkedHashMap<>();
-    for (ExecutableElement method : ElementFilter.methodsIn(elementUtils.getAllMembers(type))) {
-      if (method.getParameters().isEmpty() && !isStaticInterfaceMethodNotIn(method, type)) {
-        methods.put(method.getSimpleName().toString(), method);
-      }
-    }
-    return methods;
+    return methodsOf(type, 0);
+  }
+
+  private ImmutableMap<String, ExecutableElement> oneArgumentMethodsOf(TypeElement type) {
+    return methodsOf(type, 1);
+  }
+
+  private ImmutableMap<String, ExecutableElement> methodsOf(TypeElement type, int argumentCount) {
+    return ElementFilter.methodsIn(elementUtils.getAllMembers(type)).stream()
+        .filter(method -> method.getParameters().size() == argumentCount)
+        .filter(method -> !isStaticInterfaceMethodNotIn(method, type))
+        .collect(
+            collectingAndThen(
+                toMap(
+                    method -> method.getSimpleName().toString(),
+                    Function.identity(),
+                    (method1, method2) -> method1),
+                ImmutableMap::copyOf));
   }
 
   // Work around an Eclipse compiler bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=547185
@@ -399,18 +451,4 @@
             })
         .findFirst();
   }
-
-  private static boolean isNullable(ExecutableElement getter) {
-    List<List<? extends AnnotationMirror>> annotationLists =
-        ImmutableList.of(
-            getter.getAnnotationMirrors(), getter.getReturnType().getAnnotationMirrors());
-    for (List<? extends AnnotationMirror> annotations : annotationLists) {
-      for (AnnotationMirror annotation : annotations) {
-        if (annotation.getAnnotationType().asElement().getSimpleName().contentEquals("Nullable")) {
-          return true;
-        }
-      }
-    }
-    return false;
-  }
 }
diff --git a/value/src/main/java/com/google/auto/value/processor/Reformatter.java b/value/src/main/java/com/google/auto/value/processor/Reformatter.java
index 6926517..89b6d0d 100644
--- a/value/src/main/java/com/google/auto/value/processor/Reformatter.java
+++ b/value/src/main/java/com/google/auto/value/processor/Reformatter.java
@@ -15,6 +15,8 @@
  */
 package com.google.auto.value.processor;
 
+import com.google.common.base.CharMatcher;
+
 /**
  * Postprocessor that runs over the output of the template engine in order to make it look nicer.
  * Mostly, this involves removing surplus horizontal and vertical space.
@@ -22,6 +24,14 @@
  * @author emcmanus@google.com (Éamonn McManus)
  */
 class Reformatter {
+  /**
+   * Characters that might start a continuation line. Since Google Style requires splitting before
+   * operators, we expect that a continuation line will begin with one of these (or be inside
+   * parentheses). We've omitted {@code /} from this list for now since none of our templates splits
+   * before one, and otherwise we'd have to handle {@code //} and {@code /*}.
+   */
+  private static final CharMatcher OPERATORS = CharMatcher.anyOf("+-*%&|^<>=?:.").precomputed();
+
   static String fixup(String s) {
     StringBuilder out = new StringBuilder();
     JavaScanner scanner = new JavaScanner(s);
@@ -49,27 +59,43 @@
         case ' ':
           // This token is a string of consecutive spaces that is not at the start of a line.
           // Consecutive spaces at the start of a line are attached to the previous newline, and
-          // we don't expect the first line to start with spaces. So we are going to compress this
+          // we delete spaces at the start of the first line. So we are going to compress this
           // into just one space, and we are going to delete it entirely if it follows '(' or
           // precedes a newline or one of the punctuation characters here.
-          if (s.charAt(previous) != '(' && "\n.,;)".indexOf(s.charAt(end)) < 0) {
+          if (start > 0 && s.charAt(previous) != '(' && "\n.,;)".indexOf(s.charAt(end)) < 0) {
             out.append(' ');
           }
           continue;
         case '\n':
           // This token is a newline plus any following spaces (the indentation of the next line).
-          // If it is followed by something other than a newline then we will output it. Otherwise,
-          // it is part of a sequence of newlines but it is not the last one. If this is a context
-          // where we delete blank lines, or if this is not the first new line in the sequence, or
-          // if we are at the start of the file, we will delete this one. Otherwise we will output a
-          // single newline with no following indentation. Contexts where we delete blank lines are
-          // inside parentheses or inside more than one set of braces.
+          // If it is followed by something other than a newline then we will output the
+          // newline, and replace the following spaces by our computed indentation. Otherwise, the
+          // token is part of a sequence of newlines but it is not the last one. If this is a
+          // context where we delete blank lines, or if this is not the first new line in the
+          // sequence, or if we are at the start of the file, we will delete this one. Otherwise we
+          // will output a single newline with no following indentation. Contexts where we delete
+          // blank lines are inside parentheses or inside more than one set of braces.
           if (end < len && s.charAt(end) != '\n') {
-            if (out.length() == 0) {
-              // Omit newlines at the very start of the file.
-              start++;
+            // Omit newlines at the very start of the file. Also delete newline+indent between
+            // ( and ), since that shows up in some places where we output one parameter per line,
+            // when there are no parameters.
+            char prev = s.charAt(previous);
+            char next = s.charAt(end);
+            if (out.length() > 0 && (prev != '(' || next != ')')) {
+              out.append('\n');
+              // Replace any space after the newline with our computed indentation. The algorithm
+              // here is simplistic but works OK for our current templates.
+              int indent = braces * 2;
+              if (parens > 0 || OPERATORS.matches(next)) {
+                indent += 4;
+              } else if (next == '}') {
+                indent -= 2;
+              }
+              for (int i = 0; i < indent; i++) {
+                out.append(' ');
+              }
             }
-            break; // Output the newline and its following indentation.
+            continue;
           }
           if (parens == 0 && braces < 2 && s.charAt(previous) != '\n' && out.length() > 0) {
             out.append('\n');
@@ -82,4 +108,6 @@
     }
     return out.toString();
   }
+
+  private Reformatter() {}
 }
diff --git a/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java b/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java
index 0351023..c144bc0 100644
--- a/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java
+++ b/value/src/main/java/com/google/auto/value/processor/SimpleMethod.java
@@ -15,6 +15,8 @@
  */
 package com.google.auto.value.processor;
 
+import static java.util.stream.Collectors.joining;
+
 import java.util.Set;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
@@ -31,10 +33,12 @@
 public final class SimpleMethod {
   private final String access;
   private final String name;
+  private final String throwsString;
 
   SimpleMethod(ExecutableElement method) {
     this.access = access(method);
     this.name = method.getSimpleName().toString();
+    this.throwsString = throwsString(method);
   }
 
   public String getAccess() {
@@ -45,6 +49,10 @@
     return name;
   }
 
+  public String getThrows() {
+    return throwsString;
+  }
+
   /**
    * Returns an appropriate string to be used in code for the access specification of the given
    * method. This will be {@code public} or {@code protected} followed by a space, or the empty
@@ -60,4 +68,12 @@
       return "";
     }
   }
+
+  private static String throwsString(ExecutableElement method) {
+    if (method.getThrownTypes().isEmpty()) {
+      return "";
+    }
+    return "throws "
+        + method.getThrownTypes().stream().map(TypeEncoder::encode).collect(joining(", "));
+  }
 }
diff --git a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java
index 55f9ae4..23f6674 100644
--- a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java
+++ b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java
@@ -15,20 +15,23 @@
  */
 package com.google.auto.value.processor;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Streams;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URL;
 import java.net.URLConnection;
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import java.util.ServiceConfigurationError;
+import java.util.regex.Pattern;
 
 /**
  * A replacement for {@link java.util.ServiceLoader} that avoids certain long-standing bugs. This
@@ -43,6 +46,11 @@
   private SimpleServiceLoader() {}
 
   public static <T> ImmutableList<T> load(Class<? extends T> service, ClassLoader loader) {
+    return load(service, loader, Optional.empty());
+  }
+
+  public static <T> ImmutableList<T> load(
+      Class<? extends T> service, ClassLoader loader, Optional<Pattern> allowedMissingClasses) {
     String resourceName = "META-INF/services/" + service.getName();
     List<URL> resourceUrls;
     try {
@@ -50,49 +58,64 @@
     } catch (IOException e) {
       throw new ServiceConfigurationError("Could not look up " + resourceName, e);
     }
-    ImmutableList.Builder<T> providers = ImmutableList.builder();
+    ImmutableSet.Builder<Class<? extends T>> providerClasses = ImmutableSet.builder();
     for (URL resourceUrl : resourceUrls) {
       try {
-        providers.addAll(providersFromUrl(resourceUrl, service, loader));
+        providerClasses.addAll(
+            providerClassesFromUrl(resourceUrl, service, loader, allowedMissingClasses));
       } catch (IOException e) {
         throw new ServiceConfigurationError("Could not read " + resourceUrl, e);
       }
     }
+    ImmutableList.Builder<T> providers = ImmutableList.builder();
+    for (Class<? extends T> providerClass : providerClasses.build()) {
+      try {
+        T provider = providerClass.getConstructor().newInstance();
+        providers.add(provider);
+      } catch (ReflectiveOperationException e) {
+        throw new ServiceConfigurationError("Could not construct " + providerClass.getName(), e);
+      }
+    }
     return providers.build();
   }
 
-  private static <T> ImmutableList<T> providersFromUrl(
-      URL resourceUrl, Class<T> service, ClassLoader loader) throws IOException {
-    ImmutableList.Builder<T> providers = ImmutableList.builder();
+  private static <T> ImmutableSet<Class<? extends T>> providerClassesFromUrl(
+      URL resourceUrl,
+      Class<? extends T> service,
+      ClassLoader loader,
+      Optional<Pattern> allowedMissingClasses)
+      throws IOException {
+    ImmutableSet.Builder<Class<? extends T>> providerClasses = ImmutableSet.builder();
     URLConnection urlConnection = resourceUrl.openConnection();
     urlConnection.setUseCaches(false);
+    List<String> lines;
     try (InputStream in = urlConnection.getInputStream();
-        BufferedReader reader =
-            new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
-      for (String line : reader.lines().collect(toList())) {
-        Optional<String> maybeClassName = parseClassName(line);
-        if (maybeClassName.isPresent()) {
-          String className = maybeClassName.get();
-          Class<?> c;
-          try {
-            c = Class.forName(className, false, loader);
-          } catch (ClassNotFoundException e) {
-            throw new ServiceConfigurationError("Could not load " + className, e);
-          }
-          if (!service.isAssignableFrom(c)) {
-            throw new ServiceConfigurationError(
-                "Class " + className + " is not assignable to " + service.getName());
-          }
-          try {
-            Object provider = c.getConstructor().newInstance();
-            providers.add(service.cast(provider));
-          } catch (ReflectiveOperationException e) {
-            throw new ServiceConfigurationError("Could not construct " + className, e);
-          }
-        }
-      }
-      return providers.build();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(in, UTF_8))) {
+      lines = reader.lines().collect(toList());
     }
+    List<String> classNames =
+        lines.stream()
+            .map(SimpleServiceLoader::parseClassName)
+            .flatMap(Streams::stream)
+            .collect(toList());
+    for (String className : classNames) {
+      Class<?> c;
+      try {
+        c = Class.forName(className, false, loader);
+      } catch (ClassNotFoundException e) {
+        if (allowedMissingClasses.isPresent()
+            && allowedMissingClasses.get().matcher(className).matches()) {
+          continue;
+        }
+        throw new ServiceConfigurationError("Could not load " + className, e);
+      }
+      if (!service.isAssignableFrom(c)) {
+        throw new ServiceConfigurationError(
+            "Class " + className + " is not assignable to " + service.getName());
+      }
+      providerClasses.add(c.asSubclass(service));
+    }
+    return providerClasses.build();
   }
 
   private static Optional<String> parseClassName(String line) {
diff --git a/value/src/main/java/com/google/auto/value/processor/TemplateVars.java b/value/src/main/java/com/google/auto/value/processor/TemplateVars.java
index 235f0ad..d9e3337 100644
--- a/value/src/main/java/com/google/auto/value/processor/TemplateVars.java
+++ b/value/src/main/java/com/google/auto/value/processor/TemplateVars.java
@@ -99,7 +99,7 @@
     return parsedTemplate().evaluate(vars);
   }
 
-  private Map<String, Object> toVars() {
+  private ImmutableMap<String, Object> toVars() {
     Map<String, Object> vars = new TreeMap<>();
     for (Field field : fields) {
       Object value = fieldValue(field, this);
@@ -114,6 +114,11 @@
     return ImmutableMap.copyOf(vars);
   }
 
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + toVars();
+  }
+
   static Template parsedTemplateForResource(String resourceName) {
     try {
       return Template.parseFrom(resourceName, TemplateVars::readerFromResource);
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
index d1bbbab..81968a5 100644
--- a/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
+++ b/value/src/main/java/com/google/auto/value/processor/TypeEncoder.java
@@ -22,10 +22,12 @@
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import com.google.auto.value.processor.MissingTypes.MissingTypeException;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.util.List;
 import java.util.OptionalInt;
 import java.util.Set;
+import java.util.function.Function;
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.TypeElement;
@@ -97,20 +99,36 @@
    * covers the details of annotation encoding.
    */
   static String encodeWithAnnotations(TypeMirror type) {
-    return encodeWithAnnotations(type, ImmutableSet.of());
+    return encodeWithAnnotations(type, ImmutableList.of(), ImmutableSet.of());
   }
 
   /**
    * Encodes the given type and its type annotations. The class comment for {@link TypeEncoder}
    * covers the details of annotation encoding.
    *
+   * @param extraAnnotations additional type annotations to include with the type
    * @param excludedAnnotationTypes annotations not to include in the encoding. For example, if
    *     {@code com.example.Nullable} is in this set then the encoding will not include this
    *     {@code @Nullable} annotation.
    */
-  static String encodeWithAnnotations(TypeMirror type, Set<TypeMirror> excludedAnnotationTypes) {
+  static String encodeWithAnnotations(
+      TypeMirror type,
+      ImmutableList<AnnotationMirror> extraAnnotations,
+      Set<TypeMirror> excludedAnnotationTypes) {
     StringBuilder sb = new StringBuilder();
-    return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes).visit2(type, sb).toString();
+    // A function that is equivalent to t.getAnnotationMirrors() except when the t in question is
+    // our starting type. In that case we also add extraAnnotations to the result.
+    Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations =
+        t ->
+            (t == type)
+                ? ImmutableList.<AnnotationMirror>builder()
+                    .addAll(t.getAnnotationMirrors())
+                    .addAll(extraAnnotations)
+                    .build()
+                : t.getAnnotationMirrors();
+    return new AnnotatedEncodingTypeVisitor(excludedAnnotationTypes, getTypeAnnotations)
+        .visit2(type, sb)
+        .toString();
   }
 
   /**
@@ -145,9 +163,11 @@
   }
 
   /**
-   * Returns the formal type parameters of the given type. If we have {@code @AutoValue abstract
-   * class Foo<T extends SomeClass>} then this method will return an encoding of {@code <T extends
-   * SomeClass>} for {@code Foo}. Likewise it will return an encoding of the angle-bracket part of:
+   * Returns a string representing the given type parameters as they would appear in a class
+   * declaration. For example, if we have {@code @AutoValue abstract
+   * class Foo<T extends SomeClass>} then if we call {@link TypeElement#getTypeParameters()} on
+   * the representation of {@code Foo}, this method will return an encoding of {@code <T extends
+   * SomeClass>}. Likewise it will return an encoding of the angle-bracket part of:
    * <br>
    * {@code Foo<SomeClass>}<br>
    * {@code Foo<T extends Number>}<br>
@@ -161,8 +181,7 @@
    * {@code <E extends `java.lang.Enum`<E>>}<br>
    * {@code <K, V extends `java.lang.Comparable`<? extends K>>}.
    */
-  static String formalTypeParametersString(TypeElement type) {
-    List<? extends TypeParameterElement> typeParameters = type.getTypeParameters();
+  static String typeParametersString(List<? extends TypeParameterElement> typeParameters) {
     if (typeParameters.isEmpty()) {
       return "";
     } else {
@@ -308,9 +327,13 @@
    */
   private static class AnnotatedEncodingTypeVisitor extends EncodingTypeVisitor {
     private final Set<TypeMirror> excludedAnnotationTypes;
+    private final Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations;
 
-    AnnotatedEncodingTypeVisitor(Set<TypeMirror> excludedAnnotationTypes) {
+    AnnotatedEncodingTypeVisitor(
+        Set<TypeMirror> excludedAnnotationTypes,
+        Function<TypeMirror, List<? extends AnnotationMirror>> getTypeAnnotations) {
       this.excludedAnnotationTypes = excludedAnnotationTypes;
+      this.getTypeAnnotations = getTypeAnnotations;
     }
 
     private void appendAnnotationsWithExclusions(
@@ -330,7 +353,7 @@
 
     @Override
     public StringBuilder visitPrimitive(PrimitiveType type, StringBuilder sb) {
-      appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
+      appendAnnotationsWithExclusions(getTypeAnnotations.apply(type), sb);
       // We can't just append type.toString(), because that will also have the annotation, but
       // without encoding.
       return sb.append(type.getKind().toString().toLowerCase());
@@ -338,7 +361,7 @@
 
     @Override
     public StringBuilder visitTypeVariable(TypeVariable type, StringBuilder sb) {
-      appendAnnotationsWithExclusions(type.getAnnotationMirrors(), sb);
+      appendAnnotationsWithExclusions(getTypeAnnotations.apply(type), sb);
       return sb.append(type.asElement().getSimpleName());
     }
 
@@ -350,7 +373,7 @@
     @Override
     public StringBuilder visitArray(ArrayType type, StringBuilder sb) {
       visit2(type.getComponentType(), sb);
-      List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
+      List<? extends AnnotationMirror> annotationMirrors = getTypeAnnotations.apply(type);
       if (!annotationMirrors.isEmpty()) {
         sb.append(" ");
         appendAnnotationsWithExclusions(annotationMirrors, sb);
@@ -360,7 +383,7 @@
 
     @Override
     public StringBuilder visitDeclared(DeclaredType type, StringBuilder sb) {
-      List<? extends AnnotationMirror> annotationMirrors = type.getAnnotationMirrors();
+      List<? extends AnnotationMirror> annotationMirrors = getTypeAnnotations.apply(type);
       if (annotationMirrors.isEmpty()) {
         super.visitDeclared(type, sb);
       } else {
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
index 290e59e..e7bab77 100644
--- a/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
+++ b/value/src/main/java/com/google/auto/value/processor/TypeSimplifier.java
@@ -140,8 +140,7 @@
     if (typeParameters.isEmpty()) {
       return "";
     } else {
-      return typeParameters
-          .stream()
+      return typeParameters.stream()
           .map(e -> e.getSimpleName().toString())
           .collect(joining(", ", "<", ">"));
     }
@@ -264,8 +263,7 @@
    * {@code Map.Entry} everywhere rather than {@code Entry}.
    */
   private static Set<TypeMirror> topLevelTypes(Types typeUtil, Set<TypeMirror> types) {
-    return types
-        .stream()
+    return types.stream()
         .map(typeMirror -> MoreElements.asType(typeUtil.asElement(typeMirror)))
         .map(typeElement -> topLevelType(typeElement).asType())
         .collect(toCollection(TypeMirrorSet::new));
diff --git a/value/src/main/java/com/google/auto/value/processor/TypeVariables.java b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java
index d664ff4..ae27f91 100644
--- a/value/src/main/java/com/google/auto/value/processor/TypeVariables.java
+++ b/value/src/main/java/com/google/auto/value/processor/TypeVariables.java
@@ -15,6 +15,8 @@
  */
 package com.google.auto.value.processor;
 
+import static com.google.auto.common.MoreStreams.toImmutableMap;
+
 import com.google.auto.common.MoreElements;
 import com.google.auto.common.MoreTypes;
 import com.google.common.base.Equivalence;
@@ -24,6 +26,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
@@ -38,9 +41,7 @@
 import javax.lang.model.util.SimpleTypeVisitor8;
 import javax.lang.model.util.Types;
 
-/**
- * Methods for handling type variables.
- */
+/** Methods for handling type variables. */
 final class TypeVariables {
   private TypeVariables() {}
 
@@ -60,13 +61,13 @@
    * }
    * </pre>
    *
-   * We want to be able to check that the parameter type of {@code setFoo} is the same as the
-   * return type of {@code getFoo}. But in fact it isn't, because the {@code T} of {@code Foo<T>}
-   * is not the same as the {@code T} of {@code Foo.Builder<T>}. So we create a parallel
-   * {@code Foo<T>} where the {@code T} <i>is</i> the one from {@code Foo.Builder<T>}. That way the
-   * types do correspond. This method then returns the return types of the given methods as they
-   * appear in that parallel class, meaning the type given for {@code getFoo()} is the {@code T} of
-   * {@code Foo.Builder<T>}.
+   * We want to be able to check that the parameter type of {@code setFoo} is the same as the return
+   * type of {@code getFoo}. But in fact it isn't, because the {@code T} of {@code Foo<T>} is not
+   * the same as the {@code T} of {@code Foo.Builder<T>}. So we create a parallel {@code Foo<T>}
+   * where the {@code T} <i>is</i> the one from {@code Foo.Builder<T>}. That way the types do
+   * correspond. This method then returns the return types of the given methods as they appear in
+   * that parallel class, meaning the type given for {@code getFoo()} is the {@code T} of {@code
+   * Foo.Builder<T>}.
    *
    * <p>We do the rewrite this way around (applying the type parameter from {@code Foo.Builder} to
    * {@code Foo}) because if we hit one of the historical Eclipse bugs with {@link Types#asMemberOf}
@@ -101,14 +102,12 @@
     }
     DeclaredType parallelSource = typeUtils.getDeclaredType(sourceType, targetTypeParameterMirrors);
     return methods.stream()
-        .collect(
-            ImmutableMap.toImmutableMap(
-                m -> m, m -> eclipseHack.methodReturnType(m, parallelSource)));
+        .collect(toImmutableMap(m -> m, m -> eclipseHack.methodReturnType(m, parallelSource)));
   }
 
   /**
-   * Tests whether a given parameter can be given to a static method like
-   * {@code ImmutableMap.copyOf} to produce a value that can be assigned to the given target type.
+   * Tests whether a given parameter can be given to a static method like {@code
+   * ImmutableMap.copyOf} to produce a value that can be assigned to the given target type.
    *
    * <p>For example, suppose we have this method in {@code ImmutableMap}:<br>
    * {@code static <K, V> ImmutableMap<K, V> copyOf(Map<? extends K, ? extends V>)}<br>
@@ -122,14 +121,14 @@
    * We will infer {@code K=String}, {@code V=Number} based on the target type, and then rewrite the
    * formal parameter type from<br>
    * {@code Map<? extends K, ? extends V>} to<br>
-   * {@code Map<? extends String, ? extends Number>}. Then we can check whether
-   * {@code actualParameter} is assignable to that.
+   * {@code Map<? extends String, ? extends Number>}. Then we can check whether {@code
+   * actualParameter} is assignable to that.
    *
    * <p>The logic makes some simplifying assumptions, which are met for the {@code copyOf} and
    * {@code of} methods that we use this for. The method must be static, it must have exactly one
    * parameter, and it must have type parameters without bounds that are the same as the type
-   * parameters of its return type. We can see that these assumptions are met for the
-   * {@code ImmutableMap.copyOf} example above.
+   * parameters of its return type. We can see that these assumptions are met for the {@code
+   * ImmutableMap.copyOf} example above.
    */
   static boolean canAssignStaticMethodResult(
       ExecutableElement method,
@@ -152,8 +151,10 @@
       TypeVariable v = MoreTypes.asTypeVariable(typeParameters.get(i).asType());
       typeVariables.put(MoreTypes.equivalence().wrap(v), targetTypeArguments.get(i));
     }
+    Function<TypeVariable, TypeMirror> substitute =
+        v -> typeVariables.get(MoreTypes.equivalence().wrap(v));
     TypeMirror formalParameterType = method.getParameters().get(0).asType();
-    SubstitutionVisitor substitutionVisitor = new SubstitutionVisitor(typeVariables, typeUtils);
+    SubstitutionVisitor substitutionVisitor = new SubstitutionVisitor(substitute, typeUtils);
     TypeMirror substitutedParameterType = substitutionVisitor.visit(formalParameterType, null);
     if (substitutedParameterType.getKind().equals(TypeKind.WILDCARD)) {
       // If the target type is Optional<? extends Foo> then <T> T Optional.of(T) will give us
@@ -167,31 +168,38 @@
     return typeUtils.isAssignable(actualParameterType, substitutedParameterType);
   }
 
+  static TypeMirror substituteTypeVariables(
+      TypeMirror input, Function<TypeVariable, TypeMirror> substitute, Types typeUtils) {
+    SubstitutionVisitor substitutionVisitor = new SubstitutionVisitor(substitute, typeUtils);
+    return substitutionVisitor.visit(input, null);
+  }
+
   /**
    * Rewrites types such that references to type variables in the given map are replaced by the
    * values of those variables.
    */
   private static class SubstitutionVisitor extends SimpleTypeVisitor8<TypeMirror, Void> {
-    private final Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables;
+    private final Function<TypeVariable, TypeMirror> substitute;
     private final Types typeUtils;
 
-    SubstitutionVisitor(
-        Map<Equivalence.Wrapper<TypeVariable>, TypeMirror> typeVariables,
-        Types typeUtils) {
-      this.typeVariables = ImmutableMap.copyOf(typeVariables);
+    SubstitutionVisitor(Function<TypeVariable, TypeMirror> substitute, Types typeUtils) {
+      this.substitute = substitute;
       this.typeUtils = typeUtils;
     }
 
-    @Override protected TypeMirror defaultAction(TypeMirror t, Void p) {
+    @Override
+    protected TypeMirror defaultAction(TypeMirror t, Void p) {
       return t;
     }
 
-    @Override public TypeMirror visitTypeVariable(TypeVariable t, Void p) {
-      TypeMirror substituted = typeVariables.get(MoreTypes.equivalence().wrap(t));
+    @Override
+    public TypeMirror visitTypeVariable(TypeVariable t, Void p) {
+      TypeMirror substituted = substitute.apply(t);
       return (substituted == null) ? t : substituted;
     }
 
-    @Override public TypeMirror visitDeclared(DeclaredType t, Void p) {
+    @Override
+    public TypeMirror visitDeclared(DeclaredType t, Void p) {
       List<? extends TypeMirror> typeArguments = t.getTypeArguments();
       TypeMirror[] substitutedTypeArguments = new TypeMirror[typeArguments.size()];
       for (int i = 0; i < typeArguments.size(); i++) {
@@ -201,7 +209,8 @@
           MoreElements.asType(t.asElement()), substitutedTypeArguments);
     }
 
-    @Override public TypeMirror visitWildcard(WildcardType t, Void p) {
+    @Override
+    public TypeMirror visitWildcard(WildcardType t, Void p) {
       TypeMirror ext = visitOrNull(t.getExtendsBound());
       if (ext != null && ext.getKind().equals(TypeKind.WILDCARD)) {
         // An example of where this happens is if we have this method in ImmutableSet:
@@ -216,7 +225,8 @@
       return typeUtils.getWildcardType(ext, visitOrNull(t.getSuperBound()));
     }
 
-    @Override public TypeMirror visitArray(ArrayType t, Void p) {
+    @Override
+    public TypeMirror visitArray(ArrayType t, Void p) {
       TypeMirror comp = visit(t.getComponentType());
       if (comp.getKind().equals(TypeKind.WILDCARD)) {
         // An example of where this happens is if we have this method in ImmutableSet:
diff --git a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm
index a953eda..5cdad62 100644
--- a/value/src/main/java/com/google/auto/value/processor/autoannotation.vm
+++ b/value/src/main/java/com/google/auto/value/processor/autoannotation.vm
@@ -45,7 +45,8 @@
 #else
 // Generated by com.google.auto.value.processor.AutoAnnotationProcessor
 #end
-final class $className implements $annotationName {
+final class $className implements $annotationName, `java.io.Serializable` {
+  private static final long serialVersionUID = ${serialVersionUID}L;
 
 ## Fields
 
@@ -212,7 +213,7 @@
 #end
 
   @`java.lang.Override`
-  public boolean equals(Object o) {
+  public boolean equals($equalsParameterType o) {
     if (o == this) {
       return true;
     }
diff --git a/value/src/main/java/com/google/auto/value/processor/autobuilder.vm b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
new file mode 100644
index 0000000..01f61b9
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/autobuilder.vm
@@ -0,0 +1,41 @@
+## Copyright 2014 Google LLC
+##
+## 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.
+
+## Template for each generated AutoValue_Foo class.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($pkg, $props, and so on) are defined by the fields of AutoBuilderTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+
+#if (!$pkg.empty)
+package $pkg;
+#end
+
+## The following line will be replaced by the required imports during post-processing.
+`import`
+
+#if (!$generated.empty)
+@${generated}("com.google.auto.value.processor.AutoBuilderProcessor")
+#else
+// Generated by com.google.auto.value.processor.AutoBuilderProcessor
+#end
+#set($autoBuilder = true)
+#parse("builder.vm")
diff --git a/value/src/main/java/com/google/auto/value/processor/autooneof.vm b/value/src/main/java/com/google/auto/value/processor/autooneof.vm
index 0249d9e..328f6a5 100644
--- a/value/src/main/java/com/google/auto/value/processor/autooneof.vm
+++ b/value/src/main/java/com/google/auto/value/processor/autooneof.vm
@@ -94,19 +94,23 @@
 
   #end
 
+#if (!$props.empty)
   // Parent class that each implementation will inherit from.
   private abstract static class Parent_$formalTypes extends $origClass$actualTypes {
 
-#foreach ($p in $props)
+  $serialVersionUID
+
+  #foreach ($p in $props)
 
     @`java.lang.Override`
     $p.access $p.type ${p.getter}() {
       throw new UnsupportedOperationException(${kindGetter}().toString());
     }
 
-#end
+  #end
 
   }
+#end
 
 #foreach ($p in $props)
 
@@ -120,6 +124,8 @@
   // Implementation when the contained property is "${p}".
   private static final class Impl_$p$formalTypes extends Parent_$actualTypes {
 
+  $serialVersionUID
+
   #if ($p.type == "void")
 
     // There is only one instance of this class.
diff --git a/value/src/main/java/com/google/auto/value/processor/autovalue.vm b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
index 6f50e93..86cfe49 100644
--- a/value/src/main/java/com/google/auto/value/processor/autovalue.vm
+++ b/value/src/main/java/com/google/auto/value/processor/autovalue.vm
@@ -177,9 +177,7 @@
   }
 #end
 
-#if (!$serialVersionUID.empty)
-  private static final long serialVersionUID = $serialVersionUID;
-#end
+  $serialVersionUID
 
 #if ($builderTypeName != "")
 
@@ -194,241 +192,8 @@
 
   ## BUILDER CLASS
 
-  #foreach ($a in $builderAnnotations)
-
-  $a##
-  #end
-
-  static #if ($isFinal) final #end class Builder${builderFormalTypes} ##
-  #if ($builderIsInterface) implements #else extends #end
-      ${builderTypeName}${builderActualTypes} {
-
-  #foreach ($p in $props)
-
-    #if ($p.kind.primitive)
-
-    private $types.boxedClass($p.typeMirror).simpleName $p;
-
-    #else
-
-      #if ($builderPropertyBuilders[$p.name])
-      ## If you have ImmutableList.Builder<String> stringsBuilder() then we define two fields:
-      ## private ImmutableList.Builder<String> stringsBuilder$;
-      ## private ImmutableList<String> strings;
-
-    private ${builderPropertyBuilders[$p.name].builderType} ##
-        ${builderPropertyBuilders[$p.name].name};
-
-      #end
-
-    private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ;
-
-    #end
-  #end
-
-    Builder() {
-    }
-
-  #if (!$toBuilderMethods.empty)
-
-    private Builder(${origClass}${actualTypes} source) {
-
-    #foreach ($p in $props)
-
-      this.$p = source.${p.getter}();
-
-    #end
-
-    }
-
-  #end
-
-  #foreach ($p in $props)
-
-    ## The following is either null or an instance of PropertyBuilderClassifier.PropertyBuilder
-    #set ($propertyBuilder = $builderPropertyBuilders[$p.name])
-
-    ## Setter and/or property builder
-
-    #foreach ($setter in $builderSetters[$p.name])
-
-    @`java.lang.Override`
-    ${setter.access}${builderTypeName}${builderActualTypes} ##
-        ${setter.name}(${setter.nullableAnnotation}$setter.parameterType $p) {
-
-      ## Omit null check for primitive, or @Nullable, or if we are going to be calling a copy method
-      ## such as Optional.of, which will have its own null check if appropriate.
-      #if (!$setter.primitiveParameter && !$p.nullable && ${setter.copy($p)} == $p)
-
-        #if ($identifiers)
-
-      if ($p == null) {
-        throw new NullPointerException("Null $p.name");
-      }
-        #else
-          ## Just throw NullPointerException with no message if it's null.
-          ## The Object cast has no effect on the code but silences an ErrorProne warning.
-
-      ((`java.lang.Object`) ${p}).getClass();
-        #end
-
-      #end
-
-      #if ($propertyBuilder)
-
-      if (${propertyBuilder.name} != null) {
-        throw new IllegalStateException(#if ($identifiers)"Cannot set $p after calling ${p.name}Builder()"#end);
-      }
-
-      #end
-
-      this.$p = ${setter.copy($p)};
-      return this;
-    }
-
-    #end
-
-    #if ($propertyBuilder)
-
-    @`java.lang.Override`
-    ${propertyBuilder.access}$propertyBuilder.builderType ${p.name}Builder() {
-      if (${propertyBuilder.name} == null) {
-
-        ## This is the first time someone has asked for the builder. If the property it sets already
-        ## has a value (because it came from a toBuilder() call on the AutoValue class, or because
-        ## there is also a setter for this property) then we copy that value into the builder.
-        ## Otherwise the builder starts out empty.
-        ## If we have neither a setter nor a toBuilder() method, then the builder always starts
-        ## off empty.
-
-        #if ($builderSetters[$p.name].empty && $toBuilderMethods.empty)
-
-        ${propertyBuilder.name} = ${propertyBuilder.initializer};
-
-        #else
-
-        if ($p == null) {
-          ${propertyBuilder.name} = ${propertyBuilder.initializer};
-        } else {
-
-          #if (${propertyBuilder.builtToBuilder})
-
-          ${propertyBuilder.name} = ${p}.${propertyBuilder.builtToBuilder}();
-
-          #else
-
-          ${propertyBuilder.name} = ${propertyBuilder.initializer};
-          ${propertyBuilder.name}.${propertyBuilder.copyAll}($p);
-
-          #end
-
-          $p = null;
-        }
-
-        #end
-
-      }
-      return $propertyBuilder.name;
-    }
-
-    #end
-
-    ## Getter
-
-    #if ($builderGetters[$p.name])
-
-    @`java.lang.Override`
-    ${p.nullableAnnotation}${builderGetters[$p.name].access}$builderGetters[$p.name].type ${p.getter}() {
-      #if ($builderGetters[$p.name].optional)
-
-      if ($p == null) {
-        return $builderGetters[$p.name].optional.empty;
-      } else {
-        return ${builderGetters[$p.name].optional.rawType}.of($p);
-      }
-
-      #else
-        #if ($builderRequiredProperties.contains($p))
-
-      if ($p == null) {
-        throw new IllegalStateException(#if ($identifiers)"Property \"$p.name\" has not been set"#end);
-      }
-
-        #end
-
-        #if ($propertyBuilder)
-
-      if (${propertyBuilder.name} != null) {
-        return ${propertyBuilder.name}.build();
-      }
-      if ($p == null) {
-        ${propertyBuilder.beforeInitDefault}
-        $p = ${propertyBuilder.initDefault};
-      }
-
-        #end
-
-      return $p;
-
-      #end
-
-    }
-
-    #end
-  #end
-
-    @`java.lang.Override`
-    ${buildMethod.get().access}${origClass}${actualTypes} ${buildMethod.get().name}() {
-
-  #foreach ($p in $props)
-    #set ($propertyBuilder = $builderPropertyBuilders[$p.name])
-    #if ($propertyBuilder)
-
-      if (${propertyBuilder.name} != null) {
-        this.$p = ${propertyBuilder.name}.build();
-      } else if (this.$p == null) {
-        ${propertyBuilder.beforeInitDefault}
-        this.$p = ${propertyBuilder.initDefault};
-      }
-
-    #end
-  #end
-
-  #if (!$builderRequiredProperties.empty)
-    #if ($identifiers)  ## build a friendly message showing all missing properties
-
-      `java.lang.String` missing = "";
-
-      #foreach ($p in $builderRequiredProperties)
-
-      if (this.$p == null) {
-        missing += " $p.name";
-      }
-
-      #end
-
-      if (!missing.isEmpty()) {
-        throw new IllegalStateException("Missing required properties:" + missing);
-      }
-
-    #else  ## just throw an exception if anything is missing
-
-      if (#foreach ($p in $builderRequiredProperties)##
-          this.$p == null##
-          #if ($foreach.hasNext) || #end
-          #end) {
-        throw new IllegalStateException();
-      }
-    #end
-  #end
-
-      return new ${finalSubclass}${actualTypes}(
-  #foreach ($p in $props)
-
-          this.$p #if ($foreach.hasNext) , #end
-  #end );
-    }
-  }
+  #set($autoBuilder = false)
+  #parse("builder.vm")
 #end
 
 }
diff --git a/value/src/main/java/com/google/auto/value/processor/builder.vm b/value/src/main/java/com/google/auto/value/processor/builder.vm
new file mode 100644
index 0000000..630330c
--- /dev/null
+++ b/value/src/main/java/com/google/auto/value/processor/builder.vm
@@ -0,0 +1,285 @@
+## Copyright 2014 Google LLC
+##
+## 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.
+##
+##
+##
+## Template for AutoValue and AutoBuilder builders.
+## This template uses the Apache Velocity Template Language (VTL).
+## The variables ($isFinal, $props, and so on) are defined by the fields of AutoValueOrBuilderTemplateVars.
+##
+## Comments, like this one, begin with ##. The comment text extends up to and including the newline
+## character at the end of the line. So comments also serve to join a line to the next one.
+## Velocity deletes a newline after a directive (#if, #foreach, #end etc) so ## is not needed there.
+## That does mean that we sometimes need an extra blank line after such a directive.
+##
+## Post-processing will remove unwanted spaces and blank lines, but will not join two lines.
+## It will also replace classes spelled as (e.g.) `java.util.Arrays`, with the backquotes, to
+## use just Arrays if that class can be imported unambiguously, or java.util.Arrays if not.
+##
+#foreach ($a in $builderAnnotations)
+$a
+#end
+#if (!$autoBuilder) static #end##
+#if ($isFinal) final #end##
+class ${builderName}${builderFormalTypes} ##
+#if ($builderIsInterface) implements #else extends #end
+    ${builderTypeName}${builderActualTypes} {
+
+#foreach ($p in $props)
+
+  #if ($p.kind.primitive)
+
+  private $types.boxedClass($p.typeMirror).simpleName $p;
+
+  #else
+
+    #if ($builderPropertyBuilders[$p.name])
+    ## If you have ImmutableList.Builder<String> stringsBuilder() then we define two fields:
+    ## private ImmutableList.Builder<String> stringsBuilder$;
+    ## private ImmutableList<String> strings;
+
+  private ${builderPropertyBuilders[$p.name].builderType} ##
+      ${builderPropertyBuilders[$p.name].name};
+
+    #end
+
+  private $p.type $p #if ($p.optional && !$p.nullable) = $p.optional.empty #end ;
+
+  #end
+#end
+
+  ${builderName}() {
+  }
+
+#if ($toBuilderConstructor)
+
+  private ${builderName}(${origClass}${actualTypes} source) {
+
+  #foreach ($p in $props)
+
+    this.$p = source.${p.getter}();
+
+  #end
+
+  }
+
+#end
+
+#foreach ($p in $props)
+
+  ## The following is either null or an instance of PropertyBuilderClassifier.PropertyBuilder
+  #set ($propertyBuilder = $builderPropertyBuilders[$p.name])
+
+  ## Setter and/or property builder
+
+  #foreach ($setter in $builderSetters[$p.name])
+
+  @`java.lang.Override`
+  ${setter.access}${builderTypeName}${builderActualTypes} ##
+      ${setter.name}(${setter.nullableAnnotation}$setter.parameterType $p) {
+
+    ## Omit null check for primitive, or @Nullable, or if we are going to be calling a copy method
+    ## such as Optional.of, which will have its own null check if appropriate.
+    #if (!$setter.primitiveParameter && !$p.nullable && ${setter.copy($p)} == $p)
+
+      #if ($identifiers)
+
+    if ($p == null) {
+      throw new NullPointerException("Null $p.name");
+    }
+      #else
+        ## Just throw NullPointerException with no message if it's null.
+        ## The Object cast has no effect on the code but silences an ErrorProne warning.
+
+    ((`java.lang.Object`) ${p}).getClass();
+      #end
+
+    #end
+
+    #if ($propertyBuilder)
+
+    if (${propertyBuilder.name} != null) {
+      throw new IllegalStateException(#if ($identifiers)"Cannot set $p after calling ${p.name}Builder()"#end);
+    }
+
+    #end
+
+    this.$p = ${setter.copy($p)};
+    return this;
+  }
+
+  #end
+
+  #if ($propertyBuilder)
+
+  @`java.lang.Override`
+  ${propertyBuilder.access}$propertyBuilder.builderType ${p.name}Builder($propertyBuilder.propertyBuilderMethodParameters) {
+    if (${propertyBuilder.name} == null) {
+
+      ## This is the first time someone has asked for the builder. If the property it sets already
+      ## has a value (because it came from a toBuilder() call on the AutoValue class, or because
+      ## there is also a setter for this property) then we copy that value into the builder.
+      ## Otherwise the builder starts out empty.
+      ## If we have neither a setter nor a toBuilder() method, then the builder always starts
+      ## off empty.
+
+      #if ($builderSetters[$p.name].empty && $toBuilderMethods.empty)
+
+      ${propertyBuilder.name} = ${propertyBuilder.initializer};
+
+      #else
+
+      if ($p == null) {
+        ${propertyBuilder.name} = ${propertyBuilder.initializer};
+      } else {
+
+        #if (${propertyBuilder.builtToBuilder})
+
+        ${propertyBuilder.name} = ${p}.${propertyBuilder.builtToBuilder}();
+
+        #else
+
+        ${propertyBuilder.name} = ${propertyBuilder.initializer};
+        ${propertyBuilder.name}.${propertyBuilder.copyAll}($p);
+
+        #end
+
+        $p = null;
+      }
+
+      #end
+
+    } #if (!$propertyBuilder.propertyBuilderMethodParameters.empty) else {
+        ## This check only happens if the property-builder method has a parameter.
+        ## We don't know if the existing builder was created with the same parameter,
+        ## so we throw to avoid possibly giving you a builder that is different from
+        ## the one you asked for.
+
+      throw new IllegalStateException("Property builder for $p.name is already defined");
+    }
+      #end
+
+    return $propertyBuilder.name;
+  }
+
+  #end
+
+  ## Getter
+
+  #if ($builderGetters[$p.name])
+
+  @`java.lang.Override`
+  ${p.nullableAnnotation}${builderGetters[$p.name].access}$builderGetters[$p.name].type ${p.getter}() {
+    #if ($builderGetters[$p.name].optional)
+
+    if ($p == null) {
+      return $builderGetters[$p.name].optional.empty;
+    } else {
+      return ${builderGetters[$p.name].optional.rawType}.of($p);
+    }
+
+    #else
+      #if ($builderRequiredProperties.contains($p))
+
+    if ($p == null) {
+      throw new IllegalStateException(#if ($identifiers)"Property \"$p.name\" has not been set"#end);
+    }
+
+      #end
+
+      #if ($propertyBuilder)
+
+    if (${propertyBuilder.name} != null) {
+      return ${propertyBuilder.name}.build();
+    }
+    if ($p == null) {
+      ${propertyBuilder.beforeInitDefault}
+      $p = ${propertyBuilder.initDefault};
+    }
+
+      #end
+
+    return $p;
+
+    #end
+
+  }
+
+  #end
+#end
+
+## build() method
+
+  @`java.lang.Override`
+  ${buildMethod.get().access}${builtType} ${buildMethod.get().name}() ${buildMethod.get().throws} {
+
+#foreach ($p in $props)
+  #set ($propertyBuilder = $builderPropertyBuilders[$p.name])
+  #if ($propertyBuilder)
+
+    if (${propertyBuilder.name} != null) {
+      this.$p = ${propertyBuilder.name}.build();
+    } else if (this.$p == null) {
+      ${propertyBuilder.beforeInitDefault}
+      this.$p = ${propertyBuilder.initDefault};
+    }
+
+  #end
+#end
+
+#if (!$builderRequiredProperties.empty)
+    if (#foreach ($p in $builderRequiredProperties)##
+        this.$p == null##
+          #if ($foreach.hasNext)
+
+        || #end
+        #end) {
+
+  #if ($identifiers)  ## build a friendly message showing all missing properties
+    #if ($builderRequiredProperties.size() == 1)
+
+    `java.lang.String` missing = " $builderRequiredProperties.iterator().next()";
+
+    #else
+
+    `java.lang.StringBuilder` missing = new `java.lang.StringBuilder`();
+
+      #foreach ($p in $builderRequiredProperties)
+
+    if (this.$p == null) {
+      missing.append(" $p.name");
+    }
+
+      #end
+    #end
+
+    throw new IllegalStateException("Missing required properties:" + missing);
+
+  #else  ## just throw an exception if anything is missing
+
+    throw new IllegalStateException();
+
+  #end
+
+  }
+
+#end
+
+    #if ($builtType != "void") return #end ${build}(
+#foreach ($p in $props)
+
+        this.$p #if ($foreach.hasNext) , #end
+#end );
+  }
+}
diff --git a/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
index 094f6c3..03e25c2 100644
--- a/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
+++ b/value/src/main/java/com/google/auto/value/processor/equalshashcode.vm
@@ -49,7 +49,7 @@
     this.$p == that.${p.getter}() ##
   #elseif ($p.kind == "ARRAY")
     `java.util.Arrays`.equals(this.$p, ##
-        (that instanceof $subclass) ? (($subclass) that).$p : that.${p.getter}()) ##
+        (that instanceof $subclass) ? (($subclass$wildcardTypes) that).$p : that.${p.getter}()) ##
   #elseif ($p.nullable)
     (this.$p == null ? that.${p.getter}() == null : this.${p}.equals(that.${p.getter}())) ##
   #else
diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java
index 18c7736..5d1462d 100644
--- a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java
+++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedMethodSubject.java
@@ -51,9 +51,6 @@
         javac()
             .withProcessors(new AutoValueProcessor(ImmutableList.of(new MemoizeExtension())))
             .compile(file);
-    assertThat(compilation)
-        .hadErrorContaining(error)
-        .inFile(file)
-        .onLineContaining(actual);
+    assertThat(compilation).hadErrorContaining(error).inFile(file).onLineContaining(actual);
   }
 }
diff --git a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
index 7bd61ac..5beb686 100644
--- a/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
+++ b/value/src/test/java/com/google/auto/value/extension/memoized/MemoizedTest.java
@@ -22,6 +22,8 @@
 import com.google.auto.value.AutoValue.CopyAnnotations;
 import com.google.auto.value.extension.memoized.MemoizedTest.HashCodeEqualsOptimization.EqualsCounter;
 import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.Immutable;
+import com.google.errorprone.annotations.ImmutableTypeParameter;
 import java.lang.reflect.AnnotatedType;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
@@ -123,7 +125,9 @@
     @org.checkerframework.checker.nullness.qual.Nullable
     String nullableWithTypeAnnotation() {
       nullableWithTypeAnnotationCount++;
-      return "nullable derived " + stringWithTypeAnnotation() + " "
+      return "nullable derived "
+          + stringWithTypeAnnotation()
+          + " "
           + nullableWithTypeAnnotationCount;
     }
 
@@ -234,8 +238,9 @@
 
   @Before
   public void setUp() {
-    value = new AutoValue_MemoizedTest_Value(
-        "string", "stringWithTypeAnnotation", new HashCodeAndToStringCounter());
+    value =
+        new AutoValue_MemoizedTest_Value(
+            "string", "stringWithTypeAnnotation", new HashCodeAndToStringCounter());
     listValue = new AutoValue_MemoizedTest_ListValue<Integer, String>(0, "hello");
   }
 
@@ -375,8 +380,9 @@
     Method nullable =
         AutoValue_MemoizedTest_Value.class.getDeclaredMethod("nullableWithTypeAnnotation");
     AnnotatedType returnType = nullable.getAnnotatedReturnType();
-    assertThat(returnType.isAnnotationPresent(
-                   org.checkerframework.checker.nullness.qual.Nullable.class))
+    assertThat(
+            returnType.isAnnotationPresent(
+                org.checkerframework.checker.nullness.qual.Nullable.class))
         .isTrue();
   }
 
@@ -387,11 +393,13 @@
     // [1] @org.checkerframework.checker.nullness.qual.Nullable String stringWithTypeAnnotation,
     // [2] HashCodeAndToStringCounter counter
     // We don't currently copy @javax.annotation.Nullable because it is not a TYPE_USE annotation.
-    Constructor<?> constructor = AutoValue_MemoizedTest_Value.class.getDeclaredConstructor(
-        String.class, String.class, HashCodeAndToStringCounter.class);
+    Constructor<?> constructor =
+        AutoValue_MemoizedTest_Value.class.getDeclaredConstructor(
+            String.class, String.class, HashCodeAndToStringCounter.class);
     AnnotatedType paramType = constructor.getAnnotatedParameterTypes()[1];
-    assertThat(paramType.isAnnotationPresent(
-                   org.checkerframework.checker.nullness.qual.Nullable.class))
+    assertThat(
+            paramType.isAnnotationPresent(
+                org.checkerframework.checker.nullness.qual.Nullable.class))
         .isTrue();
   }
 
@@ -448,8 +456,8 @@
     assertThat(memoizedHashCodeAndFinalEqualsMethod.equals(second)).isTrue();
     assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(0);
 
-    memoizedHashCodeAndFinalEqualsMethod.hashCode();
-    memoizedHashCodeAndFinalEqualsMethod.hashCode();
+    int unused1 = memoizedHashCodeAndFinalEqualsMethod.hashCode();
+    int unused2 = memoizedHashCodeAndFinalEqualsMethod.hashCode();
     assertThat(memoizedHashCodeAndFinalEqualsMethod.hashCodeCount).isEqualTo(1);
   }
 
@@ -465,8 +473,7 @@
 
   @AutoValue
   abstract static class ResourceUriPath<InputT> extends AbstractTypePath<InputT, ResourceUri> {
-    static <InputT> ResourceUriPath<InputT> create(
-        TypeEdgeIterable<InputT, ResourceUri> edges) {
+    static <InputT> ResourceUriPath<InputT> create(TypeEdgeIterable<InputT, ResourceUri> edges) {
       return new AutoValue_MemoizedTest_ResourceUriPath<>(edges);
     }
 
@@ -482,4 +489,27 @@
         ResourceUriPath.create(new TypeEdgeIterable<String, ResourceUri>() {});
     assertThat(path.edges()).isNotNull();
   }
+
+  @Immutable
+  @AutoValue
+  abstract static class Unchanging<@ImmutableTypeParameter T> {
+    abstract T value();
+
+    @Override
+    @Memoized
+    public abstract int hashCode();
+
+    static <@ImmutableTypeParameter T> Unchanging<T> of(T value) {
+      return new AutoValue_MemoizedTest_Unchanging<T>(value);
+    }
+  }
+
+  @Test
+  public void copiedTypeAnnotations() {
+    for (Class<?> c = Unchanging.of("foo").getClass(); c != Object.class; c = c.getSuperclass()) {
+      assertThat(c.getTypeParameters()).hasLength(1);
+      assertThat(c.getTypeParameters()[0].isAnnotationPresent(ImmutableTypeParameter.class))
+          .isTrue();
+    }
+  }
 }
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
index 1879d1e..064406d 100644
--- a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java
@@ -415,4 +415,38 @@
 
     assertThat(actualAutoValue).isEqualTo(autoValue);
   }
+
+  /**
+   * Type that may result in nested lambdas in the generated code. Including this allows us to
+   * verify that we handle those correctly, in particular not reusing a lambda parameter name in
+   * another lambda nested inside the first one.
+   */
+  @SerializableAutoValue
+  @AutoValue
+  abstract static class ComplexType implements Serializable {
+    abstract ImmutableMap<String, ImmutableMap<String, Optional<String>>> a();
+
+    static ComplexType.Builder builder() {
+      return new AutoValue_SerializableAutoValueExtensionTest_ComplexType.Builder();
+    }
+
+    @AutoValue.Builder
+    abstract static class Builder {
+      abstract ComplexType.Builder setA(
+          ImmutableMap<String, ImmutableMap<String, Optional<String>>> a);
+
+      abstract ComplexType build();
+    }
+  }
+
+  @Test
+  public void complexType() {
+    ImmutableMap<String, ImmutableMap<String, Optional<String>>> map =
+        ImmutableMap.of("foo", ImmutableMap.of("bar", Optional.of("baz")));
+    ComplexType complexType = ComplexType.builder().setA(map).build();
+
+    ComplexType reserialized = SerializableTester.reserialize(complexType);
+
+    assertThat(reserialized).isEqualTo(complexType);
+  }
 }
diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java
index 388977f..162d7cf 100644
--- a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java
+++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java
@@ -42,6 +42,15 @@
     return new FakeIdentitySerializer(type, isIdentity);
   }
 
+  // This doesn't follow the contract, and always returns the same string for a given prefix.
+  // That means it will be wrong if two identifiers with the same prefix are in the same scope in
+  // the generated code, but for our purposes in this fake it is OK, and means we don't have to
+  // hardwire knowledge of the uniqueness algorithm into golden text in tests.
+  @Override
+  public CodeBlock newIdentifier(String prefix) {
+    return CodeBlock.of("$L$$", prefix);
+  }
+
   private static class FakeIdentitySerializer implements Serializer {
 
     private final TypeMirror typeMirror;
diff --git a/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringTest.java b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringTest.java
new file mode 100644
index 0000000..f519747
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringTest.java
@@ -0,0 +1,961 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.toprettystring.ToPrettyStringTest.CollectionSubtypesWithFixedTypeParameters.StringList;
+import com.google.auto.value.extension.toprettystring.ToPrettyStringTest.CollectionSubtypesWithFixedTypeParameters.StringMap;
+import com.google.auto.value.extension.toprettystring.ToPrettyStringTest.PropertyHasToPrettyString.HasToPrettyString;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.primitives.ImmutableIntArray;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SuppressWarnings("AutoValueImmutableFields")
+@RunWith(JUnit4.class)
+public class ToPrettyStringTest {
+  @AutoValue
+  abstract static class Primitives {
+    abstract int i();
+
+    abstract long l();
+
+    abstract byte b();
+
+    abstract short s();
+
+    abstract char c();
+
+    abstract float f();
+
+    abstract double d();
+
+    abstract boolean bool();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void primitives() {
+    Primitives valueType =
+        new AutoValue_ToPrettyStringTest_Primitives(
+            1, 2L, (byte) 3, (short) 4, 'C', 6.6f, 7.7, false);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "Primitives {"
+                + "\n  i = 1,"
+                + "\n  l = 2,"
+                + "\n  b = 3,"
+                + "\n  s = 4,"
+                + "\n  c = C,"
+                + "\n  f = 6.6,"
+                + "\n  d = 7.7,"
+                + "\n  bool = false,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class PrimitiveArray {
+    @Nullable
+    @SuppressWarnings("mutable")
+    abstract long[] longs();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void primitiveArray() {
+    PrimitiveArray valueType =
+        new AutoValue_ToPrettyStringTest_PrimitiveArray(new long[] {1L, 2L, 10L, 200L});
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrimitiveArray {"
+                + "\n  longs = ["
+                + "\n    1,"
+                + "\n    2,"
+                + "\n    10,"
+                + "\n    200,"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @Test
+  public void primitiveArray_empty() {
+    PrimitiveArray valueType = new AutoValue_ToPrettyStringTest_PrimitiveArray(new long[0]);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrimitiveArray {" // force newline
+                + "\n  longs = [],"
+                + "\n}");
+  }
+
+  @Test
+  public void primitiveArray_null() {
+    PrimitiveArray valueType = new AutoValue_ToPrettyStringTest_PrimitiveArray(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrimitiveArray {" // force newline
+                + "\n  longs = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class PrettyCollection {
+    @Nullable
+    abstract Collection<Object> collection();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void prettyCollection() {
+    PrettyCollection valueType =
+        new AutoValue_ToPrettyStringTest_PrettyCollection(ImmutableList.of("hello", "world"));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyCollection {"
+                + "\n  collection = ["
+                + "\n    hello,"
+                + "\n    world,"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyCollection_elementsWithNewlines() {
+    PrettyCollection valueType =
+        new AutoValue_ToPrettyStringTest_PrettyCollection(
+            ImmutableList.of("hello\nworld\nnewline"));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyCollection {"
+                + "\n  collection = ["
+                + "\n    hello"
+                + "\n    world"
+                + "\n    newline,"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyCollection_empty() {
+    PrettyCollection valueType =
+        new AutoValue_ToPrettyStringTest_PrettyCollection(ImmutableList.of());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyCollection {" // force newline
+                + "\n  collection = [],"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyCollection_null() {
+    PrettyCollection valueType = new AutoValue_ToPrettyStringTest_PrettyCollection(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyCollection {" // force newline
+                + "\n  collection = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class NestedCollection {
+    @Nullable
+    abstract Collection<Collection<Object>> nestedCollection();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void nestedCollection() {
+    NestedCollection valueType =
+        new AutoValue_ToPrettyStringTest_NestedCollection(
+            Arrays.asList(
+                ImmutableList.of("hello", "world"),
+                ImmutableList.of("hello2", "world2"),
+                null,
+                Arrays.asList("not null", null)));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "NestedCollection {"
+                + "\n  nestedCollection = ["
+                + "\n    ["
+                + "\n      hello,"
+                + "\n      world,"
+                + "\n    ],"
+                + "\n    ["
+                + "\n      hello2,"
+                + "\n      world2,"
+                + "\n    ],"
+                + "\n    null,"
+                + "\n    ["
+                + "\n      not null,"
+                + "\n      null,"
+                + "\n    ],"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @Test
+  public void nestedCollection_elementsWithNewlines() {
+    NestedCollection valueType =
+        new AutoValue_ToPrettyStringTest_NestedCollection(
+            ImmutableList.of(
+                ImmutableList.of((Object) "hello\nworld\nnewline", "hello2\nworld2\nnewline2")));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "NestedCollection {"
+                + "\n  nestedCollection = ["
+                + "\n    ["
+                + "\n      hello"
+                + "\n      world"
+                + "\n      newline,"
+                + "\n      hello2"
+                + "\n      world2"
+                + "\n      newline2,"
+                + "\n    ],"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @Test
+  public void nestedCollection_empty() {
+    NestedCollection valueType =
+        new AutoValue_ToPrettyStringTest_NestedCollection(ImmutableList.of());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "NestedCollection {" // force newline
+                + "\n  nestedCollection = [],"
+                + "\n}");
+  }
+
+  @Test
+  public void nestedCollection_nestedEmpty() {
+    NestedCollection valueType =
+        new AutoValue_ToPrettyStringTest_NestedCollection(
+            ImmutableList.of(ImmutableList.of(), ImmutableList.of()));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "NestedCollection {"
+                + "\n  nestedCollection = ["
+                + "\n    [],"
+                + "\n    [],"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @Test
+  public void nestedCollection_null() {
+    NestedCollection valueType = new AutoValue_ToPrettyStringTest_NestedCollection(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "NestedCollection {" // force newline
+                + "\n  nestedCollection = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class ImmutablePrimitiveArray {
+    @Nullable
+    abstract ImmutableIntArray immutableIntArray();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void immutablePrimitiveArray() {
+    ImmutablePrimitiveArray valueType =
+        new AutoValue_ToPrettyStringTest_ImmutablePrimitiveArray(ImmutableIntArray.of(1, 2));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "ImmutablePrimitiveArray {"
+                + "\n  immutableIntArray = ["
+                + "\n    1,"
+                + "\n    2,"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @Test
+  public void immutablePrimitiveArray_empty() {
+    ImmutablePrimitiveArray valueType =
+        new AutoValue_ToPrettyStringTest_ImmutablePrimitiveArray(ImmutableIntArray.of());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "ImmutablePrimitiveArray {" // force newline
+                + "\n  immutableIntArray = [],"
+                + "\n}");
+  }
+
+  @Test
+  public void immutablePrimitiveArray_null() {
+    ImmutablePrimitiveArray valueType =
+        new AutoValue_ToPrettyStringTest_ImmutablePrimitiveArray(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "ImmutablePrimitiveArray {" // force newline
+                + "\n  immutableIntArray = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class PrettyMap {
+    @Nullable
+    abstract Map<Object, Object> map();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void prettyMap() {
+    PrettyMap valueType = new AutoValue_ToPrettyStringTest_PrettyMap(ImmutableMap.of(1, 2));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMap {" // force newline
+                + "\n  map = {"
+                + "\n    1: 2,"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyMap_keysAndValuesWithNewlines() {
+    PrettyMap valueType =
+        new AutoValue_ToPrettyStringTest_PrettyMap(
+            ImmutableMap.of(
+                "key1\nnewline", "value1\nnewline", "key2\nnewline", "value2\nnewline"));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMap {"
+                + "\n  map = {"
+                + "\n    key1"
+                + "\n    newline: value1"
+                + "\n    newline,"
+                + "\n    key2"
+                + "\n    newline: value2"
+                + "\n    newline,"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyMap_empty() {
+    PrettyMap valueType = new AutoValue_ToPrettyStringTest_PrettyMap(ImmutableMap.of());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMap {" // force newline
+                + "\n  map = {},"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyMap_null() {
+    PrettyMap valueType = new AutoValue_ToPrettyStringTest_PrettyMap(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMap {" // force newline
+                + "\n  map = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class MapOfMaps {
+    @Nullable
+    abstract Map<Map<Object, Object>, Map<Object, Object>> mapOfMaps();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  private static <K, V> Map<K, V> mapWithNulls(K k, V v) {
+    Map<K, V> map = new LinkedHashMap<>();
+    map.put(k, v);
+    return map;
+  }
+
+  @Test
+  public void mapOfMaps() {
+    Map<Map<Object, Object>, Map<Object, Object>> mapOfMaps = new LinkedHashMap<>();
+    mapOfMaps.put(ImmutableMap.of("k1_k", "k1_v"), ImmutableMap.of("v1_k", "v1_v"));
+    mapOfMaps.put(ImmutableMap.of("k2_k", "k2_v"), ImmutableMap.of("v2_k", "v2_v"));
+    mapOfMaps.put(mapWithNulls("keyForNullValue", null), mapWithNulls(null, "valueForNullKey"));
+    mapOfMaps.put(null, ImmutableMap.of("nullKeyKey", "nullKeyValue"));
+    mapOfMaps.put(ImmutableMap.of("nullValueKey", "nullValueValue"), null);
+    mapOfMaps.put(
+        ImmutableMap.of("keyForMapOfNullsKey", "keyForMapOfNullsValue"), mapWithNulls(null, null));
+    MapOfMaps valueType = new AutoValue_ToPrettyStringTest_MapOfMaps(mapOfMaps);
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "MapOfMaps {"
+                + "\n  mapOfMaps = {"
+                + "\n    {"
+                + "\n      k1_k: k1_v,"
+                + "\n    }: {"
+                + "\n      v1_k: v1_v,"
+                + "\n    },"
+                + "\n    {"
+                + "\n      k2_k: k2_v,"
+                + "\n    }: {"
+                + "\n      v2_k: v2_v,"
+                + "\n    },"
+                + "\n    {"
+                + "\n      keyForNullValue: null,"
+                + "\n    }: {"
+                + "\n      null: valueForNullKey,"
+                + "\n    },"
+                + "\n    null: {"
+                + "\n      nullKeyKey: nullKeyValue,"
+                + "\n    },"
+                + "\n    {"
+                + "\n      nullValueKey: nullValueValue,"
+                + "\n    }: null,"
+                + "\n    {"
+                + "\n      keyForMapOfNullsKey: keyForMapOfNullsValue,"
+                + "\n    }: {"
+                + "\n      null: null,"
+                + "\n    },"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @Test
+  public void mapOfMaps_elementsWithNewlines() {
+    MapOfMaps valueType =
+        new AutoValue_ToPrettyStringTest_MapOfMaps(
+            ImmutableMap.of(
+                ImmutableMap.of((Object) "k_k\nnewline", (Object) "k_v\nnewline"),
+                ImmutableMap.of((Object) "v_k\nnewline", (Object) "v_v\nnewline")));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "MapOfMaps {"
+                + "\n  mapOfMaps = {"
+                + "\n    {"
+                + "\n      k_k"
+                + "\n      newline: k_v"
+                + "\n      newline,"
+                + "\n    }: {"
+                + "\n      v_k"
+                + "\n      newline: v_v"
+                + "\n      newline,"
+                + "\n    },"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @Test
+  public void mapOfMaps_empty() {
+    MapOfMaps valueType = new AutoValue_ToPrettyStringTest_MapOfMaps(ImmutableMap.of());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "MapOfMaps {" // force newline
+                + "\n  mapOfMaps = {},"
+                + "\n}");
+  }
+
+  @Test
+  public void mapOfMaps_nestedEmpty() {
+    MapOfMaps valueType =
+        new AutoValue_ToPrettyStringTest_MapOfMaps(
+            ImmutableMap.of(ImmutableMap.of(), ImmutableMap.of()));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "MapOfMaps {" // force newline
+                + "\n  mapOfMaps = {"
+                + "\n    {}: {},"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @Test
+  public void mapOfMaps_null() {
+    MapOfMaps valueType = new AutoValue_ToPrettyStringTest_MapOfMaps(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "MapOfMaps {" // force newline
+                + "\n  mapOfMaps = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class PrettyMultimap {
+    @Nullable
+    abstract Multimap<Object, Object> multimap();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void prettyMultimap() {
+    PrettyMultimap valueType =
+        new AutoValue_ToPrettyStringTest_PrettyMultimap(
+            ImmutableMultimap.builder().putAll("k", "v1", "v2").build());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMultimap {" // force newline
+                + "\n  multimap = {"
+                + "\n    k: ["
+                + "\n      v1,"
+                + "\n      v2,"
+                + "\n    ],"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyMultimap_keysAndValuesWithNewlines() {
+    PrettyMultimap valueType =
+        new AutoValue_ToPrettyStringTest_PrettyMultimap(
+            ImmutableMultimap.builder()
+                .putAll("key\nnewline", "value1\nnewline", "value2\nnewline")
+                .build());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMultimap {"
+                + "\n  multimap = {"
+                + "\n    key"
+                + "\n    newline: ["
+                + "\n      value1"
+                + "\n      newline,"
+                + "\n      value2"
+                + "\n      newline,"
+                + "\n    ],"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyMultimap_empty() {
+    PrettyMultimap valueType =
+        new AutoValue_ToPrettyStringTest_PrettyMultimap(ImmutableMultimap.of());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMultimap {" // force newline
+                + "\n  multimap = {},"
+                + "\n}");
+  }
+
+  @Test
+  public void prettyMultimap_null() {
+    PrettyMultimap valueType = new AutoValue_ToPrettyStringTest_PrettyMultimap(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PrettyMultimap {" // force newline
+                + "\n  multimap = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class JavaOptional {
+    @Nullable
+    abstract java.util.Optional<Object> optional();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void javaOptional_present() {
+    JavaOptional valueType =
+        new AutoValue_ToPrettyStringTest_JavaOptional(java.util.Optional.of("hello, world"));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "JavaOptional {" // force newline
+                + "\n  optional = hello, world,"
+                + "\n}");
+  }
+
+  @Test
+  public void javaOptional_empty() {
+    JavaOptional valueType =
+        new AutoValue_ToPrettyStringTest_JavaOptional(java.util.Optional.empty());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "JavaOptional {" // force newline
+                + "\n  optional = <empty>,"
+                + "\n}");
+  }
+
+  @Test
+  public void javaOptional_valueWithNewlines() {
+    JavaOptional valueType =
+        new AutoValue_ToPrettyStringTest_JavaOptional(
+            java.util.Optional.of("optional\nwith\nnewline"));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "JavaOptional {" // force newline
+                + "\n  optional = optional"
+                + "\n  with"
+                + "\n  newline,"
+                + "\n}");
+  }
+
+  @Test
+  public void javaOptional_null() {
+    @SuppressWarnings("NullOptional")
+    JavaOptional valueType = new AutoValue_ToPrettyStringTest_JavaOptional(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "JavaOptional {" // force newline
+                + "\n  optional = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class GuavaOptional {
+    @Nullable
+    abstract com.google.common.base.Optional<Object> optional();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void guavaOptional_present() {
+    GuavaOptional valueType =
+        new AutoValue_ToPrettyStringTest_GuavaOptional(
+            com.google.common.base.Optional.of("hello, world"));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "GuavaOptional {" // force newline
+                + "\n  optional = hello, world,"
+                + "\n}");
+  }
+
+  @Test
+  public void guavaOptional_absent() {
+    GuavaOptional valueType =
+        new AutoValue_ToPrettyStringTest_GuavaOptional(com.google.common.base.Optional.absent());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "GuavaOptional {" // force newline
+                + "\n  optional = <absent>,"
+                + "\n}");
+  }
+
+  @Test
+  public void guavaOptional_valueWithNewlines() {
+    GuavaOptional valueType =
+        new AutoValue_ToPrettyStringTest_GuavaOptional(
+            com.google.common.base.Optional.of("optional\nwith\nnewline"));
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "GuavaOptional {" // force newline
+                + "\n  optional = optional"
+                + "\n  with"
+                + "\n  newline,"
+                + "\n}");
+  }
+
+  @Test
+  public void guavaOptional_null() {
+    @SuppressWarnings("NullOptional")
+    GuavaOptional valueType = new AutoValue_ToPrettyStringTest_GuavaOptional(null);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "GuavaOptional {" // force newline
+                + "\n  optional = null,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class NestAllTheThings {
+    @Nullable
+    abstract com.google.common.base.Optional<
+            java.util.Optional<
+                List< // open list
+                    Map<ImmutableIntArray, Multimap<int[][], Object>>
+                // close list
+                >>>
+        value();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void nestAllTheThings() {
+    NestAllTheThings valueType =
+        new AutoValue_ToPrettyStringTest_NestAllTheThings(
+            com.google.common.base.Optional.of(
+                java.util.Optional.of(
+                    ImmutableList.of(
+                        ImmutableMap.of(
+                            ImmutableIntArray.of(-1, -2, -3),
+                            ImmutableMultimap.of(
+                                new int[][] {{1, 2}, {3, 4, 5}, {}}, "value\nwith\nnewline"))))));
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "NestAllTheThings {"
+                + "\n  value = ["
+                + "\n    {"
+                + "\n      ["
+                + "\n        -1,"
+                + "\n        -2,"
+                + "\n        -3,"
+                + "\n      ]: {"
+                + "\n        ["
+                + "\n          ["
+                + "\n            1,"
+                + "\n            2,"
+                + "\n          ],"
+                + "\n          ["
+                + "\n            3,"
+                + "\n            4,"
+                + "\n            5,"
+                + "\n          ],"
+                + "\n          [],"
+                + "\n        ]: ["
+                + "\n          value"
+                + "\n          with"
+                + "\n          newline,"
+                + "\n        ],"
+                + "\n      },"
+                + "\n    },"
+                + "\n  ],"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class WithCustomName {
+    abstract int i();
+
+    @ToPrettyString
+    abstract String customName();
+  }
+
+  @Test
+  public void withCustomName() {
+    WithCustomName valueType = new AutoValue_ToPrettyStringTest_WithCustomName(1);
+
+    assertThat(valueType.customName())
+        .isEqualTo(
+            "WithCustomName {" // force newline
+                + "\n  i = 1,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class OverridesToString {
+    abstract int i();
+
+    @ToPrettyString
+    @Override
+    public abstract String toString();
+  }
+
+  @Test
+  public void overridesToString() {
+    OverridesToString valueType = new AutoValue_ToPrettyStringTest_OverridesToString(1);
+
+    assertThat(valueType.toString())
+        .isEqualTo(
+            "OverridesToString {" // force newline
+                + "\n  i = 1,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class PropertyHasToPrettyString {
+    static class HasToPrettyString<A> {
+      @Override
+      public String toString() {
+        throw new AssertionError();
+      }
+
+      @ToPrettyString
+      String toPrettyString() {
+        return "custom\n@ToPrettyString\nmethod";
+      }
+    }
+
+    static class HasInheritedToPrettyString extends HasToPrettyString<String> {}
+
+    interface HasToPrettyStringInInterface {
+      @ToPrettyString
+      default String toPrettyString() {
+        return "custom\n@ToPrettyString\nmethod\ninterface";
+      }
+    }
+
+    static class HasToPrettyStringFromSuperInterface implements HasToPrettyStringInInterface {}
+
+    abstract HasToPrettyString<String> parameterizedWithString();
+
+    abstract HasToPrettyString<Void> parameterizedWithVoid();
+
+    abstract HasInheritedToPrettyString superclass();
+
+    abstract HasToPrettyStringFromSuperInterface superinterface();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void propertyHasToPrettyString() {
+    PropertyHasToPrettyString valueType =
+        new AutoValue_ToPrettyStringTest_PropertyHasToPrettyString(
+            new PropertyHasToPrettyString.HasToPrettyString<>(),
+            new PropertyHasToPrettyString.HasToPrettyString<>(),
+            new PropertyHasToPrettyString.HasInheritedToPrettyString(),
+            new PropertyHasToPrettyString.HasToPrettyStringFromSuperInterface());
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "PropertyHasToPrettyString {"
+                + "\n  parameterizedWithString = custom"
+                + "\n  @ToPrettyString"
+                + "\n  method,"
+                + "\n  parameterizedWithVoid = custom"
+                + "\n  @ToPrettyString"
+                + "\n  method,"
+                + "\n  superclass = custom"
+                + "\n  @ToPrettyString"
+                + "\n  method,"
+                + "\n  superinterface = custom"
+                + "\n  @ToPrettyString"
+                + "\n  method"
+                + "\n  interface,"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class CollectionSubtypesWithFixedTypeParameters {
+    static class StringList extends ArrayList<String> {}
+
+    static class StringMap extends LinkedHashMap<String, String> {}
+
+    abstract StringList list();
+
+    abstract StringMap map();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void fixedTypeParameters() {
+    StringList stringList = new StringList();
+    stringList.addAll(ImmutableList.of("a", "b", "c"));
+    StringMap stringMap = new StringMap();
+    stringMap.putAll(ImmutableMap.of("A", "a", "B", "b"));
+    CollectionSubtypesWithFixedTypeParameters valueType =
+        new AutoValue_ToPrettyStringTest_CollectionSubtypesWithFixedTypeParameters(
+            stringList, stringMap);
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "CollectionSubtypesWithFixedTypeParameters {"
+                + "\n  list = ["
+                + "\n    a,"
+                + "\n    b,"
+                + "\n    c,"
+                + "\n  ],"
+                + "\n  map = {"
+                + "\n    A: a,"
+                + "\n    B: b,"
+                + "\n  },"
+                + "\n}");
+  }
+
+  @AutoValue
+  abstract static class JavaBeans {
+    abstract int getInt();
+
+    abstract boolean isBoolean();
+
+    abstract String getNotAJavaIdentifier();
+
+    @ToPrettyString
+    abstract String toPrettyString();
+  }
+
+  @Test
+  public void javaBeans() {
+    JavaBeans valueType = new AutoValue_ToPrettyStringTest_JavaBeans(4, false, "not");
+
+    assertThat(valueType.toPrettyString())
+        .isEqualTo(
+            "JavaBeans {"
+                + "\n  int = 4,"
+                + "\n  boolean = false,"
+                + "\n  notAJavaIdentifier = not,"
+                + "\n}");
+
+    // Check to make sure that we use the same property names that AutoValue does. This is mostly
+    // defensive, since in some scenarios AutoValue considers the property names of a java bean as
+    // having the prefix removed.
+    assertThat(valueType.toString())
+        .isEqualTo("JavaBeans{int=4, boolean=false, notAJavaIdentifier=not}");
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringValidatorTest.java b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringValidatorTest.java
new file mode 100644
index 0000000..6c51be1
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/extension/toprettystring/ToPrettyStringValidatorTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.extension.toprettystring;
+
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.auto.value.extension.toprettystring.processor.ToPrettyStringValidator;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ToPrettyStringValidatorTest {
+  @Test
+  public void cannotBeStatic() {
+    JavaFileObject file =
+        JavaFileObjects.forSourceLines(
+            "test.Test",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "class Test {",
+            "  @ToPrettyString",
+            "  static String toPretty() {",
+            "   return new String();",
+            "  }",
+            "}",
+            "");
+    Compilation compilation = compile(file);
+
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining("must be instance methods")
+        .inFile(file)
+        .onLineContaining("static String toPretty()");
+  }
+
+  @Test
+  public void mustReturnString() {
+    JavaFileObject file =
+        JavaFileObjects.forSourceLines(
+            "test.Test",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "class Test {",
+            "  @ToPrettyString",
+            "  CharSequence toPretty() {",
+            "   return new String();",
+            "  }",
+            "}",
+            "");
+    Compilation compilation = compile(file);
+
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining("must return String")
+        .inFile(file)
+        .onLineContaining("CharSequence toPretty()");
+  }
+
+  @Test
+  public void noParameters() {
+    JavaFileObject file =
+        JavaFileObjects.forSourceLines(
+            "test.Test",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "class Test {",
+            "  @ToPrettyString",
+            "  String toPretty(String value) {",
+            "   return value;",
+            "  }",
+            "}",
+            "");
+    Compilation compilation = compile(file);
+
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining("cannot have parameters")
+        .inFile(file)
+        .onLineContaining("String toPretty(String value)");
+  }
+
+  @Test
+  public void onlyOneToPrettyStringMethod_sameClass() {
+    JavaFileObject file =
+        JavaFileObjects.forSourceLines(
+            "test.Test",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "class Test {",
+            "  @ToPrettyString",
+            "  String toPretty1() {",
+            "   return new String();",
+            "  }",
+            "",
+            "  @ToPrettyString",
+            "  String toPretty2() {",
+            "   return new String();",
+            "  }",
+            "}",
+            "");
+    Compilation compilation = compile(file);
+
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            error(
+                "test.Test has multiple @ToPrettyString methods:",
+                "  - test.Test.toPretty1()",
+                "  - test.Test.toPretty2()"))
+        .inFile(file)
+        .onLineContaining("class Test");
+  }
+
+  @Test
+  public void onlyOneToPrettyStringMethod_superclass() {
+    JavaFileObject superclass =
+        JavaFileObjects.forSourceLines(
+            "test.Superclass",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "class Superclass {",
+            "  @ToPrettyString",
+            "  String toPretty1() {",
+            "   return new String();",
+            "  }",
+            "}",
+            "");
+    JavaFileObject subclass =
+        JavaFileObjects.forSourceLines(
+            "test.Subclass",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "class Subclass extends Superclass {",
+            "  @ToPrettyString",
+            "  String toPretty2() {",
+            "   return new String();",
+            "  }",
+            "}",
+            "");
+    Compilation compilation = compile(superclass, subclass);
+
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            error(
+                "test.Subclass has multiple @ToPrettyString methods:",
+                "  - test.Superclass.toPretty1()",
+                "  - test.Subclass.toPretty2()"))
+        .inFile(subclass)
+        .onLineContaining("class Subclass");
+  }
+
+  @Test
+  public void onlyOneToPrettyStringMethod_superinterface() {
+    JavaFileObject superinterface =
+        JavaFileObjects.forSourceLines(
+            "test.Superinterface",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "interface Superinterface {",
+            "  @ToPrettyString",
+            "  default String toPretty1() {",
+            "   return new String();",
+            "  }",
+            "}",
+            "");
+    JavaFileObject subclass =
+        JavaFileObjects.forSourceLines(
+            "test.Subclass",
+            "package test;",
+            "",
+            "import com.google.auto.value.extension.toprettystring.ToPrettyString;",
+            "",
+            "class Subclass implements Superinterface {",
+            "  @ToPrettyString",
+            "  String toPretty2() {",
+            "   return new String();",
+            "  }",
+            "}",
+            "");
+    Compilation compilation = compile(superinterface, subclass);
+
+    assertThat(compilation).failed();
+    assertThat(compilation).hadErrorCount(1);
+    assertThat(compilation)
+        .hadErrorContaining(
+            error(
+                "test.Subclass has multiple @ToPrettyString methods:",
+                "  - test.Superinterface.toPretty1()",
+                "  - test.Subclass.toPretty2()"))
+        .inFile(subclass)
+        .onLineContaining("class Subclass");
+  }
+
+  private static Compilation compile(JavaFileObject... javaFileObjects) {
+    return javac().withProcessors(new ToPrettyStringValidator()).compile(javaFileObjects);
+  }
+
+  private static String error(String... lines) {
+    return String.join("\n  ", lines);
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java
index b8a36ea..1f79a07 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationCompilationTest.java
@@ -74,11 +74,13 @@
             "",
             "import com.example.annotations.MyAnnotation;",
             "import com.example.enums.MyEnum;",
+            "import java.io.Serializable;",
             GeneratedImport.importGeneratedAnnotationType(),
             "",
             "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
             "final class AutoAnnotation_AnnotationFactory_newMyAnnotation",
-            "     implements MyAnnotation {",
+            "     implements MyAnnotation, Serializable {",
+            "  private static final long serialVersionUID = -7473814294717163169L;",
             "  private final MyEnum value;",
             "  private static final int defaultedValue = 23;",
             "",
@@ -129,6 +131,7 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoAnnotationProcessor())
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(annotationFactoryJavaFile, myAnnotationJavaFile, myEnumJavaFile);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -157,11 +160,13 @@
     JavaFileObject expectedOutput =
         JavaFileObjects.forSourceLines(
             "AutoAnnotation_AnnotationFactory_newMyAnnotation",
+            "import java.io.Serializable;",
             GeneratedImport.importGeneratedAnnotationType(),
             "",
             "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
             "final class AutoAnnotation_AnnotationFactory_newMyAnnotation",
-            "    implements MyAnnotation {",
+            "    implements MyAnnotation, Serializable {",
+            "  private static final long serialVersionUID = 0L;",
             "  AutoAnnotation_AnnotationFactory_newMyAnnotation() {",
             "  }",
             "",
@@ -191,6 +196,7 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoAnnotationProcessor())
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(annotationFactoryJavaFile, myAnnotationJavaFile);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -237,12 +243,14 @@
             "package com.example.factories;",
             "",
             "import com.example.annotations.MyAnnotation;",
+            "import java.io.Serializable",
             "import java.util.Arrays;",
             GeneratedImport.importGeneratedAnnotationType(),
             "",
             "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
-            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation"
-                + " {",
+            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation,"
+                + " Serializable {",
+            "  private static final long serialVersionUID = -8116050813861599066L;",
             "  private final int[] value;",
             "",
             "  AutoAnnotation_AnnotationFactory_newMyAnnotation(int[] value) {",
@@ -288,6 +296,7 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoAnnotationProcessor())
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(annotationFactoryJavaFile, myAnnotationJavaFile, gwtCompatibleJavaFile);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -343,6 +352,7 @@
             "",
             "import com.example.annotations.MyAnnotation;",
             "import com.example.enums.MyEnum;",
+            "import java.io.Serializable;",
             "import java.util.Arrays;",
             "import java.util.Collection;",
             "import java.util.List;",
@@ -350,8 +360,9 @@
             GeneratedImport.importGeneratedAnnotationType(),
             "",
             "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
-            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation"
-                + " {",
+            "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation,"
+                + " Serializable {",
+            "  private static final long serialVersionUID = -2102364343628921304L;",
             "  private final int[] value;",
             "  private final MyEnum[] enums;",
             "",
@@ -426,6 +437,7 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoAnnotationProcessor())
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(annotationFactoryJavaFile, myEnumJavaFile, myAnnotationJavaFile);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -457,9 +469,7 @@
             "  @NotAutoAnnotation Empty notNewEmpty() {}",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(erroneousJavaFileObject);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(erroneousJavaFileObject);
     assertThat(compilation)
         .hadErrorContaining("NotAutoAnnotation")
         .inFile(erroneousJavaFileObject)
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
index 55a4c5d..094b570 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoAnnotationErrorsTest.java
@@ -57,9 +57,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation).succeededWithoutWarnings();
   }
 
@@ -79,9 +77,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation)
         .hadErrorContaining("must be static")
         .inFile(testSource)
@@ -103,9 +99,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation)
         .hadErrorContaining("must be an annotation type, not java.lang.String")
         .inFile(testSource)
@@ -132,9 +126,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation)
         .hadErrorContaining("@AutoAnnotation methods cannot be overloaded")
         .inFile(testSource)
@@ -196,9 +188,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation)
         .hadErrorContaining("method parameter 'fred' must have the same name")
         .inFile(testSource)
@@ -221,9 +211,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation)
         .hadErrorContaining(
             "method parameter 'value' has type java.lang.String "
@@ -267,9 +255,7 @@
               "  }",
               "}");
       Compilation compilation =
-          javac()
-              .withProcessors(new AutoAnnotationProcessor())
-              .compile(testSource, testAnnotation);
+          javac().withProcessors(new AutoAnnotationProcessor()).compile(testSource, testAnnotation);
       assertThat(compilation)
           .hadErrorContaining(
               "method parameter 'value' has type "
@@ -296,9 +282,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation)
         .hadErrorContaining(
             "method parameter 'other' must have the same name as a member of "
@@ -323,9 +307,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(TEST_ANNOTATION, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(TEST_ANNOTATION, testSource);
     assertThat(compilation)
         .hadErrorContaining("method needs a parameter with name 'value' and type int")
         .inFile(testSource)
@@ -357,9 +339,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(annotationSource, testSource);
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(annotationSource, testSource);
     assertThat(compilation)
         .hadErrorContaining(
             "@AutoAnnotation cannot yet supply a default value for annotation-valued member "
@@ -399,10 +379,7 @@
             "  }",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoAnnotationProcessor())
-            .compile(annotationSource, testSource);
-    assertThat(compilation)
-        .hadErrorContaining("variable value$ is already defined in constructor");
+        javac().withProcessors(new AutoAnnotationProcessor()).compile(annotationSource, testSource);
+    assertThat(compilation).hadErrorContaining("variable value$ is already defined in constructor");
   }
 }
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
new file mode 100644
index 0000000..50b6b27
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/AutoBuilderCompilationTest.java
@@ -0,0 +1,875 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
+import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
+
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.JavaFileObjects;
+import javax.tools.JavaFileObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AutoBuilderCompilationTest {
+  private static final JavaFileObject EXPECTED_SIMPLE_OUTPUT =
+      JavaFileObjects.forSourceLines(
+          "foo.bar.AutoBuilder_Baz_Builder",
+          "package foo.bar;",
+          "",
+          GeneratedImport.importGeneratedAnnotationType(),
+          "",
+          "@Generated(\"" + AutoBuilderProcessor.class.getName() + "\")",
+          "class AutoBuilder_Baz_Builder implements Baz.Builder {",
+          "  private Integer anInt;",
+          "  private String aString;",
+          "",
+          "  AutoBuilder_Baz_Builder() {}",
+          "",
+          "  @Override public Baz.Builder setAnInt(int anInt) {",
+          "    this.anInt = anInt;",
+          "    return this;",
+          "  }",
+          "",
+          "  @Override public Baz.Builder setAString(String aString) {",
+          "    if (aString == null) {",
+          "      throw new NullPointerException(\"Null aString\");",
+          "    }",
+          "    this.aString = aString;",
+          "    return this;",
+          "  }",
+          "",
+          "  @Override",
+          "  public Baz build() {",
+          "    if (this.anInt == null",
+          "        || this.aString == null) {",
+          "      StringBuilder missing = new StringBuilder();",
+          "      if (this.anInt == null) {",
+          "        missing.append(\" anInt\");",
+          "      }",
+          "      if (this.aString == null) {",
+          "        missing.append(\" aString\");",
+          "      }",
+          "      throw new IllegalStateException(\"Missing required properties:\" + missing);",
+          "    }",
+          "    return new Baz(",
+          "        this.anInt,",
+          "        this.aString);",
+          "  }",
+          "}");
+
+  @Test
+  public void simpleSuccess() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  private final int anInt;",
+            "  private final String aString;",
+            "",
+            "  public Baz(int anInt, String aString) {",
+            "    this.anInt = anInt;",
+            "    this.aString = aString;",
+            "  }",
+            "",
+            "  public int anInt() {",
+            "    return anInt;",
+            "  }",
+            "",
+            "  public String aString() {",
+            "    return aString;",
+            "  }",
+            "",
+            "  public static Builder builder() {",
+            "    return new AutoBuilder_Baz_Builder();",
+            "  }",
+            "",
+            "  @AutoBuilder",
+            "  public interface Builder {",
+            "    Builder setAnInt(int x);",
+            "    Builder setAString(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
+        .hasSourceEquivalentTo(EXPECTED_SIMPLE_OUTPUT);
+  }
+
+  @Test
+  public void simpleRecord() {
+    double version = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value());
+    assume().that(version).isAtLeast(16.0);
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public record Baz(int anInt, String aString) {",
+            "  public static Builder builder() {",
+            "    return new AutoBuilder_Baz_Builder();",
+            "  }",
+            "",
+            "  @AutoBuilder",
+            "  public interface Builder {",
+            "    Builder setAnInt(int x);",
+            "    Builder setAString(String x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoBuilder_Baz_Builder")
+        .hasSourceEquivalentTo(EXPECTED_SIMPLE_OUTPUT);
+  }
+
+  @Test
+  public void buildOtherPackage() {
+    JavaFileObject built =
+        JavaFileObjects.forSourceLines(
+            "com.example.Built",
+            "package com.example;",
+            "",
+            "public class Built {",
+            "  private final int anInt;",
+            "  private final String aString;",
+            "",
+            "  public Built(int anInt, String aString) {",
+            "    this.anInt = anInt;",
+            "    this.aString = aString;",
+            "  }",
+            "}");
+    JavaFileObject builder =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Builder",
+            "package foo.bar;",
+            "",
+            "import com.example.Built;",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "@AutoBuilder(ofClass = Built.class)",
+            "public interface Builder {",
+            "  public static Builder builder() {",
+            "    return new AutoBuilder_Builder();",
+            "  }",
+            "",
+            "  Builder setAnInt(int x);",
+            "  Builder setAString(String x);",
+            "  Built build();",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(built, builder);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation).generatedSourceFile("foo.bar.AutoBuilder_Builder");
+  }
+
+  @Test
+  public void autoBuilderOnEnum() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "@AutoBuilder",
+            "public enum Baz {",
+            "  ZIG, ZAG, DUSTIN,",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderWrongType] @AutoBuilder only applies to classes and interfaces")
+        .inFile(javaFileObject)
+        .onLineContaining("enum Baz");
+  }
+
+  @Test
+  public void autoBuilderPrivate() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  @AutoBuilder",
+            "  private interface Builder {",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("[AutoBuilderPrivate] @AutoBuilder class must not be private")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void autoBuilderNestedInPrivate() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  private static class Private {",
+            "    @AutoBuilder",
+            "    public interface Builder {",
+            "      Baz build();",
+            "    }",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderInPrivate] @AutoBuilder class must not be nested in a private class")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void autoBuilderInner() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  @AutoBuilder",
+            "  abstract class Builder {",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("[AutoBuilderInner] Nested @AutoBuilder class must be static")
+        .inFile(javaFileObject)
+        .onLineContaining("class Builder");
+  }
+
+  @Test
+  public void innerConstructor() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  class Inner {}",
+            "",
+            "  @AutoBuilder(ofClass = Inner.class)",
+            "  interface Builder {",
+            "    abstract Inner build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("[AutoBuilderInner] Nested @AutoBuilder ofClass class must be static")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void noVisibleConstructor() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  @AutoBuilder(ofClass = System.class)",
+            "  interface Builder {",
+            "    abstract System build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("[AutoBuilderNoVisible] No visible constructor for java.lang.System")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void noVisibleMethod() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  private static Baz of() {",
+            "    return new Baz();",
+            "  }",
+            "",
+            "  @AutoBuilder(callMethod = \"of\")",
+            "  interface Builder {",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderNoVisible] No visible static method named \"of\" for foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void methodNotStatic() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  Baz of() {",
+            "    return this;",
+            "  }",
+            "",
+            "  @AutoBuilder(callMethod = \"of\")",
+            "  interface Builder {",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderNoVisible] No visible static method named \"of\" for foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void noMatchingConstructor() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  public Baz(int notMe) {}",
+            "",
+            "  public Baz(String notMeEither) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    Builder setBuh(String x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderNoMatch] Property names do not correspond to the parameter names of any"
+                + " constructor:\n"
+                + "    Baz(int notMe)\n"
+                + "    Baz(java.lang.String notMeEither)")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void twoMatchingConstructors() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public class Baz {",
+            "  public Baz() {}",
+            "",
+            "  public Baz(int buh) {}",
+            "",
+            "  public Baz(String buh) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    Builder setBuh(String x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderAmbiguous] Property names correspond to more than one constructor:\n"
+                + "    Baz(int buh)\n"
+                + "    Baz(java.lang.String buh)")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void constructInterface() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "public interface Baz {",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderEnclosing] @AutoBuilder must specify ofClass=Something.class or it must"
+                + " be nested inside the class to be built; actually nested inside interface"
+                + " foo.bar.Baz")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void inconsistentSetPrefix() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  Baz(int one, int two) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder one(int x);",
+            "    abstract Builder setTwo(int x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderSetNotSet] If any setter methods use the setFoo convention then all must")
+        .inFile(javaFileObject)
+        .onLineContaining("Builder one(int x)");
+  }
+
+  @Test
+  public void missingSetter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  Baz(int one, int two) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder one(int x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderBuilderMissingMethod] Expected a method with this signature:"
+                + " foo.bar.Baz.Builder two(int), or a twoBuilder() method")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder");
+  }
+
+  @Test
+  public void tooManyArgs() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  Baz(int one, int two) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder one(int x);",
+            "    abstract Builder two(int x);",
+            "    abstract Builder many(int x, int y);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining("[AutoBuilderBuilderArgs] Builder methods must have 0 or 1 parameters")
+        .inFile(javaFileObject)
+        .onLineContaining("many(int x, int y)");
+  }
+
+  @Test
+  public void alienNoArgMethod() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  Baz(int one, int two) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder one(int x);",
+            "    abstract Builder two(int x);",
+            "    abstract String alien();",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderBuilderNoArg] Method without arguments should be a build method returning"
+                + " foo.bar.Baz, or a getter method with the same name and type as a parameter of"
+                + " Baz(int one, int two), or fooBuilder() where foo is a parameter of Baz(int"
+                + " one, int two)")
+        .inFile(javaFileObject)
+        .onLineContaining("String alien()");
+  }
+
+  @Test
+  public void alienOneArgMethod() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  Baz(int one, int two) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder one(int x);",
+            "    abstract Builder two(int x);",
+            "    abstract Builder three(int x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderBuilderWhatProp] Method three does not correspond to "
+                + "a parameter of Baz(int one, int two)")
+        .inFile(javaFileObject)
+        .onLineContaining("three(int x)");
+  }
+
+  @Test
+  public void setterReturnType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  Baz(int one, int two) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder one(int x);",
+            "    abstract void two(int x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderBuilderRet] Setter methods must return foo.bar.Baz.Builder")
+        .inFile(javaFileObject)
+        .onLineContaining("two(int x)");
+  }
+
+  @Test
+  public void nullableSetterForNonNullableParameter() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "import org.checkerframework.checker.nullness.qual.Nullable;",
+            "",
+            "class Baz {",
+            "  Baz(String thing) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder thing(@Nullable String x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    JavaFileObject nullableFileObject =
+        JavaFileObjects.forSourceLines(
+            "org.checkerframework.checker.nullness.qual.Nullable",
+            "package org.jspecify.nullness;",
+            "",
+            "import java.lang.annotation.ElementType;",
+            "import java.lang.annotation.Target;",
+            "",
+            "@Target(ElementType.TYPE_USE)",
+            "public @interface Nullable {}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject, nullableFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderNullNotNull] Parameter of setter method is @Nullable but parameter"
+                + " \"thing\" of Baz(java.lang.String thing) is not")
+        .inFile(javaFileObject)
+        .onLineContaining("thing(@Nullable String x)");
+  }
+
+  @Test
+  public void setterWrongType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "",
+            "class Baz {",
+            "  Baz(int up, int down) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder up(int x);",
+            "    abstract Builder down(String x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderGetVsSet] Parameter type java.lang.String of setter method should be int"
+                + " to match parameter \"down\" of Baz(int up, int down)")
+        .inFile(javaFileObject)
+        .onLineContaining("down(String x)");
+  }
+
+  @Test
+  public void setterWrongTypeEvenWithConversion() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "import java.util.Optional;",
+            "",
+            "class Baz {",
+            "  Baz(Optional<String> maybe) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder {",
+            "    abstract Builder maybe(int x);",
+            "    abstract Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderGetVsSetOrConvert] Parameter type int of setter method should be"
+                + " java.util.Optional<java.lang.String> to match parameter \"maybe\" of"
+                + " Baz(java.util.Optional<java.lang.String> maybe), or it should be a type that"
+                + " can be passed to Optional.of to produce java.util.Optional<java.lang.String>")
+        .inFile(javaFileObject)
+        .onLineContaining("maybe(int x)");
+  }
+
+  @Test
+  public void typeParamMismatch() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoBuilder;",
+            "import java.util.Optional;",
+            "",
+            "class Baz<T> {",
+            "  Baz(T param) {}",
+            "",
+            "  @AutoBuilder",
+            "  interface Builder<E> {",
+            "    abstract Builder<E> param(E param);",
+            "    abstract Baz<E> build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoBuilderProcessor())
+            .withOptions("-Acom.google.auto.value.AutoBuilderIsUnstable")
+            .compile(javaFileObject);
+    assertThat(compilation).failed();
+    assertThat(compilation)
+        .hadErrorContaining(
+            "[AutoBuilderTypeParams] Builder type parameters <E> must match type parameters <T> of"
+                + " Baz(T param)")
+        .inFile(javaFileObject)
+        .onLineContaining("interface Builder<E>");
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
index 63e8419..788b543 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoOneOfCompilationTest.java
@@ -43,7 +43,9 @@
             "import java.io.Serializable;",
             "",
             "@AutoOneOf(TaskResult.Kind.class)",
-            "public abstract class TaskResult<V, T extends Throwable> {",
+            "public abstract class TaskResult<V, T extends Throwable> implements Serializable {",
+            "  private static final long serialVersionUID = 1234L;",
+            "",
             "  public enum Kind {VALUE, EXCEPTION, EMPTY}",
             "  public abstract Kind getKind();",
             "",
@@ -92,6 +94,8 @@
             "  // Parent class that each implementation will inherit from.",
             "  private abstract static class Parent_<V, T extends Throwable> "
                 + "extends TaskResult<V, T> {",
+            "    private static final long serialVersionUID = 1234L;",
+            "",
             "    @Override",
             "    public V value() {",
             "      throw new UnsupportedOperationException(getKind().toString());",
@@ -111,6 +115,8 @@
             "  // Implementation when the contained property is \"value\".",
             "  private static final class Impl_value<V, T extends Throwable> "
                 + "extends Parent_<V, T> {",
+            "    private static final long serialVersionUID = 1234L;",
+            "",
             "    private final V value;",
             "",
             "    Impl_value(V value) {",
@@ -152,6 +158,8 @@
             "  // Implementation when the contained property is \"exception\".",
             "  private static final class Impl_exception<V, T extends Throwable> "
                 + "extends Parent_<V, T> {",
+            "    private static final long serialVersionUID = 1234L;",
+            "",
             "    private final Throwable exception;",
             "",
             "    Impl_exception(Throwable exception) {",
@@ -193,6 +201,8 @@
             "  // Implementation when the contained property is \"empty\".",
             "  private static final class Impl_empty<V, T extends Throwable> "
                 + "extends Parent_<V, T> {",
+            "    private static final long serialVersionUID = 1234L;",
+            "",
             "    static final Impl_empty<?, ?> INSTANCE = new Impl_empty<>();",
             "",
             "    private Impl_empty() {}",
@@ -200,6 +210,10 @@
             "    @Override",
             "    public void empty() {}",
             "",
+            "    private Object readResolve() {",
+            "      return INSTANCE;",
+            "    }",
+            "",
             "    @Override",
             "    public String toString() {",
             "      return \"TaskResult{empty}\";",
@@ -223,7 +237,8 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoOneOfProcessor())
-            .withOptions("-Xlint:-processing", "-implicit:none")
+            .withOptions(
+                "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(javaFileObject);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -307,7 +322,8 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoOneOfProcessor())
-            .withOptions("-Xlint:-processing", "-implicit:none")
+            .withOptions(
+                "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(javaFileObject);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
diff --git a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
index e46c9f5..ab6690f 100644
--- a/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/AutoValueCompilationTest.java
@@ -57,8 +57,8 @@
   public void simpleSuccess() {
     // Positive test case that ensures we generate the expected code for at least one case.
     // Most AutoValue code-generation tests are functional, meaning that we check that the generated
-    // code does the right thing rather than checking what it looks like, but this test is a sanity
-    // check that we are not generating correct but weird code.
+    // code does the right thing rather than checking what it looks like, but this test checks that
+    // we are not generating correct but weird code.
     JavaFileObject javaFileObject =
         JavaFileObjects.forSourceLines(
             "foo.bar.Baz",
@@ -118,7 +118,10 @@
             "  }",
             "}");
     Compilation compilation =
-        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
+            .compile(javaFileObject);
     assertThat(compilation)
         .generatedSourceFile("foo.bar.AutoValue_Baz")
         .hasSourceEquivalentTo(expectedOutput);
@@ -216,7 +219,10 @@
             "  }",
             "}");
     Compilation compilation =
-        javac().withProcessors(new AutoValueProcessor()).compile(javaFileObject);
+        javac()
+            .withProcessors(new AutoValueProcessor())
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
+            .compile(javaFileObject);
     assertThat(compilation)
         .generatedSourceFile("foo.bar.AutoValue_Baz")
         .hasSourceEquivalentTo(expectedOutput);
@@ -335,7 +341,6 @@
             "    return false;",
             "  }",
             "",
-
             "  @Override",
             "  public int hashCode() {",
             "    int h$ = 1;",
@@ -348,7 +353,8 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoValueProcessor())
-            .withOptions("-Xlint:-processing", "-implicit:none")
+            .withOptions(
+                "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(annotFileObject, outerFileObject, nestyFileObject);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -1129,10 +1135,10 @@
             "      return this.anInt == that.anInt()",
             "          && Arrays.equals(this.aByteArray, "
                 + "(that instanceof AutoValue_Baz) "
-                + "? ((AutoValue_Baz) that).aByteArray : that.aByteArray())",
+                + "? ((AutoValue_Baz<?>) that).aByteArray : that.aByteArray())",
             "          && Arrays.equals(this.aNullableIntArray, "
                 + "(that instanceof AutoValue_Baz) "
-                + "? ((AutoValue_Baz) that).aNullableIntArray : that.aNullableIntArray())",
+                + "? ((AutoValue_Baz<?>) that).aNullableIntArray : that.aNullableIntArray())",
             "          && this.aList.equals(that.aList())",
             "          && this.anImmutableList.equals(that.anImmutableList())",
             "          && this.anOptionalString.equals(that.anOptionalString())",
@@ -1312,17 +1318,19 @@
                 + "NestedAutoValue.builder();",
             "        this.aNestedAutoValue = aNestedAutoValue$builder.build();",
             "      }",
-            "      String missing = \"\";",
-            "      if (this.anInt == null) {",
-            "        missing += \" anInt\";",
-            "      }",
-            "      if (this.aByteArray == null) {",
-            "        missing += \" aByteArray\";",
-            "      }",
-            "      if (this.aList == null) {",
-            "        missing += \" aList\";",
-            "      }",
-            "      if (!missing.isEmpty()) {",
+            "      if (this.anInt == null",
+            "          || this.aByteArray == null",
+            "          || this.aList == null) {",
+            "        StringBuilder missing = new StringBuilder();",
+            "        if (this.anInt == null) {",
+            "          missing.append(\" anInt\");",
+            "        }",
+            "        if (this.aByteArray == null) {",
+            "          missing.append(\" aByteArray\");",
+            "        }",
+            "        if (this.aList == null) {",
+            "          missing.append(\" aList\");",
+            "        }",
             "        throw new IllegalStateException(\"Missing required properties:\" + missing);",
             "      }",
             "      return new AutoValue_Baz<T>(",
@@ -1339,7 +1347,8 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoValueProcessor())
-            .withOptions("-Xlint:-processing", "-implicit:none")
+            .withOptions(
+                "-Xlint:-processing", "-implicit:none", "-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(javaFileObject, nestedJavaFileObject);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -1587,7 +1596,7 @@
     assertThat(compilation)
         .hadErrorContaining(
             "Parameter type java.lang.String of setter method should be int "
-                + "to match getter foo.bar.Baz.blim")
+                + "to match property method foo.bar.Baz.blim()")
         .inFile(javaFileObject)
         .onLineContaining("Builder blim(String x)");
   }
@@ -1620,10 +1629,10 @@
             .compile(javaFileObject);
     assertThat(compilation)
         .hadErrorContaining(
-            "Parameter type java.lang.String of setter method should be "
-                + "com.google.common.collect.ImmutableList<java.lang.String> to match getter "
-                + "foo.bar.Baz.blam, or it should be a type that can be passed to "
-                + "ImmutableList.copyOf")
+            "Parameter type java.lang.String of setter method should be"
+                + " com.google.common.collect.ImmutableList<java.lang.String> to match property"
+                + " method foo.bar.Baz.blam(), or it should be a type that can be passed to"
+                + " ImmutableList.copyOf")
         .inFile(javaFileObject)
         .onLineContaining("Builder blam(String x)");
   }
@@ -1657,11 +1666,11 @@
             .compile(javaFileObject);
     assertThat(compilation)
         .hadErrorContaining(
-            "Parameter type java.util.Collection<java.lang.Integer> of setter method should be "
-                + "com.google.common.collect.ImmutableList<java.lang.String> to match getter "
-                + "foo.bar.Baz.blam, or it should be a type that can be passed to "
-                + "ImmutableList.copyOf to produce "
-                + "com.google.common.collect.ImmutableList<java.lang.String>")
+            "Parameter type java.util.Collection<java.lang.Integer> of setter method should be"
+                + " com.google.common.collect.ImmutableList<java.lang.String> to match property"
+                + " method foo.bar.Baz.blam(), or it should be a type that can be passed to"
+                + " ImmutableList.copyOf to produce"
+                + " com.google.common.collect.ImmutableList<java.lang.String>")
         .inFile(javaFileObject)
         .onLineContaining("Builder blam(Collection<Integer> x)");
   }
@@ -1694,7 +1703,7 @@
     assertThat(compilation)
         .hadErrorContaining(
             "Parameter type java.lang.String of setter method should be int "
-                + "to match getter foo.bar.Baz.getBlim")
+                + "to match property method foo.bar.Baz.getBlim()")
         .inFile(javaFileObject)
         .onLineContaining("Builder blim(String x)");
   }
@@ -1768,7 +1777,8 @@
             .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
             .compile(javaFileObject);
     assertThat(compilation)
-        .hadErrorContaining("Method does not correspond to a property of foo.bar.Item")
+        .hadErrorContaining(
+            "Method setTitle does not correspond to a property method of foo.bar.Item")
         .inFile(javaFileObject)
         .onLineContaining("Builder setTitle(String title)");
     assertThat(compilation)
@@ -1802,7 +1812,7 @@
             .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
             .compile(javaFileObject);
     assertThat(compilation)
-        .hadErrorContaining("Method does not correspond to a property of foo.bar.Baz")
+        .hadErrorContaining("Method blim does not correspond to a property method of foo.bar.Baz")
         .inFile(javaFileObject)
         .onLineContaining("Builder blim(int x)");
   }
@@ -1839,6 +1849,35 @@
   }
 
   @Test
+  public void autoValueBuilderSetterReturnType() {
+    JavaFileObject javaFileObject =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "public abstract class Baz {",
+            "  abstract int blim();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    void blim(int x);",
+            "    Baz build();",
+            "  }",
+            "}");
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation)
+        .hadErrorContaining("Setter methods must return foo.bar.Baz.Builder")
+        .inFile(javaFileObject)
+        .onLineContaining("void blim(int x)");
+  }
+
+  @Test
   public void autoValueBuilderWrongTypeGetter() {
     JavaFileObject javaFileObject =
         JavaFileObjects.forSourceLines(
@@ -1866,10 +1905,15 @@
             .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
             .compile(javaFileObject);
     assertThat(compilation)
-        .hadErrorContaining(
-            "Method matches a property of foo.bar.Baz but has return type T instead of U")
+        .hadErrorContainingMatch(
+            "Method matches a property of foo\\.bar\\.Baz<T, ?U> but has return type T instead of"
+                + " U")
         .inFile(javaFileObject)
         .onLineContaining("T blam()");
+    // The <T, ?U> is because we're depending on TypeMirror.toString(), and the JDK actually spells
+    // this as <T,U> with no space. While it's not completely sound to expect a given string from
+    // TypeMirror.toString(), in practice it's hard to imagine that it would be anything other
+    // than "foo.bar.Baz<T,U>" or "foo.bar.Baz<T, U>" given the specification.
   }
 
   @Test
@@ -1929,9 +1973,9 @@
             .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
             .compile(javaFileObject);
     assertThat(compilation)
-        .hadErrorContaining("Property strings has a property builder so it cannot be @Nullable")
+        .hadErrorContaining("Property strings is @Nullable so it cannot have a property builder")
         .inFile(javaFileObject)
-        .onLineContaining("@Nullable ImmutableList<String> strings()");
+        .onLineContaining("stringsBuilder()");
   }
 
   @Test
@@ -1963,9 +2007,9 @@
             .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
             .compile(javaFileObject);
     assertThat(compilation)
-        .hadErrorContaining("Property strings has a property builder so it cannot be @Nullable")
+        .hadErrorContaining("Property strings is @Nullable so it cannot have a property builder")
         .inFile(javaFileObject)
-        .onLineContaining("@Nullable ImmutableList<String> strings()");
+        .onLineContaining("stringsBuilder()");
   }
 
   @Test
@@ -2451,8 +2495,8 @@
     assertThat(compilation)
         .hadErrorContaining(
             "Method without arguments should be a build method returning foo.bar.Baz, or a getter"
-                + " method with the same name and type as a getter method of foo.bar.Baz, or"
-                + " fooBuilder() where foo() or getFoo() is a getter method of foo.bar.Baz")
+                + " method with the same name and type as a property method of foo.bar.Baz, or"
+                + " fooBuilder() where foo() or getFoo() is a property method of foo.bar.Baz")
         .inFile(javaFileObject)
         .onLineContaining("Builder whut()");
   }
@@ -2481,7 +2525,7 @@
             .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
             .compile(javaFileObject);
     assertThat(compilation)
-        .hadErrorContaining("Method does not correspond to a property of foo.bar.Baz")
+        .hadErrorContaining("Method whut does not correspond to a property method of foo.bar.Baz")
         .inFile(javaFileObject)
         .onLineContaining("void whut(String x)");
   }
@@ -2539,7 +2583,8 @@
             .compile(javaFileObject);
     assertThat(compilation)
         .hadErrorContaining(
-            "Builder must have a single no-argument method returning foo.bar.Baz<T>")
+            "Builder must have a single no-argument method, typically called build(), that returns"
+                + " foo.bar.Baz<T>")
         .inFile(javaFileObject)
         .onLineContaining("public interface Builder<T>");
   }
@@ -2569,11 +2614,15 @@
             .withProcessors(new AutoValueProcessor(), new AutoValueBuilderProcessor())
             .compile(javaFileObject);
     assertThat(compilation)
-        .hadErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
+        .hadErrorContaining(
+            "Builder must have a single no-argument method, typically called build(), that returns"
+                + " foo.bar.Baz")
         .inFile(javaFileObject)
         .onLineContaining("Baz build()");
     assertThat(compilation)
-        .hadErrorContaining("Builder must have a single no-argument method returning foo.bar.Baz")
+        .hadErrorContaining(
+            "Builder must have a single no-argument method, typically called build(), that returns"
+                + " foo.bar.Baz")
         .inFile(javaFileObject)
         .onLineContaining("Baz create()");
   }
@@ -3286,7 +3335,7 @@
             "}");
     private static final String GENERATED_PROPERTY_TYPE =
         String.join(
-            "\n",
+            "\n", //
             "package foo.baz;",
             "",
             "public class GeneratedPropertyType {}");
@@ -3315,18 +3364,14 @@
         GENERATED_TYPES.forEach(
             (typeName, source) -> {
               try {
-                JavaFileObject generated =
-                    processingEnv
-                        .getFiler()
-                        .createSourceFile(typeName);
+                JavaFileObject generated = processingEnv.getFiler().createSourceFile(typeName);
                 try (Writer writer = generated.openWriter()) {
                   writer.write(source);
                 }
               } catch (IOException e) {
                 throw new UncheckedIOException(e);
               }
-            }
-        );
+            });
       }
       return false;
     }
@@ -3337,7 +3382,41 @@
     }
   }
 
+  // This is a regression test for the problem described in
+  // https://github.com/google/auto/issues/1087.
+  @Test
+  public void kotlinMetadataAnnotationsAreImplicitlyExcludedFromCopying() {
+    JavaFileObject metadata =
+        JavaFileObjects.forSourceLines(
+            "kotlin.Metadata", "package kotlin;", "", "public @interface Metadata {", "}");
+    JavaFileObject test =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Test",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import kotlin.Metadata;",
+            "",
+            "@AutoValue.CopyAnnotations",
+            "@Metadata",
+            "@AutoValue",
+            "public abstract class Test {",
+            "  public abstract String string();",
+            "}");
+    AutoValueProcessor autoValueProcessor = new AutoValueProcessor();
+    Compilation compilation =
+        javac()
+            .withProcessors(autoValueProcessor)
+            .withOptions("-Xlint:-processing", "-implicit:none")
+            .compile(test, metadata);
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Test")
+        .contentsAsUtf8String()
+        .doesNotContain("kotlin.Metadata");
+  }
+
   private String sorted(String... imports) {
-     return Arrays.stream(imports).sorted().collect(joining("\n"));
- }
+    return Arrays.stream(imports).sorted().collect(joining("\n"));
+  }
 }
diff --git a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
index ce9eeed..56eaad2 100644
--- a/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/ExtensionTest.java
@@ -172,6 +172,7 @@
     Compilation compilation =
         javac()
             .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
             .compile(javaFileObject);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
@@ -250,9 +251,7 @@
             "  abstract String dizzle();",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoValueProcessor(ImmutableList.of(ext1, ext2)))
-            .compile(impl);
+        javac().withProcessors(new AutoValueProcessor(ImmutableList.of(ext1, ext2))).compile(impl);
     assertThat(compilation)
         .hadErrorContaining("wants to consume a method that was already consumed")
         .inFile(impl)
@@ -596,10 +595,9 @@
             "}");
     Compilation compilation =
         javac()
-        .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
-        .compile(javaFileObject);
-    assertThat(compilation)
-        .hadErrorContaining("writeToParcel");
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(new FooExtension())))
+            .compile(javaFileObject);
+    assertThat(compilation).hadErrorContaining("writeToParcel");
     assertThat(compilation)
         .hadWarningContaining(
             "Abstract method is neither a property getter nor a Builder converter, "
@@ -647,13 +645,12 @@
             "public abstract class Baz {",
             "}");
     Compilation compilation =
-        javac()
-            .withProcessors(new AutoValueProcessor(badJarLoader))
-            .compile(javaFileObject);
+        javac().withProcessors(new AutoValueProcessor(badJarLoader)).compile(javaFileObject);
     assertThat(compilation).succeeded();
-    assertThat(compilation).hadWarningContaining(
-        "This may be due to a corrupt jar file in the compiler's classpath.\n  "
-            + ServiceConfigurationError.class.getName());
+    assertThat(compilation)
+        .hadWarningContaining(
+            "This may be due to a corrupt jar file in the compiler's classpath.\n  "
+                + ServiceConfigurationError.class.getName());
     assertThat(compilation).generatedSourceFile("foo.bar.AutoValue_Baz");
   }
 
@@ -857,8 +854,12 @@
       String sideClassName = "Side_" + context.autoValueClass().getSimpleName();
       String sideClass =
           "" //
-          + "package " + context.packageName() + ";\n"
-          + "class " + sideClassName + " {}\n";
+              + "package "
+              + context.packageName()
+              + ";\n"
+              + "class "
+              + sideClassName
+              + " {}\n";
       Filer filer = context.processingEnvironment().getFiler();
       try {
         String sideClassFqName = context.packageName() + "." + sideClassName;
@@ -912,25 +913,27 @@
 
   @Test
   public void propertyTypes() {
-    JavaFileObject parent = JavaFileObjects.forSourceLines(
-        "foo.bar.Parent",
-        "package foo.bar;",
-        "",
-        "import java.util.List;",
-        "",
-        "interface Parent<T> {",
-        "  T thing();",
-        "  List<T> list();",
-        "}");
-    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
-        "foo.bar.Baz",
-        "package foo.bar;",
-        "",
-        "import com.google.auto.value.AutoValue;",
-        "",
-        "@AutoValue",
-        "abstract class Baz implements Parent<String> {",
-        "}");
+    JavaFileObject parent =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Parent",
+            "package foo.bar;",
+            "",
+            "import java.util.List;",
+            "",
+            "interface Parent<T> {",
+            "  T thing();",
+            "  List<T> list();",
+            "}");
+    JavaFileObject autoValueClass =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "abstract class Baz implements Parent<String> {",
+            "}");
     ContextChecker checker =
         context -> {
           assertThat(context.builder()).isEmpty();
@@ -955,15 +958,16 @@
 
   @Test
   public void finalAutoValueClassName() {
-    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
-        "foo.bar.Baz",
-        "package foo.bar;",
-        "",
-        "import com.google.auto.value.AutoValue;",
-        "",
-        "@AutoValue",
-        "abstract class Baz {",
-        "}");
+    JavaFileObject autoValueClass =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "abstract class Baz {",
+            "}");
     ContextChecker checker =
         context -> {
           assertThat(context.finalAutoValueClassName()).isEqualTo("foo.bar.AutoValue_Baz");
@@ -982,43 +986,45 @@
 
   @Test
   public void builderContext() {
-    JavaFileObject parent = JavaFileObjects.forSourceLines(
-        "foo.bar.Parent",
-        "package foo.bar;",
-        "",
-        "import com.google.common.collect.ImmutableList;",
-        "",
-        "interface Parent<T> {",
-        "  T thing();",
-        "  ImmutableList<T> list();",
-        "}");
-    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
-        "foo.bar.Baz",
-        "package foo.bar;",
-        "",
-        "import com.google.auto.value.AutoValue;",
-        "import com.google.common.collect.ImmutableList;",
-        "",
-        "@AutoValue",
-        "abstract class Baz implements Parent<String> {",
-        "  static Builder builder() {",
-        "    return new AutoValue_Baz.Builder();",
-        "  }",
-        "",
-        "  abstract Builder toBuilder();",
-        "",
-        "  @AutoValue.Builder",
-        "  abstract static class Builder {",
-        "    abstract Builder setThing(String x);",
-        "    abstract Builder setList(Iterable<String> x);",
-        "    abstract Builder setList(ImmutableList<String> x);",
-        "    abstract ImmutableList.Builder<String> listBuilder();",
-        "    abstract Baz autoBuild();",
-        "    Baz build() {",
-        "      return autoBuild();",
-        "    }",
-        "  }",
-        "}");
+    JavaFileObject parent =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Parent",
+            "package foo.bar;",
+            "",
+            "import com.google.common.collect.ImmutableList;",
+            "",
+            "interface Parent<T> {",
+            "  T thing();",
+            "  ImmutableList<T> list();",
+            "}");
+    JavaFileObject autoValueClass =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "",
+            "@AutoValue",
+            "abstract class Baz implements Parent<String> {",
+            "  static Builder builder() {",
+            "    return new AutoValue_Baz.Builder();",
+            "  }",
+            "",
+            "  abstract Builder toBuilder();",
+            "",
+            "  @AutoValue.Builder",
+            "  abstract static class Builder {",
+            "    abstract Builder setThing(String x);",
+            "    abstract Builder setList(Iterable<String> x);",
+            "    abstract Builder setList(ImmutableList<String> x);",
+            "    abstract ImmutableList.Builder<String> listBuilder();",
+            "    abstract Baz autoBuild();",
+            "    Baz build() {",
+            "      return autoBuild();",
+            "    }",
+            "  }",
+            "}");
     ContextChecker checker =
         context -> {
           assertThat(context.builder()).isPresent();
@@ -1075,27 +1081,102 @@
   }
 
   @Test
+  public void builderContextWithInheritance() {
+    JavaFileObject parent =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Parent",
+            "package foo.bar;",
+            "",
+            "interface Parent<BuilderT> {",
+            "  BuilderT toBuilder();",
+            "  interface Builder<T, BuilderT, BuiltT> {",
+            "    BuilderT setThing(T x);",
+            "    BuiltT build();",
+            "  }",
+            "}");
+    JavaFileObject autoValueClass =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "",
+            "@AutoValue",
+            "abstract class Baz<T> implements Parent<Baz.Builder<T>> {",
+            "  abstract T thing();",
+            "  static <T> Builder<T> builder() {",
+            "    return new AutoValue_Baz.Builder<>();",
+            "  }",
+            "",
+            "  @AutoValue.Builder",
+            "  abstract static class Builder<T> implements Parent.Builder<T, Builder<T>, Baz<T>> {",
+            "  }",
+            "}");
+    ContextChecker checker =
+        context -> {
+          assertThat(context.builder()).isPresent();
+          BuilderContext builderContext = context.builder().get();
+
+          assertThat(builderContext.builderType().getQualifiedName().toString())
+              .isEqualTo("foo.bar.Baz.Builder");
+
+          Set<ExecutableElement> builderMethods = builderContext.builderMethods();
+          assertThat(builderMethods).hasSize(1);
+          ExecutableElement builderMethod = Iterables.getOnlyElement(builderMethods);
+          assertThat(builderMethod.getSimpleName().toString()).isEqualTo("builder");
+
+          Set<ExecutableElement> toBuilderMethods = builderContext.toBuilderMethods();
+          assertThat(toBuilderMethods).hasSize(1);
+          ExecutableElement toBuilderMethod = Iterables.getOnlyElement(toBuilderMethods);
+          assertThat(toBuilderMethod.getSimpleName().toString()).isEqualTo("toBuilder");
+
+          Optional<ExecutableElement> buildMethod = builderContext.buildMethod();
+          assertThat(buildMethod).isPresent();
+          assertThat(buildMethod.get().getSimpleName().toString()).isEqualTo("build");
+          assertThat(buildMethod.get().getParameters()).isEmpty();
+          assertThat(buildMethod.get().getReturnType().toString()).isEqualTo("BuiltT");
+
+          ExecutableElement autoBuildMethod = builderContext.autoBuildMethod();
+          assertThat(autoBuildMethod).isEqualTo(buildMethod.get());
+
+          Map<String, Set<ExecutableElement>> setters = builderContext.setters();
+          assertThat(setters.keySet()).containsExactly("thing");
+          Set<ExecutableElement> thingSetters = setters.get("thing");
+          assertThat(thingSetters).hasSize(1);
+          ExecutableElement thingSetter = Iterables.getOnlyElement(thingSetters);
+          assertThat(thingSetter.getSimpleName().toString()).isEqualTo("setThing");
+        };
+    ContextCheckingExtension extension = new ContextCheckingExtension(checker);
+    Compilation compilation =
+        javac()
+            .withProcessors(new AutoValueProcessor(ImmutableList.of(extension)))
+            .compile(autoValueClass, parent);
+    assertThat(compilation).succeededWithoutWarnings();
+  }
+
+  @Test
   public void oddBuilderContext() {
-    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
-        "foo.bar.Baz",
-        "package foo.bar;",
-        "",
-        "import com.google.auto.value.AutoValue;",
-        "import com.google.common.collect.ImmutableList;",
-        "",
-        "@AutoValue",
-        "abstract class Baz {",
-        "  abstract String string();",
-        "",
-        "  @AutoValue.Builder",
-        "  abstract static class Builder {",
-        "    abstract Builder setString(String x);",
-        "    abstract Baz oddBuild();",
-        "    Baz build(int butNotReallyBecauseOfThisParameter) {",
-        "      return null;",
-        "    }",
-        "  }",
-        "}");
+    JavaFileObject autoValueClass =
+        JavaFileObjects.forSourceLines(
+            "foo.bar.Baz",
+            "package foo.bar;",
+            "",
+            "import com.google.auto.value.AutoValue;",
+            "import com.google.common.collect.ImmutableList;",
+            "",
+            "@AutoValue",
+            "abstract class Baz {",
+            "  abstract String string();",
+            "",
+            "  @AutoValue.Builder",
+            "  abstract static class Builder {",
+            "    abstract Builder setString(String x);",
+            "    abstract Baz oddBuild();",
+            "    Baz build(int butNotReallyBecauseOfThisParameter) {",
+            "      return null;",
+            "    }",
+            "  }",
+            "}");
     ContextChecker checker =
         context -> {
           assertThat(context.builder()).isPresent();
@@ -1126,25 +1207,26 @@
   // https://github.com/google/auto/issues/809
   @Test
   public void propertyErrorShouldNotCrash() {
-    JavaFileObject autoValueClass = JavaFileObjects.forSourceLines(
-        "test.Test",
-        "package test;",
-        "import com.google.auto.value.AutoValue;",
-        "import java.util.List;",
-        "",
-        "@AutoValue",
-        "public abstract class Test {",
-        "  abstract Integer property();",
-        "  abstract List<String> listProperty();",
-        "",
-        "  @AutoValue.Builder",
-        "  public interface Builder {",
-        "    Builder property(Integer property);",
-        "    Builder listProperty(List<String> listProperty);",
-        "    Builder listProperty(Integer listPropertyValues);",
-        "    Test build();",
-        "  }",
-        "}");
+    JavaFileObject autoValueClass =
+        JavaFileObjects.forSourceLines(
+            "test.Test",
+            "package test;",
+            "import com.google.auto.value.AutoValue;",
+            "import java.util.List;",
+            "",
+            "@AutoValue",
+            "public abstract class Test {",
+            "  abstract Integer property();",
+            "  abstract List<String> listProperty();",
+            "",
+            "  @AutoValue.Builder",
+            "  public interface Builder {",
+            "    Builder property(Integer property);",
+            "    Builder listProperty(List<String> listProperty);",
+            "    Builder listProperty(Integer listPropertyValues);",
+            "    Test build();",
+            "  }",
+            "}");
     // We don't actually expect the extension to be invoked. Previously it was, and that led to a
     // NullPointerException when calling .setters() in the checker.
     ContextChecker checker =
diff --git a/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
index f3d3d61..18cca5e 100644
--- a/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/GeneratedDoesNotExistTest.java
@@ -48,6 +48,8 @@
  */
 @RunWith(Parameterized.class)
 public class GeneratedDoesNotExistTest {
+  private static final ImmutableList<String> STANDARD_OPTIONS =
+      ImmutableList.of("-A" + Nullables.NULLABLE_OPTION + "=");
 
   @Parameters(name = "{0}")
   public static Collection<Object[]> data() {
@@ -57,12 +59,16 @@
       // TODO(b/72513371): use --release 8 once compile-testing supports that
       params.add(
           new Object[] {
-            ImmutableList.of(), "javax.annotation.processing.Generated",
+            STANDARD_OPTIONS, "javax.annotation.processing.Generated",
           });
     }
     params.add(
         new Object[] {
-          ImmutableList.of("-source", "8", "-target", "8"), "javax.annotation.Generated",
+          ImmutableList.<String>builder()
+              .addAll(STANDARD_OPTIONS)
+              .add("-source", "8", "-target", "8")
+              .build(),
+          "javax.annotation.Generated",
         });
     return params.build();
   }
@@ -223,9 +229,9 @@
     Processor noGeneratedProcessor = partialProxy(Processor.class, handler);
     Compilation compilation =
         javac()
-        .withOptions(javacOptions)
-        .withProcessors(noGeneratedProcessor)
-        .compile(javaFileObject);
+            .withOptions(javacOptions)
+            .withProcessors(noGeneratedProcessor)
+            .compile(javaFileObject);
     assertThat(compilation).succeededWithoutWarnings();
     assertThat(compilation)
         .generatedSourceFile("foo.bar.AutoValue_Baz")
diff --git a/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java
index 27cb093..472f62d 100644
--- a/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/IncrementalExtensionTest.java
@@ -22,6 +22,7 @@
 import com.google.auto.value.extension.AutoValueExtension.IncrementalExtensionType;
 import com.google.auto.value.extension.memoized.processor.MemoizeExtension;
 import com.google.auto.value.extension.serializable.processor.SerializableAutoValueExtension;
+import com.google.auto.value.extension.toprettystring.processor.ToPrettyStringExtension;
 import com.google.common.collect.ImmutableList;
 import javax.annotation.processing.ProcessingEnvironment;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
@@ -46,7 +47,10 @@
     // different <?>.
     assertThat(builtInExtensions)
         .comparingElementsUsing(transforming(e -> (Object) e.getClass(), "is class"))
-        .containsExactly(MemoizeExtension.class, SerializableAutoValueExtension.class);
+        .containsExactly(
+            MemoizeExtension.class,
+            SerializableAutoValueExtension.class,
+            ToPrettyStringExtension.class);
 
     AutoValueProcessor processor = new AutoValueProcessor(builtInExtensions);
     assertThat(processor.getSupportedOptions())
@@ -58,10 +62,12 @@
     AutoValueExtension nonIsolatingExtension = new NonIsolatingExtension();
     assertThat(nonIsolatingExtension.incrementalType((ProcessingEnvironment) null))
         .isEqualTo(IncrementalExtensionType.UNKNOWN);
-    ImmutableList<AutoValueExtension> extensions = ImmutableList.<AutoValueExtension>builder()
-        .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader()))
-        .add(nonIsolatingExtension)
-        .build();
+    ImmutableList<AutoValueExtension> extensions =
+        ImmutableList.<AutoValueExtension>builder()
+            .addAll(
+                AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader()))
+            .add(nonIsolatingExtension)
+            .build();
 
     AutoValueProcessor processor = new AutoValueProcessor(extensions);
     assertThat(processor.getSupportedOptions())
@@ -73,10 +79,12 @@
     AutoValueExtension isolatingExtension = new IsolatingExtension();
     assertThat(isolatingExtension.incrementalType((ProcessingEnvironment) null))
         .isEqualTo(IncrementalExtensionType.ISOLATING);
-    ImmutableList<AutoValueExtension> extensions = ImmutableList.<AutoValueExtension>builder()
-        .addAll(AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader()))
-        .add(isolatingExtension)
-        .build();
+    ImmutableList<AutoValueExtension> extensions =
+        ImmutableList.<AutoValueExtension>builder()
+            .addAll(
+                AutoValueProcessor.extensionsFromLoader(AutoValueProcessor.class.getClassLoader()))
+            .add(isolatingExtension)
+            .build();
 
     AutoValueProcessor processor = new AutoValueProcessor(extensions);
     assertThat(processor.getSupportedOptions())
diff --git a/value/src/test/java/com/google/auto/value/processor/NullablesTest.java b/value/src/test/java/com/google/auto/value/processor/NullablesTest.java
new file mode 100644
index 0000000..9e345f5
--- /dev/null
+++ b/value/src/test/java/com/google/auto/value/processor/NullablesTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * 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 com.google.auto.value.processor;
+
+import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
+import static com.google.common.truth.OptionalSubject.optionals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static java.util.stream.Collectors.partitioningBy;
+
+import com.google.auto.common.MoreTypes;
+import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Expect;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.util.ElementFilter;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NullablesTest {
+  @Rule public Expect expect = Expect.create();
+
+  @Target(ElementType.TYPE_USE)
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface Nullable {}
+
+  @Target(ElementType.TYPE_USE)
+  @Retention(RetentionPolicy.RUNTIME)
+  public @interface Irrelevant {}
+
+  // The class here has various methods that we will examine with
+  // Nullables.nullableMentionedInMethods to ensure that we do indeed detect @Nullable annotations
+  // in various contexts.
+  // This test is a lot more complicated than it should be. Ideally we would just have this Methods
+  // class be an actual class nested inside this test, and we would use CompilationRule to get
+  // the corresponding TypeElement so we could check the various methods. Unfortunately, if we
+  // do that then we get a TypeElement where all type annotations have disappeared because of
+  // https://bugs.openjdk.java.net/browse/JDK-8225377. So instead we have to use Compiler to compile
+  // the code here with a special annotation processor that will check the annotations of the
+  // just-compiled class. Since the processor is running as part of the same compilation, we don't
+  // lose the type annotations.
+
+  private static final ImmutableList<String> METHOD_LINES =
+      ImmutableList.of(
+          // Methods in this class whose names begin with "no" do not mention @Nullable anywhere.",
+          // All other methods do.",
+          "package foo.bar;",
+          "",
+          "import " + Irrelevant.class.getCanonicalName() + ";",
+          "import " + Nullable.class.getCanonicalName() + ";",
+          "import java.util.List;",
+          "",
+          "abstract class Methods {",
+          "  void noAnnotations() {}",
+          "  abstract int noAnnotationsEither(int x);",
+          "  abstract @Irrelevant String noRelevantAnnotations(@Irrelevant int x);",
+          "  abstract @Nullable String nullableString();",
+          "  abstract String @Nullable [] nullableArrayOfString();",
+          "  abstract @Nullable String[] arrayOfNullableString();",
+          "  abstract @Nullable String @Nullable [] nullableArrayOfNullableString();",
+          "  abstract List<@Nullable String> listOfNullableString();",
+          "  abstract List<? extends @Nullable Object> listOfExtendsNullable();",
+          "  abstract List<? super @Nullable Number> listOfSuperNullable();",
+          "  abstract <T extends @Nullable Object> T nullableTypeParamBound();",
+          "  abstract <T> @Nullable T nullableTypeParamRef();",
+          "  void nullableParam(@Nullable String x) {}",
+          "  void nullableParamBound(List<? extends @Nullable String> x) {}",
+          "}");
+
+  @Test
+  public void nullableMentionedInMethods() {
+    // Sadly we can't rely on JDK 8 to handle type annotations correctly.
+    // Some versions do, some don't. So skip the test unless we are on at least JDK 9.
+    double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value());
+    assume().that(javaVersion).isAtLeast(9.0);
+    NullableProcessor processor = new NullableProcessor(expect);
+    Compilation compilation =
+        Compiler.javac()
+            .withProcessors(processor)
+            .compile(JavaFileObjects.forSourceLines("foo.bar.Methods", METHOD_LINES));
+    assertThat(compilation).succeededWithoutWarnings();
+    assertThat(processor.ran).isTrue();
+    // If any `expect` calls failed then the test will fail now because of the Expect rule.
+  }
+
+  @SupportedAnnotationTypes("*")
+  private static class NullableProcessor extends AbstractProcessor {
+
+    private final Expect expect;
+    boolean ran;
+
+    NullableProcessor(Expect expect) {
+      this.expect = expect;
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+      return SourceVersion.latestSupported();
+    }
+
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      if (roundEnv.processingOver()) {
+        TypeElement methodsElement =
+            processingEnv.getElementUtils().getTypeElement("foo.bar.Methods");
+        expect.that(methodsElement).isNotNull();
+
+        List<ExecutableElement> methods =
+            ElementFilter.methodsIn(methodsElement.getEnclosedElements());
+        Map<Boolean, List<ExecutableElement>> partitionedMethods =
+            methods.stream()
+                .collect(partitioningBy(p -> !p.getSimpleName().toString().startsWith("no")));
+        List<ExecutableElement> nullableMethods = partitionedMethods.get(true);
+        List<ExecutableElement> notNullableMethods = partitionedMethods.get(false);
+
+        expect
+            .about(optionals())
+            .that(Nullables.nullableMentionedInMethods(notNullableMethods))
+            .isEmpty();
+
+        TypeElement nullableElement =
+            processingEnv.getElementUtils().getTypeElement(Nullable.class.getCanonicalName());
+        expect.that(nullableElement).isNotNull();
+        DeclaredType nullableType = MoreTypes.asDeclared(nullableElement.asType());
+
+        for (ExecutableElement nullableMethod : nullableMethods) {
+          // Make a list with all the methods that don't have @Nullable plus one method that does.
+          ImmutableList<ExecutableElement> notNullablePlusNullable =
+              ImmutableList.<ExecutableElement>builder()
+                  .addAll(notNullableMethods)
+                  .add(nullableMethod)
+                  .build();
+          expect
+              .withMessage("method %s should have @Nullable", nullableMethod)
+              .about(optionals())
+              .that(
+                  Nullables.nullableMentionedInMethods(notNullablePlusNullable)
+                      .map(AnnotationMirror::getAnnotationType))
+              .hasValue(nullableType);
+        }
+        ran = true;
+      }
+      return false;
+    }
+  }
+}
diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
index 2c3bea0..48d8cd6 100644
--- a/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/PropertyAnnotationsTest.java
@@ -15,12 +15,13 @@
  */
 package com.google.auto.value.processor;
 
-import static com.google.common.truth.Truth.assertAbout;
-import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
+import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.testing.compile.Compiler.javac;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
+import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Inherited;
@@ -40,8 +41,7 @@
  */
 @RunWith(JUnit4.class)
 public class PropertyAnnotationsTest {
-  private static final String TEST_ANNOTATION =
-      "@PropertyAnnotationsTest.TestAnnotation";
+  private static final String TEST_ANNOTATION = "@PropertyAnnotationsTest.TestAnnotation";
   private static final String TEST_ARRAY_ANNOTATION =
       "@PropertyAnnotationsTest.TestArrayAnnotation";
 
@@ -272,12 +272,15 @@
             .addMethodAnnotations(expectedMethodAnnotations)
             .build();
 
-    assertAbout(javaSource())
-        .that(javaFileObject)
-        .processedWith(new AutoValueProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(expectedOutput);
+    Compilation compilation =
+        javac()
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
+            .withProcessors(new AutoValueProcessor())
+            .compile(javaFileObject);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(expectedOutput);
   }
 
   @Test
@@ -445,10 +448,12 @@
     assertGeneratedMatches(
         getImports(PropertyAnnotationsTest.class),
         ImmutableList.of(
-            TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A,"
+            TEST_ARRAY_ANNOTATION
+                + "(testEnums = {PropertyAnnotationsTest.TestEnum.A,"
                 + " PropertyAnnotationsTest.TestEnum.B})"),
         ImmutableList.of(
-            TEST_ARRAY_ANNOTATION + "(testEnums = {PropertyAnnotationsTest.TestEnum.A,"
+            TEST_ARRAY_ANNOTATION
+                + "(testEnums = {PropertyAnnotationsTest.TestEnum.A,"
                 + " PropertyAnnotationsTest.TestEnum.B})"));
   }
 
@@ -512,12 +517,15 @@
             .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
             .build();
 
-    assertAbout(javaSource())
-        .that(inputFile)
-        .processedWith(new AutoValueProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(outputFile);
+    Compilation compilation =
+        javac()
+            .withOptions("-A" + Nullables.NULLABLE_OPTION)
+            .withProcessors(new AutoValueProcessor())
+            .compile(inputFile);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(outputFile);
   }
 
   /**
@@ -545,16 +553,17 @@
             .setImports(getImports(PropertyAnnotationsTest.class))
             .addFieldAnnotations("@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation")
             .addMethodAnnotations(
-                "@Deprecated",
-                "@PropertyAnnotationsTest.InheritedAnnotation",
-                "@Baz.MethodsOnly")
+                "@Deprecated", "@PropertyAnnotationsTest.InheritedAnnotation", "@Baz.MethodsOnly")
             .build();
 
-    assertAbout(javaSource())
-        .that(inputFile)
-        .processedWith(new AutoValueProcessor())
-        .compilesWithoutError()
-        .and()
-        .generatesSources(outputFile);
+    Compilation compilation =
+        javac()
+            .withOptions("-A" + Nullables.NULLABLE_OPTION + "=")
+            .withProcessors(new AutoValueProcessor())
+            .compile(inputFile);
+    assertThat(compilation).succeeded();
+    assertThat(compilation)
+        .generatedSourceFile("foo.bar.AutoValue_Baz")
+        .hasSourceEquivalentTo(outputFile);
   }
 }
diff --git a/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java
index 7af240c..d488e59 100644
--- a/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/PropertyNamesTest.java
@@ -34,14 +34,12 @@
           .put("x", "x")
           .put("", "")
           .build();
-  
+
   @Test
   public void decapitalizeLikeJavaBeans() {
-    NORMAL_CASES
-        .forEach(
-            (input, output) -> {
-              expect.that(PropertyNames.decapitalizeLikeJavaBeans(input)).isEqualTo(output);
-            });
+    NORMAL_CASES.forEach(
+        (input, output) ->
+            expect.that(PropertyNames.decapitalizeLikeJavaBeans(input)).isEqualTo(output));
     expect.that(PropertyNames.decapitalizeLikeJavaBeans(null)).isNull();
     expect.that(PropertyNames.decapitalizeLikeJavaBeans("HTMLPage")).isEqualTo("HTMLPage");
     expect.that(PropertyNames.decapitalizeLikeJavaBeans("OAuth")).isEqualTo("OAuth");
@@ -49,11 +47,9 @@
 
   @Test
   public void decapitalizeNormally() {
-    NORMAL_CASES
-        .forEach(
-            (input, output) -> {
-              expect.that(PropertyNames.decapitalizeNormally(input)).isEqualTo(output);
-            });
+    NORMAL_CASES.forEach(
+        (input, output) ->
+            expect.that(PropertyNames.decapitalizeNormally(input)).isEqualTo(output));
     expect.that(PropertyNames.decapitalizeNormally(null)).isNull();
     expect.that(PropertyNames.decapitalizeNormally("HTMLPage")).isEqualTo("hTMLPage");
     expect.that(PropertyNames.decapitalizeNormally("OAuth")).isEqualTo("oAuth");
diff --git a/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java b/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java
index 98d31b4..48ecf5b 100644
--- a/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/ReformatterTest.java
@@ -15,7 +15,7 @@
  */
 package com.google.auto.value.processor;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,7 +29,7 @@
 @RunWith(JUnit4.class)
 public class ReformatterTest {
   @Test
-  public void testSimple() {
+  public void simple() {
     String input =
         "\n"
             + "package com.latin.declension;  \n"
@@ -50,7 +50,7 @@
             + "\n"
             + "  Eodem ( Eadem eodem ) { }\n";
     String output =
-            "package com.latin.declension;\n"
+        "package com.latin.declension;\n"
             + "\n"
             + "public class Idem {\n"
             + "\n"
@@ -62,11 +62,11 @@
             + "  }\n"
             + "\n"
             + "  Eodem (Eadem eodem) { }\n";
-    assertEquals(output, Reformatter.fixup(input));
+    assertThat(Reformatter.fixup(input)).isEqualTo(output);
   }
 
   @Test
-  public void testSpecialSpaces() {
+  public void specialSpaces() {
     String input =
         "\n"
             + "package com.example.whatever;\n"
@@ -79,7 +79,7 @@
             + "  static final char QUOTE2 = '\\\"'  ;\n"
             + "}\n";
     String output =
-            "package com.example.whatever;\n"
+        "package com.example.whatever;\n"
             + "\n"
             + "public class SomeClass {\n"
             + "  static final String STRING = \"  hello  world  \\n\";\n"
@@ -88,13 +88,66 @@
             + "  static final char QUOTE = '\"';\n"
             + "  static final char QUOTE2 = '\\\"';\n"
             + "}\n";
-    assertEquals(output, Reformatter.fixup(input));
+    assertThat(Reformatter.fixup(input)).isEqualTo(output);
   }
 
   @Test
   public void noTrailingNewline() {
     String input = "package com.example.whatever;\n\npublic class SomeClass {}";
     String output = input + "\n";
-    assertEquals(output, Reformatter.fixup(input));
+    assertThat(Reformatter.fixup(input)).isEqualTo(output);
+  }
+
+  @Test
+  public void indent() {
+    String input =
+        "  class Test {\n"
+            + "private final int field;\n"
+            + "\n"
+            + "Test(\n"
+            + "@Interesting Integer field,\n"
+            + "boolean ignored) {\n"
+            + "this.field = field;\n"
+            + "}\n"
+            + "\n"
+            + "@Override\n"
+            + "public boolean equals(Object x) {\n"
+            + "return x instanceof Test\n"
+            + "&& ((Test) x).field == field;\n"
+            + "// interesting\n"
+            + "}\n"
+            + "\n"
+            + "@Override\n"
+            + "public String toString() {\n"
+            + "return \"Test{\"\n"
+            + "+ \"field=\" + field\n"
+            + "+ \"}\";\n"
+            + "}\n"
+            + "}\n";
+    String output =
+        "class Test {\n"
+            + "  private final int field;\n"
+            + "\n"
+            + "  Test(\n"
+            + "      @Interesting Integer field,\n"
+            + "      boolean ignored) {\n"
+            + "    this.field = field;\n"
+            + "  }\n"
+            + "\n"
+            + "  @Override\n"
+            + "  public boolean equals(Object x) {\n"
+            + "    return x instanceof Test\n"
+            + "        && ((Test) x).field == field;\n"
+            + "    // interesting\n"
+            + "  }\n"
+            + "\n"
+            + "  @Override\n"
+            + "  public String toString() {\n"
+            + "    return \"Test{\"\n"
+            + "        + \"field=\" + field\n"
+            + "        + \"}\";\n"
+            + "  }\n"
+            + "}\n";
+    assertThat(Reformatter.fixup(input)).isEqualTo(output);
   }
 }
diff --git a/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java b/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java
index fab1805..5e2d230 100644
--- a/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/SimpleServiceLoaderTest.java
@@ -28,6 +28,8 @@
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.ServiceConfigurationError;
@@ -54,6 +56,35 @@
     assertThat(classes).containsExactly(String.class, StringBuilder.class).inOrder();
   }
 
+  // Sometimes you can have the same jar appear more than once in the classpath, perhaps in
+  // different versions. In that case we don't want to instantiate the same class more than once.
+  // This test checks that we don't.
+  @Test
+  public void loadWithDuplicates() throws Exception {
+    ClassLoader loader1 =
+        loaderForJarWithEntries(
+            CharSequence.class.getName(), String.class.getName(), StringBuilder.class.getName());
+    ClassLoader loader2 =
+        loaderForJarWithEntries(
+            CharSequence.class.getName(), String.class.getName(), StringBuilder.class.getName());
+    ClassLoader combinedLoader =
+        new ClassLoader() {
+          @Override
+          public Enumeration<URL> getResources(String name) throws IOException {
+            List<URL> urls = new ArrayList<>(Collections.list(loader1.getResources(name)));
+            urls.addAll(Collections.list(loader2.getResources(name)));
+            return Collections.enumeration(urls);
+          }
+        };
+
+    ImmutableList<CharSequence> providers =
+        SimpleServiceLoader.load(CharSequence.class, combinedLoader);
+
+    assertThat(providers).contains("");
+    List<Class<?>> classes = providers.stream().map(Object::getClass).collect(toList());
+    assertThat(classes).containsExactly(String.class, StringBuilder.class).inOrder();
+  }
+
   @Test
   public void blankLinesAndComments() throws Exception {
     ClassLoader loader =
diff --git a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java
index 39e2dc0..7bc6779 100644
--- a/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/SimplifyWithAnnotationsTest.java
@@ -157,8 +157,7 @@
 
     void testTypeSpellings(TypeElement testClass) {
       ExecutableElement witness =
-          ElementFilter.methodsIn(testClass.getEnclosedElements())
-              .stream()
+          ElementFilter.methodsIn(testClass.getEnclosedElements()).stream()
               .filter(m -> m.getSimpleName().contentEquals("witness"))
               .collect(onlyElement());
       if (witness.getReturnType().getAnnotationMirrors().isEmpty()) {
diff --git a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java
index c31f711..83951e0 100644
--- a/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/TypeEncoderTest.java
@@ -292,7 +292,7 @@
     TypeMirror multipleBoundsMirror = multipleBoundsElement.asType();
     String text = "`import`\n";
     text += "{" + TypeEncoder.encode(multipleBoundsMirror) + "}";
-    text += "{" + TypeEncoder.formalTypeParametersString(multipleBoundsElement) + "}";
+    text += "{" + TypeEncoder.typeParametersString(multipleBoundsElement.getTypeParameters()) + "}";
     String myPackage = getClass().getPackage().getName();
     String decoded =
         TypeEncoder.decode(text, elementUtils, typeUtils, myPackage, baseWithoutContainedTypes());
@@ -306,6 +306,7 @@
   @SuppressWarnings("ClassCanBeStatic")
   static class Outer<T extends Number> {
     class InnerWithoutTypeParam {}
+
     class Middle<U> {
       class InnerWithTypeParam<V> {}
     }
diff --git a/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java
index 078ef51..895bed2 100644
--- a/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java
+++ b/value/src/test/java/com/google/auto/value/processor/TypeVariablesTest.java
@@ -115,10 +115,12 @@
 
   abstract static class Outer<T, U extends T> {
     abstract Map<T, U> getFoo();
+
     abstract List<? extends T> getBar();
 
     abstract static class Inner<T, U extends T> {
       abstract void setFoo(Map<T, U> foo);
+
       abstract void setBar(List<? extends T> bar);
     }
   }
@@ -165,13 +167,15 @@
     List<ExecutableElement> immutableMapMethods =
         ElementFilter.methodsIn(immutableMap.getEnclosedElements());
     ExecutableElement copyOf = methodNamed(immutableMapMethods, "copyOf", erasedMap);
-    expect.that(
-        TypeVariables.canAssignStaticMethodResult(
-            copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils))
+    expect
+        .that(
+            TypeVariables.canAssignStaticMethodResult(
+                copyOf, immutableMapStringInteger, immutableMapStringNumber, typeUtils))
         .isTrue();
-    expect.that(
-        TypeVariables.canAssignStaticMethodResult(
-            copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils))
+    expect
+        .that(
+            TypeVariables.canAssignStaticMethodResult(
+                copyOf, immutableMapStringNumber, immutableMapStringInteger, typeUtils))
         .isFalse();
   }
 
@@ -184,7 +188,9 @@
     return methods.stream()
         .filter(m -> m.getSimpleName().contentEquals(name))
         .filter(m -> m.getParameters().size() == 1)
-        .filter(m -> typeUtils.isSameType(
+        .filter(
+            m ->
+                typeUtils.isSameType(
                     erasedParameterType, typeUtils.erasure(m.getParameters().get(0).asType())))
         .findFirst()
         .get();
diff --git a/value/userguide/autobuilder.md b/value/userguide/autobuilder.md
new file mode 100644
index 0000000..ccd191c
--- /dev/null
+++ b/value/userguide/autobuilder.md
@@ -0,0 +1,491 @@
+# AutoBuilder
+
+
+AutoBuilder makes it easy to create a generalized builder, with setter methods
+that accumulate values, and a build method that calls a constructor or static
+method with those values as parameters. Callers don't need to know the order of
+those parameters. Parameters can also have default values. There can be
+validation before the constructor or method call.
+
+If you are familiar with [AutoValue builders](builders.md) then AutoBuilder
+should also be familiar. Where an `@AutoValue.Builder` has setter methods
+corresponding to the getter methods in the `@AutoValue` class, an `@AutoBuilder`
+has setter methods corresponding to the parameters of a constructor or static
+method. Apart from that, the two are very similar.
+
+AutoBuilder is **unstable** and it is possible that its API
+may change. We do not recommend depending on it for production code yet.
+
+## Example: calling a constructor
+
+Here is a simple example:
+
+```
+@AutoBuilder(ofClass = Person.class)
+abstract class PersonBuilder {
+  static PersonBuilder personBuilder() {
+    return new AutoBuilder_PersonBuilder();
+  }
+
+  abstract PersonBuilder setName(String name);
+  abstract PersonBuilder setId(int id);
+  abstract Person build();
+}
+```
+
+It might be used like this:
+
+```
+Person p = PersonBuilder.personBuilder().setName("Priz").setId(6).build();
+```
+
+That would have the same effect as this:
+
+```
+Person p = new Person("Priz", 6);
+```
+
+But it doesn't require you to know what order the constructor parameters are in.
+
+Here, `setName` and `setId` are _setter methods_. Calling
+`builder.setName("Priz")` records the value `"Priz"` for the parameter `name`,
+and likewise with `setId`.
+
+There is also a `build()` method. Calling that method invokes the `Person`
+constructor with the parameters that were previously set.
+
+## Example: calling a Kotlin constructor
+
+Kotlin has named arguments and default arguments for constructors and functions,
+which means there is not much need for anything like AutoBuilder there. But if
+you are constructing an instance of a Kotlin data class from Java code,
+AutoBuilder can help.
+
+Given this trivial Kotlin data class:
+
+```
+class KotlinData(val int: Int, val string: String?)
+```
+
+You might make a builder for it like this:
+
+```
+@AutoBuilder(ofClass = KotlinData.class)
+public abstract class KotlinDataBuilder {
+  public static KotlinDataBuilder kotlinDataBuilder() {
+    return new AutoBuilder_KotlinDataBuilder();
+  }
+
+  public abstract setInt(int x);
+  public abstract setString(@Nullable String x);
+  public abstract KotlinData build();
+}
+```
+
+The Kotlin type `String?` corresponds to `@Nullable String` in the AutoBuilder
+class, where `@Nullable` is any annotation with that name, such as
+`org.jetbrains.annotations.Nullable`.
+
+## The generated subclass
+
+Like `@AutoValue.Builder`, compiling an `@AutoBuilder` class will generate a
+concrete subclass. In the example above, this will be `class
+AutoBuilder_PersonBuilder extends PersonBuilder`. It is common to have a static
+`builder()` method, as in the example, which calls `new AutoBuilder_...()`. That
+will typically be the only reference to the generated class.
+
+If the `@AutoBuilder` type is nested then the name of the generated class
+reflects that nesting. For example:
+
+```
+class Outer {
+  static class Inner {
+    @AutoBuilder
+    abstract static class Builder {...}
+  }
+  static Inner.Builder builder() {
+    return new AutoBuilder_Outer_Inner_Builder();
+  }
+}
+```
+
+## `@AutoBuilder` annotation parameters
+
+`@AutoBuilder` has two annotation parameters, `ofClass` and `callMethod`.
+
+If `ofClass` is specified, then `build()` will call a constructor or static
+method of that class. Otherwise it will call a constructor or static method of
+the class _containing_ the `@AutoBuilder` class.
+
+If `callMethod` is specified, then `build()` will call a static method with that
+name. Otherwise `build()` will call a constructor.
+
+The following examples illustrate the various possibilities. These examples use
+an interface for the `@AutoBuilder` type. You can also use an abstract class; if
+it is nested then it must be static.
+
+### Both `callMethod` and `ofClass`
+
+```
+@AutoBuilder(callMethod = "of", ofClass = LocalTime.class)
+interface LocalTimeBuilder {
+  ...
+  LocalTime build(); // calls: LocalTime.of(...)
+}
+```
+
+### Only `ofClass`
+
+```
+@AutoBuilder(ofClass = Thread.class)
+interface ThreadBuilder {
+  ...
+  Thread build(); // calls: new Thread(...)
+}
+```
+
+### Only `callMethod`
+
+```
+class Foo {
+  static String concat(String first, String middle, String last) {...}
+
+  @AutoBuilder(callMethod = "concat")
+  interface ConcatBuilder {
+    ...
+    String build(); // calls: Foo.concat(first, middle, last)
+  }
+}
+```
+
+Notice in this example that the static method returns `String`. The implicit
+`ofClass` is `Foo`, but the static method can return any type.
+
+### Neither `callMethod` nor `ofClass`
+
+```
+class Person {
+  Person(String name, int id) {...}
+
+  @AutoBuilder
+  interface Builder {
+    ...
+    Person build(); // calls: new Person(name, id)
+  }
+}
+```
+
+## The build method
+
+The build method must have a certain return type. If it calls a constructor then
+its return type must be the type of the constructed class. If it calls a static
+method then its return type must be the return type of the static method.
+
+The build method is often called `build()` but it does not have to be. The only
+requirement is that there must be exactly one no-arg abstract method that has
+the return type just described and that does not correspond to a parameter name.
+
+The following example uses the name `call()` since that more accurately reflects
+what it does:
+
+```
+public class LogUtil {
+  public static void log(Level severity, String message, Object... params) {...}
+
+  @AutoBuilder(callMethod = "log")
+  public interface Caller {
+    Caller setSeverity(Level level);
+    Caller setMessage(String message);
+    Caller setParams(Object... params);
+    void call(); // calls: LogUtil.log(severity, message, params)
+  }
+```
+
+## Overloaded constructors or methods
+
+There might be more than one constructor or static method that matches the
+`callMethod` and `ofClass`. AutoBuilder will ignore any that are not visible to
+the generated class, meaning private, or package-private and in a different
+package. Of the others, it will pick the one whose parameter names match the
+`@AutoBuilder` setter methods. It is a compilation error if there is not exactly
+one such method or constructor.
+
+## Generics
+
+If the builder calls the constructor of a generic type, then it must have the
+same type parameters as that type, as in this example:
+
+```
+class NumberPair<T extends Number> {
+  NumberPair(T first, T second) {...}
+
+  @AutoBuilder
+  interface Builder<T extends Number> {
+    Builder<T> setFirst(T x);
+    Builder<T> setSecond(T x);
+    NumberPair<T> build();
+  }
+}
+```
+
+If the builder calls a static method with type parameters, then it must have the
+same type parameters, as in this example:
+
+```
+class Utils {
+  static <K extends Number, V> Map<K, V> singletonNumberMap(K key, V value) {...}
+
+  @AutoBuilder(callMethod = "singletonNumberMap")
+  interface Builder<K extends Number, V> {
+    Builder<K, V> setKey(K x);
+    Builder<K, V> setValue(V x);
+    Map<K, V> build();
+  }
+}
+```
+
+Although it's unusual, a Java constructor can have its own type parameters,
+separately from any that its containing class might have. A builder that calls a
+constructor like that must have the type parameters of the class followed by the
+type parameters of the constructor:
+
+```
+class CheckedSet<E> implements Set<E> {
+  <T extends E> CheckedSet(Class<T> type) {...}
+
+  @AutoBuilder
+  interface Builder<E, T extends E> {
+    Builder<E, T> setType(Class<T> type);
+    CheckedSet<E> build();
+  }
+}
+```
+
+## Required, optional, and nullable parameters
+
+Parameters that are annotated `@Nullable` are null by default. Parameters of
+type `Optional`, `OptionalInt`, `OptionalLong`, and `OptionalDouble` are empty
+by default. Every other parameter is _required_, meaning that the build method
+will throw `IllegalStateException` if any are omitted.
+
+To establish default values for parameters, set them in the `builder()` method
+before returning the builder.
+
+```
+class Foo {
+  Foo(String bar, @Nullable String baz, String buh) {...}
+
+  static Builder builder() {
+    return new AutoBuilder_Foo_Builder()
+        .setBar(DEFAULT_BAR);
+  }
+
+  @AutoBuilder
+  interface Builder {
+    Builder setBar(String x);
+    Builder setBaz(String x);
+    Builder setBuh(String x);
+    Foo build();
+  }
+
+  {
+     builder().build(); // IllegalStateException, buh is not set
+     builder().setBuh("buh").build(); // OK, bar=DEFAULT_BAR and baz=null
+     builder().setBaz(null).setBuh("buh").build(); // OK
+     builder().setBar(null); // NullPointerException, bar is not @Nullable
+  }
+}
+```
+
+Trying to set a parameter that is _not_ annotated `@Nullable` to `null` will
+produce a `NullPointerException`.
+
+`@Nullable` here is any annotation with that name, such as
+`javax.annotation.Nullable` or
+`org.checkerframework.checker.nullness.qual.Nullable`.
+
+## Getters
+
+The `@AutoBuilder` class or interface can also have _getter_ methods. A getter
+method returns the value that has been set for a certain parameter. Its return
+type can be either the same as the parameter type, or an `Optional` wrapping
+that type. Calling the getter before any value has been set will throw an
+exception in the first case or return an empty `Optional` in the second.
+
+In this example, the `nickname` parameter defaults to the same value as the
+`name` parameter but can also be set to a different value:
+
+```
+public class Named {
+  Named(String name, String nickname) {...}
+
+  @AutoBuilder
+  public abstract static class Builder {
+    public abstract Builder setName(String x);
+    public abstract Builder setNickname(String x);
+    abstract String getName();
+    abstract Optional<String> getNickname();
+    abstract Named autoBuild();
+
+    public Named build() {
+      if (!getNickname().isPresent()) {
+        setNickname(getName());
+      }
+      return autoBuild();
+    }
+  }
+}
+```
+
+The example illustrates having a package-private `autoBuild()` method that
+AutoBuilder implements. The public `build()` method calls it after adjusting the
+nickname if necessary.
+
+The builder in the example is an abstract class rather than an interface. An
+abstract class allows us to distinguish between public methods for users of the
+builder to call, and package-private methods that the builder's own logic uses.
+
+## Naming conventions
+
+A setter method for the parameter `foo` can be called either `setFoo` or `foo`.
+A getter method can be called either `getFoo` or `foo`, and for a `boolean`
+parameter it can also be called `isFoo`. The choice for getters and setters is
+independent. For example your getter might be `foo()` while your setter is
+`setFoo(T)`.
+
+By convention, the parameter name of a setter method either echoes the parameter
+being set:<br>
+`Builder setName(String name);`<br>
+or it is just `x`:<br>
+`Builder setName(String x);`<br>
+
+If class `Foo` has a nested `@AutoBuilder` that builds instances of `Foo`, then
+conventionally that type is called `Builder`, and instances of it are obtained
+by calling a static `Foo.builder()` method:
+
+```
+Foo foo1 = Foo.builder().setBar(bar).setBaz(baz).build();
+Foo.Builder fooBuilder = Foo.builder();
+```
+
+If an `@AutoBuilder` for `Foo` is its own top-level class then that class will
+typically be called `FooBuilder` and it will have a static `fooBuilder()` method
+that returns an instance of `FooBuilder`. That way callers can statically import
+`FooBuilder.fooBuilder` and just write `fooBuilder()` in their code.
+
+```
+@AutoBuilder(ofClass = Foo.class)
+public abstract class FooBuilder {
+  public static FooBuilder fooBuilder() {
+    return new AutoBuilder_FooBuilder();
+  }
+  ...
+  public abstract Foo build();
+}
+```
+
+If an `@AutoBuilder` is designed to call a static method that is not a factory
+method, the word "call" is better than "build" in the name of the type
+(`FooCaller`), the static method (`fooCaller()`), and the "build" method (`call()`).
+
+```
+@AutoBuilder(callMethod = "log", ofClass = MyLogger.class)
+public abstract class LogCaller {
+  public static LogCaller logCaller() {
+    return new AutoBuilder_LogCaller();
+  }
+  ...
+  public abstract void call();
+}
+
+// used as:
+logCaller().setLevel(Level.INFO).setMessage("oops").call();
+```
+
+## Other builder features
+
+There are a number of other builder features that have not been detailed here
+because they are the same as for `@AutoValue.Builder`. They include:
+
+*   [Special treatment of collections](builders-howto.md#collection)
+*   [Handling of nested builders](builders-howto.md#nested_builders)
+
+There is currently no equivalent of AutoValue's
+[`toBuilder()`](builders-howto.md#to_builder). Unlike AutoValue, there is not
+generally a mapping back from the result of the constructor or method to its
+parameters.
+
+## When parameter names are unavailable
+
+AutoBuilder depends on knowing the names of parameters. But parameter names are
+not always available in Java. They _are_ available in these cases, at least:
+
+*   In code that is being compiled at the same time as the `@AutoBuilder` class
+    or interface.
+*   In _records_ (from Java 16 onwards).
+*   In the constructors of Kotlin data classes.
+*   In code that was compiled with the [`-parameters`] option.
+
+A Java compiler bug means that parameter names are not available to AutoBuilder
+when compiling with JDK versions before 11, in any of these cases except the
+first. We recommend building with a recent JDK, using the `--release` option if
+necessary to produce code that can run on earlier versions.
+
+If parameter names are unavailable, you always have the option of introducing a
+static method in the same class as the `@AutoBuilder` type, and having it call
+the method you want. Since it is compiled at the same time, its parameter names
+are available.
+
+Here's an example of fixing a problem this way. The code here typically will not
+compile, since parameter names of JDK methods are not available:
+
+```
+import java.time.LocalTime;
+
+public class TimeUtils {
+  // Does not work, since parameter names from LocalTime.of are unavailable.
+  @AutoBuilder(callMethod = "of", ofClass = LocalTime.class)
+  public interface TimeBuilder {
+    TimeBuilder setHour(int x);
+    TimeBuilder setMinute(int x);
+    TimeBuilder setSecond(int x);
+    LocalTime build();
+  }
+}
+```
+
+It will produce an error message like this:
+
+```
+error: [AutoBuilderNoMatch] Property names do not correspond to the parameter names of any static method named "of":
+  public interface TimeBuilder {
+  ^
+    of(int arg0, int arg1)
+    of(int arg0, int arg1, int arg2)
+    of(int arg0, int arg1, int arg2, int arg3)
+```
+
+The names `arg0`, `arg1`, etc are concocted by the compiler because it doesn't
+have the real names.
+
+Introducing a static method fixes the problem:
+
+```
+import java.time.LocalTime;
+
+public class TimeUtils {
+  static LocalTime localTimeOf(int hour, int second, int second) {
+    return LocalTime.of(hour, minute, second);
+  }
+
+  @AutoBuilder(callMethod = "localTimeOf")
+  public interface TimeBuilder {
+    TimeBuilder setHour(int x);
+    TimeBuilder setMinute(int x);
+    TimeBuilder setSecond(int x);
+    LocalTime build();
+  }
+}
+```
+
+[`-parameters`]: https://docs.oracle.com/en/java/javase/16/docs/specs/man/javac.html#option-parameters
diff --git a/value/userguide/builders-howto.md b/value/userguide/builders-howto.md
index 00038e7..e7cf537 100644
--- a/value/userguide/builders-howto.md
+++ b/value/userguide/builders-howto.md
@@ -32,6 +32,8 @@
     *   ... [offer **both** accumulation and set-at-once methods for the same
         collection-valued property?](#collection_both)
 *   ... [access nested builders while building?](#nested_builders)
+*   ... [create a "step builder"?](#step)
+*   ... [create a builder for something other than an `@AutoValue`?](#autobuilder)
 
 ## <a name="beans"></a>... use (or not use) `set` prefixes?
 
@@ -79,10 +81,13 @@
 [Optional](#optional) it will default to an empty `Optional` as you might also
 expect. But if it isn't either of those things (including if it is a
 primitive-valued property, which *can't* be null), then `build()` will throw an
-unchecked exception.
+unchecked exception. This includes collection properties, which must be given a
+value. They don't default to empty unless there is a
+[collection builder](#accumulate).
 
-But this presents a problem, since one of the main *advantages* of a builder in
-the first place is that callers can specify only the properties they care about!
+But this requirement to supply a value presents a problem, since one of the main
+*advantages* of a builder in the first place is that callers can specify only
+the properties they care about!
 
 The solution is to provide a default value for such properties. Fortunately this
 is easy: just set it on the newly-constructed builder instance before returning
@@ -306,7 +311,8 @@
 `Optional` without needing to [specify](#default) a default explicitly. And,
 instead of or as well as the normal `setFoo(Optional<String>)` method, you can
 have `setFoo(String)`. Then `setFoo(s)` is equivalent to
-`setFoo(Optional.of(s))`.
+`setFoo(Optional.of(s))`. (If it is `setFoo(@Nullable String)`, then `setFoo(s)`
+is equivalent to `setFoo(Optional.ofNullable(s))`.)
 
 Here, `Optional` means either [`java.util.Optional`] from Java (Java 8 or
 later), or [`com.google.common.base.Optional`] from Guava. Java 8 also
@@ -419,7 +425,22 @@
 `getCountries()` convention, the builder method must be `countriesBuilder()`
 and not `getCountriesBuilder()`.
 
-You may notice a small problem with this example: the caller can no longer
+It's also possible to have a method like `countriesBuilder` with a single
+argument, provided that the `Builder` class has a public constructor or a
+static `builder` method, with one parameter that the argument can be assigned
+to. For example, if `countries()` were an `ImmutableSortedSet<String>` and you
+wanted to supply a `Comparator` to `ImmutableSortedSet.Builder`, you could
+write:
+
+```java
+    public abstract ImmutableSortedSet.Builder<String>
+        countriesBuilder(Comparator<String> comparator);
+```
+
+That works because `ImmutableSortedSet.Builder` has a constructor that
+accepts a `Comparator` parameter.
+
+You may notice a small problem with these examples: the caller can no longer
 create their instance in a single chained statement:
 
 ```java
@@ -598,5 +619,21 @@
 be set as if by `speciesBuilder().build()`. In the example, that would result
 in an exception because the required properties of `Species` have not been set.
 
+## <a name="step"></a>... create a "step builder"?
+
+A [_step builder_](http://rdafbn.blogspot.com/2012/07/step-builder-pattern_28.html)
+is a collection of builder interfaces that take you step by step through the
+setting of each of a list of required properties. We think that these are a nice
+idea in principle but not necessarily in practice. Regardless, if you want to
+use AutoValue to implement a step builder,
+[this example](https://github.com/google/auto/issues/1000#issuecomment-792875738)
+shows you how.
+
+## <a name="autobuilder"></a> ... create a builder for something other than an `@AutoValue`?
+
+Sometimes you want to make a builder like the kind described here, but have it
+build something other than an `@AutoValue` class, or even call a static method.
+In that case you can use `@AutoBuilder`. See
+[its documentation](autobuilder.md).
 
 [protobuf]: https://developers.google.com/protocol-buffers/docs/reference/java-generated#builders
diff --git a/value/userguide/builders.md b/value/userguide/builders.md
index 11c68c6..1de6bfa 100644
--- a/value/userguide/builders.md
+++ b/value/userguide/builders.md
@@ -10,15 +10,14 @@
 Note that we recommend reading and understanding the basic usage shown in the
 [introduction](index.md) first.
 
-
-## How to use AutoValue with Builders<a name="howto"></a>
+## How to use AutoValue with Builders <a name="howto"></a>
 
 As explained in the introduction, the AutoValue concept is that **you write an
 abstract value class, and AutoValue implements it**. Builder generation works in
 the exact same way: you also create an abstract builder class, nesting it inside
 your abstract value class, and AutoValue generates implementations for both.
 
-### In `Animal.java`<a name="example_java"></a>
+### In `Animal.java` <a name="example_java"></a>
 
 ```java
 import com.google.auto.value.AutoValue;
@@ -45,7 +44,7 @@
 have **Javadoc**. We're leaving these off in the User Guide only to keep the
 examples clean and short.
 
-### Usage<a name="usage"></a>
+### Usage <a name="usage"></a>
 
 ```java
 public void testAnimal() {
@@ -65,12 +64,12 @@
 }
 ```
 
-### What does AutoValue generate?<a name="generated"></a>
+### What does AutoValue generate? <a name="generated"></a>
 
 For the `Animal` example shown above, here is [typical code AutoValue might
 generate](generated-builder-example.md).
 
-## Warnings<a name="warnings"></a>
+## Warnings <a name="warnings"></a>
 
 Be sure to put the static `builder()` method directly in your value class (e.g.,
 `Animal`) and not the nested abstract `Builder` class. That ensures that the
@@ -92,10 +91,16 @@
     time?](builders-howto.md#normalize)
 *   ... [expose **both** a builder and a factory
     method?](builders-howto.md#both)
+*   ... [handle `Optional` properties?](builders-howto.md#optional)
 *   ... [use a **collection**-valued property?](builders-howto.md#collection)
     *   ... [let my builder **accumulate** values for a collection-valued
         property (not require them all at once)?](builders-howto.md#accumulate)
     *   ... [accumulate values for a collection-valued property, without
-        **breaking the chain**?](builders-howto.md#add)
+        **"breaking the chain"**?](builders-howto.md#add)
     *   ... [offer **both** accumulation and set-at-once methods for the same
         collection-valued property?](builders-howto.md#collection_both)
+*   ... [access nested builders while
+    building?](builders-howto.md#nested_builders)
+*   ... [create a "step builder"?](builders-howto.md#step)
+*   ... [create a builder for something other than an
+    `@AutoValue`?](builders-howto.md#autobuilder)
diff --git a/value/userguide/extensions.md b/value/userguide/extensions.md
index ec6c467..6acb3ca 100644
--- a/value/userguide/extensions.md
+++ b/value/userguide/extensions.md
@@ -9,7 +9,6 @@
 Each extension is a class. If that class is on the `processorpath` when you
 compile your `@AutoValue` class, the extension can run.
 
-
 Some extensions are triggered by their own annotations, which you add to your
 class; others may be triggered in other ways. Consult the extension's
 documentation for usage instructions.
@@ -40,4 +39,3 @@
 [AutoService]: https://github.com/google/auto/tree/master/service
 [`AutoValueExtension`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/AutoValueExtension.java
 [`ServiceLoader`]: http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html
-
diff --git a/value/userguide/howto.md b/value/userguide/howto.md
index 6f9142c..c451185 100644
--- a/value/userguide/howto.md
+++ b/value/userguide/howto.md
@@ -42,6 +42,7 @@
 *   ... [make a class where only one of its properties is ever set?](#oneof)
 *   ... [copy annotations from a class/method to the implemented
     class/method/field?](#copy_annotations)
+*   ... [create a **pretty string** representation?](#toprettystring)
 
 ## <a name="builder"></a>... also generate a builder for my value class?
 
@@ -239,16 +240,16 @@
 abstract class IgnoreExample {
   static IgnoreExample create(String normalProperty, String ignoredProperty) {
     IgnoreExample ie = new AutoValue_IgnoreExample(normalProperty);
-    ie.ignoredProperty = ignoredProperty;
+    ie.ignoredProperty.set(ignoredProperty);
     return ie;
   }
 
   abstract String normalProperty();
 
-  private String ignoredProperty; // sadly, it can't be `final`
+  private final AtomicReference<String> ignoredProperty = new AtomicReference<>();
 
   final String ignoredProperty() {
-    return ignoredProperty;
+    return ignoredProperty.get();
   }
 }
 ```
@@ -256,6 +257,11 @@
 Note that this means the field is also ignored by `toString`; to AutoValue
 it simply doesn't exist.
 
+Note that we use `AtomicReference<String>` to ensure that other threads will
+correctly see the value that was written. You could also make the field
+`volatile`, or use `synchronized` (`synchronized (ie)` around the assignment and
+`synchronized` on the `ignoredProperty()` method).
+
 ## <a name="supertypes"></a>... have AutoValue also implement abstract methods from my supertypes?
 
 AutoValue will recognize every abstract accessor method whether it is defined
@@ -347,7 +353,6 @@
 arrays, they can be replaced with a proper `List` implementation for very little
 added cost.
 
-
 If it's important to accept an object array at construction time, refer to the
 *first* example shown [here](#mutable_property).
 
@@ -676,3 +681,44 @@
 
 [`@AutoValue.CopyAnnotations`]: http://static.javadoc.io/com.google.auto.value/auto-value/1.6/com/google/auto/value/AutoValue.CopyAnnotations.html
 
+## <a name="toprettystring"></a>... create a pretty string representation?
+
+If you have a value class with a long `toString()` representation, annotate a
+method with [`@ToPrettyString`] and AutoValue will generate an implementation that
+returns a pretty String rendering of the instance. For example:
+
+```java
+@AutoValue
+abstract class Song {
+  abstract String lyrics();
+  abstract List<Artist> artists();
+
+  @ToPrettyString
+  abstract String toPrettyString();
+}
+```
+
+Below is a sample rendering of the result of calling `toPrettyString()`.
+
+```
+Song {
+  lyrics = I'm off the deep end, watch as I dive in
+    I'll never meet the ground
+    Crash through the surface, where they can't hurt us
+    We're far from the shallow now.,
+  artists = [
+    Artist {
+      name = Lady Gaga,
+    },
+    Artist {
+      name = Bradley Cooper,
+    }
+  ],
+}
+```
+
+`@ToPrettyString` can be used on the default `toString()` to override the
+default AutoValue-generated `toString()` implementation, or on another
+user-defined method.
+
+[`@ToPrettyString`]: https://github.com/google/auto/blob/master/value/src/main/java/com/google/auto/value/extension/toprettystring/ToPrettyString.java
diff --git a/value/userguide/index.md b/value/userguide/index.md
index 2e05d54..d6ef95c 100644
--- a/value/userguide/index.md
+++ b/value/userguide/index.md
@@ -13,7 +13,6 @@
 >
 > -- *Joshua Bloch, author, Effective Java*
 
-
 ## <a name="background"></a>Background
 
 **Value classes** are extremely common in Java projects. These are classes for
@@ -150,18 +149,18 @@
 
 ```groovy
 dependencies {
-  // Use 'api' rather than 'compile' for Android or java-library projects.
-  compile             "com.google.auto.value:auto-value-annotations:${autoValueVersion}"
+  compileOnlyApi      "com.google.auto.value:auto-value-annotations:${autoValueVersion}"
   annotationProcessor "com.google.auto.value:auto-value:${autoValueVersion}"
 }
 ```
 
-Note: If you are using a version of Gradle prior to 4.6, you must apply an
-annotation processing plugin [as described in these instructions][tbroyer-apt].
+Note: If you are using a version of Gradle prior to 6.7, use `compile` or (for
+Android or java-library projects) `api` instead of `compileOnlyApi`. If you are
+using a version prior to 4.6, you must apply an annotation processing plugin
+[as described in these instructions][tbroyer-apt].
 
 [tbroyer-apt]: https://plugins.gradle.org/plugin/net.ltgt.apt
 
-
 ### <a name="usage"></a>Usage
 
 Your choice to use AutoValue is essentially *API-invisible*. This means that, to
@@ -276,13 +275,7 @@
     set?](howto.md#oneof)
 *   ... [copy annotations from a class/method to the implemented
     class/method/field?](howto.md#copy_annotations)
+*   ... [create a **pretty string** representation?](howto.md#toprettystring)
+
 
 <!-- TODO(kevinb): should the above be only a selected subset? -->
-
-## <a name="more"></a>More information
-
-See the links in the sidebar at the top left.
-
-<!-- TODO(kevinb): there are some tidbits of information that don't seem to
-     belong anywhere yet; such as how it implements floating-point equality -->
-
diff --git a/value/userguide/practices.md b/value/userguide/practices.md
index 0121bd8..e78c6e5 100644
--- a/value/userguide/practices.md
+++ b/value/userguide/practices.md
@@ -1,7 +1,6 @@
 # Best practices
 
 
-
 ## <a name="interchangeable"></a>"Equals means interchangeable"
 
 Don't use AutoValue to implement value semantics unless you really want value
diff --git a/value/userguide/why.md b/value/userguide/why.md
index 603abb8..40c994f 100644
--- a/value/userguide/why.md
+++ b/value/userguide/why.md
@@ -15,5 +15,4 @@
 [slide presentation] compares AutoValue to numerous alternatives and explains
 why we think it is better.
 
-
 [slide presentation]: https://docs.google.com/presentation/d/14u_h-lMn7f1rXE1nDiLX0azS3IkgjGl5uxp5jGJ75RE/edit