Merge Android U (ab/10368041)

Bug: 291102124
Merged-In: Ib28f095544608907fc3191b88bf58375c3b2f1ee
Change-Id: I6ba34d8fbadc22a3bccf1e9b368d463b52ccb372
diff --git a/.github/actions/artifact-android-emulator-tests/action.yml b/.github/actions/artifact-android-emulator-tests/action.yml
index 393227c..2fc2987 100644
--- a/.github/actions/artifact-android-emulator-tests/action.yml
+++ b/.github/actions/artifact-android-emulator-tests/action.yml
@@ -12,7 +12,14 @@
     - name: 'Check out repository'
       uses: actions/checkout@v3
     - name: 'Cache Gradle files'
-      uses: gradle/gradle-build-action@v2
+      uses: actions/cache@v2
+      with:
+        path: |
+          ~/.gradle/caches
+          ~/.gradle/wrapper
+        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+        restore-keys: |
+          ${{ runner.os }}-gradle-
     - name: 'Download local snapshot for tests'
       uses: actions/download-artifact@v3
       with:
diff --git a/.github/actions/artifact-android-local-tests/action.yml b/.github/actions/artifact-android-local-tests/action.yml
index 5b6e837..5410985 100644
--- a/.github/actions/artifact-android-local-tests/action.yml
+++ b/.github/actions/artifact-android-local-tests/action.yml
@@ -12,7 +12,14 @@
     - name: 'Check out repository'
       uses: actions/checkout@v3
     - name: 'Cache Gradle files'
-      uses: gradle/gradle-build-action@v2
+      uses: actions/cache@v2
+      with:
+        path: |
+          ~/.gradle/caches
+          ~/.gradle/wrapper
+        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+        restore-keys: |
+          ${{ runner.os }}-gradle-
     - name: 'Download local snapshot for tests'
       uses: actions/download-artifact@v3
       with:
diff --git a/.github/actions/artifact-java-local-tests/action.yml b/.github/actions/artifact-java-local-tests/action.yml
index 040dbe3..e3cf770 100644
--- a/.github/actions/artifact-java-local-tests/action.yml
+++ b/.github/actions/artifact-java-local-tests/action.yml
@@ -7,7 +7,14 @@
     - name: 'Check out repository'
       uses: actions/checkout@v3
     - name: 'Cache Gradle files'
-      uses: gradle/gradle-build-action@v2
+      uses: actions/cache@v2
+      with:
+        path: |
+          ~/.gradle/caches
+          ~/.gradle/wrapper
+        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+        restore-keys: |
+          ${{ runner.os }}-gradle-
     - name: 'Download local snapshot for tests'
       uses: actions/download-artifact@v3
       with:
diff --git a/.github/actions/bazel-build/action.yml b/.github/actions/bazel-build/action.yml
index 63783e6..c464dfe 100644
--- a/.github/actions/bazel-build/action.yml
+++ b/.github/actions/bazel-build/action.yml
@@ -21,6 +21,9 @@
     - name: 'Java build'
       run: bazel build //java/...
       shell: bash
+    - name: 'Install maven version'
+      run: ./util/install-maven.sh ${{ env.USE_MAVEN_VERSION }}
+      shell: bash
     - name: 'Install local snapshot'
       run: ./util/install-local-snapshot.sh
       shell: bash
diff --git a/.github/actions/bazel-test/action.yml b/.github/actions/bazel-test/action.yml
index d0469ec..d84f061 100644
--- a/.github/actions/bazel-test/action.yml
+++ b/.github/actions/bazel-test/action.yml
@@ -31,7 +31,14 @@
         restore-keys: |
           ${{ runner.os }}-bazel-test-
     - name: 'Cache Gradle files'
-      uses: gradle/gradle-build-action@v2
+      uses: actions/cache@v2
+      with:
+        path: |
+          ~/.gradle/caches
+          ~/.gradle/wrapper
+        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+        restore-keys: |
+          ${{ runner.os }}-gradle-
     - name: 'Run Bazel tests'
       run: bazel test --test_output=errors //...
       shell: bash
diff --git a/.github/actions/build-gradle-plugin/action.yml b/.github/actions/build-gradle-plugin/action.yml
index c06bbde..4fb3293 100644
--- a/.github/actions/build-gradle-plugin/action.yml
+++ b/.github/actions/build-gradle-plugin/action.yml
@@ -33,7 +33,17 @@
         restore-keys: |
           ${{ runner.os }}-bazel-build-
     - name: 'Cache Gradle files'
-      uses: gradle/gradle-build-action@v2
+      uses: actions/cache@v2
+      with:
+        path: |
+          ~/.gradle/caches
+          ~/.gradle/wrapper
+        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+        restore-keys: |
+          ${{ runner.os }}-gradle-
+    - name: 'Install maven version'
+      run: ./util/install-maven.sh ${{ env.USE_MAVEN_VERSION }}
+      shell: bash
     - name: 'Build and install Hilt Gradle plugin local snapshot'
       run: ./util/deploy-hilt-gradle-plugin.sh "install:install-file" "LOCAL-SNAPSHOT"
       shell: bash
diff --git a/.github/actions/test-gradle-plugin/action.yml b/.github/actions/test-gradle-plugin/action.yml
index f68d79b..5523278 100644
--- a/.github/actions/test-gradle-plugin/action.yml
+++ b/.github/actions/test-gradle-plugin/action.yml
@@ -28,7 +28,14 @@
         restore-keys: |
           ${{ runner.os }}-bazel-build-
     - name: 'Cache Gradle files'
-      uses: gradle/gradle-build-action@v2
+      uses: actions/cache@v2
+      with:
+        path: |
+          ~/.gradle/caches
+          ~/.gradle/wrapper
+        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+        restore-keys: |
+          ${{ runner.os }}-gradle-
     - name: 'Download local snapshot for tests'
       uses: actions/download-artifact@v3
       with:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 397b0c2..f4650e7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,6 +14,9 @@
   # Our Bazel builds currently rely on 5.3.2. The version is set via
   # baselisk by USE_BAZEL_VERSION: https://github.com/bazelbuild/bazelisk.
   USE_BAZEL_VERSION: '5.3.2'
+  # The default Maven 3.9.0 has a regression so we manually install 3.8.7.
+  # https://issues.apache.org/jira/browse/MNG-7679
+  USE_MAVEN_VERSION: '3.8.7'
 
 jobs:
   validate-latest-dagger-version:
@@ -71,7 +74,7 @@
     steps:
       - uses: actions/checkout@v3
       - uses: ./.github/actions/artifact-android-emulator-tests
-        timeout-minutes: 25
+        timeout-minutes: 30 # TODO(b/287486065) investigate whether there is performance regression
         with:
           api-level: '30'
   artifact-android-emulator-legacy-api-tests:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0575623..fd371b1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -14,6 +14,9 @@
   # baselisk by USE_BAZEL_VERSION: https://github.com/bazelbuild/bazelisk.
   USE_BAZEL_VERSION: '5.3.2'
   DAGGER_RELEASE_VERSION: "${{ github.event.inputs.dagger_release_version }}"
+  # The default Maven 3.9.0 has a regression so we manually install 3.8.7.
+  # https://issues.apache.org/jira/browse/MNG-7679
+  USE_MAVEN_VERSION: '3.8.7'
 
 # TODO(bcorso):Convert these jobs into local composite actions to share with the
 # continuous integration workflow.
diff --git a/Android.bp b/Android.bp
index 39149e1..f3f6498 100644
--- a/Android.bp
+++ b/Android.bp
@@ -155,6 +155,7 @@
         "java/dagger/model/*.java",
         "java/dagger/spi/*.java",
         "java/dagger/spi/model/*.java",
+        "java/dagger/spi/model/*.kt",
     ],
 
     exclude_srcs: [
@@ -162,10 +163,6 @@
         "java/dagger/internal/codegen/kythe/DaggerKythePlugin.java",
     ],
 
-    // Manually include META-INF/services/javax.annotation.processing.Processor
-    // as the AutoService processor doesn't work properly.
-    java_resource_dirs: ["resources"],
-
     static_libs: [
         "auto_common",
         "dagger2",
@@ -175,6 +172,7 @@
         "guava",
         "javapoet",
         "jsr330",
+        "kotlin_symbol_processing_api",
         "kotlin-stdlib",
         "kotlin-stdlib-jdk8",
         "kotlinpoet",
@@ -188,7 +186,6 @@
         "auto_value_annotations",
         "auto_value_memoized_extension_annotations",
         "dagger2-android-annotation-stubs",
-        "kotlin_symbol_processing_api",
     ],
 
     plugins: [
@@ -484,6 +481,10 @@
         "java/dagger/hilt/processor/internal/**/*.java",
         "java/dagger/hilt/processor/internal/**/*.kt",
     ],
+    exclude_srcs: [
+        // Depends on DefineComponentValidationProcessingStep which is not present in github.
+        "java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentValidationProcessor.java",
+    ],
     plugins: [
         "auto_service_plugin",
         "auto_value_plugin",
diff --git a/METADATA b/METADATA
index c9c7925..5a52462 100644
--- a/METADATA
+++ b/METADATA
@@ -1,7 +1,9 @@
-name: "dagger2"
-description:
-    "A fast dependency injector for Android and Java."
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update dagger2
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
 
+name: "dagger2"
+description: "A fast dependency injector for Android and Java."
 third_party {
   url {
     type: HOMEPAGE
@@ -11,7 +13,11 @@
     type: GIT
     value: "https://github.com/google/dagger"
   }
-  version: "dagger-2.41"
-  last_upgrade_date { year: 2022 month: 04 day: 04 }
+  version: "dagger-2.47"
   license_type: NOTICE
+  last_upgrade_date {
+    year: 2023
+    month: 8
+    day: 2
+  }
 }
diff --git a/README.md b/README.md
index 5f9f5bf..01f3c91 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,9 @@
 reflection or runtime bytecode generation, does all its analysis at
 compile-time, and generates plain Java source code.
 
-Dagger is actively maintained by the same team that works on [Guava]. Snapshot
-releases are auto-deployed to Sonatype's central Maven repository on every clean
-build with the version `HEAD-SNAPSHOT`. The current version builds upon previous
-work done at [Square][square].
+Dagger is actively maintained by Google.  Snapshot releases are auto-deployed to
+Sonatype's central Maven repository on every clean build with the version
+`HEAD-SNAPSHOT`. The current version builds upon previous work done at [Square][square].
 
 ## Documentation
 
@@ -39,8 +38,8 @@
 
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 
-DAGGER_TAG = "2.44.2"
-DAGGER_SHA = "cbff42063bfce78a08871d5a329476eb38c96af9cf20d21f8b412fee76296181"
+DAGGER_TAG = "2.46.1"
+DAGGER_SHA = "bbd75275faa3186ebaa08e6779dc5410741a940146d43ef532306eb2682c13f7"
 http_archive(
     name = "dagger",
     strip_prefix = "dagger-dagger-%s" % DAGGER_TAG,
@@ -333,7 +332,6 @@
 [GitHub Issues]: https://github.com/google/dagger/issues
 [gradle-api-implementation]: https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation
 [gradle-api-implementation-android]: https://developer.android.com/studio/build/dependencies#dependency_configurations
-[Guava]: https://github.com/google/guava
 [`kapt`]: https://kotlinlang.org/docs/reference/kapt.html
 [latestapi]: https://dagger.dev/api/latest/
 [mavenbadge-svg]: https://maven-badges.herokuapp.com/maven-central/com.google.dagger/dagger/badge.svg
diff --git a/WORKSPACE b/WORKSPACE
index 84fa4f1..860daf5 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -171,7 +171,7 @@
 
 ERROR_PRONE_VERSION = "2.14.0"
 
-KSP_VERSION = "1.8.0-1.0.9"
+KSP_VERSION = KOTLIN_VERSION + "-1.0.9"
 
 maven_install(
     artifacts = [
@@ -245,7 +245,7 @@
         "org.jetbrains.kotlin:kotlin-compiler-embeddable:%s" % KOTLIN_VERSION,
         "org.jetbrains.kotlin:kotlin-daemon-embeddable:%s" % KOTLIN_VERSION,
         "org.jetbrains.kotlin:kotlin-stdlib:%s" % KOTLIN_VERSION,
-        "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.0",
+        "org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.6.2",
         "org.mockito:mockito-core:2.28.2",
         "org.objenesis:objenesis:1.0",
         "org.robolectric:robolectric:4.4",
diff --git a/java/dagger/BindsInstance.java b/java/dagger/BindsInstance.java
index eab8796..5ea7286 100644
--- a/java/dagger/BindsInstance.java
+++ b/java/dagger/BindsInstance.java
@@ -52,7 +52,18 @@
  *
  * <p>will allow clients of the builder or factory to pass their own instances of {@code Foo} and
  * {@code Bar}, and those instances can be injected within the component as {@code Foo} or
- * {@code @Blue Bar}, respectively.
+ * {@code @Blue Bar}, respectively. It's important to note that unlike in factories, the methods in
+ * builders should only accept and bind a single parameter each. Using the following will result in
+ * an error:
+ *
+ * <pre>
+ *   {@literal @Component.Builder}
+ *   interface Builder {
+ *     // Error! Builder methods can only have one parameter
+ *     {@literal @BindsInstance} Builder fooAndBar(Foo foo, {@literal @Blue} Bar bar);
+ *     ...
+ *   }
+ * </pre>
  *
  * <p>{@code @BindsInstance} arguments may not be {@code null} unless the parameter is annotated
  * with {@code @Nullable}.
diff --git a/java/dagger/hilt/BUILD b/java/dagger/hilt/BUILD
index 1de6165..8d25adc 100644
--- a/java/dagger/hilt/BUILD
+++ b/java/dagger/hilt/BUILD
@@ -115,11 +115,11 @@
     srcs = glob(["*"]) + [
         "//java/dagger/hilt/codegen:srcs_filegroup",
         "//java/dagger/hilt/components:srcs_filegroup",
-        "//java/dagger/hilt/migration:srcs_filegroup",
         "//java/dagger/hilt/internal:srcs_filegroup",
         "//java/dagger/hilt/internal/aliasof:srcs_filegroup",
         "//java/dagger/hilt/internal/definecomponent:srcs_filegroup",
         "//java/dagger/hilt/internal/generatesrootinput:srcs_filegroup",
+        "//java/dagger/hilt/migration:srcs_filegroup",
     ],
 )
 
diff --git a/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java b/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java
index 5be3144..806377b 100644
--- a/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java
+++ b/java/dagger/hilt/android/internal/lifecycle/RetainedLifecycleImpl.java
@@ -25,7 +25,9 @@
 import java.util.Set;
 
 /** Internal implementation. Do not use. */
-public final class RetainedLifecycleImpl implements ActivityRetainedLifecycle, ViewModelLifecycle {
+public final class RetainedLifecycleImpl
+    implements ActivityRetainedLifecycle,
+        ViewModelLifecycle {
 
   private final Set<RetainedLifecycle.OnClearedListener> listeners = new HashSet<>();
   private boolean onClearedDispatched = false;
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle
index d333297..3c03495 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/build.gradle
@@ -2,6 +2,10 @@
   id 'org.jetbrains.kotlin.jvm'
 }
 
+kotlin {
+  jvmToolchain(11)
+}
+
 dependencies {
   implementation project(':agp-wrapper')
   compileOnly gradleApi()
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/AndroidComponentsExtensionCompatApi70Impl.kt b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/AndroidComponentsExtensionCompatApi70Impl.kt
index 1536af5..1f50794 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/AndroidComponentsExtensionCompatApi70Impl.kt
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/AndroidComponentsExtensionCompatApi70Impl.kt
@@ -25,7 +25,6 @@
   private val project: Project
 ) : AndroidComponentsExtensionCompat {
 
-  @Suppress("UnstableApiUsage")
   override fun onAllVariants(block: (ComponentCompat) -> Unit) {
     val actual = project.extensions.getByType(AndroidComponentsExtension::class.java)
     actual.onVariants { variant ->
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi70Impl.kt b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi70Impl.kt
index 5c626ef..36c7d77 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi70Impl.kt
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-0/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi70Impl.kt
@@ -27,7 +27,7 @@
   override val name: String
     get() = component.name
 
-  @Suppress("UnstableApiUsage")
+  @Suppress("UnstableApiUsage") // Due to ASM pipeline APIs
   override fun <ParamT : InstrumentationParameters> transformClassesWith(
     classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<ParamT>>,
     scope: InstrumentationScope,
@@ -36,6 +36,7 @@
     component.transformClassesWith(classVisitorFactoryImplClass, scope, instrumentationParamsConfig)
   }
 
+  @Suppress("UnstableApiUsage") // Due to ASM pipeline APIs
   override fun setAsmFramesComputationMode(mode: FramesComputationMode) {
     component.setAsmFramesComputationMode(mode)
   }
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle
index ee75692..5bc8d3d 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-1/build.gradle
@@ -2,6 +2,10 @@
   id 'org.jetbrains.kotlin.jvm'
 }
 
+kotlin {
+  jvmToolchain(11)
+}
+
 dependencies {
   implementation project(':agp-wrapper')
   compileOnly gradleApi()
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-1/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi71Impl.kt b/java/dagger/hilt/android/plugin/agp-wrapper-7-1/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi71Impl.kt
index 9184864..d7c9658 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-7-1/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi71Impl.kt
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-1/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi71Impl.kt
@@ -27,7 +27,6 @@
   override val name: String
     get() = component.name
 
-  @Suppress("UnstableApiUsage")
   override fun <ParamT : InstrumentationParameters> transformClassesWith(
     classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<ParamT>>,
     scope: InstrumentationScope,
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle
index a6b9207..a413301 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-2/build.gradle
@@ -2,6 +2,10 @@
   id 'org.jetbrains.kotlin.jvm'
 }
 
+kotlin {
+  jvmToolchain(11)
+}
+
 dependencies {
   implementation project(':agp-wrapper')
   compileOnly gradleApi()
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-7-2/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi72Impl.kt b/java/dagger/hilt/android/plugin/agp-wrapper-7-2/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi72Impl.kt
index 555a775..ac4abf5 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-7-2/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi72Impl.kt
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-7-2/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompatApi72Impl.kt
@@ -27,7 +27,6 @@
   override val name: String
     get() = component.name
 
-  @Suppress("UnstableApiUsage")
   override fun <ParamT : InstrumentationParameters> transformClassesWith(
     classVisitorFactoryImplClass: Class<out AsmClassVisitorFactory<ParamT>>,
     scope: InstrumentationScope,
@@ -40,7 +39,6 @@
     )
   }
 
-  @Suppress("UnstableApiUsage")
   override fun setAsmFramesComputationMode(mode: FramesComputationMode) {
     component.instrumentation.setAsmFramesComputationMode(mode)
   }
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle
index 7f2bfac..3ad558a 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-impl/build.gradle
@@ -2,6 +2,10 @@
   id 'org.jetbrains.kotlin.jvm'
 }
 
+kotlin {
+  jvmToolchain(11)
+}
+
 dependencies {
   api project(':agp-wrapper')
   implementation project(':agp-wrapper-7-0')
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper-impl/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt b/java/dagger/hilt/android/plugin/agp-wrapper-impl/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt
index e078003..6c5e781 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper-impl/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt
+++ b/java/dagger/hilt/android/plugin/agp-wrapper-impl/src/main/kotlin/dagger/hilt/android/plugin/util/SimpleAGPVersion.kt
@@ -24,6 +24,10 @@
   val minor: Int,
 ) : Comparable<SimpleAGPVersion> {
 
+  override fun toString(): String {
+    return "$major.$minor"
+  }
+
   override fun compareTo(other: SimpleAGPVersion): Int {
     return compareValuesBy(
       this,
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle b/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle
index e959d7a..71ea724 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle
+++ b/java/dagger/hilt/android/plugin/agp-wrapper/build.gradle
@@ -2,6 +2,10 @@
   id 'org.jetbrains.kotlin.jvm'
 }
 
+kotlin {
+  jvmToolchain(11)
+}
+
 dependencies {
   compileOnly gradleApi()
   compileOnly "com.android.tools.build:gradle:$agp_version"
diff --git a/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt b/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt
index 2a024c1..acc59e7 100644
--- a/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt
+++ b/java/dagger/hilt/android/plugin/agp-wrapper/src/main/kotlin/dagger/hilt/android/plugin/util/ComponentCompat.kt
@@ -26,7 +26,6 @@
  * - In AGP 4.2 its package is 'com.android.build.api.component'
  * - In AGP 7.0 its packages is 'com.android.build.api.variant'
  */
-@Suppress("UnstableApiUsage") // ASM Pipeline APIs
 abstract class ComponentCompat {
 
   /** Redeclaration of [com.android.build.api.variant.ComponentIdentity.name] */
diff --git a/java/dagger/hilt/android/plugin/build.gradle b/java/dagger/hilt/android/plugin/build.gradle
index 1f75cf9..869d885 100644
--- a/java/dagger/hilt/android/plugin/build.gradle
+++ b/java/dagger/hilt/android/plugin/build.gradle
@@ -1,7 +1,8 @@
 buildscript {
   ext {
-    kotlin_version = "1.8.0"
+    kotlin_version = "1.8.20"
     agp_version = System.getenv('AGP_VERSION') ?: "7.2.0"
+    ksp_version = "$kotlin_version-1.0.11"
     pluginArtifactId = 'hilt-android-gradle-plugin'
     pluginId = 'com.google.dagger.hilt.android'
   }
diff --git a/java/dagger/hilt/android/plugin/main/build.gradle b/java/dagger/hilt/android/plugin/main/build.gradle
index 12ba7f4..96a28b9 100644
--- a/java/dagger/hilt/android/plugin/main/build.gradle
+++ b/java/dagger/hilt/android/plugin/main/build.gradle
@@ -64,14 +64,17 @@
   implementation gradleApi()
   compileOnly "com.android.tools.build:gradle:$agp_version"
   compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-  implementation 'org.javassist:javassist:3.26.0-GA'
+  compileOnly "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp_version"
   implementation 'org.ow2.asm:asm:9.0'
   implementation "com.squareup:javapoet:1.13.0"
 
   testImplementation gradleTestKit()
   testImplementation 'junit:junit:4.12'
   testImplementation 'com.google.truth:truth:1.0.1'
+  testImplementation 'org.javassist:javassist:3.26.0-GA'
   testPluginCompile 'com.android.tools.build:gradle:7.1.2'
+  testPluginCompile 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
+  testPluginCompile 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.8.0-1.0.9'
 }
 
 // Configure the generating task of plugin-under-test-metadata.properties to
@@ -82,16 +85,15 @@
   it.pluginClasspath.from(configurations.testPluginCompile)
 }
 
-compileKotlin {
-  kotlinOptions {
-    jvmTarget = '11'
-    allWarningsAsErrors = true
-  }
+kotlin {
+  jvmToolchain(11)
 }
 
-java {
-  sourceCompatibility JavaVersion.VERSION_11
-  targetCompatibility JavaVersion.VERSION_11
+compileKotlin {
+  kotlinOptions {
+    allWarningsAsErrors = true
+    freeCompilerArgs += [ "-opt-in=kotlin.ExperimentalStdlibApi" ]
+  }
 }
 
 // Imports a shared library from the main project. The library and its classes
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt
deleted file mode 100644
index f31399a..0000000
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassTransformer.kt
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * Copyright (C) 2020 The Dagger Authors.
- *
- * 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 dagger.hilt.android.plugin
-
-import dagger.hilt.android.plugin.util.isClassFile
-import dagger.hilt.android.plugin.util.isJarFile
-import java.io.File
-import java.io.FileInputStream
-import java.util.zip.ZipInputStream
-import javassist.ClassPool
-import javassist.CtClass
-import javassist.Modifier
-import javassist.bytecode.Bytecode
-import javassist.bytecode.CodeIterator
-import javassist.bytecode.Opcode
-import org.slf4j.LoggerFactory
-
-typealias CodeArray = javassist.bytecode.ByteArray // Avoids conflict with Kotlin's stdlib ByteArray
-
-/**
- * A helper class for performing the transform.
- *
- * Create it with the list of all available source directories along with the root output directory
- * and use [AndroidEntryPointClassTransformer.transformFile] or
- * [AndroidEntryPointClassTransformer.transformJarContents] to perform the actual transformation.
- */
-internal class AndroidEntryPointClassTransformer(
-  val taskName: String,
-  allInputs: List<File>,
-  private val sourceRootOutputDir: File,
-  private val copyNonTransformed: Boolean
-) {
-  private val logger = LoggerFactory.getLogger(AndroidEntryPointClassTransformer::class.java)
-
-  // A ClassPool created from the given input files, this allows us to use the higher
-  // level Javaassit APIs, but requires class parsing/loading.
-  private val classPool: ClassPool = ClassPool(true).also { pool ->
-    allInputs.forEach {
-      pool.appendClassPath(it.path)
-    }
-  }
-
-  init {
-    sourceRootOutputDir.mkdirs()
-  }
-
-  /**
-   * Transforms the classes inside the jar and copies re-written class files if and only if they are
-   * transformed.
-   *
-   * @param inputFile The jar file to transform, must be a jar.
-   * @return true if at least one class within the jar was transformed.
-   */
-  fun transformJarContents(inputFile: File): Boolean {
-    require(inputFile.isJarFile()) {
-      "Invalid file, '$inputFile' is not a jar."
-    }
-    // Validate transform is not applied to a jar when copying is enabled, meaning the transformer
-    // is being used in the Android transform API pipeline which does not need to transform jars
-    // and handles copying them.
-    check(!copyNonTransformed) {
-      "Transforming a jar is not supported with 'copyNonTransformed'."
-    }
-    var transformed = false
-    ZipInputStream(FileInputStream(inputFile)).use { input ->
-      var entry = input.nextEntry
-      while (entry != null) {
-        if (entry.isClassFile()) {
-          val clazz = classPool.makeClass(input, false)
-          transformed = transformClassToOutput(clazz) || transformed
-          clazz.detach()
-        }
-        entry = input.nextEntry
-      }
-    }
-    return transformed
-  }
-
-  /**
-   * Transform a single class file.
-   *
-   * @param inputFile The file to transform, must be a class file.
-   * @return true if the class file was transformed.
-   */
-  fun transformFile(inputFile: File): Boolean {
-    check(inputFile.isClassFile()) {
-      "Invalid file, '$inputFile' is not a class."
-    }
-    val clazz = inputFile.inputStream().use { classPool.makeClass(it, false) }
-    val transformed = transformClassToOutput(clazz)
-    clazz.detach()
-    return transformed
-  }
-
-  private fun transformClassToOutput(clazz: CtClass): Boolean {
-    val transformed = transformClass(clazz)
-    if (transformed || copyNonTransformed) {
-      clazz.writeFile(sourceRootOutputDir.path)
-    }
-    return transformed
-  }
-
-  private fun transformClass(clazz: CtClass): Boolean {
-    if (ANDROID_ENTRY_POINT_ANNOTATIONS.none { clazz.hasAnnotation(it) }) {
-      // Not a Android entry point annotated class, don't do anything.
-      return false
-    }
-
-    // TODO(danysantiago): Handle classes with '$' in their name if they do become an issue.
-    val superclassName = clazz.classFile.superclass
-    val entryPointSuperclassName =
-      clazz.packageName + ".Hilt_" + clazz.simpleName.replace("$", "_")
-    logger.info(
-      "[$taskName] Transforming ${clazz.name} to extend $entryPointSuperclassName instead of " +
-        "$superclassName."
-    )
-    val entryPointSuperclass = classPool.get(entryPointSuperclassName)
-    clazz.superclass = entryPointSuperclass
-    transformSuperMethodCalls(clazz, superclassName, entryPointSuperclassName)
-
-    // Check if Hilt generated class is a BroadcastReceiver with the marker field which means
-    // a super.onReceive invocation has to be inserted in the implementation.
-    if (entryPointSuperclass.declaredFields.any { it.name == "onReceiveBytecodeInjectionMarker" }) {
-      transformOnReceive(clazz, entryPointSuperclassName)
-    }
-
-    return true
-  }
-
-  /**
-   * Iterates over each declared method, finding in its bodies super calls. (e.g. super.onCreate())
-   * and rewrites the method reference of the invokespecial instruction to one that uses the new
-   * superclass.
-   *
-   * The invokespecial instruction is emitted for code that between other things also invokes a
-   * method of a superclass of the current class. The opcode invokespecial takes two operands, each
-   * of 8 bit, that together represent an address in the constant pool to a method reference. The
-   * method reference is computed at compile-time by looking the direct superclass declaration, but
-   * at runtime the code behaves like invokevirtual, where as the actual method invoked is looked up
-   * based on the class hierarchy.
-   *
-   * However, it has been observed that on APIs 19 to 22 the Android Runtime (ART) jumps over the
-   * direct superclass and into the method reference class, causing unexpected behaviours.
-   * Therefore, this method performs the additional transformation to rewrite direct super call
-   * invocations to use a method reference whose class in the pool is the new superclass. Note that
-   * this is not necessary for constructor calls since the Javassist library takes care of those.
-   *
-   * @see: https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokespecial
-   * @see: https://source.android.com/devices/tech/dalvik/dalvik-bytecode
-   */
-  private fun transformSuperMethodCalls(
-    clazz: CtClass,
-    oldSuperclassName: String,
-    newSuperclassName: String
-  ) {
-    val constantPool = clazz.classFile.constPool
-    clazz.declaredMethods
-      .filter {
-        it.methodInfo.isMethod &&
-          !Modifier.isStatic(it.modifiers) &&
-          !Modifier.isAbstract(it.modifiers) &&
-          !Modifier.isNative(it.modifiers)
-      }
-      .forEach { method ->
-        val codeAttr = method.methodInfo.codeAttribute
-        val code = codeAttr.code
-        codeAttr.iterator().forEachInstruction { index, opcode ->
-          // We are only interested in 'invokespecial' instructions.
-          if (opcode != Opcode.INVOKESPECIAL) {
-            return@forEachInstruction
-          }
-          // If the method reference of the instruction is not using the old superclass then we
-          // should not rewrite it.
-          val methodRef = CodeArray.readU16bit(code, index + 1)
-          val currentClassRef = constantPool.getMethodrefClassName(methodRef)
-          if (currentClassRef != oldSuperclassName) {
-            return@forEachInstruction
-          }
-          // If the method reference of the instruction is a constructor, then we should not
-          // rewrite it since its an instantiation and not a `super()` call.
-          val methodRefName = constantPool.getMethodrefName(methodRef)
-          if (methodRefName == "<init>") {
-            return@forEachInstruction
-          }
-          val nameAndTypeRef = constantPool.getMethodrefNameAndType(methodRef)
-          val newSuperclassRef = constantPool.addClassInfo(newSuperclassName)
-          val newMethodRef = constantPool.addMethodrefInfo(newSuperclassRef, nameAndTypeRef)
-          logger.info(
-            "[$taskName] Redirecting an invokespecial in " +
-              "${clazz.name}.${method.name}:${method.signature} at code index $index from " +
-              "method ref #$methodRef to #$newMethodRef."
-          )
-          CodeArray.write16bit(newMethodRef, code, index + 1)
-        }
-      }
-  }
-
-  // Iterate over each instruction in a CodeIterator.
-  private fun CodeIterator.forEachInstruction(body: CodeIterator.(Int, Int) -> Unit) {
-    while (hasNext()) {
-      val index = next()
-      this.body(index, byteAt(index))
-    }
-  }
-
-  /**
-   * For a BroadcastReceiver insert a super call in the onReceive method implementation since
-   * after the class is transformed onReceive will no longer be abstract (it is implemented by
-   * Hilt generated receiver).
-   */
-  private fun transformOnReceive(clazz: CtClass, entryPointSuperclassName: String) {
-    val method = clazz.declaredMethods.first {
-      it.name + it.signature == ON_RECEIVE_METHOD_NAME + ON_RECEIVE_METHOD_SIGNATURE
-    }
-    val constantPool = clazz.classFile.constPool
-    val newCode = Bytecode(constantPool).apply {
-      addAload(0) // Loads 'this'
-      addAload(1) // Loads method param 1 (Context)
-      addAload(2) // Loads method param 2 (Intent)
-      addInvokespecial(
-        entryPointSuperclassName, ON_RECEIVE_METHOD_NAME, ON_RECEIVE_METHOD_SIGNATURE
-      )
-    }
-    val newCodeAttribute = newCode.toCodeAttribute()
-    val currentCodeAttribute = method.methodInfo.codeAttribute
-    currentCodeAttribute.maxStack =
-      maxOf(newCodeAttribute.maxStack, currentCodeAttribute.maxStack)
-    currentCodeAttribute.maxLocals =
-      maxOf(newCodeAttribute.maxLocals, currentCodeAttribute.maxLocals)
-    val codeIterator = currentCodeAttribute.iterator()
-    val pos = codeIterator.insertEx(newCode.get()) // insert new code
-    codeIterator.insert(newCodeAttribute.exceptionTable, pos) // offset exception table
-    method.methodInfo.rebuildStackMap(clazz.classPool) // update stack table
-  }
-
-  companion object {
-    val ANDROID_ENTRY_POINT_ANNOTATIONS = setOf(
-      "dagger.hilt.android.AndroidEntryPoint",
-      "dagger.hilt.android.HiltAndroidApp"
-    )
-    val ON_RECEIVE_METHOD_NAME = "onReceive"
-    val ON_RECEIVE_METHOD_SIGNATURE =
-      "(Landroid/content/Context;Landroid/content/Intent;)V"
-  }
-}
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt
index 5eff60b..da5fb3c 100644
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointClassVisitor.kt
@@ -39,13 +39,11 @@
   private val additionalClasses: File
 ) : ClassVisitor(apiVersion, nextClassVisitor) {
 
-  @Suppress("UnstableApiUsage") // ASM Pipeline APIs
   interface AndroidEntryPointParams : InstrumentationParameters {
     @get:Internal
     val additionalClassesDir: Property<File>
   }
 
-  @Suppress("UnstableApiUsage") // ASM Pipeline APIs
   abstract class Factory : AsmClassVisitorFactory<AndroidEntryPointParams> {
     override fun createClassVisitor(
       classContext: ClassContext,
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointTransform.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointTransform.kt
deleted file mode 100644
index c9c6708..0000000
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/AndroidEntryPointTransform.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2020 The Dagger Authors.
- *
- * 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.
- */
-
-@file:Suppress("DEPRECATION") // com.android.build.api.transform is deprecated
-
-package dagger.hilt.android.plugin
-
-import com.android.build.api.transform.DirectoryInput
-import com.android.build.api.transform.Format
-import com.android.build.api.transform.JarInput
-import com.android.build.api.transform.QualifiedContent
-import com.android.build.api.transform.Status
-import com.android.build.api.transform.Transform
-import com.android.build.api.transform.TransformInput
-import com.android.build.api.transform.TransformInvocation
-import dagger.hilt.android.plugin.util.isClassFile
-import java.io.File
-
-/**
- * Bytecode transformation to make @AndroidEntryPoint annotated classes extend the Hilt
- * generated android classes, including the @HiltAndroidApp application class.
- *
- * A transform receives input as a collection [TransformInput], which is composed of [JarInput]s and
- * [DirectoryInput]s. The resulting files must be placed in the
- * [TransformInvocation.getOutputProvider]. The bytecode transformation can be done with any library
- * (in our case Javaassit). The [QualifiedContent.Scope] defined in a transform defines the input
- * the transform will receive and if it can be applied to only the Android application projects or
- * Android libraries too.
- *
- * See: [TransformPublic Docs](https://google.github.io/android-gradle-dsl/javadoc/current/com/android/build/api/transform/Transform.html)
- */
-class AndroidEntryPointTransform : Transform() {
-  // The name of the transform. This name appears as a gradle task.
-  override fun getName() = "AndroidEntryPointTransform"
-
-  // The type of input this transform will handle.
-  override fun getInputTypes() = setOf(QualifiedContent.DefaultContentType.CLASSES)
-
-  override fun isIncremental() = true
-
-  // The project scope this transform is applied to.
-  override fun getScopes() = mutableSetOf(QualifiedContent.Scope.PROJECT)
-
-  /**
-   * Performs the transformation of the bytecode.
-   *
-   * The inputs will be available in the [TransformInvocation] along with referenced inputs that
-   * should not be transformed. The inputs received along with the referenced inputs depend on the
-   * scope of the transform.
-   *
-   * The invocation will also indicate if an incremental transform has to be applied or not. Even
-   * though a transform might return true in its [isIncremental] function, the invocation might
-   * return false in [TransformInvocation.isIncremental], therefore both cases must be handled.
-   */
-  override fun transform(invocation: TransformInvocation) {
-    if (!invocation.isIncremental) {
-      // Remove any lingering files on a non-incremental invocation since everything has to be
-      // transformed.
-      invocation.outputProvider.deleteAll()
-    }
-
-    invocation.inputs.forEach { transformInput ->
-      transformInput.jarInputs.forEach { jarInput ->
-        val outputJar =
-          invocation.outputProvider.getContentLocation(
-            jarInput.name,
-            jarInput.contentTypes,
-            jarInput.scopes,
-            Format.JAR
-          )
-        if (invocation.isIncremental) {
-          when (jarInput.status) {
-            Status.ADDED, Status.CHANGED -> copyJar(jarInput.file, outputJar)
-            Status.REMOVED -> outputJar.delete()
-            Status.NOTCHANGED -> {
-              // No need to transform.
-            }
-            else -> {
-              error("Unknown status: ${jarInput.status}")
-            }
-          }
-        } else {
-          copyJar(jarInput.file, outputJar)
-        }
-      }
-      transformInput.directoryInputs.forEach { directoryInput ->
-        val outputDir = invocation.outputProvider.getContentLocation(
-          directoryInput.name,
-          directoryInput.contentTypes,
-          directoryInput.scopes,
-          Format.DIRECTORY
-        )
-        val classTransformer =
-          createHiltClassTransformer(invocation.inputs, invocation.referencedInputs, outputDir)
-        if (invocation.isIncremental) {
-          directoryInput.changedFiles.forEach { (file, status) ->
-            val outputFile = toOutputFile(outputDir, directoryInput.file, file)
-            when (status) {
-              Status.ADDED, Status.CHANGED ->
-                transformFile(file, outputFile.parentFile, classTransformer)
-              Status.REMOVED -> outputFile.delete()
-              Status.NOTCHANGED -> {
-                // No need to transform.
-              }
-              else -> {
-                error("Unknown status: $status")
-              }
-            }
-          }
-        } else {
-          directoryInput.file.walkTopDown().forEach { file ->
-            val outputFile = toOutputFile(outputDir, directoryInput.file, file)
-            transformFile(file, outputFile.parentFile, classTransformer)
-          }
-        }
-      }
-    }
-  }
-
-  // Create a transformer given an invocation inputs. Note that since this is a PROJECT scoped
-  // transform the actual transformation is only done on project files and not its dependencies.
-  private fun createHiltClassTransformer(
-    inputs: Collection<TransformInput>,
-    referencedInputs: Collection<TransformInput>,
-    outputDir: File
-  ): AndroidEntryPointClassTransformer {
-    val classFiles = (inputs + referencedInputs).flatMap { input ->
-      (input.directoryInputs + input.jarInputs).map { it.file }
-    }
-    return AndroidEntryPointClassTransformer(
-      taskName = name,
-      allInputs = classFiles,
-      sourceRootOutputDir = outputDir,
-      copyNonTransformed = true
-    )
-  }
-
-  // Transform a single file. If the file is not a class file it is just copied to the output dir.
-  private fun transformFile(
-    inputFile: File,
-    outputDir: File,
-    transformer: AndroidEntryPointClassTransformer
-  ) {
-    if (inputFile.isClassFile()) {
-      transformer.transformFile(inputFile)
-    } else if (inputFile.isFile) {
-      // Copy all non .class files to the output.
-      outputDir.mkdirs()
-      val outputFile = File(outputDir, inputFile.name)
-      inputFile.copyTo(target = outputFile, overwrite = true)
-    }
-  }
-
-  // We are only interested in project compiled classes but we have to copy received jars to the
-  // output.
-  private fun copyJar(inputJar: File, outputJar: File) {
-    outputJar.parentFile?.mkdirs()
-    inputJar.copyTo(target = outputJar, overwrite = true)
-  }
-
-  private fun toOutputFile(outputDir: File, inputDir: File, inputFile: File) =
-    File(outputDir, inputFile.relativeTo(inputDir).path)
-}
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltCommandLineArgumentProvider.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltCommandLineArgumentProvider.kt
new file mode 100644
index 0000000..88c9f87
--- /dev/null
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltCommandLineArgumentProvider.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.plugin
+
+import dagger.hilt.processor.internal.optionvalues.GradleProjectType
+import org.gradle.api.tasks.Input
+import org.gradle.process.CommandLineArgumentProvider
+
+/**
+ * Plugin configured annotation processor options provider.
+ */
+internal class HiltCommandLineArgumentProvider(
+  @get:Input
+  val forKsp: Boolean,
+  @get:Input
+  val projectType: GradleProjectType,
+  @get:Input
+  val enableAggregatingTask: Boolean,
+  @get:Input
+  val disableCrossCompilationRootValidation: Boolean
+): CommandLineArgumentProvider {
+
+  private val prefix = if (forKsp) "" else "-A"
+
+  override fun asArguments() = buildMap {
+    // Enable Dagger's fast-init, the best mode for Hilt.
+    put("dagger.fastInit", "enabled")
+    // Disable @AndroidEntryPoint superclass validation.
+    put("dagger.hilt.android.internal.disableAndroidSuperclassValidation", "true")
+    // Report project type for root validation.
+    put("dagger.hilt.android.internal.projectType", projectType.toString())
+
+    // Disable the aggregating processor if aggregating task is enabled.
+    if (enableAggregatingTask) {
+      put("dagger.hilt.internal.useAggregatingRootProcessor", "false")
+    }
+    // Disable cross compilation root validation.
+    // The plugin option duplicates the processor flag because it is an input of the
+    // aggregating task.
+    if (disableCrossCompilationRootValidation) {
+      put("dagger.hilt.disableCrossCompilationRootValidation", "true")
+    }
+  }.map { (key, value) -> "$prefix$key=$value" }
+}
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt
index f5f71a9..4c78d1b 100644
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltExtension.kt
@@ -23,10 +23,10 @@
    * If set to `true`, Hilt will adjust the compile classpath such that it includes transitive
    * dependencies, ignoring `api` or `implementation` boundaries during compilation. You should
    * enable this option if your project has multiple level of transitive dependencies that contain
-   * injected classes or entry points.
+   * injected classes or entry points. The default value is `false`.
    *
-   * Enabling this option also requires android.lintOptions.checkReleaseBuilds to be set to 'false'
-   * if the Android Gradle Plugin version being used is less than 7.0.
+   * This option should be enable as a last resort to avoid classpath issues if
+   * [enableAggregatingTask] (set to `true` by default) causes issues.
    *
    * See https://github.com/google/dagger/issues/1991 for more context.
    */
@@ -37,22 +37,26 @@
    * annotated classes before the host-side JVM tests run. You should enable this option if you are
    * running Robolectric UI tests as part of your JUnit tests.
    *
-   * This flag is not necessary if when com.android.tools.build:gradle:4.2.0+ is used and will be
-   * deprecated in a future version.
+   * This flag is not necessary when com.android.tools.build:gradle:4.2.0+ is used.
    */
+  @Deprecated("Since Hilt Android Gradle plugin requires the usage of the Android " +
+      "Gradle plugin (AGP) version 7.0 or higher this option is no longer necessary and has no " +
+      "effect in the configuration.")
   var enableTransformForLocalTests: Boolean
 
   /**
    * If set to `true`, Hilt will perform module and entry points aggregation in a task instead of an
-   * aggregating annotation processor. Enabling this flag improves incremental build times.
+   * aggregating annotation processor. Enabling this flag improves incremental build times. The
+   * default value is `true`.
    *
    * When this flag is enabled, 'enableExperimentalClasspathAggregation' has no effect since
-   * classpath aggregation will be done by default.
+   * classpath aggregation is already performed by the aggregation task.
    */
   var enableAggregatingTask: Boolean
 
   /**
-   * If set to `true`, Hilt will disable cross compilation root validation.
+   * If set to `true`, Hilt will disable cross compilation root validation. The default value is
+   * `false`.
    *
    * See [documentation](https://dagger.dev/hilt/flags#disable-cross-compilation-root-validation)
    * for more information.
@@ -62,6 +66,9 @@
 
 internal open class HiltExtensionImpl : HiltExtension {
   override var enableExperimentalClasspathAggregation: Boolean = false
+  @Deprecated("Since Hilt Android Gradle plugin requires the usage of the Android " +
+      "Gradle plugin (AGP) version 7.0 or higher this option is no longer necessary and has no " +
+      "effect in the configuration.")
   override var enableTransformForLocalTests: Boolean = false
   override var enableAggregatingTask: Boolean = true
   override var disableCrossCompilationRootValidation: Boolean = false
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
index f2dd881..4e2b878 100644
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt
@@ -22,30 +22,32 @@
 import com.android.build.gradle.BaseExtension
 import com.android.build.gradle.LibraryExtension
 import com.android.build.gradle.TestExtension
-import com.android.build.gradle.TestedExtension
 import com.android.build.gradle.api.AndroidBasePlugin
 import com.android.build.gradle.tasks.JdkImageInput
 import dagger.hilt.android.plugin.task.AggregateDepsTask
-import dagger.hilt.android.plugin.task.HiltTransformTestClassesTask
 import dagger.hilt.android.plugin.util.AggregatedPackagesTransform
 import dagger.hilt.android.plugin.util.ComponentCompat
 import dagger.hilt.android.plugin.util.CopyTransform
 import dagger.hilt.android.plugin.util.SimpleAGPVersion
+import dagger.hilt.android.plugin.util.addJavaTaskProcessorOptions
+import dagger.hilt.android.plugin.util.addKaptTaskProcessorOptions
+import dagger.hilt.android.plugin.util.addKspTaskProcessorOptions
 import dagger.hilt.android.plugin.util.capitalize
 import dagger.hilt.android.plugin.util.getAndroidComponentsExtension
 import dagger.hilt.android.plugin.util.getKaptConfigName
-import dagger.hilt.android.plugin.util.getSdkPath
+import dagger.hilt.android.plugin.util.getKspConfigName
+import dagger.hilt.android.plugin.util.isKspTask
 import dagger.hilt.processor.internal.optionvalues.GradleProjectType
 import java.io.File
 import javax.inject.Inject
 import org.gradle.api.JavaVersion
 import org.gradle.api.Plugin
 import org.gradle.api.Project
+import org.gradle.api.Task
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.component.ProjectComponentIdentifier
 import org.gradle.api.attributes.Attribute
 import org.gradle.api.provider.ProviderFactory
-import org.gradle.api.tasks.Internal
 import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.process.CommandLineArgumentProvider
 import org.gradle.util.GradleVersion
@@ -60,7 +62,7 @@
  * update the superclass.
  */
 class HiltGradlePlugin @Inject constructor(
-  val providers: ProviderFactory
+  private val providers: ProviderFactory
 ) : Plugin<Project> {
   override fun apply(project: Project) {
     var configured = false
@@ -82,15 +84,13 @@
     val hiltExtension = project.extensions.create(
       HiltExtension::class.java, "hilt", HiltExtensionImpl::class.java
     )
+    if (SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(7, 0)) {
+      error("The Hilt Android Gradle plugin is only compatible with Android Gradle plugin (AGP) " +
+              "version 7.0 or higher (found ${SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION}).")
+    }
     configureDependencyTransforms(project)
     configureCompileClasspath(project, hiltExtension)
-    if (SimpleAGPVersion.ANDROID_GRADLE_PLUGIN_VERSION < SimpleAGPVersion(4, 2)) {
-      // Configures bytecode transform using older APIs pre AGP 4.2
-      configureBytecodeTransform(project, hiltExtension)
-    } else {
-      // Configures bytecode transform using AGP 4.2 ASM pipeline.
-      configureBytecodeTransformASM(project, hiltExtension)
-    }
+    configureBytecodeTransformASM(project)
     configureAggregatingTask(project, hiltExtension)
     configureProcessorFlags(project, hiltExtension)
   }
@@ -242,17 +242,8 @@
     project.dependencies.add(compileOnlyConfigName, artifactView.files)
   }
 
-  @Suppress("UnstableApiUsage") // ASM Pipeline APIs
-  private fun configureBytecodeTransformASM(project: Project, hiltExtension: HiltExtension) {
-    var warnAboutLocalTestsFlag = false
+  private fun configureBytecodeTransformASM(project: Project) {
     fun registerTransform(androidComponent: ComponentCompat) {
-      if (hiltExtension.enableTransformForLocalTests && !warnAboutLocalTestsFlag) {
-        project.logger.warn(
-          "The Hilt configuration option 'enableTransformForLocalTests' is no longer necessary " +
-            "when com.android.tools.build:gradle:4.2.0+ is used."
-        )
-        warnAboutLocalTestsFlag = true
-      }
       androidComponent.transformClassesWith(
         classVisitorFactoryImplClass = AndroidEntryPointClassVisitor.Factory::class.java,
         scope = InstrumentationScope.PROJECT
@@ -268,24 +259,6 @@
     getAndroidComponentsExtension(project).onAllVariants { registerTransform(it) }
   }
 
-  private fun configureBytecodeTransform(project: Project, hiltExtension: HiltExtension) {
-    val androidExtension = project.baseExtension() ?: error("Android BaseExtension not found.")
-    androidExtension::class.java.getMethod(
-      "registerTransform",
-      Class.forName("com.android.build.api.transform.Transform"),
-      Array<Any>::class.java
-    ).invoke(androidExtension, AndroidEntryPointTransform(), emptyArray<Any>())
-
-    // Create and configure a task for applying the transform for host-side unit tests. b/37076369
-    project.testedExtension()?.unitTestVariants?.all { unitTestVariant ->
-      HiltTransformTestClassesTask.create(
-        project = project,
-        unitTestVariant = unitTestVariant,
-        extension = hiltExtension
-      )
-    }
-  }
-
   private fun configureAggregatingTask(project: Project, hiltExtension: HiltExtension) {
     val androidExtension = project.baseExtension() ?: error("Android BaseExtension not found.")
     androidExtension.forEachRootVariant { variant ->
@@ -307,15 +280,18 @@
     val hiltCompileConfiguration = project.configurations.create(
       "hiltCompileOnly${variant.name.capitalize()}"
     ).apply {
+      description = "Hilt aggregated compile only dependencies for '${variant.name}'"
       isCanBeConsumed = false
       isCanBeResolved = true
+      isVisible = false
     }
     // Add the JavaCompile task classpath and output dir to the config, the task's classpath
     // will contain:
     //  * compileOnly dependencies
-    //  * KAPT and Kotlinc generated bytecode
+    //  * KAPT, KSP and Kotlinc generated bytecode
     //  * R.jar
     //  * Tested classes if the variant is androidTest
+    // TODO(danysantiago): Revisit to support K2 compiler
     project.dependencies.add(
       hiltCompileConfiguration.name,
       project.files(variant.javaCompileProvider.map { it.classpath })
@@ -325,8 +301,34 @@
       project.files(variant.javaCompileProvider.map {it.destinationDirectory.get() })
     )
 
+    val hiltAnnotationProcessorConfiguration = project.configurations.create(
+      "hiltAnnotationProcessor${variant.name.capitalize()}"
+    ).also { config ->
+      config.description = "Hilt annotation processor classpath for '${variant.name}'"
+      config.isCanBeConsumed = false
+      config.isCanBeResolved = true
+      config.isVisible = false
+      // Add user annotation processor configuration, so that SPI plugins and other processors
+      // are discoverable.
+      val apConfigurations: List<Configuration> = buildList {
+        add(variant.annotationProcessorConfiguration)
+        project.plugins.withId("kotlin-kapt") {
+          project.configurations.findByName(getKaptConfigName(variant))?.let { add(it) }
+        }
+        project.plugins.withId("com.google.devtools.ksp") {
+          // Add the main 'ksp' config since the variant aware config does not extend main.
+          // https://github.com/google/ksp/issues/1433
+          project.configurations.findByName("ksp")?.let { add(it) }
+          project.configurations.findByName(getKspConfigName(variant))?.let { add(it) }
+        }
+      }
+      config.extendsFrom(*apConfigurations.toTypedArray())
+      // Add hilt-compiler even though it might be in the AP configurations already.
+      project.dependencies.add(config.name, "com.google.dagger:hilt-compiler:$HILT_VERSION")
+    }
+
     fun getInputClasspath(artifactAttributeValue: String) =
-      mutableListOf<Configuration>().apply {
+      buildList<Configuration> {
         @Suppress("DEPRECATION") // Older variant API is deprecated
         if (variant is com.android.build.gradle.api.TestVariant) {
           add(variant.testedVariant.runtimeConfiguration)
@@ -399,21 +401,7 @@
       }
       compileTask.destinationDirectory.set(componentClasses.singleFile)
       compileTask.options.apply {
-        annotationProcessorPath = project.configurations.create(
-          "hiltAnnotationProcessor${variant.name.capitalize()}"
-        ).also { config ->
-          config.isCanBeConsumed = false
-          config.isCanBeResolved = true
-          // Add user annotation processor configuration, so that SPI plugins and other processors
-          // are discoverable.
-          val apConfigurations: List<Configuration> = mutableListOf<Configuration>().apply {
-            add(variant.annotationProcessorConfiguration)
-            project.configurations.findByName(getKaptConfigName(variant))?.let { add(it) }
-          }
-          config.extendsFrom(*apConfigurations.toTypedArray())
-          // Add hilt-compiler even though it might be in the AP configurations already.
-          project.dependencies.add(config.name, "com.google.dagger:hilt-compiler:$HILT_VERSION")
-        }
+        annotationProcessorPath = hiltAnnotationProcessorConfiguration
         generatedSourceOutputDirectory.set(
           project.file(
             project.buildDir.resolve("generated/hilt/component_sources/${variant.name}/")
@@ -440,12 +428,8 @@
     variant.registerPostJavacGeneratedBytecode(componentClasses)
   }
 
-  private fun getAndroidJar(project: Project, compileSdkVersion: String) =
-    project.files(File(project.getSdkPath(), "platforms/$compileSdkVersion/android.jar"))
-
   private fun configureProcessorFlags(project: Project, hiltExtension: HiltExtension) {
     val androidExtension = project.baseExtension() ?: error("Android BaseExtension not found.")
-
     val projectType = when (androidExtension) {
       is AppExtension -> GradleProjectType.APP
       is LibraryExtension -> GradleProjectType.LIBRARY
@@ -453,37 +437,23 @@
       else -> error("Hilt plugin does not know how to configure '$this'")
     }
 
-    // Pass annotation processor flags via a CommandLineArgumentProvider so that plugin
-    // options defined in the extension are populated from the user's build file. Checking the
-    // option too early would make it seem like it is never set.
-    androidExtension.defaultConfig.javaCompileOptions.annotationProcessorOptions
-        .compilerArgumentProvider(HiltCommandLineArgumentProvider(hiltExtension, projectType))
-  }
-
-  private class HiltCommandLineArgumentProvider(
-      private val hiltExtension: HiltExtension,
-      private val projectType: GradleProjectType
-  ): CommandLineArgumentProvider {
-    override fun asArguments() = mutableListOf<Pair<String, String>>().apply {
-      // Pass annotation processor flag to enable Dagger's fast-init, the best mode for Hilt.
-      add("dagger.fastInit" to "enabled")
-      // Pass annotation processor flag to disable @AndroidEntryPoint superclass validation.
-      add("dagger.hilt.android.internal.disableAndroidSuperclassValidation" to "true")
-
-      add("dagger.hilt.android.internal.projectType" to projectType.toString())
-
-      // Pass annotation processor flag to disable the aggregating processor if aggregating
-      // task is enabled.
-      if (hiltExtension.enableAggregatingTask) {
-        add("dagger.hilt.internal.useAggregatingRootProcessor" to "false")
+    getAndroidComponentsExtension(project).onAllVariants { component ->
+      // Pass annotation processor flags via a CommandLineArgumentProvider so that plugin
+      // options defined in the extension are populated from the user's build file.
+      val argsProducer: (Task) -> CommandLineArgumentProvider = { task ->
+        HiltCommandLineArgumentProvider(
+          forKsp = task.isKspTask(),
+          projectType = projectType,
+          enableAggregatingTask =
+            hiltExtension.enableAggregatingTask,
+          disableCrossCompilationRootValidation =
+            hiltExtension.disableCrossCompilationRootValidation
+        )
       }
-      // Pass annotation processor flag to disable cross compilation root validation.
-      // The plugin option duplicates the processor flag because it is an input of the
-      // aggregating task.
-      if (hiltExtension.disableCrossCompilationRootValidation) {
-        add("dagger.hilt.disableCrossCompilationRootValidation" to "true")
-      }
-    }.map { (key, value) -> "-A$key=$value" }
+      addJavaTaskProcessorOptions(project, component, argsProducer)
+      addKaptTaskProcessorOptions(project, component, argsProducer)
+      addKspTaskProcessorOptions(project, component, argsProducer)
+    }
   }
 
   private fun verifyDependencies(project: Project) {
@@ -491,35 +461,36 @@
     if (project.state.failure != null) {
       return
     }
-    val dependencies = project.configurations.flatMap { configuration ->
-      configuration.dependencies.map { dependency -> dependency.group to dependency.name }
-    }
+    val dependencies = project.configurations
+      .filterNot {
+        // Exclude plugin created config since plugin adds the deps to them.
+        it.name.startsWith("hiltAnnotationProcessor") ||
+          it.name.startsWith("hiltCompileOnly")
+      }
+      .flatMap { configuration ->
+        configuration.dependencies.map { dependency -> dependency.group to dependency.name }
+      }.toSet()
+    fun getMissingDepMsg(depCoordinate: String): String =
+      "The Hilt Android Gradle plugin is applied but no $depCoordinate dependency was found."
     if (!dependencies.contains(LIBRARY_GROUP to "hilt-android")) {
-      error(missingDepError("$LIBRARY_GROUP:hilt-android"))
+      error(getMissingDepMsg("$LIBRARY_GROUP:hilt-android"))
     }
     if (
       !dependencies.contains(LIBRARY_GROUP to "hilt-android-compiler") &&
       !dependencies.contains(LIBRARY_GROUP to "hilt-compiler")
     ) {
-      error(missingDepError("$LIBRARY_GROUP:hilt-compiler"))
+      error(getMissingDepMsg("$LIBRARY_GROUP:hilt-compiler"))
     }
   }
 
   private fun Project.baseExtension(): BaseExtension?
       = extensions.findByType(BaseExtension::class.java)
 
-  private fun Project.testedExtension(): TestedExtension?
-      = extensions.findByType(TestedExtension::class.java)
-
   companion object {
-    val ARTIFACT_TYPE_ATTRIBUTE = Attribute.of("artifactType", String::class.java)
+    private val ARTIFACT_TYPE_ATTRIBUTE = Attribute.of("artifactType", String::class.java)
     const val DAGGER_ARTIFACT_TYPE_VALUE = "jar-for-dagger"
     const val AGGREGATED_HILT_ARTIFACT_TYPE_VALUE = "aggregated-jar-for-hilt"
 
     const val LIBRARY_GROUP = "com.google.dagger"
-
-    val missingDepError: (String) -> String = { depCoordinate ->
-      "The Hilt Android Gradle plugin is applied but no $depCoordinate dependency was found."
-    }
   }
 }
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/root/Aggregator.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/root/Aggregator.kt
index 27ea2c7..ba41935 100644
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/root/Aggregator.kt
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/root/Aggregator.kt
@@ -123,10 +123,10 @@
           return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
             lateinit var rootClass: String
             var rootPackage: String? = null
-            val rootSimpleNames: MutableList<String> = mutableListOf<String>()
+            val rootSimpleNames = mutableListOf<String>()
             lateinit var originatingRootClass: String
             var originatingRootPackage: String? = null
-            val originatingRootSimpleNames: MutableList<String> = mutableListOf<String>()
+            val originatingRootSimpleNames = mutableListOf<String>()
             lateinit var rootAnnotationClassName: Type
 
             override fun visit(name: String, value: Any?) {
@@ -136,14 +136,13 @@
                 "originatingRoot" -> originatingRootClass = value as String
                 "originatingRootPackage" -> originatingRootPackage = value as String
                 "rootAnnotation" -> rootAnnotationClassName = (value as Type)
-                else -> throw IllegalStateException("Unexpected annotation value: " + name)
+                else -> error("Unexpected annotation value: $name")
               }
               super.visit(name, value)
             }
 
-            override fun visitArray(name: String): AnnotationVisitor? {
+            override fun visitArray(name: String): AnnotationVisitor {
               return object : AnnotationVisitor(asmApiVersion, super.visitArray(name)) {
-                @Suppress("UNCHECKED_CAST")
                 override fun visit(passThroughValueName: String?, value: Any?) {
                   // Note that passThroughValueName should usually be null since the real name
                   // is the name passed to visitArray.
@@ -239,7 +238,7 @@
                 AliasOfPropagatedDataIr(
                   fqName = annotatedClassName,
                   defineComponentScopes =
-                    defineComponentScopeClassNames.map({ it.toClassName() }).toList(),
+                    defineComponentScopeClassNames.map { it.toClassName() }.toList(),
                   alias = aliasClassName.toClassName(),
                 )
               )
@@ -439,7 +438,7 @@
       fallbackCanonicalName: String
     ): ClassName {
       if (packageName != null) {
-        check(simpleNames.size > 0)
+        check(simpleNames.isNotEmpty())
         return ClassName.get(
           packageName,
           simpleNames.first(),
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/task/HiltTransformTestClassesTask.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/task/HiltTransformTestClassesTask.kt
deleted file mode 100644
index d033c60..0000000
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/task/HiltTransformTestClassesTask.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2020 The Dagger Authors.
- *
- * 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 dagger.hilt.android.plugin.task
-
-import dagger.hilt.android.plugin.AndroidEntryPointClassTransformer
-import dagger.hilt.android.plugin.HiltExtension
-import dagger.hilt.android.plugin.util.capitalize
-import dagger.hilt.android.plugin.util.getCompileKotlin
-import dagger.hilt.android.plugin.util.isClassFile
-import dagger.hilt.android.plugin.util.isJarFile
-import java.io.File
-import javax.inject.Inject
-import org.gradle.api.Action
-import org.gradle.api.DefaultTask
-import org.gradle.api.Project
-import org.gradle.api.file.ConfigurableFileCollection
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.file.FileCollection
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Classpath
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.TaskProvider
-import org.gradle.api.tasks.testing.Test
-import org.gradle.workers.WorkAction
-import org.gradle.workers.WorkParameters
-import org.gradle.workers.WorkerExecutor
-import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
-
-/**
- * Task that transform classes used by host-side unit tests. See b/37076369
- */
-abstract class HiltTransformTestClassesTask @Inject constructor(
-  private val workerExecutor: WorkerExecutor
-) : DefaultTask() {
-
-  @get:Classpath
-  abstract val compiledClasses: ConfigurableFileCollection
-
-  @get:OutputDirectory
-  abstract val outputDir: DirectoryProperty
-
-  internal interface Parameters : WorkParameters {
-    val name: Property<String>
-    val compiledClasses: ConfigurableFileCollection
-    val outputDir: DirectoryProperty
-  }
-
-  abstract class WorkerAction : WorkAction<Parameters> {
-    override fun execute() {
-      val outputDir = parameters.outputDir.asFile.get()
-      outputDir.deleteRecursively()
-      outputDir.mkdirs()
-
-      val allInputs = parameters.compiledClasses.files.toList()
-      val classTransformer = AndroidEntryPointClassTransformer(
-        taskName = parameters.name.get(),
-        allInputs = allInputs,
-        sourceRootOutputDir = outputDir,
-        copyNonTransformed = false
-      )
-      // Parse the classpath in reverse so that we respect overwrites, if it ever happens.
-      allInputs.reversed().forEach {
-        if (it.isDirectory) {
-          it.walkTopDown().forEach { file ->
-            if (file.isClassFile()) {
-              classTransformer.transformFile(file)
-            }
-          }
-        } else if (it.isJarFile()) {
-          classTransformer.transformJarContents(it)
-        }
-      }
-    }
-  }
-
-  @TaskAction
-  fun transformClasses() {
-    workerExecutor.noIsolation().submit(WorkerAction::class.java) {
-      it.compiledClasses.from(compiledClasses)
-      it.outputDir.set(outputDir)
-      it.name.set(name)
-    }
-  }
-
-  internal class ConfigAction(
-    private val outputDir: File,
-    private val inputClasspath: FileCollection
-  ) : Action<HiltTransformTestClassesTask> {
-    override fun execute(transformTask: HiltTransformTestClassesTask) {
-      transformTask.description = "Transforms AndroidEntryPoint annotated classes for JUnit tests."
-      transformTask.outputDir.set(outputDir)
-      transformTask.compiledClasses.from(inputClasspath)
-    }
-  }
-
-  companion object {
-
-    private const val TASK_PREFIX = "hiltTransformFor"
-
-    fun create(
-      project: Project,
-      @Suppress("DEPRECATION") unitTestVariant: com.android.build.gradle.api.UnitTestVariant,
-      extension: HiltExtension
-    ) {
-      if (!extension.enableTransformForLocalTests) {
-        // Not enabled, nothing to do here.
-        return
-      }
-
-      // TODO(danysantiago): Only use project compiled sources as input, and not all dependency jars
-      // Using 'null' key to obtain the full compile classpath since we are not using the
-      // registerPreJavacGeneratedBytecode() API that would have otherwise given us a key to get
-      // a classpath up to the generated bytecode associated with the key.
-      val inputClasspath =
-        project.files(unitTestVariant.getCompileClasspath(null))
-
-      // Find the test sources Java compile task and add its output directory into our input
-      // classpath file collection. This also makes the transform task depend on the test compile
-      // task.
-      val testCompileTaskProvider = unitTestVariant.javaCompileProvider
-      inputClasspath.from(testCompileTaskProvider.map { it.destinationDirectory })
-
-      // Similarly, if the Kotlin plugin is configured, find the test sources Kotlin compile task
-      // and add its output directory to our input classpath file collection.
-      project.plugins.withType(KotlinBasePluginWrapper::class.java) {
-        val kotlinCompileTaskProvider = getCompileKotlin(unitTestVariant, project)
-        inputClasspath.from(kotlinCompileTaskProvider.map { it.destinationDirectory })
-      }
-
-      // Create and configure the transform task.
-      val outputDir =
-        project.buildDir.resolve("intermediates/hilt/${unitTestVariant.dirName}Output")
-      val hiltTransformProvider = project.tasks.register(
-        "$TASK_PREFIX${unitTestVariant.name.capitalize()}",
-        HiltTransformTestClassesTask::class.java,
-        ConfigAction(outputDir, inputClasspath)
-      )
-      // Map the transform task's output to a file collection.
-      val outputFileCollection =
-        project.files(hiltTransformProvider.map { it.outputDir })
-
-      // Configure test classpath by appending the transform output file collection to the start of
-      // the test classpath so they override the original ones. This also makes test task (the one
-      // that runs the tests) depend on the transform task.
-
-      @Suppress("UNCHECKED_CAST")
-      val testTaskProvider = project.tasks.named(
-        "test${unitTestVariant.name.capitalize()}"
-      ) as TaskProvider<Test>
-      testTaskProvider.configure {
-        it.classpath = outputFileCollection + it.classpath
-      }
-    }
-  }
-}
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Configurations.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Configurations.kt
index 0fe2f83..1f4a792 100644
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Configurations.kt
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Configurations.kt
@@ -17,20 +17,31 @@
 package dagger.hilt.android.plugin.util
 
 @Suppress("DEPRECATION") // Older variant API is deprecated
-internal fun getKaptConfigName(variant: com.android.build.gradle.api.BaseVariant): String {
-  // KAPT config names don't follow the usual convention:
+internal fun getKaptConfigName(variant: com.android.build.gradle.api.BaseVariant)
+  = getConfigName(variant, "kapt")
+
+@Suppress("DEPRECATION") // Older variant API is deprecated
+internal fun getKspConfigName(variant: com.android.build.gradle.api.BaseVariant)
+  = getConfigName(variant, "ksp")
+
+@Suppress("DEPRECATION") // Older variant API is deprecated
+internal fun getConfigName(
+  variant: com.android.build.gradle.api.BaseVariant,
+  prefix: String
+): String {
+  // Config names don't follow the usual task name conventions:
   // <Variant Name>   -> <Config Name>
-  // debug            -> kaptDebug
-  // debugAndroidTest -> kaptAndroidTestDebug
-  // debugUnitTest    -> kaptTestDebug
-  // release          -> kaptRelease
-  // releaseUnitTest  -> kaptTestRelease
+  // debug            -> <prefix>Debug
+  // debugAndroidTest -> <prefix>AndroidTestDebug
+  // debugUnitTest    -> <prefix>TestDebug
+  // release          -> <prefix>Release
+  // releaseUnitTest  -> <prefix>TestRelease
   return when (variant) {
     is com.android.build.gradle.api.TestVariant ->
-      "kaptAndroidTest${variant.name.substringBeforeLast("AndroidTest").capitalize()}"
+      "${prefix}AndroidTest${variant.name.substringBeforeLast("AndroidTest").capitalize()}"
     is com.android.build.gradle.api.UnitTestVariant ->
-      "kaptTest${variant.name.substringBeforeLast("UnitTest").capitalize()}"
+      "${prefix}Test${variant.name.substringBeforeLast("UnitTest").capitalize()}"
     else ->
-      "kapt${variant.name}"
+      "${prefix}${variant.name.capitalize()}"
   }
 }
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt
index e7b0234..b837d4e 100644
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Files.kt
@@ -18,10 +18,8 @@
 
 import java.io.File
 import java.io.InputStream
-import java.util.Properties
 import java.util.zip.ZipEntry
 import java.util.zip.ZipInputStream
-import org.gradle.api.Project
 
 /* Checks if a file is a .class file. */
 fun File.isClassFile() = this.isFile && this.extension == "class"
@@ -48,36 +46,3 @@
     inputEntry = nextEntry
   }
 }
-
-/* Gets the Android Sdk Path. */
-fun Project.getSdkPath(): File {
-  val localPropsFile = rootProject.projectDir.resolve("local.properties")
-  if (localPropsFile.exists()) {
-    val localProps = Properties()
-    localPropsFile.inputStream().use { localProps.load(it) }
-    val localSdkDir = localProps["sdk.dir"]?.toString()
-    if (localSdkDir != null) {
-      val sdkDirectory = File(localSdkDir)
-      if (sdkDirectory.isDirectory) {
-        return sdkDirectory
-      }
-    }
-  }
-  return getSdkPathFromEnvironmentVariable()
-}
-
-private fun getSdkPathFromEnvironmentVariable(): File {
-  // Check for environment variables, in the order AGP checks.
-  listOf("ANDROID_HOME", "ANDROID_SDK_ROOT").forEach {
-    val envValue = System.getenv(it)
-    if (envValue != null) {
-      val sdkDirectory = File(envValue)
-      if (sdkDirectory.isDirectory) {
-        return sdkDirectory
-      }
-    }
-  }
-  // Only print the error for SDK ROOT since ANDROID_HOME is deprecated but we first check
-  // it because it is prioritized according to the documentation.
-  error("ANDROID_SDK_ROOT environment variable is not set")
-}
diff --git a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt
index 2bab91b..2c803b7 100644
--- a/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt
+++ b/java/dagger/hilt/android/plugin/main/src/main/kotlin/dagger/hilt/android/plugin/util/Tasks.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Dagger Authors.
+ * Copyright (C) 2023 The Dagger Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,17 +16,60 @@
 
 package dagger.hilt.android.plugin.util
 
+import com.google.devtools.ksp.gradle.KspTaskJvm
 import org.gradle.api.Project
-import org.gradle.api.tasks.TaskProvider
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+import org.gradle.api.Task
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.process.CommandLineArgumentProvider
+import org.jetbrains.kotlin.gradle.internal.KaptTask
 
-/**
- * Gets [KotlinCompile] task of an Android variant.
- */
-@Suppress("UNCHECKED_CAST")
-internal fun getCompileKotlin(
-  @Suppress("DEPRECATION") variant: com.android.build.gradle.api.BaseVariant,
-  project: Project
-) = project.tasks.named(
-  "compile${variant.name.capitalize()}Kotlin"
-) as TaskProvider<KotlinCompile>
+internal fun addJavaTaskProcessorOptions(
+  project: Project,
+  component: ComponentCompat,
+  produceArgProvider: (Task) -> CommandLineArgumentProvider
+) = project.tasks.withType(JavaCompile::class.java) { task ->
+  if (task.name == "compile${component.name.capitalize()}JavaWithJavac") {
+    task.options.compilerArgumentProviders.add(produceArgProvider.invoke(task))
+  }
+}
+
+internal fun addKaptTaskProcessorOptions(
+  project: Project,
+  component: ComponentCompat,
+  produceArgProvider: (Task) -> CommandLineArgumentProvider
+) = project.plugins.withId("kotlin-kapt") {
+  project.tasks.withType(KaptTask::class.java) { task ->
+    if (task.name == "kapt${component.name.capitalize()}Kotlin") {
+      val argProvider = produceArgProvider.invoke(task)
+      // TODO: Update once KT-58009 is fixed.
+      try {
+        // Because of KT-58009, we need to add a `listOf(argProvider)` instead
+        // of `argProvider`.
+        task.annotationProcessorOptionProviders.add(listOf(argProvider))
+      } catch (e: Throwable) {
+        // Once KT-58009 is fixed, adding `listOf(argProvider)` will fail, we will
+        // pass `argProvider` instead, which is the correct way.
+        task.annotationProcessorOptionProviders.add(argProvider)
+      }
+    }
+  }
+}
+
+internal fun addKspTaskProcessorOptions(
+  project: Project,
+  component: ComponentCompat,
+  produceArgProvider: (Task) -> CommandLineArgumentProvider
+) = project.plugins.withId("com.google.devtools.ksp") {
+  project.tasks.withType(KspTaskJvm::class.java) { task ->
+    if (task.name == "ksp${component.name.capitalize()}Kotlin") {
+      task.commandLineArgumentProviders.add(produceArgProvider.invoke(task))
+    }
+  }
+}
+
+internal fun Task.isKspTask(): Boolean = try {
+  val kspTaskClass = Class.forName("com.google.devtools.ksp.gradle.KspTask")
+  kspTaskClass.isAssignableFrom(this::class.java)
+} catch (ex: ClassNotFoundException) {
+  false
+}
\ No newline at end of file
diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/java/spi/TestPlugin.java b/java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/java/spi/TestPlugin.java
index 1104ba0..f2209f3 100644
--- a/java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/java/spi/TestPlugin.java
+++ b/java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/java/spi/TestPlugin.java
@@ -16,10 +16,10 @@
 
 package spi;
 
-import dagger.model.BindingGraph;
-import dagger.model.BindingGraph.ComponentNode;
-import dagger.spi.BindingGraphPlugin;
-import dagger.spi.DiagnosticReporter;
+import dagger.spi.model.BindingGraph;
+import dagger.spi.model.BindingGraph.ComponentNode;
+import dagger.spi.model.BindingGraphPlugin;
+import dagger.spi.model.DiagnosticReporter;
 import javax.tools.Diagnostic;
 
 /**
diff --git a/java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/resources/META-INF/services/dagger.spi.BindingGraphPlugin b/java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/resources/META-INF/services/dagger.spi.model.BindingGraphPlugin
similarity index 100%
rename from java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/resources/META-INF/services/dagger.spi.BindingGraphPlugin
rename to java/dagger/hilt/android/plugin/main/src/test/data/spi-plugin/src/main/resources/META-INF/services/dagger.spi.model.BindingGraphPlugin
diff --git a/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt b/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt
index 9b1d677..fb47bdb 100644
--- a/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt
+++ b/java/dagger/hilt/android/plugin/main/src/test/kotlin/GradleTestRunner.kt
@@ -22,10 +22,13 @@
 
 /** Testing utility class that sets up a simple Android project that applies the Hilt plugin. */
 class GradleTestRunner(val tempFolder: TemporaryFolder) {
+  private val pluginClasspaths = mutableListOf<String>()
+  private val pluginIds = mutableListOf<String>()
   private val dependencies = mutableListOf<String>()
   private val activities = mutableListOf<String>()
   private val additionalAndroidOptions = mutableListOf<String>()
   private val hiltOptions = mutableListOf<String>()
+  private val additionalClosures = mutableListOf<String>()
   private var appClassName: String? = null
   private var buildFile: File? = null
   private var gradlePropertiesFile: File? = null
@@ -39,6 +42,17 @@
     tempFolder.newFolder("src", "main", "res")
   }
 
+  // Adds a Gradle plugin classpath to the test project,
+  // e.g. "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0"
+  fun addPluginClasspath(pluginClasspath: String) {
+    pluginClasspaths.add(pluginClasspath)
+  }
+
+  // Adds a Gradle plugin id to the test project, e.g. "kotlin-android"
+  fun addPluginId(pluginId: String) {
+    pluginIds.add(pluginId)
+  }
+
   // Adds project dependencies, e.g. "implementation <group>:<id>:<version>"
   fun addDependencies(vararg deps: String) {
     dependencies.addAll(deps)
@@ -61,6 +75,10 @@
     hiltOptions.addAll(options)
   }
 
+  fun addAdditionalClosure(closure: String) {
+    additionalClosures.add(closure)
+  }
+
   // Adds a source package to the project. The package path is relative to 'src/main/java'.
   fun addSrcPackage(packagePath: String) {
     File(tempFolder.root, "src/main/java/$packagePath").mkdirs()
@@ -127,12 +145,14 @@
           }
           dependencies {
             classpath 'com.android.tools.build:gradle:7.1.2'
+            ${pluginClasspaths.joinToString(separator = "\n") { "classpath '$it'" }}
           }
         }
 
         plugins {
           id '${ if (isAppProject) "com.android.application" else "com.android.library" }'
           id 'com.google.dagger.hilt.android'
+          ${pluginIds.joinToString(separator = "\n") { "id '$it'" }}
         }
 
         android {
@@ -168,6 +188,7 @@
         hilt {
           ${hiltOptions.joinToString(separator = "\n")}
         }
+        ${additionalClosures.joinToString(separator = "\n")}
         """.trimIndent()
         )
       }
diff --git a/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt b/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt
index d13253b..1e8490f 100644
--- a/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt
+++ b/java/dagger/hilt/android/plugin/main/src/test/kotlin/IncrementalProcessorTest.kt
@@ -195,7 +195,6 @@
 
     // Compute directory paths
     val defaultGenSrcDir = "build/generated/ap_generated_sources/debug/out/"
-    val testDefaultGenSrcDir = "build/generated/ap_generated_sources/debugUnitTest/out/"
     fun getComponentTreeDepsGenSrcDir(variant: String) = if (incapMode == ISOLATING_MODE) {
       "build/generated/hilt/component_trees/$variant/"
     } else {
diff --git a/java/dagger/hilt/android/plugin/main/src/test/kotlin/SPIPluginTest.kt b/java/dagger/hilt/android/plugin/main/src/test/kotlin/SPIPluginTest.kt
index ffe6dc0..963d262 100644
--- a/java/dagger/hilt/android/plugin/main/src/test/kotlin/SPIPluginTest.kt
+++ b/java/dagger/hilt/android/plugin/main/src/test/kotlin/SPIPluginTest.kt
@@ -21,8 +21,11 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
-class SPIPluginTest {
+@RunWith(Parameterized::class)
+class SPIPluginTest(val backend: Backend) {
   @get:Rule
   val testProjectDir = TemporaryFolder()
 
@@ -40,12 +43,30 @@
         """.trimIndent()
       )
     }
+    val processorConfig = when (backend) {
+      Backend.JAVAC -> "annotationProcessor"
+      Backend.KAPT -> "kapt"
+      Backend.KSP -> "ksp"
+    }
+    if (backend == Backend.KAPT || backend == Backend.KSP) {
+      gradleRunner.addPluginId("kotlin-android")
+      if (backend == Backend.KAPT) {
+        gradleRunner.addPluginId("kotlin-kapt")
+      } else {
+        gradleRunner.addPluginId("com.google.devtools.ksp")
+      }
+      gradleRunner.addAdditionalClosure("""
+      |kotlin {
+      |  jvmToolchain(11)
+      |}
+      """.trimMargin())
+    }
     gradleRunner.addHiltOption("enableAggregatingTask = true")
     gradleRunner.addDependencies(
       "implementation 'androidx.appcompat:appcompat:1.1.0'",
       "implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'",
-      "annotationProcessor 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'",
-      "annotationProcessor project(':spi-plugin')",
+      "$processorConfig 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'",
+      "$processorConfig project(':spi-plugin')",
     )
     gradleRunner.addSrc(
       srcPath = "minimal/MyApp.java",
@@ -74,4 +95,14 @@
       "[spi.TestPlugin] Found component: minimal.MyApp_HiltComponents.SingletonC"
     )
   }
+
+  companion object {
+    @JvmStatic
+    @Parameterized.Parameters(name = "backend = {0}")
+    fun params() = listOf(Backend.JAVAC, Backend.KAPT, Backend.KSP)
+
+    enum class Backend {
+      JAVAC, KAPT, KSP
+    }
+  }
 }
\ No newline at end of file
diff --git a/java/dagger/hilt/android/processor/BUILD b/java/dagger/hilt/android/processor/BUILD
index 6ca29b4..6f45216 100644
--- a/java/dagger/hilt/android/processor/BUILD
+++ b/java/dagger/hilt/android/processor/BUILD
@@ -36,7 +36,6 @@
     artifact_target = ":artifact-lib",
     artifact_target_libs = [
         "//java/dagger/hilt/android/processor/internal:android_classnames",
-        "//java/dagger/hilt/android/processor/internal:utils",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib",
@@ -48,10 +47,11 @@
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:compiler_options",
+        "//java/dagger/hilt/processor/internal:dagger_models",
         "//java/dagger/hilt/processor/internal:component_descriptor",
         "//java/dagger/hilt/processor/internal:component_names",
         "//java/dagger/hilt/processor/internal:components",
-        "//java/dagger/hilt/processor/internal:element_descriptors",
+        "//java/dagger/hilt/processor/internal:hilt_processing_env_configs",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies",
@@ -60,6 +60,7 @@
         "//java/dagger/hilt/processor/internal/aliasof:alias_ofs",
         "//java/dagger/hilt/processor/internal/aliasof:processor_lib",
         "//java/dagger/hilt/processor/internal/definecomponent:define_components",
+        "//java/dagger/hilt/processor/internal/definecomponent:metadatas",
         "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
         "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata",
         "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib",
@@ -77,19 +78,18 @@
         "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
     ],
     artifact_target_maven_deps = [
-        "com.google.auto:auto-common",
         "com.google.code.findbugs:jsr305",
         "com.google.dagger:dagger-compiler",
         "com.google.dagger:dagger",
         "com.google.dagger:dagger-spi",
+        "com.google.devtools.ksp:symbol-processing-api",
         "com.google.guava:failureaccess",
         "com.google.guava:guava",
         "com.squareup:javapoet",
-        "javax.annotation:javax.annotation-api",
+        "com.squareup:kotlinpoet",
         "javax.inject:javax.inject",
         "net.ltgt.gradle.incap:incap",
         "org.jetbrains.kotlin:kotlin-stdlib",
-        "org.jetbrains.kotlinx:kotlinx-metadata-jvm",
     ],
     javadoc_android_api_level = 32,
     javadoc_root_packages = [
@@ -100,10 +100,6 @@
     javadoc_srcs = [
         "//java/dagger/hilt:hilt_processing_filegroup",
     ],
-    # The shaded deps are added using jarjar, but they won't be shaded until later
-    # due to: https://github.com/google/dagger/issues/2765. For the shaded rules see
-    # util/deploy-hilt.sh
-    shaded_deps = ["//third_party/java/auto:common"],
 )
 
 filegroup(
diff --git a/java/dagger/hilt/android/processor/internal/BUILD b/java/dagger/hilt/android/processor/internal/BUILD
index dfd7333..45615e4 100644
--- a/java/dagger/hilt/android/processor/internal/BUILD
+++ b/java/dagger/hilt/android/processor/internal/BUILD
@@ -27,18 +27,6 @@
     ],
 )
 
-# TODO(erichang): Merge this into other utils
-java_library(
-    name = "utils",
-    srcs = [
-        "MoreTypes.java",
-    ],
-    deps = [
-        "//third_party/java/guava/base",
-        "//third_party/java/guava/collect",
-    ],
-)
-
 filegroup(
     name = "srcs_filegroup",
     srcs = glob(["**/*"]),
diff --git a/java/dagger/hilt/android/processor/internal/MoreTypes.java b/java/dagger/hilt/android/processor/internal/MoreTypes.java
deleted file mode 100644
index e5a2cdd..0000000
--- a/java/dagger/hilt/android/processor/internal/MoreTypes.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Dagger Authors.
- *
- * 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 dagger.hilt.android.processor.internal;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Iterables;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.ArrayType;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.ErrorType;
-import javax.lang.model.type.ExecutableType;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementFilter;
-import javax.lang.model.util.SimpleTypeVisitor7;
-import javax.lang.model.util.Types;
-
-/** More utility methods for types. */
-public final class MoreTypes {
-  private MoreTypes() {}
-
-  /**
-   * If the received mirror represents a declared type or an array of declared types, this returns
-   * the represented declared type. Otherwise throws an IllegalStateException.
-   */
-  public static DeclaredType getDeclaredType(TypeMirror type) {
-    return type.accept(
-        new SimpleTypeVisitor7<DeclaredType, Void>() {
-          @Override public DeclaredType visitArray(ArrayType type, Void unused) {
-            return getDeclaredType(type.getComponentType());
-          }
-
-          @Override public DeclaredType visitDeclared(DeclaredType type, Void unused) {
-            return type;
-          }
-
-          @Override public DeclaredType visitError(ErrorType type, Void unused) {
-            return type;
-          }
-
-          @Override public DeclaredType defaultAction(TypeMirror type, Void unused) {
-            throw new IllegalStateException("Unhandled type: " + type);
-          }
-        }, null /* the Void accumulator */);
-  }
-
-  /** Returns the TypeElement corresponding to a TypeMirror. */
-  public static TypeElement asTypeElement(TypeMirror type) {
-    return asTypeElement(getDeclaredType(type));
-  }
-
-  /** Returns the TypeElement corresponding to a DeclaredType. */
-  public static TypeElement asTypeElement(DeclaredType type) {
-    return (TypeElement) type.asElement();
-  }
-
-  /**
-   * Returns a {@link ExecutableType} if the {@link TypeMirror} represents an executable type such
-   * as a method, constructor, or initializer or throws an {@link IllegalArgumentException}.
-   */
-  public static ExecutableType asExecutable(TypeMirror maybeExecutableType) {
-    return maybeExecutableType.accept(ExecutableTypeVisitor.INSTANCE, null);
-  }
-
-  private static final class ExecutableTypeVisitor extends CastingTypeVisitor<ExecutableType> {
-    private static final ExecutableTypeVisitor INSTANCE = new ExecutableTypeVisitor();
-
-    ExecutableTypeVisitor() {
-      super("executable type");
-    }
-
-    @Override
-    public ExecutableType visitExecutable(ExecutableType type, Void ignore) {
-      return type;
-    }
-  }
-
-  private abstract static class CastingTypeVisitor<T> extends SimpleTypeVisitor7<T, Void> {
-    private final String label;
-
-    CastingTypeVisitor(String label) {
-      this.label = label;
-    }
-
-    @Override
-    protected T defaultAction(TypeMirror e, Void v) {
-      throw new IllegalArgumentException(e + " does not represent a " + label);
-    }
-  }
-
-  /**
-   * Returns the first matching method, if one exists (starting with classElement, then searching
-   * each sub classes).
-   */
-  public static Optional<ExecutableElement> findInheritedMethod(
-      Types types, TypeElement classElement, ExecutableElement method) {
-    Optional<ExecutableElement> match = Optional.empty();
-    while (!match.isPresent() && !classElement.asType().getKind().equals(TypeKind.NONE)) {
-      match = findMethod(types, classElement, method);
-      classElement = MoreTypes.asTypeElement(classElement.getSuperclass());
-    }
-    return match;
-  }
-
-  /** Returns a method with a matching signature in classElement if one exists. */
-  public static Optional<ExecutableElement> findMethod(
-      Types types, TypeElement classElement, ExecutableElement method) {
-    ExecutableType methodType = asExecutable(method.asType());
-    Set<ExecutableElement> matchingMethods =
-        findMethods(classElement, method.getSimpleName().toString())
-            .stream()
-            .filter(clsMethod -> types.isSubsignature(asExecutable(clsMethod.asType()), methodType))
-            .collect(Collectors.toSet());
-
-    Preconditions.checkState(
-        matchingMethods.size() <= 1,
-        "Found multiple methods with matching signature in class %s: %s",
-        classElement,
-        matchingMethods);
-
-    return matchingMethods.size() == 1
-        ? Optional.of(Iterables.getOnlyElement(matchingMethods))
-        : Optional.empty();
-  }
-
-  /** Returns methods with a matching name in classElement. */
-  public static Set<ExecutableElement> findMethods(TypeElement classElement, String name) {
-    return ElementFilter.methodsIn(classElement.getEnclosedElements())
-        .stream()
-        .filter(clsMethod -> clsMethod.getSimpleName().contentEquals(name))
-        .collect(Collectors.toSet());
-  }
-}
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java
index f933a90..49f439e 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGenerator.java
@@ -16,28 +16,29 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeParameterElement;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.TypeVariableName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /** Generates an Hilt Activity class for the @AndroidEntryPoint annotated class. */
 public final class ActivityGenerator {
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName generatedClassName;
 
-  public ActivityGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
+  public ActivityGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
-
     generatedClassName = metadata.generatedClassName();
   }
 
@@ -48,10 +49,10 @@
   public void generate() throws IOException {
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(generatedClassName.simpleName())
-            .addOriginatingElement(metadata.element())
             .superclass(metadata.baseClassName())
             .addModifiers(metadata.generatedClassModifiers());
 
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
     Processors.addGeneratedAnnotation(builder, env, getClass());
 
@@ -62,7 +63,7 @@
       builder.addMethod(init());
 
     metadata.baseElement().getTypeParameters().stream()
-        .map(TypeVariableName::get)
+        .map(XTypeParameterElement::getTypeVariableName)
         .forEachOrdered(builder::addTypeVariable);
 
     Generators.addComponentOverride(metadata, builder);
@@ -76,9 +77,10 @@
       builder.addMethod(getDefaultViewModelProviderFactory());
     }
 
-    JavaFile.builder(generatedClassName.packageName(), builder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
+            XFiler.Mode.Isolating);
   }
 
   // private void init() {
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java
index 39adc18..6f925da 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointMetadata.java
@@ -16,16 +16,27 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
-import static dagger.hilt.processor.internal.HiltCompilerOptions.isAndroidSuperclassValidationDisabled;
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static androidx.room.compiler.processing.XTypeKt.isVoidObject;
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isAndroidSuperClassValidationDisabled;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
+import static dagger.internal.codegen.xprocessing.XTypeElements.isKotlinSource;
+import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
 
-import com.google.auto.common.MoreElements;
-import com.google.auto.common.MoreTypes;
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
@@ -33,33 +44,27 @@
 import com.squareup.javapoet.TypeName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
 import dagger.hilt.processor.internal.BadInputException;
-import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Components;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtil;
 import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.util.LinkedHashSet;
 import java.util.Optional;
 import java.util.stream.Collectors;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
 
 /** Metadata class for @AndroidEntryPoint annotated classes. */
 @AutoValue
 public abstract class AndroidEntryPointMetadata {
 
-  /** The class {@link Element} annotated with @AndroidEntryPoint. */
-  public abstract TypeElement element();
+  /** The class annotated with @AndroidEntryPoint. */
+  public abstract XTypeElement element();
 
-  /** The base class {@link Element} given to @AndroidEntryPoint. */
-  public abstract TypeElement baseElement();
+  /** The base class given to @AndroidEntryPoint. */
+  public abstract XTypeElement baseElement();
 
   /** The name of the generated base class, beginning with 'Hilt_'. */
   public abstract ClassName generatedClassName();
@@ -98,7 +103,7 @@
 
   /** Returns true if this class allows optional injection. */
   public boolean allowsOptionalInjection() {
-    return Processors.hasAnnotation(element(), AndroidClassNames.OPTIONAL_INJECT);
+    return element().hasAnnotation(AndroidClassNames.OPTIONAL_INJECT);
   }
 
   /** Returns true if any base class (transitively) allows optional injection. */
@@ -113,12 +118,12 @@
 
   /** The name of the class annotated with @AndroidEntryPoint */
   public ClassName elementClassName() {
-    return ClassName.get(element());
+    return element().getClassName();
   }
 
   /** The name of the base class given to @AndroidEntryPoint */
   public TypeName baseClassName() {
-    return TypeName.get(baseElement().asType());
+    return baseElement().getType().getTypeName();
   }
 
   /** The name of the generated injector for the Hilt class. */
@@ -152,13 +157,15 @@
    * https://discuss.kotlinlang.org/t/why-does-kotlin-prohibit-exposing-restricted-visibility-types/7047
    */
   public Modifier[] generatedClassModifiers() {
-    return isKotlinClass(element()) && element().getModifiers().contains(Modifier.PUBLIC)
+    // Note XElement#isPublic() refers to the jvm visibility. Since "internal" visibility is
+    // represented as public in the jvm, we have to check XElement#isInternal() explicitly.
+    return isKotlinSource(element()) && element().isPublic() && !element().isInternal()
         ? new Modifier[] {Modifier.ABSTRACT, Modifier.PUBLIC}
         : new Modifier[] {Modifier.ABSTRACT};
   }
 
-  private static ClassName generatedClassName(TypeElement element) {
-    return Processors.prepend(Processors.getEnclosedClassName(ClassName.get(element)), "Hilt_");
+  private static ClassName generatedClassName(XTypeElement element) {
+    return Processors.prepend(Processors.getEnclosedClassName(element.getClassName()), "Hilt_");
   }
 
   private static final ImmutableSet<ClassName> HILT_ANNOTATION_NAMES =
@@ -166,27 +173,25 @@
           AndroidClassNames.HILT_ANDROID_APP,
           AndroidClassNames.ANDROID_ENTRY_POINT);
 
-  private static ImmutableSet<? extends AnnotationMirror> hiltAnnotations(Element element) {
-    return element.getAnnotationMirrors().stream()
-        .filter(mirror -> HILT_ANNOTATION_NAMES.contains(ClassName.get(mirror.getAnnotationType())))
+  private static ImmutableSet<XAnnotation> hiltAnnotations(XElement element) {
+    return element.getAllAnnotations().stream()
+        .filter(annotation -> HILT_ANNOTATION_NAMES.contains(annotation.getClassName()))
         .collect(toImmutableSet());
   }
 
   /** Returns true if the given element has Android Entry Point metadata. */
-  public static boolean hasAndroidEntryPointMetadata(Element element) {
+  public static boolean hasAndroidEntryPointMetadata(XElement element) {
     return !hiltAnnotations(element).isEmpty();
   }
 
   /** Returns the {@link AndroidEntryPointMetadata} for a @AndroidEntryPoint annotated element. */
-  public static AndroidEntryPointMetadata of(ProcessingEnvironment env, Element element) {
-    LinkedHashSet<Element> inheritanceTrace = new LinkedHashSet<>();
-    inheritanceTrace.add(element);
-    return of(env, element, inheritanceTrace);
+  public static AndroidEntryPointMetadata of(XElement element) {
+    return of(element, Sets.newLinkedHashSet(ImmutableList.of(element)));
   }
 
   public static AndroidEntryPointMetadata manuallyConstruct(
-      TypeElement element,
-      TypeElement baseElement,
+      XTypeElement element,
+      XTypeElement baseElement,
       ClassName generatedClassName,
       boolean requiresBytecodeInjection,
       AndroidType androidType,
@@ -211,24 +216,22 @@
    * along the way.
    */
   private static AndroidEntryPointMetadata of(
-      ProcessingEnvironment env, Element element, LinkedHashSet<Element> inheritanceTrace) {
-    ImmutableSet<? extends AnnotationMirror> hiltAnnotations = hiltAnnotations(element);
+      XElement element, LinkedHashSet<XElement> inheritanceTrace) {
+    ImmutableSet<XAnnotation> hiltAnnotations = hiltAnnotations(element);
     ProcessorErrors.checkState(
         hiltAnnotations.size() == 1,
         element,
         "Expected exactly 1 of %s. Found: %s",
-        HILT_ANNOTATION_NAMES,
-        hiltAnnotations);
-    ClassName annotationClassName =
-        ClassName.get(
-            MoreTypes.asTypeElement(Iterables.getOnlyElement(hiltAnnotations).getAnnotationType()));
+        HILT_ANNOTATION_NAMES.stream().map(ClassName::canonicalName).collect(toImmutableSet()),
+        hiltAnnotations.stream().map(XAnnotations::toStableString).collect(toImmutableSet()));
+    ClassName annotationClassName = getOnlyElement(hiltAnnotations).getClassName();
 
     ProcessorErrors.checkState(
-        element.getKind() == ElementKind.CLASS,
+        isTypeElement(element) && asTypeElement(element).isClass(),
         element,
         "Only classes can be annotated with @%s",
         annotationClassName.simpleName());
-    TypeElement androidEntryPointElement = MoreElements.asType(element);
+    XTypeElement androidEntryPointElement = asTypeElement(element);
 
     ProcessorErrors.checkState(
         androidEntryPointElement.getTypeParameters().isEmpty(),
@@ -236,18 +239,18 @@
         "@%s-annotated classes cannot have type parameters.",
         annotationClassName.simpleName());
 
-    final TypeElement androidEntryPointClassValue =
-        Processors.getAnnotationClassValue(
-            env.getElementUtils(),
-            Processors.getAnnotationMirror(androidEntryPointElement, annotationClassName),
-            "value");
-    final TypeElement baseElement;
-    final ClassName generatedClassName;
+    XTypeElement androidEntryPointClassValue =
+        androidEntryPointElement
+            .getAnnotation(annotationClassName)
+            .getAsType("value")
+            .getTypeElement();
+    XTypeElement baseElement;
+    ClassName generatedClassName = generatedClassName(androidEntryPointElement);
     boolean requiresBytecodeInjection =
-        isAndroidSuperclassValidationDisabled(androidEntryPointElement, env)
-            && MoreTypes.isTypeOf(Void.class, androidEntryPointClassValue.asType());
+        isAndroidSuperClassValidationDisabled(androidEntryPointElement)
+            && isVoidObject(androidEntryPointClassValue.getType());
     if (requiresBytecodeInjection) {
-      baseElement = MoreElements.asType(env.getTypeUtils().asElement(androidEntryPointElement.getSuperclass()));
+      baseElement = androidEntryPointElement.getSuperClass().getTypeElement();
       // If this AndroidEntryPoint is a Kotlin class and its base type is also Kotlin and has
       // default values declared in its constructor then error out because for the short-form
       // usage of @AndroidEntryPoint the bytecode transformation will be done incorrectly.
@@ -263,11 +266,10 @@
               + "declaration.",
           baseElement.getQualifiedName(),
           androidEntryPointElement.getQualifiedName());
-      generatedClassName = generatedClassName(androidEntryPointElement);
     } else {
       baseElement = androidEntryPointClassValue;
       ProcessorErrors.checkState(
-          !MoreTypes.isTypeOf(Void.class, baseElement.asType()),
+          !isVoidObject(baseElement.getType()),
           androidEntryPointElement,
           "Expected @%s to have a value."
           + " Did you forget to apply the Gradle Plugin? (com.google.dagger.hilt.android)\n"
@@ -276,22 +278,22 @@
 
       // Check that the root $CLASS extends Hilt_$CLASS
       String extendsName =
-          env.getTypeUtils()
-              .asElement(androidEntryPointElement.getSuperclass())
-              .getSimpleName()
-              .toString();
-      generatedClassName = generatedClassName(androidEntryPointElement);
-      ProcessorErrors.checkState(
-          extendsName.contentEquals(generatedClassName.simpleName()),
-          androidEntryPointElement,
-          "@%s class expected to extend %s. Found: %s",
-          annotationClassName.simpleName(),
-          generatedClassName.simpleName(),
-          extendsName);
+          androidEntryPointElement.getSuperClass().getTypeElement().getClassName().simpleName();
+
+      // TODO(b/288210593): Add this check back to KSP once this bug is fixed.
+      if (getProcessingEnv(androidEntryPointElement).getBackend() == XProcessingEnv.Backend.JAVAC) {
+        ProcessorErrors.checkState(
+            extendsName.contentEquals(generatedClassName.simpleName()),
+            androidEntryPointElement,
+            "@%s class expected to extend %s. Found: %s",
+            annotationClassName.simpleName(),
+            generatedClassName.simpleName(),
+            extendsName);
+      }
     }
 
     Optional<AndroidEntryPointMetadata> baseMetadata =
-        baseMetadata(env, androidEntryPointElement, baseElement, inheritanceTrace);
+        baseMetadata(androidEntryPointElement, baseElement, inheritanceTrace);
 
     if (baseMetadata.isPresent()) {
       return manuallyConstruct(
@@ -320,48 +322,38 @@
   }
 
   private static Optional<AndroidEntryPointMetadata> baseMetadata(
-      ProcessingEnvironment env,
-      TypeElement element,
-      TypeElement baseElement,
-      LinkedHashSet<Element> inheritanceTrace) {
+      XTypeElement element, XTypeElement baseElement, LinkedHashSet<XElement> inheritanceTrace) {
     ProcessorErrors.checkState(
         inheritanceTrace.add(baseElement),
         element,
         cyclicInheritanceErrorMessage(inheritanceTrace, baseElement));
     if (hasAndroidEntryPointMetadata(baseElement)) {
       AndroidEntryPointMetadata baseMetadata =
-          AndroidEntryPointMetadata.of(env, baseElement, inheritanceTrace);
+          AndroidEntryPointMetadata.of(baseElement, inheritanceTrace);
       checkConsistentAnnotations(element, baseMetadata);
       return Optional.of(baseMetadata);
     }
 
-    TypeMirror superClass = baseElement.getSuperclass();
+    XType superClass = baseElement.getSuperClass();
     // None type is returned if this is an interface or Object
-    if (superClass.getKind() != TypeKind.NONE && superClass.getKind() != TypeKind.ERROR) {
-      Preconditions.checkState(superClass.getKind() == TypeKind.DECLARED);
-      return baseMetadata(env, element, MoreTypes.asTypeElement(superClass), inheritanceTrace);
+    if (superClass != null && !superClass.isError()) {
+      Preconditions.checkState(isDeclared(superClass));
+      return baseMetadata(element, superClass.getTypeElement(), inheritanceTrace);
     }
 
     return Optional.empty();
   }
 
   private static String cyclicInheritanceErrorMessage(
-      LinkedHashSet<Element> inheritanceTrace, TypeElement cycleEntryPoint) {
+      LinkedHashSet<XElement> inheritanceTrace, XTypeElement cycleEntryPoint) {
     return String.format(
         "Cyclic inheritance detected. Make sure the base class of @AndroidEntryPoint "
             + "is not the annotated class itself or subclass of the annotated class.\n"
             + "The cyclic inheritance structure: %s --> %s\n",
         inheritanceTrace.stream()
-            .map(Element::asType)
-            .map(TypeMirror::toString)
+            .map(XElements::toStableString)
             .collect(Collectors.joining(" --> ")),
-        cycleEntryPoint.asType());
-  }
-
-  private static boolean isKotlinClass(TypeElement typeElement) {
-    return typeElement.getAnnotationMirrors().stream()
-        .map(mirror -> mirror.getAnnotationType())
-        .anyMatch(type -> ClassName.get(type).equals(ClassNames.KOTLIN_METADATA));
+        XElements.toStableString(cycleEntryPoint));
   }
 
   /**
@@ -438,23 +430,22 @@
       this.componentManagerInitArgs = componentManagerInitArgs;
     }
 
-    private static Type of(TypeElement element, TypeElement baseElement) {
-      if (Processors.hasAnnotation(element, AndroidClassNames.HILT_ANDROID_APP)) {
-        return forHiltAndroidApp(element, baseElement);
-      }
-      return forAndroidEntryPoint(element, baseElement);
+    private static Type of(XTypeElement element, XTypeElement baseElement) {
+      return element.hasAnnotation(AndroidClassNames.HILT_ANDROID_APP)
+          ? forHiltAndroidApp(element, baseElement)
+          : forAndroidEntryPoint(element, baseElement);
     }
 
-    private static Type forHiltAndroidApp(TypeElement element, TypeElement baseElement) {
+    private static Type forHiltAndroidApp(XTypeElement element, XTypeElement baseElement) {
       ProcessorErrors.checkState(
           Processors.isAssignableFrom(baseElement, AndroidClassNames.APPLICATION),
           element,
           "@HiltAndroidApp base class must extend Application. Found: %s",
-          baseElement);
+          XElements.toStableString(baseElement));
       return Type.APPLICATION;
     }
 
-    private static Type forAndroidEntryPoint(TypeElement element, TypeElement baseElement) {
+    private static Type forAndroidEntryPoint(XTypeElement element, XTypeElement baseElement) {
       if (Processors.isAssignableFrom(baseElement, AndroidClassNames.ACTIVITY)) {
         ProcessorErrors.checkState(
             Processors.isAssignableFrom(baseElement, AndroidClassNames.COMPONENT_ACTIVITY),
@@ -472,7 +463,7 @@
         return Type.FRAGMENT;
       } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.VIEW)) {
         boolean withFragmentBindings =
-            Processors.hasAnnotation(element, AndroidClassNames.WITH_FRAGMENT_BINDINGS);
+            element.hasAnnotation(AndroidClassNames.WITH_FRAGMENT_BINDINGS);
         return withFragmentBindings ? Type.VIEW : Type.VIEW_NO_FRAGMENT;
       } else if (Processors.isAssignableFrom(baseElement, AndroidClassNames.APPLICATION)) {
         throw new BadInputException(
@@ -487,22 +478,22 @@
   }
 
   private static void checkConsistentAnnotations(
-      TypeElement element, AndroidEntryPointMetadata baseMetadata) {
-    TypeElement baseElement = baseMetadata.element();
+      XTypeElement element, AndroidEntryPointMetadata baseMetadata) {
+    XTypeElement baseElement = baseMetadata.element();
     checkAnnotationsMatch(element, baseElement, AndroidClassNames.WITH_FRAGMENT_BINDINGS);
 
     ProcessorErrors.checkState(
         baseMetadata.allowsOptionalInjection()
-            || !Processors.hasAnnotation(element, AndroidClassNames.OPTIONAL_INJECT),
+            || !element.hasAnnotation(AndroidClassNames.OPTIONAL_INJECT),
         element,
         "@OptionalInject Hilt class cannot extend from a non-optional @AndroidEntryPoint base: %s",
-        element);
+        XElements.toStableString(element));
   }
 
   private static void checkAnnotationsMatch(
-      TypeElement element, TypeElement baseElement, ClassName annotationName) {
-    boolean isAnnotated = Processors.hasAnnotation(element, annotationName);
-    boolean isBaseAnnotated = Processors.hasAnnotation(baseElement, annotationName);
+      XTypeElement element, XTypeElement baseElement, ClassName annotationName) {
+    boolean isAnnotated = element.hasAnnotation(annotationName);
+    boolean isBaseAnnotated = baseElement.hasAnnotation(annotationName);
     ProcessorErrors.checkState(
         isAnnotated == isBaseAnnotated,
         element,
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessingStep.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessingStep.java
new file mode 100644
index 0000000..10ca698
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessingStep.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.androidentrypoint;
+
+import static dagger.hilt.processor.internal.HiltCompilerOptions.getGradleProjectType;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.android.processor.internal.AndroidClassNames;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.hilt.processor.internal.optionvalues.GradleProjectType;
+
+/**
+ * Processor that creates a module for classes marked with {@link
+ * dagger.hilt.android.AndroidEntryPoint}.
+ */
+public final class AndroidEntryPointProcessingStep extends BaseProcessingStep {
+  public AndroidEntryPointProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(
+        AndroidClassNames.ANDROID_ENTRY_POINT, AndroidClassNames.HILT_ANDROID_APP);
+  }
+
+  @Override
+  public boolean delayErrors() {
+    return true;
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) throws Exception {
+    AndroidEntryPointMetadata metadata = AndroidEntryPointMetadata.of(element);
+    new InjectorEntryPointGenerator(processingEnv(), metadata).generate();
+    switch (metadata.androidType()) {
+      case APPLICATION:
+        GradleProjectType projectType = getGradleProjectType(processingEnv());
+        if (projectType != GradleProjectType.UNSET) {
+          ProcessorErrors.checkState(
+              projectType == GradleProjectType.APP,
+              element,
+              "Application class, %s, annotated with @HiltAndroidApp must be defined in a "
+                  + "Gradle android application module (i.e. contains a build.gradle file with "
+                  + "`plugins { id 'com.android.application' }`).",
+              metadata.element().getQualifiedName());
+        }
+
+        // The generated application references the generated component so they must be generated
+        // in the same build unit. Thus, we only generate the application here if we're using the
+        // aggregating root processor. If we're using the Hilt Gradle plugin's aggregating task, we
+        // need to generate the application within ComponentTreeDepsProcessor instead.
+        if (useAggregatingRootProcessor(processingEnv())) {
+          // While we could always generate the application in ComponentTreeDepsProcessor, even if
+          // we're using the aggregating root processor, it can lead to extraneous errors when
+          // things fail before ComponentTreeDepsProcessor runs so we generate it here to avoid that
+          new ApplicationGenerator(processingEnv(), metadata).generate();
+        } else {
+          // If we're not using the aggregating root processor, then make sure the root application
+          // does not extend the generated application directly, and instead uses bytecode injection
+          ProcessorErrors.checkState(
+              metadata.requiresBytecodeInjection(),
+              metadata.element(),
+              "'enableAggregatingTask=true' cannot be used when the application directly "
+                  + "references the generated Hilt class, %s. Either extend %s directly (relying "
+                  + "on the Gradle plugin described in "
+                  + "https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or set "
+                  + "'enableAggregatingTask=false'.",
+              metadata.generatedClassName(),
+              metadata.baseClassName());
+        }
+        break;
+      case ACTIVITY:
+        new ActivityGenerator(processingEnv(), metadata).generate();
+        break;
+      case BROADCAST_RECEIVER:
+        new BroadcastReceiverGenerator(processingEnv(), metadata).generate();
+        break;
+      case FRAGMENT:
+        new FragmentGenerator(processingEnv(), metadata).generate();
+        break;
+      case SERVICE:
+        new ServiceGenerator(processingEnv(), metadata).generate();
+        break;
+      case VIEW:
+        new ViewGenerator(processingEnv(), metadata).generate();
+        break;
+    }
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java
index 04ea06b..e7c3831 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessor.java
@@ -16,20 +16,12 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
-import static dagger.hilt.processor.internal.HiltCompilerOptions.getGradleProjectType;
-import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableSet;
-import dagger.hilt.android.processor.internal.AndroidClassNames;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.optionvalues.GradleProjectType;
-import java.util.Set;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /**
@@ -38,80 +30,9 @@
  */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class AndroidEntryPointProcessor extends BaseProcessor {
-
+public final class AndroidEntryPointProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public Set<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(
-        AndroidClassNames.ANDROID_ENTRY_POINT.toString(),
-        AndroidClassNames.HILT_ANDROID_APP.toString());
-  }
-
-  @Override
-  public boolean delayErrors() {
-    return true;
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    AndroidEntryPointMetadata metadata = AndroidEntryPointMetadata.of(getProcessingEnv(), element);
-    new InjectorEntryPointGenerator(getProcessingEnv(), metadata).generate();
-    switch (metadata.androidType()) {
-      case APPLICATION:
-        GradleProjectType projectType = getGradleProjectType(getProcessingEnv());
-        if (projectType != GradleProjectType.UNSET) {
-          ProcessorErrors.checkState(
-              projectType == GradleProjectType.APP,
-              element,
-              "Application class, %s, annotated with @HiltAndroidApp must be defined in a "
-                  + "Gradle android application module (i.e. contains a build.gradle file with "
-                  + "`plugins { id 'com.android.application' }`).",
-              metadata.element().getQualifiedName());
-        }
-
-        // The generated application references the generated component so they must be generated
-        // in the same build unit. Thus, we only generate the application here if we're using the
-        // aggregating root processor. If we're using the Hilt Gradle plugin's aggregating task, we
-        // need to generate the application within ComponentTreeDepsProcessor instead.
-        if (useAggregatingRootProcessor(getProcessingEnv())) {
-          // While we could always generate the application in ComponentTreeDepsProcessor, even if
-          // we're using the aggregating root processor, it can lead to extraneous errors when
-          // things fail before ComponentTreeDepsProcessor runs so we generate it here to avoid that
-          new ApplicationGenerator(getProcessingEnv(), metadata).generate();
-        } else {
-          // If we're not using the aggregating root processor, then make sure the root application
-          // does not extend the generated application directly, and instead uses bytecode injection
-          ProcessorErrors.checkState(
-              metadata.requiresBytecodeInjection(),
-              metadata.element(),
-              "'enableAggregatingTask=true' cannot be used when the application directly "
-                  + "references the generated Hilt class, %s. Either extend %s directly (relying "
-                  + "on the Gradle plugin described in "
-                  + "https://dagger.dev/hilt/gradle-setup#why-use-the-plugin or set "
-                  + "'enableAggregatingTask=false'.",
-              metadata.generatedClassName(),
-              metadata.baseClassName());
-        }
-        break;
-      case ACTIVITY:
-        new ActivityGenerator(getProcessingEnv(), metadata).generate();
-        break;
-      case BROADCAST_RECEIVER:
-        new BroadcastReceiverGenerator(getProcessingEnv(), metadata).generate();
-        break;
-      case FRAGMENT:
-        new FragmentGenerator(
-            getProcessingEnv(), metadata )
-            .generate();
-        break;
-      case SERVICE:
-        new ServiceGenerator(getProcessingEnv(), metadata).generate();
-        break;
-      case VIEW:
-        new ViewGenerator(getProcessingEnv(), metadata).generate();
-        break;
-      default:
-        throw new IllegalStateException("Unknown Hilt type: " + metadata.androidType());
-    }
+  protected BaseProcessingStep processingStep() {
+    return new AndroidEntryPointProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java
index dc7bd37..84a256b 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ApplicationGenerator.java
@@ -16,9 +16,15 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
-import static javax.lang.model.element.Modifier.ABSTRACT;
-import static javax.lang.model.element.Modifier.PROTECTED;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
+import static kotlin.streams.jdk8.StreamsKt.asStream;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeParameterElement;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
@@ -28,28 +34,23 @@
 import com.squareup.javapoet.ParameterSpec;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.TypeVariableName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
 import dagger.hilt.processor.internal.ComponentNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
+import java.util.stream.Stream;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.util.ElementFilter;
 
 /** Generates an Hilt Application for an @AndroidEntryPoint app class. */
 public final class ApplicationGenerator {
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName wrapperClassName;
   private final ComponentNames componentNames;
 
-  public ApplicationGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
+  public ApplicationGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
     this.wrapperClassName = metadata.generatedClassName();
@@ -63,11 +64,12 @@
   public void generate() throws IOException {
     TypeSpec.Builder typeSpecBuilder =
         TypeSpec.classBuilder(wrapperClassName.simpleName())
-            .addOriginatingElement(metadata.element())
             .superclass(metadata.baseClassName())
             .addModifiers(metadata.generatedClassModifiers())
             .addField(injectedField());
 
+    JavaPoetExtKt.addOriginatingElement(typeSpecBuilder, metadata.element());
+
     typeSpecBuilder
         .addField(componentManagerField())
         .addMethod(componentManagerMethod());
@@ -76,7 +78,7 @@
     Processors.addGeneratedAnnotation(typeSpecBuilder, env, getClass());
 
     metadata.baseElement().getTypeParameters().stream()
-        .map(TypeVariableName::get)
+        .map(XTypeParameterElement::getTypeVariableName)
         .forEachOrdered(typeSpecBuilder::addTypeVariable);
 
     Generators.copyLintAnnotations(metadata.element(), typeSpecBuilder);
@@ -90,34 +92,32 @@
         typeSpecBuilder.addMethod(onCreateMethod()).addMethod(injectionMethod());
     }
 
-    JavaFile.builder(metadata.elementClassName().packageName(), typeSpecBuilder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(metadata.elementClassName().packageName(), typeSpecBuilder.build())
+                .build(),
+            XFiler.Mode.Isolating);
   }
 
   private boolean hasCustomInject() {
-    boolean hasCustomInject =
-        Processors.hasAnnotation(metadata.element(), AndroidClassNames.CUSTOM_INJECT);
+    boolean hasCustomInject = metadata.element().hasAnnotation(AndroidClassNames.CUSTOM_INJECT);
     if (hasCustomInject) {
       // Check that the Hilt base class does not already define a customInject implementation.
-      Set<ExecutableElement> customInjectMethods =
-          ElementFilter.methodsIn(
-                  ImmutableSet.<Element>builder()
-                      .addAll(metadata.element().getEnclosedElements())
-                      .addAll(env.getElementUtils().getAllMembers(metadata.baseElement()))
-                      .build())
-              .stream()
-              .filter(method -> method.getSimpleName().contentEquals("customInject"))
+      ImmutableSet<XMethodElement> customInjectMethods =
+          Stream.concat(
+                  metadata.element().getDeclaredMethods().stream(),
+                  asStream(metadata.baseElement().getAllMethods()))
+              .filter(method -> getSimpleName(method).contentEquals("customInject"))
               .filter(method -> method.getParameters().isEmpty())
-              .collect(Collectors.toSet());
+              .collect(toImmutableSet());
 
-      for (ExecutableElement customInjectMethod : customInjectMethods) {
+      for (XMethodElement customInjectMethod : customInjectMethods) {
         ProcessorErrors.checkState(
-            customInjectMethod.getModifiers().containsAll(ImmutableSet.of(ABSTRACT, PROTECTED)),
+            customInjectMethod.isAbstract() && customInjectMethod.isProtected(),
             customInjectMethod,
             "%s#%s, must have modifiers `abstract` and `protected` when using @CustomInject.",
-            customInjectMethod.getEnclosingElement(),
-            customInjectMethod);
+            XElements.toStableString(customInjectMethod.getEnclosingElement()),
+            XElements.toStableString(customInjectMethod));
       }
     }
     return hasCustomInject;
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
index 55bfcb1..96beff3 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
@@ -32,7 +32,11 @@
 
 java_library(
     name = "processor_lib",
-    srcs = ["AndroidEntryPointProcessor.java"],
+    srcs = [
+        "AndroidEntryPointProcessingStep.java",
+        "AndroidEntryPointProcessor.java",
+        "KspAndroidEntryPointProcessor.java",
+    ],
     deps = [
         ":android_generators",
         ":metadata",
@@ -40,11 +44,13 @@
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:processor_errors",
-        "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/optionvalues",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
+        "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -63,18 +69,17 @@
     deps = [
         ":metadata",
         "//java/dagger/hilt/android/processor/internal:android_classnames",
-        "//java/dagger/hilt/android/processor/internal:utils",
         "//java/dagger/hilt/processor/internal:classnames",
-        "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:component_names",
-        "//java/dagger/hilt/processor/internal:element_descriptors",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:common",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
+        "@maven//:org_jetbrains_kotlin_kotlin_stdlib",
     ],
 )
 
@@ -85,14 +90,13 @@
     ],
     deps = [
         "//java/dagger/hilt/android/processor/internal:android_classnames",
-        "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:compiler_options",
         "//java/dagger/hilt/processor/internal:components",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/kotlin",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java
index 23c054a..156842b 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/BroadcastReceiverGenerator.java
@@ -16,9 +16,19 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
-import static dagger.hilt.processor.internal.ElementDescriptors.getMethodDescriptor;
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 
-import com.google.common.collect.Iterables;
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
+import androidx.room.compiler.processing.XTypeParameterElement;
+import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.FieldSpec;
 import com.squareup.javapoet.JavaFile;
@@ -26,33 +36,25 @@
 import com.squareup.javapoet.ParameterSpec;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.TypeVariableName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
-import dagger.hilt.android.processor.internal.MoreTypes;
 import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XExecutableTypes;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.ExecutableElement;
+import java.util.Optional;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.util.ElementFilter;
 
 /** Generates an Hilt BroadcastReceiver class for the @AndroidEntryPoint annotated class. */
 public final class BroadcastReceiverGenerator {
-
   private static final String ON_RECEIVE_DESCRIPTOR =
       "onReceive(Landroid/content/Context;Landroid/content/Intent;)V";
 
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName generatedClassName;
 
-  public BroadcastReceiverGenerator(
-      ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
+  public BroadcastReceiverGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
-
     generatedClassName = metadata.generatedClassName();
   }
 
@@ -63,17 +65,17 @@
   public void generate() throws IOException {
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(generatedClassName.simpleName())
-            .addOriginatingElement(metadata.element())
             .superclass(metadata.baseClassName())
             .addModifiers(metadata.generatedClassModifiers())
             .addMethod(onReceiveMethod());
 
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
     Processors.addGeneratedAnnotation(builder, env, getClass());
     Generators.copyConstructors(metadata.baseElement(), builder);
 
     metadata.baseElement().getTypeParameters().stream()
-        .map(TypeVariableName::get)
+        .map(XTypeParameterElement::getTypeVariableName)
         .forEachOrdered(builder::addTypeVariable);
 
     Generators.addInjectionMethods(metadata, builder);
@@ -94,21 +96,21 @@
               .build());
     }
 
-    JavaFile.builder(generatedClassName.packageName(),
-        builder.build()).build().writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
+            XFiler.Mode.Isolating);
   }
 
-  private static boolean isOnReceiveImplemented(TypeElement typeElement) {
+  private static boolean isOnReceiveImplemented(XTypeElement typeElement) {
     boolean isImplemented =
-        ElementFilter.methodsIn(typeElement.getEnclosedElements()).stream()
-            .anyMatch(
-                methodElement ->
-                    getMethodDescriptor(methodElement).equals(ON_RECEIVE_DESCRIPTOR)
-                        && !methodElement.getModifiers().contains(Modifier.ABSTRACT));
+        typeElement.getDeclaredMethods().stream()
+            .filter(method -> !method.isAbstract())
+            .anyMatch(method -> method.getJvmDescriptor().equals(ON_RECEIVE_DESCRIPTOR));
     if (isImplemented) {
       return true;
-    } else if (typeElement.getSuperclass().getKind() != TypeKind.NONE) {
-      return isOnReceiveImplemented(MoreTypes.asTypeElement(typeElement.getSuperclass()));
+    } else if (typeElement.getSuperClass() != null) {
+      return isOnReceiveImplemented(typeElement.getSuperClass().getTypeElement());
     } else {
       return false;
     }
@@ -136,19 +138,38 @@
       method.addStatement("super.onReceive(context, intent)");
     } else {
       // Get the onReceive method element from BroadcastReceiver.
-      ExecutableElement onReceiveElement =
-          Iterables.getOnlyElement(
-              MoreTypes.findMethods(
-                  env.getElementUtils()
-                      .getTypeElement(AndroidClassNames.BROADCAST_RECEIVER.toString()),
-                  "onReceive"));
+      XMethodElement onReceiveMethod =
+          getOnlyElement(
+              findMethodsByName(
+                  env.requireTypeElement(AndroidClassNames.BROADCAST_RECEIVER), "onReceive"));
 
       // If the base class or one of its super classes implements onReceive, call super.onReceive()
-      MoreTypes.findInheritedMethod(env.getTypeUtils(), metadata.baseElement(), onReceiveElement)
-          .filter(onReceive -> !onReceive.getModifiers().contains(Modifier.ABSTRACT))
+      findMethodBySubsignature(metadata.baseElement(), onReceiveMethod)
+          .filter(onReceive -> !onReceive.isAbstract())
           .ifPresent(onReceive -> method.addStatement("super.onReceive(context, intent)"));
     }
-
     return method.build();
   }
+
+  private Optional<XMethodElement> findMethodBySubsignature(
+      XTypeElement typeElement, XMethodElement method) {
+    String methodName = getSimpleName(method);
+    XType currentType = typeElement.getType();
+    Optional<XMethodElement> match = Optional.empty();
+    while (!match.isPresent() && currentType != null) {
+      match =
+          findMethodsByName(currentType.getTypeElement(), methodName).stream()
+              .filter(m -> XExecutableTypes.isSubsignature(m, method))
+              .collect(toOptional());
+      currentType = currentType.getTypeElement().getSuperClass();
+    }
+    return match;
+  }
+
+  private static ImmutableSet<XMethodElement> findMethodsByName(
+      XTypeElement typeElement, String name) {
+    return typeElement.getDeclaredMethods().stream()
+        .filter(m -> getSimpleName(m).contentEquals(name))
+        .collect(toImmutableSet());
+  }
 }
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java
index bf00e66..45017e4 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/FragmentGenerator.java
@@ -16,6 +16,10 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeParameterElement;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.FieldSpec;
@@ -23,12 +27,10 @@
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.TypeVariableName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /** Generates an Hilt Fragment class for the @AndroidEntryPoint annotated class. */
@@ -43,12 +45,12 @@
           .addModifiers(Modifier.PRIVATE)
           .build();
 
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName generatedClassName;
 
   public FragmentGenerator(
-      ProcessingEnvironment env,
+      XProcessingEnv env,
       AndroidEntryPointMetadata metadata ) {
     this.env = env;
     this.metadata = metadata;
@@ -56,9 +58,10 @@
   }
 
   public void generate() throws IOException {
-    JavaFile.builder(generatedClassName.packageName(), createTypeSpec())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(generatedClassName.packageName(), createTypeSpec()).build(),
+            XFiler.Mode.Isolating);
   }
 
   // @Generated("FragmentGenerator")
@@ -68,7 +71,6 @@
   TypeSpec createTypeSpec() {
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(generatedClassName.simpleName())
-            .addOriginatingElement(metadata.element())
             .superclass(metadata.baseClassName())
             .addModifiers(metadata.generatedClassModifiers())
             .addField(COMPONENT_CONTEXT_FIELD)
@@ -79,6 +81,7 @@
             .addMethod(inflatorMethod())
             .addField(DISABLE_GET_CONTEXT_FIX_FIELD);
 
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
     Processors.addGeneratedAnnotation(builder, env, getClass());
     Generators.copyLintAnnotations(metadata.element(), builder);
@@ -86,7 +89,7 @@
     Generators.copyConstructors(metadata.baseElement(), builder);
 
     metadata.baseElement().getTypeParameters().stream()
-        .map(TypeVariableName::get)
+        .map(XTypeParameterElement::getTypeVariableName)
         .forEachOrdered(builder::addTypeVariable);
 
     Generators.addComponentOverride(metadata, builder);
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java
index e2892aa..dc2339b 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/Generators.java
@@ -16,14 +16,22 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
+import static androidx.room.compiler.processing.JavaPoetExtKt.toAnnotationSpec;
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 import static java.util.stream.Collectors.joining;
-import static javax.lang.model.element.Modifier.PRIVATE;
 
-import com.google.auto.common.AnnotationMirrors;
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XConstructorElement;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XTypeElement;
+import androidx.room.compiler.processing.XVariableElement;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
@@ -37,17 +45,10 @@
 import dagger.hilt.android.processor.internal.AndroidClassNames;
 import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata.AndroidType;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.util.ElementFilter;
 
 /** Helper class for writing Hilt generators. */
 final class Generators {
@@ -63,22 +64,17 @@
         annotation);
   }
 
-  /**
-   * Copies all constructors with arguments to the builder.
-   */
-  static void copyConstructors(TypeElement baseClass, TypeSpec.Builder builder) {
+  /** Copies all constructors with arguments to the builder. */
+  static void copyConstructors(XTypeElement baseClass, TypeSpec.Builder builder) {
     copyConstructors(baseClass, CodeBlock.builder().build(), builder);
   }
 
-  /**
-   * Copies all constructors with arguments along with an appended body to the builder.
-   */
-  static void copyConstructors(TypeElement baseClass, CodeBlock body, TypeSpec.Builder builder) {
-    List<ExecutableElement> constructors =
-        ElementFilter.constructorsIn(baseClass.getEnclosedElements())
-            .stream()
-            .filter(constructor -> !constructor.getModifiers().contains(PRIVATE))
-            .collect(Collectors.toList());
+  /** Copies all constructors with arguments along with an appended body to the builder. */
+  static void copyConstructors(XTypeElement baseClass, CodeBlock body, TypeSpec.Builder builder) {
+    ImmutableList<XConstructorElement> constructors =
+        baseClass.getConstructors().stream()
+            .filter(constructor -> !constructor.isPrivate())
+            .collect(toImmutableList());
 
     if (constructors.size() == 1
         && getOnlyElement(constructors).getParameters().isEmpty()
@@ -91,14 +87,10 @@
   }
 
   /** Returns Optional with AnnotationSpec for Nullable if found on element, empty otherwise. */
-  private static Optional<AnnotationSpec> getNullableAnnotationSpec(Element element) {
-    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
-      if (annotationMirror
-          .getAnnotationType()
-          .asElement()
-          .getSimpleName()
-          .contentEquals("Nullable")) {
-        AnnotationSpec annotationSpec = AnnotationSpec.get(annotationMirror);
+  private static Optional<AnnotationSpec> getNullableAnnotationSpec(XElement element) {
+    for (XAnnotation annotation : element.getAllAnnotations()) {
+      if (annotation.getClassName().simpleName().contentEquals("Nullable")) {
+        AnnotationSpec annotationSpec = toAnnotationSpec(annotation);
         // If using the android internal Nullable, convert it to the externally-visible version.
         return AndroidClassNames.NULLABLE_INTERNAL.equals(annotationSpec.type)
             ? Optional.of(AnnotationSpec.builder(AndroidClassNames.NULLABLE).build())
@@ -112,26 +104,27 @@
    * Returns a ParameterSpec of the input parameter, @Nullable annotated if existing in original
    * (this does not handle Nullable type annotations).
    */
-  private static ParameterSpec getParameterSpecWithNullable(VariableElement parameter) {
-    ParameterSpec.Builder builder = ParameterSpec.get(parameter).toBuilder();
+  private static ParameterSpec getParameterSpecWithNullable(XVariableElement parameter) {
+    ParameterSpec.Builder builder =
+        ParameterSpec.builder(parameter.getType().getTypeName(), getSimpleName(parameter));
     getNullableAnnotationSpec(parameter).ifPresent(builder::addAnnotation);
     return builder.build();
   }
 
   /**
-   * Returns a {@link MethodSpec} for a constructor matching the given {@link ExecutableElement}
-   * constructor signature, and just calls super. If the constructor is
-   * {@link android.annotation.TargetApi} guarded, adds the TargetApi as well.
+   * Returns a {@link MethodSpec} for a constructor matching the given {@link XConstructorElement}
+   * constructor signature, and just calls super. If the constructor is {@link
+   * android.annotation.TargetApi} guarded, adds the TargetApi as well.
    */
   // Example:
   //   Foo(Param1 param1, Param2 param2) {
   //     super(param1, param2);
   //   }
-  static MethodSpec copyConstructor(ExecutableElement constructor) {
+  static MethodSpec copyConstructor(XConstructorElement constructor) {
     return copyConstructor(constructor, CodeBlock.builder().build());
   }
 
-  private static MethodSpec copyConstructor(ExecutableElement constructor, CodeBlock body) {
+  private static MethodSpec copyConstructor(XConstructorElement constructor, CodeBlock body) {
     List<ParameterSpec> params =
         constructor.getParameters().stream()
             .map(Generators::getParameterSpecWithNullable)
@@ -144,26 +137,26 @@
                 "super($L)", params.stream().map(param -> param.name).collect(joining(", ")))
             .addCode(body);
 
-    constructor.getAnnotationMirrors().stream()
-        .filter(a -> Processors.hasAnnotation(a, AndroidClassNames.TARGET_API))
+    constructor.getAllAnnotations().stream()
+        .filter(a -> a.getTypeElement().hasAnnotation(AndroidClassNames.TARGET_API))
         .collect(toOptional())
-        .map(AnnotationSpec::get)
+        .map(JavaPoetExtKt::toAnnotationSpec)
         .ifPresent(builder::addAnnotation);
 
     return builder.build();
   }
 
   /** Copies SuppressWarnings annotations from the annotated element to the generated element. */
-  static void copySuppressAnnotations(Element element, TypeSpec.Builder builder) {
+  static void copySuppressAnnotations(XElement element, TypeSpec.Builder builder) {
     ImmutableSet<String> suppressValues =
         SUPPRESS_ANNOTATION_PROPERTY_NAME.keySet().stream()
-            .filter(annotation -> Processors.hasAnnotation(element, annotation))
-            .map(
+            .filter(element::hasAnnotation)
+            .flatMap(
                 annotation ->
-                    AnnotationMirrors.getAnnotationValue(
-                        Processors.getAnnotationMirror(element, annotation),
-                        SUPPRESS_ANNOTATION_PROPERTY_NAME.get(annotation)))
-            .flatMap(value -> Processors.getStringArrayAnnotationValue(value).stream())
+                    element
+                        .getAnnotation(annotation)
+                        .getAsStringList(SUPPRESS_ANNOTATION_PROPERTY_NAME.get(annotation))
+                        .stream())
             .collect(toImmutableSet());
 
     if (!suppressValues.isEmpty()) {
@@ -179,11 +172,9 @@
    *
    * <p>Note: For now we only copy over {@link android.annotation.TargetApi}.
    */
-  static void copyLintAnnotations(Element element, TypeSpec.Builder builder) {
-    if (Processors.hasAnnotation(element, AndroidClassNames.TARGET_API)) {
-      builder.addAnnotation(
-          AnnotationSpec.get(
-              Processors.getAnnotationMirror(element, AndroidClassNames.TARGET_API)));
+  static void copyLintAnnotations(XElement element, TypeSpec.Builder builder) {
+    if (element.hasAnnotation(AndroidClassNames.TARGET_API)) {
+      builder.addAnnotation(toAnnotationSpec(element.getAnnotation(AndroidClassNames.TARGET_API)));
     }
   }
 
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java
index 1c5784d..5f263f2 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/InjectorEntryPointGenerator.java
@@ -16,6 +16,9 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
@@ -23,17 +26,15 @@
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /** Generates an entry point that allows for injection of the given activity */
 public final class InjectorEntryPointGenerator {
 
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final AndroidEntryPointMetadata metadata;
 
-  public InjectorEntryPointGenerator(
-      ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
+  public InjectorEntryPointGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
   }
@@ -47,7 +48,6 @@
     ClassName name = metadata.injectorClassName();
     TypeSpec.Builder builder =
         TypeSpec.interfaceBuilder(name.simpleName())
-            .addOriginatingElement(metadata.element())
             .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.element()))
             .addAnnotation(ClassNames.GENERATED_ENTRY_POINT)
             .addAnnotation(metadata.injectorInstallInAnnotation())
@@ -60,9 +60,13 @@
                         Processors.upperToLowerCamel(metadata.elementClassName().simpleName()))
                     .build());
 
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
     Processors.addGeneratedAnnotation(builder, env, getClass());
     Generators.copyLintAnnotations(metadata.element(), builder);
     Generators.copySuppressAnnotations(metadata.element(), builder);
-    JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler());
+
+    env.getFiler()
+        .write(
+            JavaFile.builder(name.packageName(), builder.build()).build(), XFiler.Mode.Isolating);
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/KspAndroidEntryPointProcessor.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/KspAndroidEntryPointProcessor.java
new file mode 100644
index 0000000..bffcbe0
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/KspAndroidEntryPointProcessor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.androidentrypoint;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/**
+ * Processor that creates a module for classes marked with {@link
+ * dagger.hilt.android.AndroidEntryPoint}.
+ */
+public final class KspAndroidEntryPointProcessor extends KspBaseProcessingStepProcessor {
+  public KspAndroidEntryPointProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new AndroidEntryPointProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspAndroidEntryPointProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspAndroidEntryPointProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java
index 2673363..8e1405e 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ServiceGenerator.java
@@ -16,32 +16,36 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
+import static java.util.stream.Collectors.joining;
+
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XConstructorElement;
+import androidx.room.compiler.processing.XExecutableParameterElement;
+import androidx.room.compiler.processing.XFiler;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeParameterElement;
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.ParameterSpec;
-import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.TypeVariableName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.util.ElementFilter;
 
 /** Generates an Hilt Service class for the @AndroidEntryPoint annotated class. */
 public final class ServiceGenerator {
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName generatedClassName;
 
-  public ServiceGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
+  public ServiceGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
-
     generatedClassName = metadata.generatedClassName();
   }
 
@@ -52,47 +56,52 @@
   public void generate() throws IOException {
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(generatedClassName.simpleName())
-            .addOriginatingElement(metadata.element())
             .superclass(metadata.baseClassName())
             .addModifiers(metadata.generatedClassModifiers())
             .addMethods(baseClassConstructors())
             .addMethod(onCreateMethod());
 
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
     Processors.addGeneratedAnnotation(builder, env, getClass());
     Generators.copyLintAnnotations(metadata.element(), builder);
     Generators.copySuppressAnnotations(metadata.element(), builder);
 
     metadata.baseElement().getTypeParameters().stream()
-        .map(TypeVariableName::get)
+        .map(XTypeParameterElement::getTypeVariableName)
         .forEachOrdered(builder::addTypeVariable);
 
     Generators.addInjectionMethods(metadata, builder);
 
     Generators.addComponentOverride(metadata, builder);
 
-    JavaFile.builder(generatedClassName.packageName(), builder.build())
-        .build().writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
+            XFiler.Mode.Isolating);
   }
 
-  private List<MethodSpec> baseClassConstructors() {
-    return ElementFilter.constructorsIn(metadata.baseElement().getEnclosedElements())
-        .stream()
-        .map((constructor) -> {
-          List<ParameterSpec> params =
-              constructor.getParameters()
-                  .stream()
-                  .map(p -> ParameterSpec.builder(TypeName.get(p.asType()), p.toString()).build())
-                  .collect(Collectors.toList());
+  private ImmutableList<MethodSpec> baseClassConstructors() {
+    return metadata.baseElement().getConstructors().stream()
+        .map(ServiceGenerator::toMethodSpec)
+        .collect(toImmutableList());
+  }
 
-          return MethodSpec.constructorBuilder()
-              .addParameters(params)
-              .addStatement(
-                  "super($L)",
-                  params.stream().map(p -> p.name).collect(Collectors.joining(",")))
-              .build();
-        })
-        .collect(Collectors.toList());
+  private static MethodSpec toMethodSpec(XConstructorElement constructor) {
+    ImmutableList<ParameterSpec> params =
+        constructor.getParameters().stream()
+            .map(ServiceGenerator::toParameterSpec)
+            .collect(toImmutableList());
+
+    return MethodSpec.constructorBuilder()
+        .addParameters(params)
+        .addStatement("super($L)", params.stream().map(p -> p.name).collect(joining(",")))
+        .build();
+  }
+
+  private static ParameterSpec toParameterSpec(XExecutableParameterElement parameter) {
+    return ParameterSpec.builder(parameter.getType().getTypeName(), getSimpleName(parameter))
+        .build();
   }
 
   // @CallSuper
diff --git a/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java b/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java
index 11a00af..799ba1f 100644
--- a/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/androidentrypoint/ViewGenerator.java
@@ -16,37 +16,35 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
-import static com.google.auto.common.MoreTypes.asTypeElement;
+import static androidx.room.compiler.processing.XTypeKt.isInt;
+import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
+import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive;
 
-import com.google.auto.common.MoreElements;
-import com.google.auto.common.Visibility;
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XConstructorElement;
+import androidx.room.compiler.processing.XExecutableParameterElement;
+import androidx.room.compiler.processing.XFiler;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeParameterElement;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.TypeSpec;
-import com.squareup.javapoet.TypeVariableName;
 import dagger.hilt.android.processor.internal.AndroidClassNames;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
 import java.util.List;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementFilter;
 
 /** Generates an Hilt View class for the @AndroidEntryPoint annotated class. */
 public final class ViewGenerator {
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final AndroidEntryPointMetadata metadata;
   private final ClassName generatedClassName;
 
-  public ViewGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
+  public ViewGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
-
     generatedClassName = metadata.generatedClassName();
   }
 
@@ -55,7 +53,7 @@
   //    ComponentManagerHolder<ViewComponentManager<$CLASS_EntryPoint>> {
   //   ...
   // }
-  public void generate() throws IOException {
+  public void generate() {
     // Note: we do not use the Generators helper methods here because injection is called
     // from the constructor where the double-check pattern doesn't work (due to the super
     // constructor being called before fields are initialized) and because it isn't necessary
@@ -63,37 +61,37 @@
 
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(generatedClassName.simpleName())
-            .addOriginatingElement(metadata.element())
             .superclass(metadata.baseClassName())
             .addModifiers(metadata.generatedClassModifiers());
 
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
     Processors.addGeneratedAnnotation(builder, env, getClass());
     Generators.copyLintAnnotations(metadata.element(), builder);
     Generators.copySuppressAnnotations(metadata.element(), builder);
 
     metadata.baseElement().getTypeParameters().stream()
-        .map(TypeVariableName::get)
+        .map(XTypeParameterElement::getTypeVariableName)
         .forEachOrdered(builder::addTypeVariable);
 
     Generators.addComponentOverride(metadata, builder);
-
     Generators.addInjectionMethods(metadata, builder);
 
-    ElementFilter.constructorsIn(metadata.baseElement().getEnclosedElements()).stream()
+    metadata.baseElement().getConstructors().stream()
         .filter(this::isConstructorVisibleToGeneratedClass)
-        .forEach(constructor -> builder.addMethod(constructorMethod(constructor)));
+        .map(this::constructorMethod)
+        .forEach(builder::addMethod);
 
-    JavaFile.builder(generatedClassName.packageName(), builder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
+            XFiler.Mode.Isolating);
   }
 
-  private boolean isConstructorVisibleToGeneratedClass(ExecutableElement constructorElement) {
-    if (Visibility.ofElement(constructorElement) == Visibility.DEFAULT
-        && !isInOurPackage(constructorElement)) {
+  private boolean isConstructorVisibleToGeneratedClass(XConstructorElement constructor) {
+    if (Processors.hasJavaPackagePrivateVisibility(constructor) && !isInOurPackage(constructor)) {
       return false;
-    } else if (Visibility.ofElement(constructorElement) == Visibility.PRIVATE) {
+    } else if (constructor.isPrivate()) {
       return false;
     }
 
@@ -114,34 +112,33 @@
    *   }
    * </pre>
    */
-  private MethodSpec constructorMethod(ExecutableElement constructorElement) {
-    MethodSpec.Builder constructor =
-        Generators.copyConstructor(constructorElement).toBuilder();
+  private MethodSpec constructorMethod(XConstructorElement constructor) {
+    MethodSpec.Builder builder = Generators.copyConstructor(constructor).toBuilder();
 
     // TODO(b/210544481): Once this bug is fixed we should require that the user adds this
     // annotation to their constructor and we'll propagate it from there rather than trying to
     // guess whether this needs @TargetApi from the signature. This check is a bit flawed. For
     // example, the user could write a 5 parameter constructor that calls the restricted 4 parameter
     // constructor and we would miss adding @TargetApi to it.
-    if (isRestrictedApiConstructor(constructorElement)) {
+    if (isRestrictedApiConstructor(constructor)) {
       // 4 parameter constructors are only available on @TargetApi(21).
-      constructor.addAnnotation(
+      builder.addAnnotation(
           AnnotationSpec.builder(AndroidClassNames.TARGET_API).addMember("value", "21").build());
     }
 
-    constructor.addStatement("inject()");
+    builder.addStatement("inject()");
 
-    return constructor.build();
+    return builder.build();
   }
 
-  private boolean isRestrictedApiConstructor(ExecutableElement constructor) {
+  private boolean isRestrictedApiConstructor(XConstructorElement constructor) {
     if (constructor.getParameters().size() != 4) {
       return false;
     }
 
-    List<? extends VariableElement> constructorParams = constructor.getParameters();
+    List<XExecutableParameterElement> constructorParams = constructor.getParameters();
     for (int i = 0; i < constructorParams.size(); i++) {
-      TypeMirror type = constructorParams.get(i).asType();
+      XType type = constructorParams.get(i).getType();
       switch (i) {
         case 0:
           if (!isFirstRestrictedParameter(type)) {
@@ -171,28 +168,28 @@
     return true;
   }
 
-  private static boolean isFourthRestrictedParameter(TypeMirror type) {
-    return type.getKind().isPrimitive()
-        && Processors.getPrimitiveType(type).getKind() == TypeKind.INT;
+  private static boolean isFourthRestrictedParameter(XType type) {
+    return isPrimitive(type) && isInt(type);
   }
 
-  private static boolean isThirdRestrictedParameter(TypeMirror type) {
-    return type.getKind().isPrimitive()
-        && Processors.getPrimitiveType(type).getKind() == TypeKind.INT;
+  private static boolean isThirdRestrictedParameter(XType type) {
+    return isPrimitive(type) && isInt(type);
   }
 
-  private static boolean isSecondRestrictedParameter(TypeMirror type) {
-    return type.getKind() == TypeKind.DECLARED
-        && Processors.isAssignableFrom(asTypeElement(type), AndroidClassNames.ATTRIBUTE_SET);
+  private static boolean isSecondRestrictedParameter(XType type) {
+    return isDeclared(type)
+        && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.ATTRIBUTE_SET);
   }
 
-  private static boolean isFirstRestrictedParameter(TypeMirror type) {
-    return type.getKind() == TypeKind.DECLARED
-        && Processors.isAssignableFrom(asTypeElement(type), AndroidClassNames.CONTEXT);
+  private static boolean isFirstRestrictedParameter(XType type) {
+    return isDeclared(type)
+        && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.CONTEXT);
   }
 
-  private boolean isInOurPackage(ExecutableElement constructorElement) {
-    return MoreElements.getPackage(constructorElement)
-        .equals(MoreElements.getPackage(metadata.element()));
+  private boolean isInOurPackage(XConstructorElement constructor) {
+    return constructor
+        .getEnclosingElement()
+        .getPackageName()
+        .contentEquals(metadata.element().getPackageName());
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BUILD b/java/dagger/hilt/android/processor/internal/bindvalue/BUILD
index 913fafe..5fb1736 100644
--- a/java/dagger/hilt/android/processor/internal/bindvalue/BUILD
+++ b/java/dagger/hilt/android/processor/internal/bindvalue/BUILD
@@ -30,7 +30,9 @@
     srcs = [
         "BindValueGenerator.java",
         "BindValueMetadata.java",
+        "BindValueProcessingStep.java",
         "BindValueProcessor.java",
+        "KspBindValueProcessor.java",
     ],
     deps = [
         "//:dagger_with_compiler",
@@ -41,14 +43,14 @@
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/kotlin",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
-        "//third_party/java/jsr250_annotations",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueGenerator.java b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueGenerator.java
index 108a15d..15bb46d 100644
--- a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueGenerator.java
@@ -20,8 +20,10 @@
 import static com.google.common.base.CaseFormat.UPPER_CAMEL;
 import static java.util.Comparator.comparing;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
 import com.squareup.javapoet.JavaFile;
@@ -34,11 +36,12 @@
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Components;
 import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
 import dagger.multibindings.ElementsIntoSet;
 import dagger.multibindings.IntoMap;
 import dagger.multibindings.IntoSet;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /**
@@ -47,15 +50,15 @@
 final class BindValueGenerator {
   private static final String SUFFIX = "_BindValueModule";
 
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final BindValueMetadata metadata;
   private final ClassName testClassName;
   private final ClassName className;
 
-  BindValueGenerator(ProcessingEnvironment env, BindValueMetadata metadata) {
+  BindValueGenerator(XProcessingEnv env, BindValueMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
-    testClassName = ClassName.get(metadata.testElement());
+    testClassName = metadata.testElement().getClassName();
     className = Processors.append(testClassName, SUFFIX);
   }
 
@@ -65,16 +68,14 @@
   //     // providesMethods ...
   //  }
   void generate() throws IOException {
-    TypeSpec.Builder builder =
-        TypeSpec.classBuilder(className)
-            .addOriginatingElement(metadata.testElement())
-            .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.testElement()))
-            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
-            .addAnnotation(Module.class)
-            .addAnnotation(
-                Components.getInstallInAnnotationSpec(
-                    ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)))
-            .addMethod(providesTestMethod());
+    TypeSpec.Builder builder = TypeSpec.classBuilder(className);
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.testElement())
+        .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.testElement()))
+        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+        .addAnnotation(Module.class)
+        .addAnnotation(
+            Components.getInstallInAnnotationSpec(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)))
+        .addMethod(providesTestMethod());
 
     Processors.addGeneratedAnnotation(builder, env, getClass());
 
@@ -83,9 +84,8 @@
         .sorted(comparing(MethodSpec::toString))
         .forEachOrdered(builder::addMethod);
 
-    JavaFile.builder(className.packageName(), builder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(JavaFile.builder(className.packageName(), builder.build()).build(), Mode.Isolating);
   }
 
   // @Provides
@@ -121,25 +121,24 @@
   // }
   private MethodSpec providesMethod(BindValueElement bindValue) {
     // We only allow fields in the Test class, which should have unique variable names.
-    String methodName = "provides"
-        + LOWER_CAMEL.to(UPPER_CAMEL, bindValue.variableElement().getSimpleName().toString());
+    String methodName =
+        "provides" + LOWER_CAMEL.to(UPPER_CAMEL, bindValue.fieldElement().getName());
     MethodSpec.Builder builder =
         MethodSpec.methodBuilder(methodName)
             .addAnnotation(Provides.class)
             .addModifiers(Modifier.STATIC)
-            .returns(ClassName.get(bindValue.variableElement().asType()));
+            .returns(bindValue.fieldElement().getType().getTypeName());
 
-    if (bindValue.variableElement().getModifiers().contains(Modifier.STATIC)) {
-      builder.addStatement(
-          "return $T.$L", testClassName, bindValue.variableElement().getSimpleName());
+    if (XElements.isStatic(bindValue.fieldElement())) {
+      builder.addStatement("return $T.$L", testClassName, bindValue.fieldElement().getName());
     } else {
       builder
           .addParameter(testClassName, "test")
           .addStatement(
               "return $L",
               bindValue.getterElement().isPresent()
-                  ? CodeBlock.of("test.$L()", bindValue.getterElement().get().getSimpleName())
-                  : CodeBlock.of("test.$L", bindValue.variableElement().getSimpleName()));
+                  ? CodeBlock.of("test.$L()", bindValue.getterElement().get().getJvmName())
+                  : CodeBlock.of("test.$L", bindValue.fieldElement().getName()));
     }
 
     ClassName annotationClassName = bindValue.annotationName();
@@ -148,13 +147,13 @@
       // It is safe to call get() on the Optional<AnnotationMirror> returned by mapKey()
       // because a @BindValueIntoMap is required to have one and is checked in
       // BindValueMetadata.BindValueElement.create().
-      builder.addAnnotation(AnnotationSpec.get(bindValue.mapKey().get()));
+      builder.addAnnotation(XAnnotations.getAnnotationSpec(bindValue.mapKey().get()));
     } else if (BindValueMetadata.BIND_VALUE_INTO_SET_ANNOTATIONS.contains(annotationClassName)) {
       builder.addAnnotation(IntoSet.class);
     } else if (BindValueMetadata.BIND_ELEMENTS_INTO_SET_ANNOTATIONS.contains(annotationClassName)) {
       builder.addAnnotation(ElementsIntoSet.class);
     }
-    bindValue.qualifier().ifPresent(q -> builder.addAnnotation(AnnotationSpec.get(q)));
+    bindValue.qualifier().ifPresent(q -> builder.addAnnotation(XAnnotations.getAnnotationSpec(q)));
     return builder.build();
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java
index 375ab6c..00a6402 100644
--- a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java
+++ b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueMetadata.java
@@ -16,9 +16,16 @@
 
 package dagger.hilt.android.processor.internal.bindvalue;
 
+import static com.google.common.collect.Iterables.getOnlyElement;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.xprocessing.XElements.asField;
 
-import com.google.auto.common.MoreElements;
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XElementKt;
+import androidx.room.compiler.processing.XFieldElement;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -28,15 +35,10 @@
 import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtil;
 import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.util.Collection;
 import java.util.Optional;
-import javax.lang.model.element.AnnotationMirror;
-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.TypeElement;
-import javax.lang.model.element.VariableElement;
 
 /**
  * Represents metadata for a test class that has {@code BindValue} fields.
@@ -56,17 +58,22 @@
       ImmutableSet.of(
           ClassNames.ANDROID_BIND_VALUE_INTO_MAP);
 
-  /** @return the {@code TestRoot} annotated class's name. */
-  abstract TypeElement testElement();
+  /**
+   * @return the {@code TestRoot} annotated class's name.
+   */
+  abstract XTypeElement testElement();
 
   /** @return a {@link ImmutableSet} of elements annotated with @BindValue. */
   abstract ImmutableSet<BindValueElement> bindValueElements();
 
-  /** @return a new BindValueMetadata instance. */
-  static BindValueMetadata create(TypeElement testElement, Collection<Element> bindValueElements) {
+  /**
+   * @return a new BindValueMetadata instance.
+   */
+  static BindValueMetadata create(
+      XTypeElement testElement, Collection<XElement> bindValueElements) {
 
     ImmutableSet.Builder<BindValueElement> elements = ImmutableSet.builder();
-    for (Element element : bindValueElements) {
+    for (XElement element : bindValueElements) {
       elements.add(BindValueElement.create(element));
     }
 
@@ -75,101 +82,104 @@
 
   @AutoValue
   abstract static class BindValueElement {
-    abstract VariableElement variableElement();
+    abstract XFieldElement fieldElement();
 
     abstract ClassName annotationName();
 
-    abstract Optional<AnnotationMirror> qualifier();
+    abstract Optional<XAnnotation> qualifier();
 
-    abstract Optional<AnnotationMirror> mapKey();
+    abstract Optional<XAnnotation> mapKey();
 
-    abstract Optional<ExecutableElement> getterElement();
+    abstract Optional<XMethodElement> getterElement();
 
-    static BindValueElement create(Element element) {
-      ImmutableList<ClassName> bindValues = BindValueProcessor.getBindValueAnnotations(element);
+    static BindValueElement create(XElement element) {
+      ImmutableList<ClassName> bindValues =
+          BindValueProcessingStep.getBindValueAnnotations(element);
       ProcessorErrors.checkState(
           bindValues.size() == 1,
           element,
           "Fields can be annotated with only one of @BindValue, @BindValueIntoMap,"
               + " @BindElementsIntoSet, @BindValueIntoSet. Found: %s",
           bindValues.stream().map(m -> "@" + m.simpleName()).collect(toImmutableList()));
-      ClassName annotationClassName = bindValues.get(0);
+      ClassName annotationClassName = getOnlyElement(bindValues);
 
       ProcessorErrors.checkState(
-          element.getKind() == ElementKind.FIELD,
+          XElementKt.isField(element),
           element,
           "@%s can only be used with fields. Found: %s",
           annotationClassName.simpleName(),
-          element);
+          XElements.toStableString(element));
+
+      XFieldElement field = asField(element);
 
       KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
-      Optional<ExecutableElement> propertyGetter =
-          metadataUtil.hasMetadata(element)
-              ? metadataUtil.getPropertyGetter(MoreElements.asVariable(element))
+      Optional<XMethodElement> propertyGetter =
+          metadataUtil.hasMetadata(field)
+              ? metadataUtil.getPropertyGetter(field)
               : Optional.empty();
       if (propertyGetter.isPresent()) {
         ProcessorErrors.checkState(
-            !propertyGetter.get().getModifiers().contains(Modifier.PRIVATE),
-            element,
+            !propertyGetter.get().isPrivate(),
+            field,
             "@%s field getter cannot be private. Found: %s",
             annotationClassName.simpleName(),
-            element);
+            XElements.toStableString(field));
       } else {
         ProcessorErrors.checkState(
-            !element.getModifiers().contains(Modifier.PRIVATE),
-            element,
+            !XElements.isPrivate(field),
+            field,
             "@%s fields cannot be private. Found: %s",
             annotationClassName.simpleName(),
-            element);
+            XElements.toStableString(field));
       }
 
       ProcessorErrors.checkState(
-          !Processors.hasAnnotation(element, ClassNames.INJECT),
-          element,
+          !field.hasAnnotation(ClassNames.INJECT),
+          field,
           "@%s fields cannot be used with @Inject annotation. Found %s",
           annotationClassName.simpleName(),
-          element);
+          XElements.toStableString(field));
 
-      ImmutableList<AnnotationMirror> qualifiers = Processors.getQualifierAnnotations(element);
+      ImmutableList<XAnnotation> qualifiers = Processors.getQualifierAnnotations(field);
       ProcessorErrors.checkState(
           qualifiers.size() <= 1,
-          element,
+          field,
           "@%s fields cannot have more than one qualifier. Found %s",
           annotationClassName.simpleName(),
-          qualifiers);
+          qualifiers.stream().map(XAnnotations::toStableString).collect(toImmutableList()));
 
-      ImmutableList<AnnotationMirror> mapKeys = Processors.getMapKeyAnnotations(element);
-      Optional<AnnotationMirror> optionalMapKeys;
+      ImmutableList<XAnnotation> mapKeys = Processors.getMapKeyAnnotations(field);
+      Optional<XAnnotation> optionalMapKeys;
       if (BIND_VALUE_INTO_MAP_ANNOTATIONS.contains(annotationClassName)) {
         ProcessorErrors.checkState(
             mapKeys.size() == 1,
-            element,
+            field,
             "@BindValueIntoMap fields must have exactly one @MapKey. Found %s",
-            mapKeys);
+            mapKeys.stream().map(XAnnotations::toStableString).collect(toImmutableList()));
         optionalMapKeys = Optional.of(mapKeys.get(0));
       } else {
         ProcessorErrors.checkState(
             mapKeys.isEmpty(),
-            element,
+            field,
             "@MapKey can only be used on @BindValueIntoMap fields, not @%s fields",
             annotationClassName.simpleName());
         optionalMapKeys = Optional.empty();
       }
 
-      ImmutableList<AnnotationMirror> scopes = Processors.getScopeAnnotations(element);
+      ImmutableList<XAnnotation> scopes = Processors.getScopeAnnotations(field);
       ProcessorErrors.checkState(
           scopes.isEmpty(),
-          element,
+          field,
           "@%s fields cannot be scoped. Found %s",
           annotationClassName.simpleName(),
-          scopes);
+          scopes.stream().map(XAnnotations::toStableString).collect(toImmutableList()));
 
       return new AutoValue_BindValueMetadata_BindValueElement(
-          (VariableElement) element,
+          field,
           annotationClassName,
           qualifiers.isEmpty()
-              ? Optional.<AnnotationMirror>empty()
-              : Optional.<AnnotationMirror>of(qualifiers.get(0)),
+              ? Optional.<XAnnotation>empty()
+              : Optional.<XAnnotation>of(qualifiers.get(0)),
           optionalMapKeys,
           propertyGetter);
     }
diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessingStep.java b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessingStep.java
new file mode 100644
index 0000000..a555212
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessingStep.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.bindvalue;
+
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
+
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ListMultimap;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.internal.codegen.xprocessing.XElements;
+import java.util.Collection;
+import java.util.Map;
+
+/** Provides a test's @BindValue fields to the SINGLETON component. */
+public final class BindValueProcessingStep extends BaseProcessingStep {
+
+  private static final ImmutableSet<ClassName> SUPPORTED_ANNOTATIONS =
+      ImmutableSet.<ClassName>builder()
+          .addAll(BindValueMetadata.BIND_VALUE_ANNOTATIONS)
+          .addAll(BindValueMetadata.BIND_VALUE_INTO_SET_ANNOTATIONS)
+          .addAll(BindValueMetadata.BIND_ELEMENTS_INTO_SET_ANNOTATIONS)
+          .addAll(BindValueMetadata.BIND_VALUE_INTO_MAP_ANNOTATIONS)
+          .build();
+
+  private final ListMultimap<XTypeElement, XElement> testRootMap = ArrayListMultimap.create();
+
+  public BindValueProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return SUPPORTED_ANNOTATIONS;
+  }
+
+  @Override
+  protected void preProcess(XProcessingEnv env, XRoundEnv round) {
+    testRootMap.clear();
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    XElement enclosingElement = element.getEnclosingElement();
+    // Restrict BindValue to the direct test class (e.g. not allowed in a base test class) because
+    // otherwise generated BindValue modules from the base class will not associate with the
+    // correct test class. This would make the modules apply globally which would be a weird
+    // difference since just moving a declaration to the parent would change whether the module is
+    // limited to the test that declares it to global.
+    ProcessorErrors.checkState(
+        isTypeElement(enclosingElement)
+            && asTypeElement(enclosingElement).isClass()
+            && (enclosingElement.hasAnnotation(ClassNames.HILT_ANDROID_TEST)
+            ),
+        enclosingElement,
+        "@%s can only be used within a class annotated with "
+            + "@HiltAndroidTest. Found: %s",
+        annotation.simpleName(),
+        XElements.toStableString(enclosingElement));
+    testRootMap.put(asTypeElement(enclosingElement), element);
+  }
+
+  @Override
+  protected void postProcess(XProcessingEnv env, XRoundEnv round) throws Exception {
+    // Generate a module for each testing class with a @BindValue field.
+    for (Map.Entry<XTypeElement, Collection<XElement>> e : testRootMap.asMap().entrySet()) {
+      BindValueMetadata metadata = BindValueMetadata.create(e.getKey(), e.getValue());
+      new BindValueGenerator(processingEnv(), metadata).generate();
+    }
+  }
+
+  static ImmutableList<ClassName> getBindValueAnnotations(XElement element) {
+    return element.getAllAnnotations().stream()
+        .map(XAnnotation::getClassName)
+        .filter(SUPPORTED_ANNOTATIONS::contains)
+        .collect(toImmutableList());
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessor.java b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessor.java
index 060b077..e8fe727 100644
--- a/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessor.java
+++ b/java/dagger/hilt/android/processor/internal/bindvalue/BindValueProcessor.java
@@ -18,96 +18,18 @@
 
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
-import com.google.auto.common.MoreElements;
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Multimap;
-import com.squareup.javapoet.AnnotationSpec;
-import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.TypeName;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
-import dagger.internal.codegen.extension.DaggerStreams;
-import java.util.Collection;
-import java.util.Map;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Provides a test's @BindValue fields to the SINGLETON component. */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class BindValueProcessor extends BaseProcessor {
-
-  private static final ImmutableSet<ClassName> SUPPORTED_ANNOTATIONS =
-      ImmutableSet.<ClassName>builder()
-          .addAll(BindValueMetadata.BIND_VALUE_ANNOTATIONS)
-          .addAll(BindValueMetadata.BIND_VALUE_INTO_SET_ANNOTATIONS)
-          .addAll(BindValueMetadata.BIND_ELEMENTS_INTO_SET_ANNOTATIONS)
-          .addAll(BindValueMetadata.BIND_VALUE_INTO_MAP_ANNOTATIONS)
-          .build();
-
-  private final Multimap<TypeElement, Element> testRootMap = ArrayListMultimap.create();
-
+public final class BindValueProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public ImmutableSet<String> getSupportedAnnotationTypes() {
-    return SUPPORTED_ANNOTATIONS.stream()
-        .map(TypeName::toString)
-        .collect(DaggerStreams.toImmutableSet());
-  }
-
-  @Override
-  protected void preRoundProcess(RoundEnvironment roundEnv) {
-    testRootMap.clear();
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    ClassName annotationClassName = ClassName.get(annotation);
-    Element enclosingElement = element.getEnclosingElement();
-    // Restrict BindValue to the direct test class (e.g. not allowed in a base test class) because
-    // otherwise generated BindValue modules from the base class will not associate with the
-    // correct test class. This would make the modules apply globally which would be a weird
-    // difference since just moving a declaration to the parent would change whether the module is
-    // limited to the test that declares it to global.
-    ProcessorErrors.checkState(
-        enclosingElement.getKind() == ElementKind.CLASS
-            && (Processors.hasAnnotation(enclosingElement, ClassNames.HILT_ANDROID_TEST)
-            ),
-        enclosingElement,
-        "@%s can only be used within a class annotated with "
-            + "@HiltAndroidTest. Found: %s",
-        annotationClassName.simpleName(),
-        enclosingElement);
-
-    testRootMap.put(MoreElements.asType(enclosingElement), element);
-  }
-
-  @Override
-  public void postRoundProcess(RoundEnvironment roundEnvironment) throws Exception {
-    // Generate a module for each testing class with a @BindValue field.
-    for (Map.Entry<TypeElement, Collection<Element>> e : testRootMap.asMap().entrySet()) {
-      BindValueMetadata metadata = BindValueMetadata.create(e.getKey(), e.getValue());
-      new BindValueGenerator(getProcessingEnv(), metadata).generate();
-    }
-  }
-
-  static ImmutableList<ClassName> getBindValueAnnotations(Element element) {
-    ImmutableList.Builder<ClassName> builder = ImmutableList.builder();
-    for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
-      TypeName tn = AnnotationSpec.get(annotation).type;
-      if (SUPPORTED_ANNOTATIONS.contains(tn)) {
-        builder.add((ClassName) tn); // the cast is checked by .contains()
-      }
-    }
-    return builder.build();
+  protected BaseProcessingStep processingStep() {
+    return new BindValueProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/bindvalue/KspBindValueProcessor.java b/java/dagger/hilt/android/processor/internal/bindvalue/KspBindValueProcessor.java
new file mode 100644
index 0000000..55546b5
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/bindvalue/KspBindValueProcessor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.bindvalue;
+
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Provides a test's @BindValue fields to the SINGLETON component. */
+public final class KspBindValueProcessor extends KspBaseProcessingStepProcessor {
+
+  public KspBindValueProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new BindValueProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspBindValueProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment environment) {
+      return new KspBindValueProcessor(environment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD b/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD
index 681ac79..a4fa94b 100644
--- a/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD
+++ b/java/dagger/hilt/android/processor/internal/customtestapplication/BUILD
@@ -29,7 +29,9 @@
     srcs = [
         "CustomTestApplicationGenerator.java",
         "CustomTestApplicationMetadata.java",
+        "CustomTestApplicationProcessingStep.java",
         "CustomTestApplicationProcessor.java",
+        "KspCustomTestApplicationProcessor.java",
     ],
     deps = [
         "//java/dagger/hilt/processor/internal:base_processor",
@@ -37,13 +39,14 @@
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java
index a9adbb9..5e4314c 100644
--- a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java
+++ b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationGenerator.java
@@ -20,6 +20,9 @@
 import static javax.lang.model.element.Modifier.PRIVATE;
 import static javax.lang.model.element.Modifier.VOLATILE;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.FieldSpec;
 import com.squareup.javapoet.JavaFile;
@@ -31,7 +34,6 @@
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /** Generates an Android Application that holds the Singleton component. */
@@ -40,38 +42,37 @@
       ParameterSpec.builder(ClassNames.TEST_APPLICATION_COMPONENT_MANAGER, "componentManager")
           .build();
 
-  private final ProcessingEnvironment processingEnv;
+  private final XProcessingEnv processingEnv;
   private final CustomTestApplicationMetadata metadata;
 
   public CustomTestApplicationGenerator(
-      ProcessingEnvironment processingEnv, CustomTestApplicationMetadata metadata) {
+      XProcessingEnv processingEnv, CustomTestApplicationMetadata metadata) {
     this.processingEnv = processingEnv;
     this.metadata = metadata;
   }
 
   public void generate() throws IOException {
-    TypeSpec.Builder generator =
-        TypeSpec.classBuilder(metadata.appName())
-            .addOriginatingElement(metadata.element())
-            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
-            .superclass(metadata.baseAppName())
-            .addSuperinterface(
-                ParameterizedTypeName.get(ClassNames.GENERATED_COMPONENT_MANAGER, TypeName.OBJECT))
-            .addSuperinterface(ClassNames.TEST_APPLICATION_COMPONENT_MANAGER_HOLDER)
-            .addField(
-                FieldSpec.builder(ClassName.OBJECT, "componentManagerLock", PRIVATE, FINAL)
-                    .initializer("new $T()", ClassName.OBJECT)
-                    .build())
-            .addField(getComponentManagerField())
-            .addMethod(getComponentManagerMethod())
-            .addMethod(getComponentMethod());
+    TypeSpec.Builder generator = TypeSpec.classBuilder(metadata.appName());
+    JavaPoetExtKt.addOriginatingElement(generator, metadata.element())
+        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+        .superclass(metadata.baseAppName())
+        .addSuperinterface(
+            ParameterizedTypeName.get(ClassNames.GENERATED_COMPONENT_MANAGER, TypeName.OBJECT))
+        .addSuperinterface(ClassNames.TEST_APPLICATION_COMPONENT_MANAGER_HOLDER)
+        .addField(
+            FieldSpec.builder(ClassName.OBJECT, "componentManagerLock", PRIVATE, FINAL)
+                .initializer("new $T()", ClassName.OBJECT)
+                .build())
+        .addField(getComponentManagerField())
+        .addMethod(getComponentManagerMethod())
+        .addMethod(getComponentMethod());
 
     Processors.addGeneratedAnnotation(
         generator, processingEnv, CustomTestApplicationProcessor.class);
 
-    JavaFile.builder(metadata.appName().packageName(), generator.build())
-        .build()
-        .writeTo(processingEnv.getFiler());
+    JavaFile javaFile =
+        JavaFile.builder(metadata.appName().packageName(), generator.build()).build();
+    processingEnv.getFiler().write(javaFile, Mode.Isolating);
   }
 
   // Initialize this in attachBaseContext to not pull it into the main dex.
diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationMetadata.java b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationMetadata.java
index de8e3f7..e9e9820 100644
--- a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationMetadata.java
+++ b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationMetadata.java
@@ -18,8 +18,11 @@
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 
-import com.google.auto.common.MoreElements;
-import com.google.auto.common.MoreTypes;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XElementKt;
+import androidx.room.compiler.processing.XExecutableElement;
+import androidx.room.compiler.processing.XFieldElement;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -27,19 +30,13 @@
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.Processors;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.util.ElementFilter;
-import javax.lang.model.util.Elements;
+import dagger.internal.codegen.xprocessing.XElements;
 
 /** Stores the metadata for a custom base test application. */
 @AutoValue
 abstract class CustomTestApplicationMetadata {
   /** Returns the annotated element. */
-  abstract TypeElement element();
+  abstract XTypeElement element();
 
   /** Returns the name of the base application. */
   abstract ClassName baseAppName();
@@ -47,49 +44,48 @@
   /** Returns the name of the generated application */
   ClassName appName() {
     return Processors.append(
-        Processors.getEnclosedClassName(ClassName.get(element())), "_Application");
+        Processors.getEnclosedClassName(element().getClassName()), "_Application");
   }
 
-  static CustomTestApplicationMetadata of(Element element, Elements elements) {
+  static CustomTestApplicationMetadata of(XElement element) {
     Preconditions.checkState(
-        Processors.hasAnnotation(element, ClassNames.CUSTOM_TEST_APPLICATION),
+        element.hasAnnotation(ClassNames.CUSTOM_TEST_APPLICATION),
         "The given element, %s, is not annotated with @%s.",
-        element,
+        XElements.toStableString(element),
         ClassNames.CUSTOM_TEST_APPLICATION.simpleName());
 
     ProcessorErrors.checkState(
-        MoreElements.isType(element),
+        XElementKt.isTypeElement(element),
         element,
         "@%s should only be used on classes or interfaces but found: %s",
         ClassNames.CUSTOM_TEST_APPLICATION.simpleName(),
-        element);
+        XElements.toStableString(element));
 
-    TypeElement baseAppElement = getBaseElement(element, elements);
+    XTypeElement baseAppElement = getBaseElement(element);
 
     return new AutoValue_CustomTestApplicationMetadata(
-        MoreElements.asType(element), ClassName.get(baseAppElement));
+        XElements.asTypeElement(element), baseAppElement.getClassName());
   }
 
-  private static TypeElement getBaseElement(Element element, Elements elements) {
-    TypeElement baseElement =
-        Processors.getAnnotationClassValue(
-            elements,
-            Processors.getAnnotationMirror(element, ClassNames.CUSTOM_TEST_APPLICATION),
-            "value");
+  private static XTypeElement getBaseElement(XElement element) {
+    XTypeElement baseElement =
+        element.getAnnotation(ClassNames.CUSTOM_TEST_APPLICATION)
+            .getAsType("value")
+            .getTypeElement();
 
-    TypeElement baseSuperclassElement = baseElement;
-    while (!baseSuperclassElement.getSuperclass().getKind().equals(TypeKind.NONE)) {
+    XTypeElement baseSuperclassElement = baseElement;
+    while (baseSuperclassElement.getSuperClass() != null) {
       ProcessorErrors.checkState(
-          !Processors.hasAnnotation(baseSuperclassElement, ClassNames.HILT_ANDROID_APP),
+          !baseSuperclassElement.hasAnnotation(ClassNames.HILT_ANDROID_APP),
           element,
           "@%s value cannot be annotated with @%s. Found: %s",
           ClassNames.CUSTOM_TEST_APPLICATION.simpleName(),
           ClassNames.HILT_ANDROID_APP.simpleName(),
-          baseSuperclassElement);
+          baseSuperclassElement.getClassName());
 
-      ImmutableList<VariableElement> injectFields =
-          ElementFilter.fieldsIn(baseSuperclassElement.getEnclosedElements()).stream()
-              .filter(field -> Processors.hasAnnotation(field, ClassNames.INJECT))
+      ImmutableList<XFieldElement> injectFields =
+          baseSuperclassElement.getDeclaredFields().stream()
+              .filter(field -> field.hasAnnotation(ClassNames.INJECT))
               .collect(toImmutableList());
       ProcessorErrors.checkState(
           injectFields.isEmpty(),
@@ -97,12 +93,12 @@
           "@%s does not support application classes (or super classes) with @Inject fields. Found "
               + "%s with @Inject fields %s.",
           ClassNames.CUSTOM_TEST_APPLICATION.simpleName(),
-          baseSuperclassElement,
-          injectFields);
+          baseSuperclassElement.getClassName(),
+          injectFields.stream().map(XElements::toStableString).collect(toImmutableList()));
 
-      ImmutableList<ExecutableElement> injectMethods =
-          ElementFilter.methodsIn(baseSuperclassElement.getEnclosedElements()).stream()
-              .filter(method -> Processors.hasAnnotation(method, ClassNames.INJECT))
+      ImmutableList<XExecutableElement> injectMethods =
+          baseSuperclassElement.getDeclaredMethods().stream()
+              .filter(method -> method.hasAnnotation(ClassNames.INJECT))
               .collect(toImmutableList());
       ProcessorErrors.checkState(
           injectMethods.isEmpty(),
@@ -110,12 +106,12 @@
           "@%s does not support application classes (or super classes) with @Inject methods. Found "
               + "%s with @Inject methods %s.",
           ClassNames.CUSTOM_TEST_APPLICATION.simpleName(),
-          baseSuperclassElement,
-          injectMethods);
+          baseSuperclassElement.getClassName(),
+          injectMethods.stream().map(XElements::toStableString).collect(toImmutableList()));
 
-      ImmutableList<ExecutableElement> injectConstructors =
-          ElementFilter.constructorsIn(baseSuperclassElement.getEnclosedElements()).stream()
-              .filter(method -> Processors.hasAnnotation(method, ClassNames.INJECT))
+      ImmutableList<XExecutableElement> injectConstructors =
+          baseSuperclassElement.getConstructors().stream()
+              .filter(method -> method.hasAnnotation(ClassNames.INJECT))
               .collect(toImmutableList());
       ProcessorErrors.checkState(
           injectConstructors.isEmpty(),
@@ -123,10 +119,10 @@
           "@%s does not support application classes (or super classes) with @Inject constructors. "
               + "Found %s with @Inject constructors %s.",
           ClassNames.CUSTOM_TEST_APPLICATION.simpleName(),
-          baseSuperclassElement,
-          injectConstructors);
+          baseSuperclassElement.getClassName().canonicalName(),
+          injectConstructors.stream().map(XElements::toStableString).collect(toImmutableList()));
 
-      baseSuperclassElement = MoreTypes.asTypeElement(baseSuperclassElement.getSuperclass());
+      baseSuperclassElement = baseSuperclassElement.getSuperClass().getTypeElement();
     }
 
     // We check this last because if the base type is a @HiltAndroidApp we'd accidentally fail
@@ -137,7 +133,7 @@
         "@%s value should be an instance of %s. Found: %s",
         ClassNames.CUSTOM_TEST_APPLICATION.simpleName(),
         ClassNames.APPLICATION,
-        baseElement);
+        baseElement.getClassName());
 
     return baseElement;
   }
diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessingStep.java b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessingStep.java
new file mode 100644
index 0000000..572664d
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessingStep.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.customtestapplication;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+
+/** Processes usages of {@link dagger.hilt.android.testing.CustomTestApplication}. */
+public final class CustomTestApplicationProcessingStep extends BaseProcessingStep {
+
+  public CustomTestApplicationProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.CUSTOM_TEST_APPLICATION);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) throws Exception {
+    CustomTestApplicationMetadata metadata = CustomTestApplicationMetadata.of(element);
+    new CustomTestApplicationGenerator(processingEnv(), metadata).generate();
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessor.java b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessor.java
index fd1a6c8..e65d2b9 100644
--- a/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessor.java
+++ b/java/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessor.java
@@ -19,28 +19,16 @@
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableSet;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Processes usages of {@link dagger.hilt.android.testing.CustomTestApplication}. */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class CustomTestApplicationProcessor extends BaseProcessor {
-
+public final class CustomTestApplicationProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public ImmutableSet<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.CUSTOM_TEST_APPLICATION.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    CustomTestApplicationMetadata metadata =
-        CustomTestApplicationMetadata.of(element, getElementUtils());
-    new CustomTestApplicationGenerator(getProcessingEnv(), metadata).generate();
+  protected CustomTestApplicationProcessingStep processingStep() {
+    return new CustomTestApplicationProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/customtestapplication/KspCustomTestApplicationProcessor.java b/java/dagger/hilt/android/processor/internal/customtestapplication/KspCustomTestApplicationProcessor.java
new file mode 100644
index 0000000..23a6e79
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/customtestapplication/KspCustomTestApplicationProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.customtestapplication;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Processes usages of {@link dagger.hilt.android.testing.CustomTestApplication}. */
+public final class KspCustomTestApplicationProcessor extends KspBaseProcessingStepProcessor {
+
+  public KspCustomTestApplicationProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected CustomTestApplicationProcessingStep processingStep() {
+    return new CustomTestApplicationProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspCustomTestApplicationProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspCustomTestApplicationProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD
index 353729d..f3686a1 100644
--- a/java/dagger/hilt/android/processor/internal/viewmodel/BUILD
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/BUILD
@@ -28,8 +28,10 @@
 kt_jvm_library(
     name = "processor_lib",
     srcs = [
+        "KspViewModelProcessor.kt",
         "ViewModelMetadata.kt",
         "ViewModelModuleGenerator.kt",
+        "ViewModelProcessingStep.kt",
         "ViewModelProcessor.kt",
     ],
     deps = [
@@ -38,11 +40,12 @@
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -59,8 +62,7 @@
     deps = [
         "//:spi",
         "//java/dagger/hilt/android/processor/internal:android_classnames",
-        "//java/dagger/hilt/processor/internal:processors",
-        "//third_party/java/auto:common",
+        "//java/dagger/hilt/processor/internal:dagger_models",
         "//third_party/java/auto:service",
         "//third_party/java/guava/graph",
         "//third_party/java/javapoet",
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/KspViewModelProcessor.kt b/java/dagger/hilt/android/processor/internal/viewmodel/KspViewModelProcessor.kt
new file mode 100644
index 0000000..793dd9b
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/KspViewModelProcessor.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.viewmodel
+
+import androidx.room.compiler.processing.ExperimentalProcessingApi
+import com.google.auto.service.AutoService
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+import dagger.hilt.processor.internal.BaseProcessingStep
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor
+
+/** Annotation processor for @ViewModelInject. */
+class KspViewModelProcessor(symbolProcessorEnvironment: SymbolProcessorEnvironment?) :
+  KspBaseProcessingStepProcessor(symbolProcessorEnvironment) {
+  @OptIn(ExperimentalProcessingApi::class)
+  override fun processingStep(): BaseProcessingStep = ViewModelProcessingStep(xProcessingEnv)
+
+  /** Provides the [KspViewModelProcessor]. */
+  @AutoService(SymbolProcessorProvider::class)
+  class Provider : SymbolProcessorProvider {
+    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+      return KspViewModelProcessor(environment)
+    }
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt
index 789fbfe..49b35a7 100644
--- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelMetadata.kt
@@ -16,76 +16,69 @@
 
 package dagger.hilt.android.processor.internal.viewmodel
 
-import com.google.auto.common.MoreElements
+import androidx.room.compiler.processing.ExperimentalProcessingApi
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XTypeElement
 import com.squareup.javapoet.ClassName
 import dagger.hilt.android.processor.internal.AndroidClassNames
 import dagger.hilt.processor.internal.ClassNames
 import dagger.hilt.processor.internal.ProcessorErrors
 import dagger.hilt.processor.internal.Processors
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.element.Modifier
-import javax.lang.model.element.NestingKind
-import javax.lang.model.element.TypeElement
-import javax.lang.model.util.ElementFilter
+import dagger.internal.codegen.xprocessing.XAnnotations
+import dagger.internal.codegen.xprocessing.XTypes
 
-/**
- * Data class that represents a Hilt injected ViewModel
- */
-internal class ViewModelMetadata private constructor(
-  val typeElement: TypeElement
-) {
-  val className = ClassName.get(typeElement)
+/** Data class that represents a Hilt injected ViewModel */
+@OptIn(ExperimentalProcessingApi::class)
+internal class ViewModelMetadata private constructor(val typeElement: XTypeElement) {
+  val className = typeElement.className
 
-  val modulesClassName = ClassName.get(
-    MoreElements.getPackage(typeElement).qualifiedName.toString(),
-    "${className.simpleNames().joinToString("_")}_HiltModules"
-  )
+  val modulesClassName =
+    ClassName.get(
+      typeElement.packageName,
+      "${className.simpleNames().joinToString("_")}_HiltModules"
+    )
 
   companion object {
     internal fun create(
-      processingEnv: ProcessingEnvironment,
-      typeElement: TypeElement,
+      processingEnv: XProcessingEnv,
+      typeElement: XTypeElement,
     ): ViewModelMetadata? {
-      val types = processingEnv.typeUtils
-      val elements = processingEnv.elementUtils
-
       ProcessorErrors.checkState(
-        types.isSubtype(
-          typeElement.asType(),
-          elements.getTypeElement(AndroidClassNames.VIEW_MODEL.toString()).asType()
-        ),
+        XTypes.isSubtype(typeElement.type, processingEnv.requireType(AndroidClassNames.VIEW_MODEL)),
         typeElement,
         "@HiltViewModel is only supported on types that subclass %s.",
         AndroidClassNames.VIEW_MODEL
       )
 
-      ElementFilter.constructorsIn(typeElement.enclosedElements).filter { constructor ->
-        ProcessorErrors.checkState(
-          !Processors.hasAnnotation(constructor, ClassNames.ASSISTED_INJECT),
-          constructor,
-          "ViewModel constructor should be annotated with @Inject instead of @AssistedInject."
-        )
-        Processors.hasAnnotation(constructor, ClassNames.INJECT)
-      }.let { injectConstructors ->
-        ProcessorErrors.checkState(
-          injectConstructors.size == 1,
-          typeElement,
-          "@HiltViewModel annotated class should contain exactly one @Inject " +
-            "annotated constructor."
-        )
-
-        injectConstructors.forEach { constructor ->
+      typeElement
+        .getConstructors()
+        .filter { constructor ->
           ProcessorErrors.checkState(
-            !constructor.modifiers.contains(Modifier.PRIVATE),
+            !constructor.hasAnnotation(ClassNames.ASSISTED_INJECT),
             constructor,
-            "@Inject annotated constructors must not be private."
+            "ViewModel constructor should be annotated with @Inject instead of @AssistedInject."
           )
+          constructor.hasAnnotation(ClassNames.INJECT)
         }
-      }
+        .let { injectConstructors ->
+          ProcessorErrors.checkState(
+            injectConstructors.size == 1,
+            typeElement,
+            "@HiltViewModel annotated class should contain exactly one @Inject " +
+              "annotated constructor."
+          )
+
+          injectConstructors.forEach { injectConstructor ->
+            ProcessorErrors.checkState(
+              !injectConstructor.isPrivate(),
+              injectConstructor,
+              "@Inject annotated constructors must not be private."
+            )
+          }
+        }
 
       ProcessorErrors.checkState(
-        typeElement.nestingKind != NestingKind.MEMBER ||
-          typeElement.modifiers.contains(Modifier.STATIC),
+        !typeElement.isNested() || typeElement.isStatic(),
         typeElement,
         "@HiltViewModel may only be used on inner classes if they are static."
       )
@@ -95,13 +88,11 @@
           scopeAnnotations.isEmpty(),
           typeElement,
           "@HiltViewModel classes should not be scoped. Found: %s",
-          scopeAnnotations.joinToString()
+          scopeAnnotations.joinToString { XAnnotations.toStableString(it) }
         )
       }
 
-      return ViewModelMetadata(
-        typeElement
-      )
+      return ViewModelMetadata(typeElement)
     }
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt
index 1bc2e93..29f8c3c 100644
--- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelModuleGenerator.kt
@@ -16,7 +16,9 @@
 
 package dagger.hilt.android.processor.internal.viewmodel
 
-import com.google.auto.common.GeneratedAnnotationSpecs
+import androidx.room.compiler.processing.ExperimentalProcessingApi
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.addOriginatingElement
 import com.squareup.javapoet.AnnotationSpec
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.JavaFile
@@ -24,10 +26,8 @@
 import com.squareup.javapoet.TypeSpec
 import dagger.hilt.android.processor.internal.AndroidClassNames
 import dagger.hilt.processor.internal.ClassNames
-import javax.annotation.processing.ProcessingEnvironment
-import javax.lang.model.SourceVersion
+import dagger.hilt.processor.internal.Processors
 import javax.lang.model.element.Modifier
-import javax.lang.model.util.Elements
 
 /**
  * Source generator to support Hilt injection of ViewModels.
@@ -57,49 +57,47 @@
  * }
  * ```
  */
+@OptIn(ExperimentalProcessingApi::class)
 internal class ViewModelModuleGenerator(
-  private val processingEnv: ProcessingEnvironment,
+  private val processingEnv: XProcessingEnv,
   private val injectedViewModel: ViewModelMetadata
 ) {
   fun generate() {
-    val modulesTypeSpec = TypeSpec.classBuilder(injectedViewModel.modulesClassName)
-      .addOriginatingElement(injectedViewModel.typeElement)
-      .addGeneratedAnnotation(processingEnv.elementUtils, processingEnv.sourceVersion)
-      .addAnnotation(
-        AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
-          .addMember(
-            "topLevelClass",
-            "$T.class",
-            injectedViewModel.className.topLevelClassName()
+    val modulesTypeSpec =
+      TypeSpec.classBuilder(injectedViewModel.modulesClassName)
+        .apply {
+          addOriginatingElement(injectedViewModel.typeElement)
+          Processors.addGeneratedAnnotation(this, processingEnv, ViewModelProcessor::class.java)
+          addAnnotation(
+            AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
+              .addMember(
+                "topLevelClass",
+                "$T.class",
+                injectedViewModel.className.topLevelClassName()
+              )
+              .build()
           )
-          .build()
-      )
-      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
-      .addType(getBindsModuleTypeSpec())
-      .addType(getKeyModuleTypeSpec())
-      .addMethod(
-        MethodSpec.constructorBuilder()
-          .addModifiers(Modifier.PRIVATE)
-          .build()
-      )
-      .build()
-    JavaFile.builder(injectedViewModel.modulesClassName.packageName(), modulesTypeSpec)
-      .build()
-      .writeTo(processingEnv.filer)
+          addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+          addType(getBindsModuleTypeSpec())
+          addType(getKeyModuleTypeSpec())
+          addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
+        }
+        .build()
+
+    processingEnv.filer.write(
+      JavaFile.builder(injectedViewModel.modulesClassName.packageName(), modulesTypeSpec).build()
+    )
   }
 
-  private fun getBindsModuleTypeSpec() = createModuleTypeSpec(
-    className = "BindsModule",
-    component = AndroidClassNames.VIEW_MODEL_COMPONENT
-  )
-    .addModifiers(Modifier.ABSTRACT)
-    .addMethod(
-      MethodSpec.constructorBuilder()
-        .addModifiers(Modifier.PRIVATE)
-        .build()
-    )
-    .addMethod(getViewModelBindsMethod())
-    .build()
+  private fun getBindsModuleTypeSpec() =
+    createModuleTypeSpec(
+        className = "BindsModule",
+        component = AndroidClassNames.VIEW_MODEL_COMPONENT
+      )
+      .addModifiers(Modifier.ABSTRACT)
+      .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
+      .addMethod(getViewModelBindsMethod())
+      .build()
 
   private fun getViewModelBindsMethod() =
     MethodSpec.methodBuilder("binds")
@@ -116,18 +114,15 @@
       .addParameter(injectedViewModel.className, "vm")
       .build()
 
-  private fun getKeyModuleTypeSpec() = createModuleTypeSpec(
-    className = "KeyModule",
-    component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT
-  )
-    .addModifiers(Modifier.FINAL)
-    .addMethod(
-      MethodSpec.constructorBuilder()
-        .addModifiers(Modifier.PRIVATE)
-        .build()
-    )
-    .addMethod(getViewModelKeyProvidesMethod())
-    .build()
+  private fun getKeyModuleTypeSpec() =
+    createModuleTypeSpec(
+        className = "KeyModule",
+        component = AndroidClassNames.ACTIVITY_RETAINED_COMPONENT
+      )
+      .addModifiers(Modifier.FINAL)
+      .addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build())
+      .addMethod(getViewModelKeyProvidesMethod())
+      .build()
 
   private fun getViewModelKeyProvidesMethod() =
     MethodSpec.methodBuilder("provide")
@@ -157,18 +152,5 @@
     const val N = "\$N"
     const val S = "\$S"
     const val W = "\$W"
-
-    private fun TypeSpec.Builder.addGeneratedAnnotation(
-      elements: Elements,
-      sourceVersion: SourceVersion
-    ) = apply {
-      GeneratedAnnotationSpecs.generatedAnnotationSpec(
-        elements,
-        sourceVersion,
-        ViewModelProcessor::class.java
-      ).ifPresent { generatedAnnotation ->
-        addAnnotation(generatedAnnotation)
-      }
-    }
   }
 }
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt
new file mode 100644
index 0000000..69f7ac2
--- /dev/null
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessingStep.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.processor.internal.viewmodel
+
+import androidx.room.compiler.processing.ExperimentalProcessingApi
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XProcessingEnv
+import com.google.common.collect.ImmutableSet
+import com.squareup.javapoet.ClassName
+import dagger.hilt.android.processor.internal.AndroidClassNames
+import dagger.hilt.processor.internal.BaseProcessingStep
+import dagger.internal.codegen.xprocessing.XElements
+
+@OptIn(ExperimentalProcessingApi::class)
+/** Annotation processor for @ViewModelInject. */
+class ViewModelProcessingStep(env: XProcessingEnv) : BaseProcessingStep(env) {
+  override fun annotationClassNames() = ImmutableSet.of(AndroidClassNames.HILT_VIEW_MODEL)
+
+  override fun processEach(annotation: ClassName, element: XElement) {
+    val typeElement = XElements.asTypeElement(element)
+    ViewModelMetadata.create(
+        processingEnv(),
+        typeElement,
+      )
+      ?.let { viewModelMetadata ->
+        ViewModelModuleGenerator(processingEnv(), viewModelMetadata).generate()
+      }
+  }
+}
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt
index 97ebe52..fd63e6e 100644
--- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessor.kt
@@ -16,49 +16,18 @@
 
 package dagger.hilt.android.processor.internal.viewmodel
 
-import com.google.auto.common.MoreElements
+import androidx.room.compiler.processing.ExperimentalProcessingApi
 import com.google.auto.service.AutoService
-import dagger.hilt.android.processor.internal.AndroidClassNames
-import dagger.hilt.processor.internal.BaseProcessor
+import dagger.hilt.processor.internal.BaseProcessingStep
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor
 import javax.annotation.processing.Processor
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.SourceVersion
-import javax.lang.model.element.Element
-import javax.lang.model.element.TypeElement
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType
 
-/**
- * Annotation processor for @ViewModelInject.
- */
+/** Annotation processor for @ViewModelInject. */
 @AutoService(Processor::class)
 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
-class ViewModelProcessor : BaseProcessor() {
-
-  private val parsedElements = mutableSetOf<TypeElement>()
-
-  override fun getSupportedAnnotationTypes() = setOf(
-    AndroidClassNames.HILT_VIEW_MODEL.toString()
-  )
-
-  override fun getSupportedSourceVersion() = SourceVersion.latest()
-
-  override fun processEach(annotation: TypeElement, element: Element) {
-    val typeElement = MoreElements.asType(element)
-    if (parsedElements.add(typeElement)) {
-      ViewModelMetadata.create(
-        processingEnv,
-        typeElement,
-      )?.let { viewModelMetadata ->
-        ViewModelModuleGenerator(
-          processingEnv,
-          viewModelMetadata
-        ).generate()
-      }
-    }
-  }
-
-  override fun postRoundProcess(roundEnv: RoundEnvironment?) {
-    parsedElements.clear()
-  }
+class ViewModelProcessor : JavacBaseProcessingStepProcessor() {
+  @OptIn(ExperimentalProcessingApi::class)
+  override fun processingStep(): BaseProcessingStep = ViewModelProcessingStep(xProcessingEnv)
 }
diff --git a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt
index a8e57dc..d5c3666 100644
--- a/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt
+++ b/java/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPlugin.kt
@@ -16,28 +16,25 @@
 
 package dagger.hilt.android.processor.internal.viewmodel
 
-import com.google.auto.common.MoreTypes.asElement
 import com.google.auto.service.AutoService
 import com.google.common.graph.EndpointPair
 import com.google.common.graph.ImmutableNetwork
-import com.squareup.javapoet.ClassName
 import dagger.hilt.android.processor.internal.AndroidClassNames
-import dagger.hilt.processor.internal.Processors.hasAnnotation
-import dagger.model.Binding
-import dagger.model.BindingGraph
-import dagger.model.BindingGraph.Edge
-import dagger.model.BindingGraph.Node
-import dagger.model.BindingKind
-import dagger.spi.BindingGraphPlugin
-import dagger.spi.DiagnosticReporter
+import dagger.hilt.processor.internal.asElement
+import dagger.hilt.processor.internal.getQualifiedName
+import dagger.hilt.processor.internal.hasAnnotation
+import dagger.spi.model.Binding
+import dagger.spi.model.BindingGraph
+import dagger.spi.model.BindingGraph.Edge
+import dagger.spi.model.BindingGraph.Node
+import dagger.spi.model.BindingGraphPlugin
+import dagger.spi.model.BindingKind
+import dagger.spi.model.DiagnosticReporter
 import javax.tools.Diagnostic.Kind
 
-/**
- * Plugin to validate users do not inject @HiltViewModel classes.
- */
+/** Plugin to validate users do not inject @HiltViewModel classes. */
 @AutoService(BindingGraphPlugin::class)
 class ViewModelValidationPlugin : BindingGraphPlugin {
-
   override fun visitGraph(bindingGraph: BindingGraph, diagnosticReporter: DiagnosticReporter) {
     if (bindingGraph.rootComponentNode().isSubcomponent()) {
       // This check does not work with partial graphs since it needs to take into account the source
@@ -50,9 +47,9 @@
       val pair: EndpointPair<Node> = network.incidentNodes(edge)
       val target: Node = pair.target()
       val source: Node = pair.source()
-      if (target is Binding &&
-        isHiltViewModelBinding(target) &&
-        !isInternalHiltViewModelUsage(source)
+      if (
+        target is Binding &&
+        isHiltViewModelBinding(target) && !isInternalHiltViewModelUsage(source)
       ) {
         diagnosticReporter.reportDependency(
           Kind.ERROR,
@@ -70,7 +67,7 @@
     // Make sure this is from an @Inject constructor rather than an overridden binding like an
     // @Provides and that the class is annotated with @HiltViewModel.
     return target.kind() == BindingKind.INJECTION &&
-      hasAnnotation(asElement(target.key().type()), AndroidClassNames.HILT_VIEW_MODEL)
+      target.key().type().asElement().hasAnnotation(AndroidClassNames.HILT_VIEW_MODEL)
   }
 
   private fun isInternalHiltViewModelUsage(source: Node): Boolean {
@@ -85,8 +82,8 @@
     // TODO(erichang): Should we check for even more things?
     return source is Binding &&
       source.key().qualifier().isPresent() &&
-      ClassName.get(source.key().qualifier().get().getAnnotationType()) ==
-      AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER &&
+      source.key().qualifier().get().getQualifiedName() ==
+        AndroidClassNames.HILT_VIEW_MODEL_MAP_QUALIFIER.canonicalName() &&
       source.key().multibindingContributionIdentifier().isPresent()
   }
 }
diff --git a/java/dagger/hilt/android/testing/compile/BUILD b/java/dagger/hilt/android/testing/compile/BUILD
index 4f49ca5..7d41e0d 100644
--- a/java/dagger/hilt/android/testing/compile/BUILD
+++ b/java/dagger/hilt/android/testing/compile/BUILD
@@ -20,11 +20,14 @@
     name = "compile",
     testonly = 1,
     srcs = [
+        "HiltCompilerProcessors.java",
         "HiltCompilerTests.java",
     ],
     deps = [
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib",
         "//java/dagger/hilt/android/processor/internal/customtestapplication:processor_lib",
+        "//java/dagger/hilt/processor/internal:base_processor",
+        "//java/dagger/hilt/processor/internal:hilt_processing_env_configs",
         "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib",
         "//java/dagger/hilt/processor/internal/aliasof:processor_lib",
         "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
@@ -35,10 +38,15 @@
         "//java/dagger/hilt/processor/internal/root:root_processor_lib",
         "//java/dagger/hilt/processor/internal/uninstallmodules:processor_lib",
         "//java/dagger/internal/codegen:processor",
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//java/dagger/testing/compile",
+        "//third_party/java/auto:value",
         "//third_party/java/compile_testing",
+        "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/junit",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
diff --git a/java/dagger/hilt/android/testing/compile/HiltCompilerProcessors.java b/java/dagger/hilt/android/testing/compile/HiltCompilerProcessors.java
new file mode 100644
index 0000000..cb00f52
--- /dev/null
+++ b/java/dagger/hilt/android/testing/compile/HiltCompilerProcessors.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.android.testing.compile;
+
+import androidx.room.compiler.processing.XProcessingEnv;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+import java.util.function.Function;
+
+/**
+ * A Javac and KSP processor to be used with the {@link HiltCompilerTests.hiltCompiler} to allow
+ * running custom processing steps during compilation tests.
+ */
+final class HiltCompilerProcessors {
+  /** A JavacBasicAnnotationProcessor that contains a single BaseProcessingStep. */
+  static final class JavacProcessor extends JavacBaseProcessingStepProcessor {
+    private final Function<XProcessingEnv, BaseProcessingStep> processingStep;
+
+    JavacProcessor(Function<XProcessingEnv, BaseProcessingStep> processingStep) {
+      this.processingStep = processingStep;
+    }
+
+    @Override
+    public BaseProcessingStep processingStep() {
+      return processingStep.apply(getXProcessingEnv());
+    }
+  }
+
+  /** A KSP processor that runs the given processing steps. */
+  static final class KspProcessor extends KspBaseProcessingStepProcessor {
+    private final Function<XProcessingEnv, BaseProcessingStep> processingStep;
+
+    private KspProcessor(
+        SymbolProcessorEnvironment symbolProcessorEnvironment,
+        Function<XProcessingEnv, BaseProcessingStep> processingStep) {
+      super(symbolProcessorEnvironment);
+      this.processingStep = processingStep;
+    }
+
+    @Override
+    public BaseProcessingStep processingStep() {
+      return processingStep.apply(getXProcessingEnv());
+    }
+
+    /** Provides the {@link KspComponentProcessor}. */
+    static final class Provider implements SymbolProcessorProvider {
+      private final Function<XProcessingEnv, BaseProcessingStep> processingStep;
+
+      Provider(Function<XProcessingEnv, BaseProcessingStep> processingStep) {
+        this.processingStep = processingStep;
+      }
+
+      @Override
+      public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+        return new KspProcessor(symbolProcessorEnvironment, processingStep);
+      }
+    }
+  }
+
+  private HiltCompilerProcessors() {}
+}
diff --git a/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java b/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java
index 4b8e194..8f2abd3 100644
--- a/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java
+++ b/java/dagger/hilt/android/testing/compile/HiltCompilerTests.java
@@ -16,38 +16,96 @@
 
 package dagger.hilt.android.testing.compile;
 
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static java.util.stream.Collectors.toMap;
 
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.util.CompilationResultSubject;
+import androidx.room.compiler.processing.util.ProcessorTestExtKt;
 import androidx.room.compiler.processing.util.Source;
 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments;
 import androidx.room.compiler.processing.util.compiler.TestCompilationResult;
 import androidx.room.compiler.processing.util.compiler.TestKotlinCompilerKt;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
 import com.google.testing.compile.Compiler;
 import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointProcessor;
+import dagger.hilt.android.processor.internal.androidentrypoint.KspAndroidEntryPointProcessor;
 import dagger.hilt.android.processor.internal.customtestapplication.CustomTestApplicationProcessor;
+import dagger.hilt.android.processor.internal.customtestapplication.KspCustomTestApplicationProcessor;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.HiltProcessingEnvConfigs;
 import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsProcessor;
+import dagger.hilt.processor.internal.aggregateddeps.KspAggregatedDepsProcessor;
 import dagger.hilt.processor.internal.aliasof.AliasOfProcessor;
+import dagger.hilt.processor.internal.aliasof.KspAliasOfProcessor;
 import dagger.hilt.processor.internal.definecomponent.DefineComponentProcessor;
+import dagger.hilt.processor.internal.definecomponent.KspDefineComponentProcessor;
 import dagger.hilt.processor.internal.earlyentrypoint.EarlyEntryPointProcessor;
+import dagger.hilt.processor.internal.earlyentrypoint.KspEarlyEntryPointProcessor;
 import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputProcessor;
+import dagger.hilt.processor.internal.generatesrootinput.KspGeneratesRootInputProcessor;
+import dagger.hilt.processor.internal.originatingelement.KspOriginatingElementProcessor;
 import dagger.hilt.processor.internal.originatingelement.OriginatingElementProcessor;
 import dagger.hilt.processor.internal.root.ComponentTreeDepsProcessor;
+import dagger.hilt.processor.internal.root.KspComponentTreeDepsProcessor;
+import dagger.hilt.processor.internal.root.KspRootProcessor;
 import dagger.hilt.processor.internal.root.RootProcessor;
+import dagger.hilt.processor.internal.uninstallmodules.KspUninstallModulesProcessor;
 import dagger.hilt.processor.internal.uninstallmodules.UninstallModulesProcessor;
 import dagger.internal.codegen.ComponentProcessor;
+import dagger.internal.codegen.KspComponentProcessor;
 import dagger.testing.compile.CompilerTests;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
+import java.util.function.Function;
 import javax.annotation.processing.Processor;
 import org.junit.rules.TemporaryFolder;
 
 /** {@link Compiler} instances for testing Android Hilt. */
 public final class HiltCompilerTests {
+  /** Returns the {@link XProcessingEnv.Backend} for the given {@link CompilationResultSubject}. */
+  public static XProcessingEnv.Backend backend(CompilationResultSubject subject) {
+    return CompilerTests.backend(subject);
+  }
+
+  /** Returns a {@link Source.KotlinSource} with the given file name and content. */
+  public static Source.KotlinSource kotlinSource(
+      String fileName, ImmutableCollection<String> srcLines) {
+    return CompilerTests.kotlinSource(fileName, srcLines);
+  }
+
+  /** Returns a {@link Source.KotlinSource} with the given file name and content. */
+  public static Source.KotlinSource kotlinSource(String fileName, String... srcLines) {
+    return CompilerTests.kotlinSource(fileName, srcLines);
+  }
+
+  /** Returns a {@link Source.JavaSource} with the given file name and content. */
+  public static Source.JavaSource javaSource(
+      String fileName, ImmutableCollection<String> srcLines) {
+    return CompilerTests.javaSource(fileName, srcLines);
+  }
+
+  /** Returns a {@link Source.JavaSource} with the given file name and content. */
+  public static Source.JavaSource javaSource(String fileName, String... srcLines) {
+    return CompilerTests.javaSource(fileName, srcLines);
+  }
+
+  /** Returns a {@link Compiler} instance with the given sources. */
+  public static HiltCompiler hiltCompiler(Source... sources) {
+    return hiltCompiler(ImmutableList.copyOf(sources));
+  }
+
+  /** Returns a {@link Compiler} instance with the given sources. */
+  public static HiltCompiler hiltCompiler(ImmutableCollection<Source> sources) {
+    return HiltCompiler.builder().sources(sources).build();
+  }
 
   public static Compiler compiler(Processor... extraProcessors) {
     return compiler(Arrays.asList(extraProcessors));
@@ -68,7 +126,8 @@
       List<Source> sources,
       TemporaryFolder tempFolder,
       Consumer<TestCompilationResult> onCompilationResult) {
-    compileWithKapt(sources, ImmutableMap.of(), tempFolder, onCompilationResult);
+    compileWithKapt(
+        sources, ImmutableMap.of(), ImmutableList.of(), tempFolder, onCompilationResult);
   }
 
   public static void compileWithKapt(
@@ -76,17 +135,40 @@
       Map<String, String> processorOptions,
       TemporaryFolder tempFolder,
       Consumer<TestCompilationResult> onCompilationResult) {
-    TestCompilationResult result = TestKotlinCompilerKt.compile(
-        tempFolder.getRoot(),
-        new TestCompilationArguments(
-            sources,
-            /*classpath=*/ ImmutableList.of(CompilerTests.compilerDepsJar()),
-            /*inheritClasspath=*/ false,
-            /*javacArguments=*/ ImmutableList.of(),
-            /*kotlincArguments=*/ ImmutableList.of(),
-            /*kaptProcessors=*/ defaultProcessors(),
-            /*symbolProcessorProviders=*/ ImmutableList.of(),
-            /*processorOptions=*/ processorOptions));
+    compileWithKapt(
+        sources, processorOptions, ImmutableList.of(), tempFolder, onCompilationResult);
+  }
+
+  public static void compileWithKapt(
+      List<Source> sources,
+      List<Processor> additionalProcessors,
+      TemporaryFolder tempFolder,
+      Consumer<TestCompilationResult> onCompilationResult) {
+    compileWithKapt(
+        sources, ImmutableMap.of(), additionalProcessors, tempFolder, onCompilationResult);
+  }
+
+  public static void compileWithKapt(
+      List<Source> sources,
+      Map<String, String> processorOptions,
+      List<Processor> additionalProcessors,
+      TemporaryFolder tempFolder,
+      Consumer<TestCompilationResult> onCompilationResult) {
+    TestCompilationResult result =
+        TestKotlinCompilerKt.compile(
+            tempFolder.getRoot(),
+            new TestCompilationArguments(
+                sources,
+                /* classpath= */ ImmutableList.of(CompilerTests.compilerDepsJar()),
+                /* inheritClasspath= */ false,
+                /* javacArguments= */ ImmutableList.of(),
+                /* kotlincArguments= */ ImmutableList.of(),
+                /* kaptProcessors= */ ImmutableList.<Processor>builder()
+                    .addAll(defaultProcessors())
+                    .addAll(additionalProcessors)
+                    .build(),
+                /* symbolProcessorProviders= */ ImmutableList.of(),
+                /* processorOptions= */ processorOptions));
     onCompilationResult.accept(result);
   }
 
@@ -106,5 +188,134 @@
         new UninstallModulesProcessor());
   }
 
+  private static ImmutableList<SymbolProcessorProvider> kspDefaultProcessors() {
+    // TODO(bcorso): Add the rest of the KSP processors here.
+    return ImmutableList.of(
+        new KspAndroidEntryPointProcessor.Provider(),
+        new KspAliasOfProcessor.Provider(),
+        new KspAggregatedDepsProcessor.Provider(),
+        new KspComponentProcessor.Provider(),
+        new KspComponentTreeDepsProcessor.Provider(),
+        new KspCustomTestApplicationProcessor.Provider(),
+        new KspDefineComponentProcessor.Provider(),
+        new KspEarlyEntryPointProcessor.Provider(),
+        new KspGeneratesRootInputProcessor.Provider(),
+        new KspOriginatingElementProcessor.Provider(),
+        new KspRootProcessor.Provider(),
+        new KspUninstallModulesProcessor.Provider());
+  }
+
+  /** Used to compile Hilt sources and inspect the compiled results. */
+  @AutoValue
+  public abstract static class HiltCompiler {
+    static Builder builder() {
+      return new AutoValue_HiltCompilerTests_HiltCompiler.Builder()
+          // Set the builder defaults.
+          .processorOptions(ImmutableMap.of())
+          .additionalJavacProcessors(ImmutableList.of())
+          .additionalKspProcessors(ImmutableList.of())
+          .processingSteps(ImmutableList.of())
+          .javacArguments(ImmutableList.of());
+    }
+
+    /** Returns the sources being compiled */
+    abstract ImmutableCollection<Source> sources();
+
+    /** Returns the annotation processors options. */
+    abstract ImmutableMap<String, String> processorOptions();
+
+    /** Returns the extra Javac processors. */
+    abstract ImmutableCollection<Processor> additionalJavacProcessors();
+
+    /** Returns the extra KSP processors. */
+    abstract ImmutableCollection<SymbolProcessorProvider> additionalKspProcessors();
+
+    /** Returns the command-line options */
+    abstract ImmutableCollection<String> javacArguments();
+
+    /** Returns a new {@link HiltCompiler} instance with the annotation processors options. */
+    public HiltCompiler withProcessorOptions(ImmutableMap<String, String> processorOptions) {
+      return toBuilder().processorOptions(processorOptions).build();
+    }
+
+    /** Returns the processing steps suppliers. */
+    abstract ImmutableCollection<Function<XProcessingEnv, BaseProcessingStep>> processingSteps();
+
+    public HiltCompiler withProcessingSteps(
+        Function<XProcessingEnv, BaseProcessingStep>... mapping) {
+      return toBuilder().processingSteps(ImmutableList.copyOf(mapping)).build();
+    }
+
+    /** Returns a new {@link HiltCompiler} instance with the additional Javac processors. */
+    public HiltCompiler withAdditionalJavacProcessors(Processor... processors) {
+      return toBuilder().additionalJavacProcessors(ImmutableList.copyOf(processors)).build();
+    }
+
+    /** Returns a new {@link HiltCompiler} instance with the additional KSP processors. */
+    public HiltCompiler withAdditionalKspProcessors(SymbolProcessorProvider... processors) {
+      return toBuilder().additionalKspProcessors(ImmutableList.copyOf(processors)).build();
+    }
+
+    /** Returns a new {@link HiltCompiler} instance with command-line options. */
+    public HiltCompiler withJavacArguments(String... arguments) {
+      return toBuilder().javacArguments(ImmutableList.copyOf(arguments)).build();
+    }
+
+    /** Returns a new {@link HiltCompiler} instance with command-line options. */
+    public HiltCompiler withJavacArguments(ImmutableCollection<String> arguments) {
+      return toBuilder().javacArguments(arguments).build();
+    }
+
+    /** Returns a builder with the current values of this {@link Compiler} as default. */
+    abstract Builder toBuilder();
+
+    public void compile(Consumer<CompilationResultSubject> onCompilationResult) {
+      ProcessorTestExtKt.runProcessorTest(
+          sources().asList(),
+          /* classpath= */ ImmutableList.of(CompilerTests.compilerDepsJar()),
+          /* options= */ processorOptions(),
+          /* javacArguments= */ javacArguments().asList(),
+          /* kotlincArguments= */ ImmutableList.of(
+              "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true"),
+          /* config= */ HiltProcessingEnvConfigs.CONFIGS,
+          /* javacProcessors= */ ImmutableList.<Processor>builder()
+              .addAll(defaultProcessors())
+              .addAll(additionalJavacProcessors())
+              .addAll(
+                  processingSteps().stream()
+                      .map(HiltCompilerProcessors.JavacProcessor::new)
+                      .collect(toImmutableList()))
+              .build(),
+          /* symbolProcessorProviders= */ ImmutableList.<SymbolProcessorProvider>builder()
+              .addAll(kspDefaultProcessors())
+              .addAll(additionalKspProcessors())
+              .addAll(
+                  processingSteps().stream()
+                      .map(HiltCompilerProcessors.KspProcessor.Provider::new)
+                      .collect(toImmutableList()))
+              .build(),
+          result -> {
+            onCompilationResult.accept(result);
+            return null;
+          });
+    }
+
+    /** Used to build a {@link HiltCompiler}. */
+    @AutoValue.Builder
+    public abstract static class Builder {
+      abstract Builder sources(ImmutableCollection<Source> sources);
+      abstract Builder processorOptions(ImmutableMap<String, String> processorOptions);
+      abstract Builder additionalJavacProcessors(ImmutableCollection<Processor> processors);
+      abstract Builder additionalKspProcessors(
+          ImmutableCollection<SymbolProcessorProvider> processors);
+      abstract Builder javacArguments(ImmutableCollection<String> arguments);
+
+      abstract Builder processingSteps(
+          ImmutableCollection<Function<XProcessingEnv, BaseProcessingStep>> processingSteps);
+
+      abstract HiltCompiler build();
+    }
+  }
+
   private HiltCompilerTests() {}
 }
diff --git a/java/dagger/hilt/processor/BUILD b/java/dagger/hilt/processor/BUILD
index 59f363d..ba7c9ee 100644
--- a/java/dagger/hilt/processor/BUILD
+++ b/java/dagger/hilt/processor/BUILD
@@ -58,7 +58,6 @@
     artifact_target = ":artifact-lib",
     artifact_target_libs = [
         "//java/dagger/hilt/android/processor/internal:android_classnames",
-        "//java/dagger/hilt/android/processor/internal:utils",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:android_generators",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:metadata",
         "//java/dagger/hilt/android/processor/internal/androidentrypoint:processor_lib",
@@ -70,10 +69,11 @@
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:compiler_options",
+        "//java/dagger/hilt/processor/internal:dagger_models",
         "//java/dagger/hilt/processor/internal:component_descriptor",
         "//java/dagger/hilt/processor/internal:component_names",
         "//java/dagger/hilt/processor/internal:components",
-        "//java/dagger/hilt/processor/internal:element_descriptors",
+        "//java/dagger/hilt/processor/internal:hilt_processing_env_configs",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/aggregateddeps:component_dependencies",
@@ -82,6 +82,7 @@
         "//java/dagger/hilt/processor/internal/aliasof:alias_ofs",
         "//java/dagger/hilt/processor/internal/aliasof:processor_lib",
         "//java/dagger/hilt/processor/internal/definecomponent:define_components",
+        "//java/dagger/hilt/processor/internal/definecomponent:metadatas",
         "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
         "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata",
         "//java/dagger/hilt/processor/internal/earlyentrypoint:processor_lib",
@@ -99,19 +100,18 @@
         "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
     ],
     artifact_target_maven_deps = [
-        "com.google.auto:auto-common",
         "com.google.code.findbugs:jsr305",
         "com.google.dagger:dagger-compiler",
         "com.google.dagger:dagger",
         "com.google.dagger:dagger-spi",
+        "com.google.devtools.ksp:symbol-processing-api",
         "com.google.guava:failureaccess",
         "com.google.guava:guava",
         "com.squareup:javapoet",
-        "javax.annotation:javax.annotation-api",
+        "com.squareup:kotlinpoet",
         "javax.inject:javax.inject",
         "net.ltgt.gradle.incap:incap",
         "org.jetbrains.kotlin:kotlin-stdlib",
-        "org.jetbrains.kotlinx:kotlinx-metadata-jvm",
     ],
     javadoc_android_api_level = 32,
     javadoc_root_packages = [
@@ -122,10 +122,6 @@
     javadoc_srcs = [
         "//java/dagger/hilt:hilt_processing_filegroup",
     ],
-    # The shaded deps are added using jarjar, but they won't be shaded until later
-    # due to: https://github.com/google/dagger/issues/2765. For the shaded rules see
-    # util/deploy-hilt.sh
-    shaded_deps = ["//third_party/java/auto:common"],
 )
 
 filegroup(
diff --git a/java/dagger/hilt/processor/internal/AggregatedElements.java b/java/dagger/hilt/processor/internal/AggregatedElements.java
index be593e9..db25a59 100644
--- a/java/dagger/hilt/processor/internal/AggregatedElements.java
+++ b/java/dagger/hilt/processor/internal/AggregatedElements.java
@@ -16,80 +16,66 @@
 
 package dagger.hilt.processor.internal;
 
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-import static javax.lang.model.element.Modifier.PUBLIC;
 
-import com.google.auto.common.MoreElements;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 import java.util.Optional;
-import javax.lang.model.element.PackageElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /** Utility class for aggregating metadata. */
 public final class AggregatedElements {
 
   /** Returns the class name of the proxy or {@link Optional#empty()} if a proxy is not needed. */
-  public static Optional<ClassName> aggregatedElementProxyName(TypeElement aggregatedElement) {
-    if (aggregatedElement.getModifiers().contains(PUBLIC)) {
+  public static Optional<ClassName> aggregatedElementProxyName(XTypeElement aggregatedElement) {
+    if (aggregatedElement.isPublic() && !aggregatedElement.isInternal()) {
       // Public aggregated elements do not have proxies.
       return Optional.empty();
     }
-    ClassName name = ClassName.get(aggregatedElement);
+    ClassName name = aggregatedElement.getClassName();
     // To avoid going over the class name size limit, just prepend a single character.
     return Optional.of(name.peerClass("_" + name.simpleName()));
   }
 
   /** Returns back the set of input {@code aggregatedElements} with all proxies unwrapped. */
-  public static ImmutableSet<TypeElement> unwrapProxies(
-      ImmutableSet<TypeElement> aggregatedElements, Elements elements) {
+  public static ImmutableSet<XTypeElement> unwrapProxies(
+      ImmutableCollection<XTypeElement> aggregatedElements) {
     return aggregatedElements.stream()
-        .map(aggregatedElement -> unwrapProxy(aggregatedElement, elements))
+        .map(AggregatedElements::unwrapProxy)
         .collect(toImmutableSet());
   }
 
-  private static TypeElement unwrapProxy(TypeElement element, Elements elements) {
-    return Processors.hasAnnotation(element, ClassNames.AGGREGATED_ELEMENT_PROXY)
-        ? Processors.getAnnotationClassValue(
-            elements,
-            Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_ELEMENT_PROXY),
-            "value")
+  private static XTypeElement unwrapProxy(XTypeElement element) {
+    return element.hasAnnotation(ClassNames.AGGREGATED_ELEMENT_PROXY)
+        ? XAnnotations.getAsTypeElement(
+            element.getAnnotation(ClassNames.AGGREGATED_ELEMENT_PROXY), "value")
         : element;
   }
 
   /** Returns all aggregated elements in the aggregating package after validating them. */
-  public static ImmutableSet<TypeElement> from(
-      String aggregatingPackage, ClassName aggregatingAnnotation, Elements elements) {
-    PackageElement packageElement = elements.getPackageElement(aggregatingPackage);
-
-    if (packageElement == null) {
-      return ImmutableSet.of();
-    }
-
-    ImmutableSet<TypeElement> aggregatedElements =
-        packageElement.getEnclosedElements().stream()
-            .map(MoreElements::asType)
+  public static ImmutableSet<XTypeElement> from(
+      String aggregatingPackage, ClassName aggregatingAnnotation, XProcessingEnv env) {
+    ImmutableSet<XTypeElement> aggregatedElements =
+        env.getTypeElementsFromPackage(aggregatingPackage).stream()
             // We're only interested in returning the original deps here. Proxies will be generated
             // (if needed) and swapped just before generating @ComponentTreeDeps.
-            .filter(
-                element -> !Processors.hasAnnotation(element, ClassNames.AGGREGATED_ELEMENT_PROXY))
+            .filter(element -> !element.hasAnnotation(ClassNames.AGGREGATED_ELEMENT_PROXY))
             .collect(toImmutableSet());
 
-    ProcessorErrors.checkState(
-        !aggregatedElements.isEmpty(),
-        packageElement,
-        "No dependencies found. Did you remove code in package %s?",
-        packageElement);
-
-    for (TypeElement aggregatedElement : aggregatedElements) {
+    for (XTypeElement aggregatedElement : aggregatedElements) {
       ProcessorErrors.checkState(
-          Processors.hasAnnotation(aggregatedElement, aggregatingAnnotation),
+          aggregatedElement.hasAnnotation(aggregatingAnnotation),
           aggregatedElement,
           "Expected element, %s, to be annotated with @%s, but only found: %s.",
-          aggregatedElement.getSimpleName(),
+          aggregatedElement.getName(),
           aggregatingAnnotation,
-          aggregatedElement.getAnnotationMirrors());
+          aggregatedElement.getAllAnnotations().stream()
+              .map(XAnnotations::toStableString)
+              .collect(toImmutableList()));
     }
 
     return aggregatedElements;
diff --git a/java/dagger/hilt/processor/internal/AnnotationValues.java b/java/dagger/hilt/processor/internal/AnnotationValues.java
deleted file mode 100644
index 9ebeeeb..0000000
--- a/java/dagger/hilt/processor/internal/AnnotationValues.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * Copyright (C) 2019 The Dagger Authors.
- *
- * 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 dagger.hilt.processor.internal;
-
-import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
-import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults;
-import static com.google.auto.common.MoreTypes.asTypeElement;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-
-import com.google.auto.common.MoreTypes;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import java.util.List;
-import java.util.Optional;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.AnnotationValueVisitor;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.SimpleAnnotationValueVisitor8;
-
-/** A utility class for working with {@link AnnotationValue} instances. */
-// TODO(bcorso): Update auto-common maven import so we can use it rather than this copy.
-public final class AnnotationValues {
-
-  private AnnotationValues() {}
-
-  private static class DefaultVisitor<T> extends SimpleAnnotationValueVisitor8<T, Void> {
-    final Class<T> clazz;
-
-    DefaultVisitor(Class<T> clazz) {
-      this.clazz = checkNotNull(clazz);
-    }
-
-    @Override
-    public T defaultAction(Object o, Void unused) {
-      throw new IllegalArgumentException(
-          "Expected a " + clazz.getSimpleName() + ", got instead: " + o);
-    }
-  }
-
-  private static final class TypeMirrorVisitor extends DefaultVisitor<DeclaredType> {
-    static final TypeMirrorVisitor INSTANCE = new TypeMirrorVisitor();
-
-    TypeMirrorVisitor() {
-      super(DeclaredType.class);
-    }
-
-    @Override
-    public DeclaredType visitType(TypeMirror value, Void unused) {
-      return MoreTypes.asDeclared(value);
-    }
-  }
-
-  /**
-   * Returns the value as a class.
-   *
-   * @throws IllegalArgumentException if the value is not a class.
-   */
-  public static DeclaredType getTypeMirror(AnnotationValue value) {
-    return TypeMirrorVisitor.INSTANCE.visit(value);
-  }
-
-  private static final class EnumVisitor extends DefaultVisitor<VariableElement> {
-    static final EnumVisitor INSTANCE = new EnumVisitor();
-
-    EnumVisitor() {
-      super(VariableElement.class);
-    }
-
-    @Override
-    public VariableElement visitEnumConstant(VariableElement value, Void unused) {
-      return value;
-    }
-  }
-
-  /** Returns a class array value as a set of {@link TypeElement}. */
-  public static ImmutableSet<TypeElement> getTypeElements(AnnotationValue value) {
-    return getAnnotationValues(value).stream()
-        .map(AnnotationValues::getTypeElement)
-        .collect(toImmutableSet());
-  }
-
-  /** Returns a class value as a {@link TypeElement}. */
-  public static TypeElement getTypeElement(AnnotationValue value) {
-    return asTypeElement(getTypeMirror(value));
-  }
-
-  /**
-   * Returns the value as a VariableElement.
-   *
-   * @throws IllegalArgumentException if the value is not an enum.
-   */
-  public static VariableElement getEnum(AnnotationValue value) {
-    return EnumVisitor.INSTANCE.visit(value);
-  }
-
-  /** Returns a string array value as a set of strings. */
-  public static ImmutableSet<String> getStrings(AnnotationValue value) {
-    return getAnnotationValues(value).stream()
-        .map(AnnotationValues::getString)
-        .collect(toImmutableSet());
-  }
-
-  /**
-   * Returns the value as a string.
-   *
-   * @throws IllegalArgumentException if the value is not a string.
-   */
-  public static String getString(AnnotationValue value) {
-    return valueOfType(value, String.class);
-  }
-
-  /**
-   * Returns the value as a boolean.
-   *
-   * @throws IllegalArgumentException if the value is not a boolean.
-   */
-  public static boolean getBoolean(AnnotationValue value) {
-    return valueOfType(value, Boolean.class);
-  }
-
-  private static <T> T valueOfType(AnnotationValue annotationValue, Class<T> type) {
-    Object value = annotationValue.getValue();
-    if (!type.isInstance(value)) {
-      throw new IllegalArgumentException(
-          "Expected " + type.getSimpleName() + ", got instead: " + value);
-    }
-    return type.cast(value);
-  }
-
-  /** Returns the int value of an annotation */
-  public static int getIntValue(AnnotationMirror annotation, String valueName) {
-    return (int) getAnnotationValue(annotation, valueName).getValue();
-  }
-
-  /** Returns an optional int value of an annotation if the value name is present */
-  public static Optional<Integer> getOptionalIntValue(
-      AnnotationMirror annotation, String valueName) {
-    return isValuePresent(annotation, valueName)
-        ? Optional.of(getIntValue(annotation, valueName))
-        : Optional.empty();
-  }
-
-  /** Returns the String value of an annotation */
-  public static String getStringValue(AnnotationMirror annotation, String valueName) {
-    return (String) getAnnotationValue(annotation, valueName).getValue();
-  }
-
-  /** Returns an optional String value of an annotation if the value name is present */
-  public static Optional<String> getOptionalStringValue(
-      AnnotationMirror annotation, String valueName) {
-    return isValuePresent(annotation, valueName)
-        ? Optional.of(getStringValue(annotation, valueName))
-        : Optional.empty();
-  }
-
-  /** Returns the int array value of an annotation */
-  public static int[] getIntArrayValue(AnnotationMirror annotation, String valueName) {
-    return getAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
-        .mapToInt(it -> (int) it.getValue())
-        .toArray();
-  }
-
-  /** Returns the String array value of an annotation */
-  public static String[] getStringArrayValue(AnnotationMirror annotation, String valueName) {
-    return getAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
-        .map(it -> (String) it.getValue())
-        .toArray(String[]::new);
-  }
-
-  private static boolean isValuePresent(AnnotationMirror annotation, String valueName) {
-    return getAnnotationValuesWithDefaults(annotation).keySet().stream()
-        .anyMatch(member -> member.getSimpleName().contentEquals(valueName));
-  }
-
-  /**
-   * Returns the list of values represented by an array annotation value.
-   *
-   * @throws IllegalArgumentException unless {@code annotationValue} represents an array
-   */
-  public static ImmutableList<AnnotationValue> getAnnotationValues(
-      AnnotationValue annotationValue) {
-    return annotationValue.accept(AS_ANNOTATION_VALUES, null);
-  }
-
-  private static final AnnotationValueVisitor<ImmutableList<AnnotationValue>, String>
-      AS_ANNOTATION_VALUES =
-          new SimpleAnnotationValueVisitor8<ImmutableList<AnnotationValue>, String>() {
-            @Override
-            public ImmutableList<AnnotationValue> visitArray(
-                List<? extends AnnotationValue> vals, String elementName) {
-              return ImmutableList.copyOf(vals);
-            }
-
-            @Override
-            protected ImmutableList<AnnotationValue> defaultAction(Object o, String elementName) {
-              throw new IllegalArgumentException(elementName + " is not an array: " + o);
-            }
-          };
-}
diff --git a/java/dagger/hilt/processor/internal/BUILD b/java/dagger/hilt/processor/internal/BUILD
index 2bf8c74..a2746ba 100644
--- a/java/dagger/hilt/processor/internal/BUILD
+++ b/java/dagger/hilt/processor/internal/BUILD
@@ -15,23 +15,37 @@
 # Description:
 #   Internal code for implementing Hilt processors.
 
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library")
+
 package(default_visibility = ["//:src"])
 
 java_library(
     name = "base_processor",
     srcs = [
-        "BaseProcessor.java",
+        "BaseProcessingStep.java",
+        "JavacBaseProcessingStepProcessor.java",
+        "KspBaseProcessingStepProcessor.java",
         "ProcessorErrorHandler.java",
     ],
     deps = [
         ":compiler_options",
+        ":hilt_processing_env_configs",
         ":processor_errors",
-        ":processors",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
+    ],
+)
+
+java_library(
+    name = "hilt_processing_env_configs",
+    srcs = ["HiltProcessingEnvConfigs.java"],
+    deps = [
+        "//java/dagger/internal/codegen/xprocessing",
     ],
 )
 
@@ -43,6 +57,8 @@
         "ProcessorErrors.java",
     ],
     deps = [
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:common",
         "//third_party/java/error_prone:annotations",
         "//third_party/java/guava/base",
@@ -54,7 +70,6 @@
 java_library(
     name = "processors",
     srcs = [
-        "AnnotationValues.java",
         "Processors.java",
     ],
     deps = [
@@ -62,10 +77,11 @@
         ":processor_errors",
         "//java/dagger/hilt/processor/internal/kotlin",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
         "@maven//:org_jetbrains_kotlin_kotlin_stdlib",
     ],
 )
@@ -81,16 +97,6 @@
 )
 
 java_library(
-    name = "element_descriptors",
-    srcs = [
-        "ElementDescriptors.java",
-    ],
-    deps = [
-        "//third_party/java/auto:common",
-    ],
-)
-
-java_library(
     name = "component_names",
     srcs = [
         "ComponentNames.java",
@@ -113,6 +119,7 @@
         ":processor_errors",
         ":processors",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:common",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
@@ -136,13 +143,10 @@
     ],
     deps = [
         ":classnames",
-        ":component_descriptor",
         ":processor_errors",
         ":processors",
-        "//java/dagger/hilt/processor/internal/definecomponent:define_components",
-        "//java/dagger/hilt/processor/internal/kotlin",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
@@ -156,12 +160,31 @@
         ":processor_errors",
         "//java/dagger/hilt/processor/internal/optionvalues",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
     ],
 )
 
+kt_jvm_library(
+    name = "dagger_models",
+    srcs = ["DaggerModels.kt"],
+    deps = [
+        ":processors",
+        "//:spi",
+        "//third_party/java/auto:common",
+        "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
+    ],
+)
+
+# See: https://github.com/bazelbuild/rules_kotlin/issues/324
+alias(
+    name = "libdagger_models-src.jar",
+    actual = ":dagger_models-sources.jar",
+)
+
 filegroup(
     name = "srcs_filegroup",
     srcs = glob(["*"]),
diff --git a/java/dagger/hilt/processor/internal/BadInputException.java b/java/dagger/hilt/processor/internal/BadInputException.java
index f57a34a..dc0ea0d 100644
--- a/java/dagger/hilt/processor/internal/BadInputException.java
+++ b/java/dagger/hilt/processor/internal/BadInputException.java
@@ -16,32 +16,31 @@
 
 package dagger.hilt.processor.internal;
 
+
+import androidx.room.compiler.processing.XElement;
 import com.google.common.collect.ImmutableList;
-import javax.lang.model.element.Element;
 
 /**
  * Exception to throw when input code has caused an error.
  * Includes elements to point to for the cause of the error
  */
 public final class BadInputException extends RuntimeException {
-  private final ImmutableList<Element> badElements;
+  private final ImmutableList<XElement> badElements;
 
-  public BadInputException(String message, Element badElement) {
-    super(message);
-    this.badElements = ImmutableList.of(badElement);
+  public BadInputException(String message) {
+    this(message, ImmutableList.of());
   }
 
-  public BadInputException(String message, Iterable<? extends Element> badElements) {
+  public BadInputException(String message, XElement badElement) {
+    this(message, ImmutableList.of(badElement));
+  }
+
+  public BadInputException(String message, Iterable<? extends XElement> badElements) {
     super(message);
     this.badElements = ImmutableList.copyOf(badElements);
   }
 
-  public BadInputException(String message) {
-    super(message);
-    this.badElements = ImmutableList.of();
-  }
-
-  public ImmutableList<Element> getBadElements() {
+  public ImmutableList<XElement> getBadElements() {
     return badElements;
   }
 }
diff --git a/java/dagger/hilt/processor/internal/BaseProcessingStep.java b/java/dagger/hilt/processor/internal/BaseProcessingStep.java
new file mode 100644
index 0000000..c15d5ae
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/BaseProcessingStep.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XProcessingStep;
+import androidx.room.compiler.processing.XRoundEnv;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implements default configurations for ProcessingSteps, and provides structure for exception
+ * handling.
+ *
+ * <p>In each round it will do the following:
+ *
+ * <ol>
+ *   <li>#preProcess()
+ *   <li>foreach element:
+ *       <ul>
+ *         <li>#processEach()
+ *       </ul>
+ *   <li>#postProcess()
+ * </ol>
+ *
+ * <p>#processEach() allows each element to be processed, even if exceptions are thrown. Due to the
+ * non-deterministic ordering of the processed elements, this is needed to ensure a consistent set
+ * of exceptions are thrown with each build.
+ */
+public abstract class BaseProcessingStep implements XProcessingStep {
+  private final ProcessorErrorHandler errorHandler;
+  boolean isAnnotationClassNamesOverridden = true;
+
+  private final XProcessingEnv processingEnv;
+
+  public BaseProcessingStep(XProcessingEnv env) {
+    errorHandler = new ProcessorErrorHandler(env);
+    processingEnv = env;
+  }
+
+  protected final XProcessingEnv processingEnv() {
+    return processingEnv;
+  }
+
+  @Override
+  public final ImmutableSet<String> annotations() {
+    ImmutableSet<ClassName> annotationClassNames = annotationClassNames();
+    if (!isAnnotationClassNamesOverridden) {
+      return ImmutableSet.of("*");
+    }
+    if (annotationClassNames == null || annotationClassNames.isEmpty()) {
+      throw new IllegalStateException("annotationClassNames() should return one or more elements.");
+    } else {
+      return annotationClassNames.stream().map(ClassName::canonicalName).collect(toImmutableSet());
+    }
+  }
+
+  // When this method is not implemented by users, all annotated elements will processed by this
+  // processing step.
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    isAnnotationClassNamesOverridden = false;
+    return ImmutableSet.of();
+  }
+
+  protected void processEach(ClassName annotation, XElement element) throws Exception {}
+
+  protected void preProcess(XProcessingEnv env, XRoundEnv round) {}
+
+  protected void postProcess(XProcessingEnv env, XRoundEnv round) throws Exception {}
+
+  public final void preRoundProcess(XProcessingEnv env, XRoundEnv round) {
+    preProcess(env, round);
+  }
+
+  public final void postRoundProcess(XProcessingEnv env, XRoundEnv round) {
+    if (errorHandler.isEmpty()) {
+      try {
+        postProcess(env, round);
+      } catch (Exception e) {
+        errorHandler.recordError(e);
+      }
+    }
+    if (!delayErrors() || round.isProcessingOver()) {
+      errorHandler.checkErrors();
+    }
+  }
+
+  @Override
+  public final ImmutableSet<XElement> process(
+      XProcessingEnv env,
+      Map<String, ? extends Set<? extends XElement>> elementsByAnnotation,
+      boolean isLastRound) {
+    ImmutableSet.Builder<XElement> elementsToReprocessBuilder = ImmutableSet.builder();
+    for (ClassName annotationName : annotationClassNames()) {
+      Set<? extends XElement> elements = elementsByAnnotation.get(annotationName.canonicalName());
+      if (elements != null) {
+        for (XElement element : elements) {
+          try {
+            processEach(annotationName, element);
+          } catch (Exception e) {
+            if (e instanceof ErrorTypeException && !isLastRound) {
+              // Allow an extra round to reprocess to try to resolve this type.
+              elementsToReprocessBuilder.add(element);
+            } else {
+              errorHandler.recordError(e);
+            }
+          }
+        }
+      }
+    }
+    return elementsToReprocessBuilder.build();
+  }
+
+  /**
+   * Returns true if you want to delay errors to the last round. Useful if the processor generates
+   * code for symbols used a lot in the user code. Delaying allows as much code to compile as
+   * possible for correctly configured types and reduces error spam.
+   */
+  protected boolean delayErrors() {
+    return false;
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/BaseProcessor.java b/java/dagger/hilt/processor/internal/BaseProcessor.java
deleted file mode 100644
index a921075..0000000
--- a/java/dagger/hilt/processor/internal/BaseProcessor.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright (C) 2019 The Dagger Authors.
- *
- * 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 dagger.hilt.processor.internal;
-
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.auto.common.MoreElements;
-import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.SetMultimap;
-import com.squareup.javapoet.ClassName;
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.Messager;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
-
-/**
- * Implements default configurations for Processors, and provides structure for exception handling.
- *
- * <p>By default #process() will do the following:
- *
- * <ol>
- *   <li> #preRoundProcess()
- *   <li> foreach element:
- *     <ul><li> #processEach()</ul>
- *   </li>
- *   <li> #postRoundProcess()
- *   <li> #claimAnnotation()
- * </ol>
- *
- * <p>#processEach() allows each element to be processed, even if exceptions are thrown. Due to the
- * non-deterministic ordering of the processed elements, this is needed to ensure a consistent set
- * of exceptions are thrown with each build.
- */
-public abstract class BaseProcessor extends AbstractProcessor {
-  /** Stores the state of processing for a given annotation and element. */
-  @AutoValue
-  abstract static class ProcessingState {
-    private static ProcessingState of(TypeElement annotation, Element element) {
-      // We currently only support TypeElements directly annotated with the annotation.
-      // TODO(bcorso): Switch to using BasicAnnotationProcessor if we need more than this.
-      // Note: Switching to BasicAnnotationProcessor is currently not possible because of cyclic
-      // references to generated types in our API. For example, an @AndroidEntryPoint annotated
-      // element will indefinitely defer its own processing because it extends a generated type
-      // that it's responsible for generating.
-      checkState(MoreElements.isType(element));
-      checkState(Processors.hasAnnotation(element, ClassName.get(annotation)));
-      return new AutoValue_BaseProcessor_ProcessingState(
-          ClassName.get(annotation),
-          ClassName.get(MoreElements.asType(element)));
-    }
-
-    /** Returns the class name of the annotation. */
-    abstract ClassName annotationClassName();
-
-    /** Returns the type name of the annotated element. */
-    abstract ClassName elementClassName();
-
-    /** Returns the annotation that triggered the processing. */
-    TypeElement annotation(Elements elements) {
-      return elements.getTypeElement(elementClassName().toString());
-    }
-
-    /** Returns the annotated element to process. */
-    TypeElement element(Elements elements) {
-      return elements.getTypeElement(annotationClassName().toString());
-    }
-  }
-
-  private final Set<ProcessingState> stateToReprocess = new LinkedHashSet<>();
-  private Elements elements;
-  private Types types;
-  private Messager messager;
-  private ProcessorErrorHandler errorHandler;
-
-  @Override
-  public final Set<String> getSupportedOptions() {
-    // This is declared here rather than in the actual processors because KAPT will issue a
-    // warning if any used option is not unsupported. This can happen when there is a module
-    // which uses Hilt but lacks any @AndroidEntryPoint annotations.
-    // See: https://github.com/google/dagger/issues/2040
-    return ImmutableSet.<String>builder()
-        .addAll(HiltCompilerOptions.getProcessorOptions())
-        .addAll(additionalProcessingOptions())
-        .build();
-  }
-
-  /** Returns additional processing options that should only be applied for a single processor. */
-  protected Set<String> additionalProcessingOptions() {
-    return ImmutableSet.of();
-  }
-
-  /** Used to perform initialization before each round of processing. */
-  protected void preRoundProcess(RoundEnvironment roundEnv) {};
-
-  /**
-   * Called for each element in a round that uses a supported annotation.
-   *
-   * Note that an exception can be thrown for each element in the round. This is usually preferred
-   * over throwing only the first exception in a round. Only throwing the first exception in the
-   * round can lead to flaky errors that are dependent on the non-deterministic ordering that the
-   * elements are processed in.
-   */
-  protected void processEach(TypeElement annotation, Element element) throws Exception {};
-
-  /**
-   * Used to perform post processing at the end of a round. This is especially useful for handling
-   * additional processing that depends on aggregate data, that cannot be handled in #processEach().
-   *
-   * <p>Note: this will not be called if an exception is thrown during #processEach() -- if we have
-   * already detected errors on an annotated element, performing post processing on an aggregate
-   * will just produce more (perhaps non-deterministic) errors.
-   */
-  protected void postRoundProcess(RoundEnvironment roundEnv) throws Exception {};
-
-  /** @return true if you want to claim annotations after processing each round. Default false. */
-  protected boolean claimAnnotations() {
-    return false;
-  }
-
-  /**
-   * @return true if you want to delay errors to the last round. Useful if the processor
-   * generates code for symbols used a lot in the user code. Delaying allows as much code to
-   * compile as possible for correctly configured types and reduces error spam.
-   */
-  protected boolean delayErrors() {
-    return false;
-  }
-
-
-  @Override
-  public synchronized void init(ProcessingEnvironment processingEnvironment) {
-    super.init(processingEnvironment);
-    this.messager = processingEnv.getMessager();
-    this.elements = processingEnv.getElementUtils();
-    this.types = processingEnv.getTypeUtils();
-    this.errorHandler = new ProcessorErrorHandler(processingEnvironment);
-    HiltCompilerOptions.checkWrongAndDeprecatedOptions(processingEnvironment);
-  }
-
-  @Override
-  public SourceVersion getSupportedSourceVersion() {
-    return SourceVersion.latestSupported();
-  }
-
-  /**
-   * This should not be overridden, as it defines the order of the processing.
-   */
-  @Override
-  public final boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-    preRoundProcess(roundEnv);
-
-    boolean roundError = false;
-
-    // Gather the set of new and deferred elements to process, grouped by annotation.
-    SetMultimap<TypeElement, Element> elementMultiMap = LinkedHashMultimap.create();
-    for (ProcessingState processingState : stateToReprocess) {
-      elementMultiMap.put(processingState.annotation(elements), processingState.element(elements));
-    }
-    for (TypeElement annotation : annotations) {
-      elementMultiMap.putAll(annotation, roundEnv.getElementsAnnotatedWith(annotation));
-    }
-
-    // Clear the processing state before reprocessing.
-    stateToReprocess.clear();
-
-    for (Map.Entry<TypeElement, Collection<Element>> entry : elementMultiMap.asMap().entrySet()) {
-      TypeElement annotation = entry.getKey();
-      for (Element element : entry.getValue()) {
-        try {
-          processEach(annotation, element);
-        } catch (Exception e) {
-          if (e instanceof ErrorTypeException && !roundEnv.processingOver()) {
-            // Allow an extra round to reprocess to try to resolve this type.
-            stateToReprocess.add(ProcessingState.of(annotation, element));
-          } else {
-            errorHandler.recordError(e);
-            roundError = true;
-          }
-        }
-      }
-    }
-
-    if (!roundError) {
-      try {
-        postRoundProcess(roundEnv);
-      } catch (Exception e) {
-        errorHandler.recordError(e);
-      }
-    }
-
-    if (!delayErrors() || roundEnv.processingOver()) {
-      errorHandler.checkErrors();
-    }
-
-    return claimAnnotations();
-  }
-
-  /** @return the error handle for the processor. */
-  protected final ProcessorErrorHandler getErrorHandler() {
-    return errorHandler;
-  }
-
-  public final ProcessingEnvironment getProcessingEnv() {
-    return processingEnv;
-  }
-
-  public final Elements getElementUtils() {
-    return elements;
-  }
-
-  public final Types getTypeUtils() {
-    return types;
-  }
-
-  public final Messager getMessager() {
-    return messager;
-  }
-}
diff --git a/java/dagger/hilt/processor/internal/ClassNames.java b/java/dagger/hilt/processor/internal/ClassNames.java
index 5a62715..ec2e73e 100644
--- a/java/dagger/hilt/processor/internal/ClassNames.java
+++ b/java/dagger/hilt/processor/internal/ClassNames.java
@@ -83,6 +83,7 @@
       get("dagger.multibindings", "Multibinds");
   public static final ClassName INTO_MAP = get("dagger.multibindings", "IntoMap");
   public static final ClassName INTO_SET = get("dagger.multibindings", "IntoSet");
+  public static final ClassName ELEMENTS_INTO_SET = get("dagger.multibindings", "ElementsIntoSet");
   public static final ClassName STRING_KEY = get("dagger.multibindings", "StringKey");
   public static final ClassName PROVIDES =
       get("dagger", "Provides");
diff --git a/java/dagger/hilt/processor/internal/Components.java b/java/dagger/hilt/processor/internal/Components.java
index 5ceeca0..a8650dc 100644
--- a/java/dagger/hilt/processor/internal/Components.java
+++ b/java/dagger/hilt/processor/internal/Components.java
@@ -16,63 +16,50 @@
 
 package dagger.hilt.processor.internal;
 
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
 
-import com.google.auto.common.MoreElements;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.definecomponent.DefineComponents;
-import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
+import dagger.internal.codegen.xprocessing.XElements;
 
 /** Helper methods for defining components and the component hierarchy. */
 public final class Components {
-  // TODO(bcorso): Remove this once all usages are replaced with #getComponents().
-  /**
-   * Returns the {@link ComponentDescriptor}s for a given element annotated with {@link
-   * dagger.hilt.InstallIn}.
-   */
-  public static ImmutableSet<ComponentDescriptor> getComponentDescriptors(
-      Elements elements, Element element) {
-    DefineComponents defineComponents = DefineComponents.create();
-    return getComponents(elements, element).stream()
-        .map(component -> elements.getTypeElement(component.canonicalName()))
-        // TODO(b/144939893): Memoize ComponentDescriptors so we're not recalculating.
-        .map(defineComponents::componentDescriptor)
-        .collect(toImmutableSet());
-  }
-
   /** Returns the {@link dagger.hilt.InstallIn} components for a given element. */
-  public static ImmutableSet<ClassName> getComponents(Elements elements, Element element) {
+  public static ImmutableSet<ClassName> getComponents(XElement element) {
     ImmutableSet<ClassName> components;
-    if (Processors.hasAnnotation(element, ClassNames.INSTALL_IN)
-        || Processors.hasAnnotation(element, ClassNames.TEST_INSTALL_IN)) {
-      components = getHiltInstallInComponents(elements, element);
+    if (element.hasAnnotation(ClassNames.INSTALL_IN)
+        || element.hasAnnotation(ClassNames.TEST_INSTALL_IN)) {
+      components = getHiltInstallInComponents(element);
     } else {
       // Check the enclosing element in case it passed in module is a companion object. This helps
       // in cases where the element was arrived at by checking a binding method and moving outward.
-      Element enclosing = element.getEnclosingElement();
+      XElement enclosing = element.getEnclosingElement();
       if (enclosing != null
-          && MoreElements.isType(enclosing)
-          && MoreElements.isType(element)
-          && Processors.hasAnnotation(enclosing, ClassNames.MODULE)
-          && KotlinMetadataUtils.getMetadataUtil().isCompanionObjectClass(
-              MoreElements.asType(element))) {
-        return getComponents(elements, enclosing);
+          && isTypeElement(enclosing)
+          && isTypeElement(element)
+          && enclosing.hasAnnotation(ClassNames.MODULE)
+          && asTypeElement(element).isCompanionObject()) {
+        return getComponents(enclosing);
       }
       if (Processors.hasErrorTypeAnnotation(element)) {
         throw new BadInputException(
-            "Error annotation found on element " + element + ". Look above for compilation errors",
+            String.format(
+                "Error annotation found on element %s. Look above for compilation errors",
+                XElements.toStableString(element)),
             element);
       } else {
         throw new BadInputException(
             String.format(
                 "An @InstallIn annotation is required for: %s." ,
-                element),
+                XElements.toStableString(element)),
             element);
       }
     }
@@ -87,36 +74,30 @@
     return builder.build();
   }
 
-  private static ImmutableSet<ClassName> getHiltInstallInComponents(
-      Elements elements, Element element) {
+  private static ImmutableSet<ClassName> getHiltInstallInComponents(XElement element) {
     Preconditions.checkArgument(
-        Processors.hasAnnotation(element, ClassNames.INSTALL_IN)
-            || Processors.hasAnnotation(element, ClassNames.TEST_INSTALL_IN));
+        element.hasAnnotation(ClassNames.INSTALL_IN)
+            || element.hasAnnotation(ClassNames.TEST_INSTALL_IN));
 
-    ImmutableSet<TypeElement> components =
-        ImmutableSet.copyOf(
-            Processors.hasAnnotation(element, ClassNames.INSTALL_IN)
-                ? Processors.getAnnotationClassValues(
-                    elements,
-                    Processors.getAnnotationMirror(element, ClassNames.INSTALL_IN),
-                    "value")
-                : Processors.getAnnotationClassValues(
-                    elements,
-                    Processors.getAnnotationMirror(element, ClassNames.TEST_INSTALL_IN),
-                    "components"));
+    ImmutableList<XTypeElement> components =
+        element.hasAnnotation(ClassNames.INSTALL_IN)
+            ? Processors.getAnnotationClassValues(
+                element.getAnnotation(ClassNames.INSTALL_IN), "value")
+            : Processors.getAnnotationClassValues(
+                element.getAnnotation(ClassNames.TEST_INSTALL_IN), "components");
 
-    ImmutableSet<TypeElement> undefinedComponents =
+    ImmutableSet<XTypeElement> undefinedComponents =
         components.stream()
-            .filter(component -> !Processors.hasAnnotation(component, ClassNames.DEFINE_COMPONENT))
+            .filter(component -> !component.hasAnnotation(ClassNames.DEFINE_COMPONENT))
             .collect(toImmutableSet());
 
     ProcessorErrors.checkState(
         undefinedComponents.isEmpty(),
         element,
         "@InstallIn, can only be used with @DefineComponent-annotated classes, but found: %s",
-        undefinedComponents);
+        undefinedComponents.stream().map(XElements::toStableString).collect(toImmutableList()));
 
-    return components.stream().map(ClassName::get).collect(toImmutableSet());
+    return components.stream().map(XTypeElement::getClassName).collect(toImmutableSet());
   }
 
   private Components() {}
diff --git a/java/dagger/hilt/processor/internal/DaggerModels.kt b/java/dagger/hilt/processor/internal/DaggerModels.kt
new file mode 100644
index 0000000..7d64d23
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/DaggerModels.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal
+
+import com.google.auto.common.MoreTypes
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.squareup.javapoet.ClassName
+import dagger.spi.model.DaggerAnnotation
+import dagger.spi.model.DaggerElement
+import dagger.spi.model.DaggerProcessingEnv
+import dagger.spi.model.DaggerType
+
+
+fun DaggerType.asElement(): DaggerElement =
+  when (checkNotNull(backend())) {
+    DaggerProcessingEnv.Backend.JAVAC -> {
+      val javaType = checkNotNull(java())
+      DaggerElement.fromJavac(MoreTypes.asElement(javaType))
+    }
+    DaggerProcessingEnv.Backend.KSP -> {
+      val kspType = checkNotNull(ksp())
+      DaggerElement.fromKsp(kspType.declaration)
+    }
+  }
+
+fun DaggerElement.hasAnnotation(className: ClassName) =
+  when (checkNotNull(backend())) {
+    DaggerProcessingEnv.Backend.JAVAC -> {
+      val javaElement = checkNotNull(java())
+      Processors.hasAnnotation(javaElement, className)
+    }
+    DaggerProcessingEnv.Backend.KSP -> {
+      val kspAnnotated = checkNotNull(ksp())
+      kspAnnotated.hasAnnotation(className)
+    }
+  }
+
+fun DaggerAnnotation.getQualifiedName() =
+  when (checkNotNull(backend())) {
+    DaggerProcessingEnv.Backend.JAVAC -> {
+      val javaAnnotation = checkNotNull(java())
+      MoreTypes.asTypeElement(javaAnnotation.annotationType).qualifiedName.toString()
+    }
+    DaggerProcessingEnv.Backend.KSP -> {
+      val kspAnnotation = checkNotNull(ksp())
+      kspAnnotation.annotationType.resolve().declaration.qualifiedName!!.asString()
+    }
+  }
+
+private fun KSAnnotated.hasAnnotation(className: ClassName) =
+  annotations.any {
+    it.annotationType.resolve().declaration.qualifiedName!!.asString() == className.canonicalName()
+  }
diff --git a/java/dagger/hilt/processor/internal/ElementDescriptors.java b/java/dagger/hilt/processor/internal/ElementDescriptors.java
deleted file mode 100644
index 07b2c07..0000000
--- a/java/dagger/hilt/processor/internal/ElementDescriptors.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2022 The Dagger Authors.
- *
- * 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 dagger.hilt.processor.internal;
-
-import static com.google.auto.common.MoreElements.asType;
-import static com.google.auto.common.MoreElements.isType;
-
-import java.util.stream.Collectors;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.ArrayType;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.ErrorType;
-import javax.lang.model.type.ExecutableType;
-import javax.lang.model.type.IntersectionType;
-import javax.lang.model.type.NoType;
-import javax.lang.model.type.PrimitiveType;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.type.TypeVariable;
-import javax.lang.model.type.WildcardType;
-import javax.lang.model.util.SimpleTypeVisitor8;
-
-/** Utility class for getting field and method descriptors. */
-public final class ElementDescriptors {
-  private ElementDescriptors() {}
-
-  /**
-   * Returns the field descriptor of the given {@code element}.
-   *
-   * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST.
-   *
-   * <p>For reference, see the <a
-   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2">JVM
-   * specification, section 4.3.2</a>.
-   */
-  public static String getFieldDescriptor(VariableElement element) {
-    return element.getSimpleName() + ":" + getDescriptor(element.asType());
-  }
-
-  /**
-   * Returns the method descriptor of the given {@code element}.
-   *
-   * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST.
-   *
-   * <p>For reference, see the <a
-   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3">JVM
-   * specification, section 4.3.3</a>.
-   */
-  public static String getMethodDescriptor(ExecutableElement element) {
-    return element.getSimpleName() + getDescriptor(element.asType());
-  }
-
-  private static String getDescriptor(TypeMirror t) {
-    return t.accept(JVM_DESCRIPTOR_TYPE_VISITOR, null);
-  }
-
-  private static final SimpleTypeVisitor8<String, Void> JVM_DESCRIPTOR_TYPE_VISITOR =
-      new SimpleTypeVisitor8<String, Void>() {
-
-        @Override
-        public String visitArray(ArrayType arrayType, Void v) {
-          return "[" + getDescriptor(arrayType.getComponentType());
-        }
-
-        @Override
-        public String visitDeclared(DeclaredType declaredType, Void v) {
-          return "L" + getInternalName(declaredType.asElement()) + ";";
-        }
-
-        @Override
-        public String visitError(ErrorType errorType, Void v) {
-          // For descriptor generating purposes we don't need a fully modeled type since we are
-          // only interested in obtaining the class name in its "internal form".
-          return visitDeclared(errorType, v);
-        }
-
-        @Override
-        public String visitExecutable(ExecutableType executableType, Void v) {
-          String parameterDescriptors =
-              executableType.getParameterTypes().stream()
-                  .map(ElementDescriptors::getDescriptor)
-                  .collect(Collectors.joining());
-          String returnDescriptor = getDescriptor(executableType.getReturnType());
-          return "(" + parameterDescriptors + ")" + returnDescriptor;
-        }
-
-        @Override
-        public String visitIntersection(IntersectionType intersectionType, Void v) {
-          // For a type variable with multiple bounds: "the erasure of a type variable is determined
-          // by the first type in its bound" - JVM Spec Sec 4.4
-          return getDescriptor(intersectionType.getBounds().get(0));
-        }
-
-        @Override
-        public String visitNoType(NoType noType, Void v) {
-          return "V";
-        }
-
-        @Override
-        public String visitPrimitive(PrimitiveType primitiveType, Void v) {
-          switch (primitiveType.getKind()) {
-            case BOOLEAN:
-              return "Z";
-            case BYTE:
-              return "B";
-            case SHORT:
-              return "S";
-            case INT:
-              return "I";
-            case LONG:
-              return "J";
-            case CHAR:
-              return "C";
-            case FLOAT:
-              return "F";
-            case DOUBLE:
-              return "D";
-            default:
-              throw new IllegalArgumentException("Unknown primitive type.");
-          }
-        }
-
-        @Override
-        public String visitTypeVariable(TypeVariable typeVariable, Void v) {
-          // The erasure of a type variable is the erasure of its leftmost bound. - JVM Spec Sec 4.6
-          return getDescriptor(typeVariable.getUpperBound());
-        }
-
-        @Override
-        public String defaultAction(TypeMirror typeMirror, Void v) {
-          throw new IllegalArgumentException("Unsupported type: " + typeMirror);
-        }
-
-        @Override
-        public String visitWildcard(WildcardType wildcardType, Void v) {
-          return "";
-        }
-
-        /**
-         * Returns the name of this element in its "internal form".
-         *
-         * <p>For reference, see the <a
-         * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.2">JVM
-         * specification, section 4.2</a>.
-         */
-        private String getInternalName(Element element) {
-          if (isType(element)) {
-            TypeElement typeElement = asType(element);
-            switch (typeElement.getNestingKind()) {
-              case TOP_LEVEL:
-                return typeElement.getQualifiedName().toString().replace('.', '/');
-              case MEMBER:
-                return getInternalName(typeElement.getEnclosingElement())
-                    + "$"
-                    + typeElement.getSimpleName();
-              default:
-                throw new IllegalArgumentException("Unsupported nesting kind.");
-            }
-          }
-          return element.getSimpleName().toString();
-        }
-      };
-}
diff --git a/java/dagger/hilt/processor/internal/ErrorTypeException.java b/java/dagger/hilt/processor/internal/ErrorTypeException.java
index 6f8f67e..4ba4991 100644
--- a/java/dagger/hilt/processor/internal/ErrorTypeException.java
+++ b/java/dagger/hilt/processor/internal/ErrorTypeException.java
@@ -16,7 +16,7 @@
 
 package dagger.hilt.processor.internal;
 
-import javax.lang.model.element.Element;
+import androidx.room.compiler.processing.XElement;
 
 /**
  * Exception to throw when a required {@link Element} is or inherits from an error kind.
@@ -24,14 +24,14 @@
  * <p>Includes element to point to for the cause of the error
  */
 public final class ErrorTypeException extends RuntimeException {
-  private final Element badElement;
+  private final XElement badElement;
 
-  public ErrorTypeException(String message, Element badElement) {
+  public ErrorTypeException(String message, XElement badElement) {
     super(message);
     this.badElement = badElement;
   }
 
-  public Element getBadElement() {
+  public XElement getBadElement() {
     return badElement;
   }
 }
diff --git a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java
index 4c2d158..cdb2d6c 100644
--- a/java/dagger/hilt/processor/internal/HiltCompilerOptions.java
+++ b/java/dagger/hilt/processor/internal/HiltCompilerOptions.java
@@ -16,8 +16,11 @@
 
 package dagger.hilt.processor.internal;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static com.google.common.base.Ascii.toUpperCase;
 
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableSet;
 import dagger.hilt.processor.internal.optionvalues.BooleanValue;
 import dagger.hilt.processor.internal.optionvalues.GradleProjectType;
@@ -26,8 +29,6 @@
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 import javax.tools.Diagnostic.Kind;
 
 /** Hilt annotation processor options. */
@@ -42,10 +43,10 @@
    * a generated {@code Hilt_} class. This flag is disabled by the Hilt Gradle plugin to enable
    * bytecode transformation to change the superclass.
    */
-  public static boolean isAndroidSuperclassValidationDisabled(
-      TypeElement element, ProcessingEnvironment env) {
+  public static boolean isAndroidSuperClassValidationDisabled(XTypeElement element) {
     EnumOption<BooleanValue> option = DISABLE_ANDROID_SUPERCLASS_VALIDATION;
-    return option.get(env) == BooleanValue.TRUE;
+    XProcessingEnv processorEnv = getProcessingEnv(element);
+    return option.get(processorEnv) == BooleanValue.TRUE;
   }
 
   /**
@@ -60,13 +61,13 @@
    * HiltAndroidApp} or {@code HiltAndroidTest} usages in the same compilation unit.
    */
   public static boolean isCrossCompilationRootValidationDisabled(
-      ImmutableSet<TypeElement> rootElements, ProcessingEnvironment env) {
+      ImmutableSet<XTypeElement> rootElements, XProcessingEnv env) {
     EnumOption<BooleanValue> option = DISABLE_CROSS_COMPILATION_ROOT_VALIDATION;
     return option.get(env) == BooleanValue.TRUE;
   }
 
   /** Returns {@code true} if the check for {@link dagger.hilt.InstallIn} is disabled. */
-  public static boolean isModuleInstallInCheckDisabled(ProcessingEnvironment env) {
+  public static boolean isModuleInstallInCheckDisabled(XProcessingEnv env) {
     return DISABLE_MODULES_HAVE_INSTALL_IN_CHECK.get(env) == BooleanValue.TRUE;
   }
 
@@ -78,7 +79,7 @@
    * dagger.hilt.android.testing.BindValue} or a test {@link dagger.Module}) cannot use the shared
    * component. In these cases, a component will be generated for the test.
    */
-  public static boolean isSharedTestComponentsEnabled(ProcessingEnvironment env) {
+  public static boolean isSharedTestComponentsEnabled(XProcessingEnv env) {
     return SHARE_TEST_COMPONENTS.get(env) == BooleanValue.TRUE;
   }
 
@@ -87,7 +88,7 @@
    *
    * <p>Note:This is for internal use only!
    */
-  public static boolean useAggregatingRootProcessor(ProcessingEnvironment env) {
+  public static boolean useAggregatingRootProcessor(XProcessingEnv env) {
     return USE_AGGREGATING_ROOT_PROCESSOR.get(env) == BooleanValue.TRUE;
   }
 
@@ -96,7 +97,7 @@
    *
    * <p>Note:This is for internal use only!
    */
-  public static GradleProjectType getGradleProjectType(ProcessingEnvironment env) {
+  public static GradleProjectType getGradleProjectType(XProcessingEnv env) {
     return GRADLE_PROJECT_TYPE.get(env);
   }
 
@@ -126,7 +127,7 @@
   private static final ImmutableSet<String> DEPRECATED_OPTIONS =
       ImmutableSet.of("dagger.hilt.android.useFragmentGetContextFix");
 
-  public static void checkWrongAndDeprecatedOptions(ProcessingEnvironment env) {
+  public static void checkWrongAndDeprecatedOptions(XProcessingEnv env) {
     Set<String> knownOptions = getProcessorOptions();
     for (String option : env.getOptions().keySet()) {
       if (knownOptions.contains(option)) {
@@ -173,7 +174,7 @@
       return "dagger.hilt." + name;
     }
 
-    E get(ProcessingEnvironment env) {
+    E get(XProcessingEnv env) {
       String value = env.getOptions().get(getQualifiedName());
       if (value == null) {
         return defaultValue;
diff --git a/java/dagger/hilt/processor/internal/HiltProcessingEnvConfigs.java b/java/dagger/hilt/processor/internal/HiltProcessingEnvConfigs.java
new file mode 100644
index 0000000..fa41b9a
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/HiltProcessingEnvConfigs.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal;
+
+import androidx.room.compiler.processing.XProcessingEnvConfig;
+
+/** The {@link XProcessingEnvConfig} used when processing Hilt. */
+public final class HiltProcessingEnvConfigs {
+  public static final XProcessingEnvConfig CONFIGS =
+      new XProcessingEnvConfig.Builder()
+          // In Hilt we disable the default element validation because we would otherwise run into a
+          // cycle where our Hilt processors are waiting on the "Hilt_Foo" classes to be generated
+          // before processing "Foo", but "Hilt_Foo" can't be generated until "Foo" is processed.
+          // Thus, we disable that validation here and we perform our own validation when necessary.
+          .disableAnnotatedElementValidation(true)
+          .build();
+
+  private HiltProcessingEnvConfigs() {}
+}
diff --git a/java/dagger/hilt/processor/internal/JavacBaseProcessingStepProcessor.java b/java/dagger/hilt/processor/internal/JavacBaseProcessingStepProcessor.java
new file mode 100644
index 0000000..74bb47f
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/JavacBaseProcessingStepProcessor.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal;
+
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XProcessingStep;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.javac.JavacBasicAnnotationProcessor;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+
+/** A JavacBasicAnnotationProcessor that contains a single BaseProcessingStep. */
+public abstract class JavacBaseProcessingStepProcessor extends JavacBasicAnnotationProcessor {
+  private BaseProcessingStep processingStep;
+
+  public JavacBaseProcessingStepProcessor() {
+    super(HiltProcessingEnvConfigs.CONFIGS);
+  }
+
+  @Override
+  public void initialize(XProcessingEnv env) {
+    HiltCompilerOptions.checkWrongAndDeprecatedOptions(env);
+    processingStep = processingStep();
+  }
+
+  @Override
+  public final SourceVersion getSupportedSourceVersion() {
+    return SourceVersion.latestSupported();
+  }
+
+  @Override
+  public final ImmutableSet<String> getSupportedOptions() {
+    // This is declared here rather than in the actual processors because KAPT will issue a
+    // warning if any used option is not unsupported. This can happen when there is a module
+    // which uses Hilt but lacks any @AndroidEntryPoint annotations.
+    // See: https://github.com/google/dagger/issues/2040
+    return ImmutableSet.<String>builder()
+        .addAll(HiltCompilerOptions.getProcessorOptions())
+        .addAll(additionalProcessingOptions())
+        .build();
+  }
+
+  @Override
+  public final ImmutableList<XProcessingStep> processingSteps() {
+    return ImmutableList.of(processingStep);
+  }
+
+  @Override
+  public void preRound(XProcessingEnv env, XRoundEnv round) {
+    processingStep.preRoundProcess(env, round);
+  }
+
+  protected abstract BaseProcessingStep processingStep();
+
+  @Override
+  public void postRound(XProcessingEnv env, XRoundEnv round) {
+    processingStep.postRoundProcess(env, round);
+  }
+
+  /** Returns additional processing options that should only be applied for a single processor. */
+  protected Set<String> additionalProcessingOptions() {
+    return ImmutableSet.of();
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/KspBaseProcessingStepProcessor.java b/java/dagger/hilt/processor/internal/KspBaseProcessingStepProcessor.java
new file mode 100644
index 0000000..df9546b
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/KspBaseProcessingStepProcessor.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal;
+
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XProcessingStep;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+
+/** A KspBasicAnnotationProcessor that contains a single BaseProcessingStep. */
+public abstract class KspBaseProcessingStepProcessor extends KspBasicAnnotationProcessor {
+  private BaseProcessingStep processingStep;
+
+  public KspBaseProcessingStepProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment, HiltProcessingEnvConfigs.CONFIGS);
+  }
+
+  @Override
+  public void initialize(XProcessingEnv env) {
+    HiltCompilerOptions.checkWrongAndDeprecatedOptions(env);
+    processingStep = processingStep();
+  }
+
+  protected abstract BaseProcessingStep processingStep();
+
+  @Override
+  public void preRound(XProcessingEnv env, XRoundEnv round) {
+    processingStep.preRoundProcess(env, round);
+  }
+
+  @Override
+  public final ImmutableList<XProcessingStep> processingSteps() {
+    return ImmutableList.of(processingStep);
+  }
+
+  @Override
+  public void postRound(XProcessingEnv env, XRoundEnv round) {
+    processingStep.postRoundProcess(env, round);
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java b/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java
index 2ecc0f0..87413fb 100644
--- a/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java
+++ b/java/dagger/hilt/processor/internal/ProcessorErrorHandler.java
@@ -16,22 +16,23 @@
 
 package dagger.hilt.processor.internal;
 
-import com.google.auto.common.MoreElements;
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XMessager;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Throwables;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-import javax.annotation.processing.Messager;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
-import javax.lang.model.util.Elements;
 import javax.tools.Diagnostic.Kind;
 
 /** Utility class to handle keeping track of errors during processing. */
 final class ProcessorErrorHandler {
 
-  private static final String FAILURE_PREFIX = "[Hilt]\n";
+  private static final String FAILURE_PREFIX = "[Hilt] ";
 
   // Special characters to make the tag red and bold to draw attention since
   // this error can get drowned out by other errors resulting from missing
@@ -39,14 +40,13 @@
   private static final String FAILURE_SUFFIX =
       "\n\033[1;31m[Hilt] Processing did not complete. See error above for details.\033[0m";
 
-  private final Messager messager;
-  private final Elements elements;
-  private final List<HiltError> hiltErrors;
+  private final XProcessingEnv processingEnv;
+  private final XMessager messager;
+  private final List<HiltError> hiltErrors = new ArrayList<>();
 
-  ProcessorErrorHandler(ProcessingEnvironment env) {
-    this.messager = env.getMessager();
-    this.elements = env.getElementUtils();
-    this.hiltErrors = new ArrayList<>();
+  ProcessorErrorHandler(XProcessingEnv processingEnv) {
+    this.processingEnv = processingEnv;
+    this.messager = processingEnv.getMessager();
   }
 
   /**
@@ -65,7 +65,7 @@
       if (badInput.getBadElements().isEmpty()) {
         hiltErrors.add(HiltError.of(badInput.getMessage()));
       }
-      for (Element element : badInput.getBadElements()) {
+      for (XElement element : badInput.getBadElements()) {
         hiltErrors.add(HiltError.of(badInput.getMessage(), element));
       }
     } else if (t instanceof ErrorTypeException) {
@@ -84,16 +84,15 @@
       hiltErrors.forEach(
           hiltError -> {
             if (hiltError.element().isPresent()) {
-              Element element = hiltError.element().get();
-              if (MoreElements.isType(element)) {
+              XElement element = hiltError.element().get();
+              if (isTypeElement(element)) {
                 // If the error type is a TypeElement, get a new one just in case it was thrown in a
                 // previous round we can report the correct instance. Otherwise, this leads to
                 // issues in AndroidStudio when linking an error to the proper element.
                 // TODO(bcorso): Consider only allowing TypeElement errors when delaying errors,
                 // or maybe even removing delayed errors altogether.
                 element =
-                    elements.getTypeElement(
-                        MoreElements.asType(element).getQualifiedName().toString());
+                    processingEnv.requireTypeElement(asTypeElement(element).getQualifiedName());
               }
               messager.printMessage(Kind.ERROR, hiltError.message(), element);
             } else {
@@ -104,23 +103,27 @@
     }
   }
 
+  public boolean isEmpty() {
+    return hiltErrors.isEmpty();
+  }
+
   @AutoValue
   abstract static class HiltError {
     static HiltError of(String message) {
       return of(message, Optional.empty());
     }
 
-    static HiltError of(String message, Element element) {
+    static HiltError of(String message, XElement element) {
       return of(message, Optional.of(element));
     }
 
-    private static HiltError of(String message, Optional<Element> element) {
+    private static HiltError of(String message, Optional<XElement> element) {
       return new AutoValue_ProcessorErrorHandler_HiltError(
           FAILURE_PREFIX + message + FAILURE_SUFFIX, element);
     }
 
     abstract String message();
 
-    abstract Optional<Element> element();
+    abstract Optional<XElement> element();
   }
 }
diff --git a/java/dagger/hilt/processor/internal/ProcessorErrors.java b/java/dagger/hilt/processor/internal/ProcessorErrors.java
index d75bb62..db1e979 100644
--- a/java/dagger/hilt/processor/internal/ProcessorErrors.java
+++ b/java/dagger/hilt/processor/internal/ProcessorErrors.java
@@ -16,18 +16,13 @@
 
 package dagger.hilt.processor.internal;
 
-import com.google.auto.common.MoreTypes;
+
+import androidx.room.compiler.processing.XElement;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
 import com.google.errorprone.annotations.FormatMethod;
 import com.google.errorprone.annotations.FormatString;
 import java.util.Collection;
-import java.util.stream.Collectors;
 import javax.annotation.Nullable;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
 
 /** Static helper methods for throwing errors during code generation. */
 public final class ProcessorErrors {
@@ -40,9 +35,7 @@
    *     string using {@link String#valueOf(Object)}
    * @throws BadInputException if {@code expression} is false
    */
-  public static void checkState(
-      boolean expression,
-      @Nullable Object errorMessage) {
+  public static void checkState(boolean expression, @Nullable Object errorMessage) {
     if (!expression) {
       throw new BadInputException(String.valueOf(errorMessage));
     }
@@ -85,9 +78,7 @@
    * @throws BadInputException if {@code expression} is false
    */
   public static void checkState(
-      boolean expression,
-      Element badElement,
-      @Nullable Object errorMessage) {
+      boolean expression, XElement badElement, @Nullable Object errorMessage) {
     Preconditions.checkNotNull(badElement);
     if (!expression) {
       throw new BadInputException(String.valueOf(errorMessage), badElement);
@@ -116,7 +107,7 @@
   @FormatMethod
   public static void checkState(
       boolean expression,
-      Element badElement,
+      XElement badElement,
       @Nullable @FormatString String errorMessageTemplate,
       @Nullable Object... errorMessageArgs) {
     Preconditions.checkNotNull(badElement);
@@ -131,27 +122,6 @@
    * involving any parameters to the calling method.
    *
    * @param expression a boolean expression
-   * @param badElements the element that were at fault
-   * @param errorMessage the exception message to use if the check fails; will be converted to a
-   *     string using {@link String#valueOf(Object)}
-   * @throws BadInputException if {@code expression} is false
-   */
-  public static void checkState(
-      boolean expression,
-      Collection<? extends Element> badElements,
-      @Nullable Object errorMessage) {
-    Preconditions.checkNotNull(badElements);
-    if (!expression) {
-      Preconditions.checkState(!badElements.isEmpty());
-      throw new BadInputException(String.valueOf(errorMessage), badElements);
-    }
-  }
-
-  /**
-   * Ensures the truth of an expression involving the state of the calling instance, but not
-   * involving any parameters to the calling method.
-   *
-   * @param expression a boolean expression
    * @param badElements the elements that were at fault
    * @param errorMessageTemplate a template for the exception message should the check fail. The
    *     message is formed by replacing each {@code %s} placeholder in the template with an
@@ -164,10 +134,12 @@
    * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or
    *     {@code errorMessageArgs} is null (don't let this happen)
    */
+  // TODO(bcorso): Rename this checkState once the javac API is removed (overloading doesn't work
+  // here since they have the same erasured signature).
   @FormatMethod
-  public static void checkState(
+  public static void checkStateX(
       boolean expression,
-      Collection<? extends Element> badElements,
+      Collection<? extends XElement> badElements,
       @Nullable @FormatString String errorMessageTemplate,
       @Nullable Object... errorMessageArgs) {
     Preconditions.checkNotNull(badElements);
@@ -178,30 +150,5 @@
     }
   }
 
-  /**
-   * Ensures that the given element is not an error kind and does not inherit from an error kind.
-   *
-   * @param element the element to check
-   * @throws ErrorTypeException if {@code element} inherits from an error kind.
-   */
-  public static void checkNotErrorKind(TypeElement element) {
-    TypeMirror currType = element.asType();
-    ImmutableList.Builder<String> typeHierarchy = ImmutableList.builder();
-    while (currType.getKind() != TypeKind.NONE) {
-      typeHierarchy.add(currType.toString());
-      if (currType.getKind() == TypeKind.ERROR) {
-        throw new ErrorTypeException(
-            String.format(
-                "%s, type hierarchy contains error kind, %s."
-                + "\n\tThe partially resolved hierarchy is:\n\t\t%s",
-                element,
-                currType,
-                typeHierarchy.build().stream().collect(Collectors.joining(" -> "))),
-            element);
-      }
-      currType = MoreTypes.asTypeElement(currType).getSuperclass();
-    }
-  }
-
   private ProcessorErrors() {}
 }
diff --git a/java/dagger/hilt/processor/internal/Processors.java b/java/dagger/hilt/processor/internal/Processors.java
index 3fdb8c0..c843821 100644
--- a/java/dagger/hilt/processor/internal/Processors.java
+++ b/java/dagger/hilt/processor/internal/Processors.java
@@ -16,72 +16,45 @@
 
 package dagger.hilt.processor.internal;
 
-import static com.google.auto.common.MoreElements.asPackage;
-import static com.google.auto.common.MoreElements.asType;
-import static com.google.auto.common.MoreElements.asVariable;
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils.getMetadataUtil;
 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
-import static javax.lang.model.element.Modifier.ABSTRACT;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static javax.lang.model.element.Modifier.PUBLIC;
-import static javax.lang.model.element.Modifier.STATIC;
 
-import com.google.auto.common.AnnotationMirrors;
-import com.google.auto.common.GeneratedAnnotations;
-import com.google.auto.common.MoreElements;
-import com.google.auto.common.MoreTypes;
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XAnnotationValue;
+import androidx.room.compiler.processing.XConstructorElement;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XExecutableElement;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XHasModifiers;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.base.CaseFormat;
-import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.SetMultimap;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
-import com.squareup.javapoet.ParameterSpec;
 import com.squareup.javapoet.ParameterizedTypeName;
 import com.squareup.javapoet.TypeName;
 import com.squareup.javapoet.TypeSpec;
-import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtil;
-import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils;
-import dagger.internal.codegen.extension.DaggerStreams;
-import java.io.IOException;
-import java.lang.annotation.Annotation;
-import java.util.LinkedHashSet;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
+import dagger.internal.codegen.xprocessing.XTypes;
 import java.util.List;
-import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.annotation.processing.RoundEnvironment;
 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.ArrayType;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.ErrorType;
-import javax.lang.model.type.PrimitiveType;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementFilter;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.SimpleAnnotationValueVisitor7;
-import javax.lang.model.util.SimpleTypeVisitor7;
 
 /** Static helper methods for writing a processor. */
 public final class Processors {
@@ -90,386 +63,112 @@
 
   public static final String STATIC_INITIALIZER_NAME = "<clinit>";
 
-  private static final String JAVA_CLASS = "java.lang.Class";
-
+  /** Generates the aggregating metadata class for an aggregating annotation. */
   public static void generateAggregatingClass(
       String aggregatingPackage,
       AnnotationSpec aggregatingAnnotation,
-      TypeElement element,
-      Class<?> generatedAnnotationClass,
-      ProcessingEnvironment env) throws IOException {
-    ClassName name = ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(element));
+      XTypeElement originatingElement,
+      Class<?> generatorClass) {
+    generateAggregatingClass(
+        aggregatingPackage,
+        aggregatingAnnotation,
+        originatingElement,
+        generatorClass,
+        Mode.Isolating);
+  }
+
+  /** Generates the aggregating metadata class for an aggregating annotation. */
+  public static void generateAggregatingClass(
+      String aggregatingPackage,
+      AnnotationSpec aggregatingAnnotation,
+      XTypeElement originatingElement,
+      Class<?> generatorClass,
+      Mode mode) {
+    ClassName name =
+        ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(originatingElement));
+    XProcessingEnv env = getProcessingEnv(originatingElement);
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(name)
             .addModifiers(PUBLIC)
-            .addOriginatingElement(element)
             .addAnnotation(aggregatingAnnotation)
             .addJavadoc("This class should only be referenced by generated code! ")
-            .addJavadoc("This class aggregates information across multiple compilations.\n");;
+            .addJavadoc("This class aggregates information across multiple compilations.\n");
+    JavaPoetExtKt.addOriginatingElement(builder, originatingElement);
+    addGeneratedAnnotation(builder, env, generatorClass);
 
-    addGeneratedAnnotation(builder, env, generatedAnnotationClass);
-
-    JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler());
+    env.getFiler().write(JavaFile.builder(name.packageName(), builder.build()).build(), mode);
   }
 
-  /** Returns a map from {@link AnnotationMirror} attribute name to {@link AnnotationValue}s */
-  public static ImmutableMap<String, AnnotationValue> getAnnotationValues(Elements elements,
-      AnnotationMirror annotation) {
-    ImmutableMap.Builder<String, AnnotationValue> annotationMembers = ImmutableMap.builder();
-    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
-        : elements.getElementValuesWithDefaults(annotation).entrySet()) {
-      annotationMembers.put(e.getKey().getSimpleName().toString(), e.getValue());
+  /** Returns a map from {@link XAnnotation} attribute name to {@link XAnnotationValue}s */
+  public static ImmutableMap<String, XAnnotationValue> getAnnotationValues(XAnnotation annotation) {
+    ImmutableMap.Builder<String, XAnnotationValue> annotationMembers = ImmutableMap.builder();
+    for (XAnnotationValue value : annotation.getAnnotationValues()) {
+      annotationMembers.put(value.getName(), value);
     }
     return annotationMembers.build();
   }
 
-  /**
-   * Returns a multimap from attribute name to the values that are an array of annotation mirrors.
-   * The returned map will not contain mappings for any attributes that are not Annotation Arrays.
-   *
-   * <p>e.g. if the input was the annotation mirror for
-   * <pre>
-   *   {@literal @}Foo({{@literal @}Bar("hello"), {@literal @}Bar("world")})
-   * </pre>
-   * the map returned would have "value" map to a set containing the two @Bar annotation mirrors.
-   */
-  public static Multimap<String, AnnotationMirror> getAnnotationAnnotationArrayValues(
-      Elements elements, AnnotationMirror annotation) {
-    SetMultimap<String, AnnotationMirror> annotationMembers = LinkedHashMultimap.create();
-    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
-        : elements.getElementValuesWithDefaults(annotation).entrySet()) {
-      String attribute = e.getKey().getSimpleName().toString();
-      Set<AnnotationMirror> annotationMirrors = new LinkedHashSet<>();
-      e.getValue().accept(new AnnotationMirrorAnnotationValueVisitor(), annotationMirrors);
-      annotationMembers.putAll(attribute, annotationMirrors);
-    }
-    return annotationMembers;
-  }
-
-  private static final class AnnotationMirrorAnnotationValueVisitor
-      extends SimpleAnnotationValueVisitor7<Void, Set<AnnotationMirror>> {
-
-    @Override
-    public Void visitArray(List<? extends AnnotationValue> vals, Set<AnnotationMirror> types) {
-      for (AnnotationValue val : vals) {
-        val.accept(this, types);
-      }
-      return null;
-    }
-
-    @Override
-    public Void visitAnnotation(AnnotationMirror a, Set<AnnotationMirror> annotationMirrors) {
-      annotationMirrors.add(a);
-      return null;
-    }
-  }
-
-  /** Returns the {@link TypeElement} for a class attribute on an annotation. */
-  public static TypeElement getAnnotationClassValue(
-      Elements elements, AnnotationMirror annotation, String key) {
-    return Iterables.getOnlyElement(getAnnotationClassValues(elements, annotation, key));
-  }
-
-  /** Returns a list of {@link TypeElement}s for a class attribute on an annotation. */
-  public static ImmutableList<TypeElement> getAnnotationClassValues(
-      Elements elements, AnnotationMirror annotation, String key) {
-    ImmutableList<TypeElement> values = getOptionalAnnotationClassValues(elements, annotation, key);
+  /** Returns a list of {@link XTypeElement}s for a class attribute on an annotation. */
+  public static ImmutableList<XTypeElement> getAnnotationClassValues(
+      XAnnotation annotation, String key) {
+    ImmutableList<XTypeElement> values = XAnnotations.getAsTypeElementList(annotation, key);
 
     ProcessorErrors.checkState(
         values.size() >= 1,
-        // TODO(b/152801981): Point to the annotation value rather than the annotated element.
-        annotation.getAnnotationType().asElement(),
+        annotation.getTypeElement(),
         "@%s, '%s' class is invalid or missing: %s",
-        annotation.getAnnotationType().asElement().getSimpleName(),
+        annotation.getName(),
         key,
-        annotation);
+        XAnnotations.toStableString(annotation));
 
     return values;
   }
 
-  /** Returns a multimap from attribute name to elements for class valued attributes. */
-  private static Multimap<String, DeclaredType> getAnnotationClassValues(
-      Elements elements, AnnotationMirror annotation) {
-    Element javaClass = elements.getTypeElement(JAVA_CLASS);
-    SetMultimap<String, DeclaredType> annotationMembers = LinkedHashMultimap.create();
-    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e :
-        elements.getElementValuesWithDefaults(annotation).entrySet()) {
-      Optional<DeclaredType> returnType = getOptionalDeclaredType(e.getKey().getReturnType());
-      if (returnType.isPresent() && returnType.get().asElement().equals(javaClass)) {
-        String attribute = e.getKey().getSimpleName().toString();
-        Set<DeclaredType> declaredTypes = new LinkedHashSet<DeclaredType>();
-        e.getValue().accept(new DeclaredTypeAnnotationValueVisitor(), declaredTypes);
-        annotationMembers.putAll(attribute, declaredTypes);
-      }
-    }
-    return annotationMembers;
-  }
-
-  /** Returns an optional {@link TypeElement} for a class attribute on an annotation. */
-  public static Optional<TypeElement> getOptionalAnnotationClassValue(
-      Elements elements, AnnotationMirror annotation, String key) {
-    return getAnnotationClassValues(elements, annotation).get(key).stream()
-        .map(MoreTypes::asTypeElement)
-        .collect(toOptional());
-  }
-
-  /** Returns a list of {@link TypeElement}s for a class attribute on an annotation. */
-  public static ImmutableList<TypeElement> getOptionalAnnotationClassValues(
-      Elements elements, AnnotationMirror annotation, String key) {
-    return ImmutableList.copyOf(
-        getAnnotationClassValues(elements, annotation).get(key).stream()
-            .map(MoreTypes::asTypeElement)
-            .collect(Collectors.toList()));
-  }
-
-  private static final class DeclaredTypeAnnotationValueVisitor
-      extends SimpleAnnotationValueVisitor7<Void, Set<DeclaredType>> {
-
-    @Override public Void visitArray(
-        List<? extends AnnotationValue> vals, Set<DeclaredType> types) {
-      for (AnnotationValue val : vals) {
-        val.accept(this, types);
-      }
-      return null;
-    }
-
-    @Override public Void visitType(TypeMirror t, Set<DeclaredType> types) {
-      DeclaredType declared = MoreTypes.asDeclared(t);
-      checkNotNull(declared);
-      types.add(declared);
-      return null;
-    }
-  }
-
-  /**
-   * If the received mirror represents a primitive type or an array of primitive types, this returns
-   * the represented primitive type. Otherwise throws an IllegalStateException.
-   */
-  public static PrimitiveType getPrimitiveType(TypeMirror type) {
-    return type.accept(
-        new SimpleTypeVisitor7<PrimitiveType, Void> () {
-          @Override public PrimitiveType visitArray(ArrayType type, Void unused) {
-            return getPrimitiveType(type.getComponentType());
-          }
-
-          @Override public PrimitiveType visitPrimitive(PrimitiveType type, Void unused) {
-            return type;
-          }
-
-          @Override public PrimitiveType defaultAction(TypeMirror type, Void unused) {
-            throw new IllegalStateException("Unhandled type: " + type);
-          }
-        }, null /* the Void accumulator */);
-  }
-
-  /**
-   * Returns an {@link Optional#of} the declared type if the received mirror represents a declared
-   * type or an array of declared types, otherwise returns {@link Optional#empty}.
-   */
-  public static Optional<DeclaredType> getOptionalDeclaredType(TypeMirror type) {
-    return Optional.ofNullable(
-        type.accept(
-            new SimpleTypeVisitor7<DeclaredType, Void>(null /* defaultValue */) {
-              @Override
-              public DeclaredType visitArray(ArrayType type, Void unused) {
-                return MoreTypes.asDeclared(type.getComponentType());
+  public static ImmutableList<XTypeElement> getOptionalAnnotationClassValues(
+      XAnnotation annotation, String key) {
+    return getOptionalAnnotationValues(annotation, key).stream()
+        .filter(XAnnotationValue::hasTypeValue)
+        .map(
+            annotationValue -> {
+              try {
+                return annotationValue.asType();
+              } catch (TypeNotPresentException e) {
+                // TODO(b/277367118): we may need a way to ignore error types in XProcessing.
+                // TODO(b/278560196): we should throw ErrorTypeException and clean up broken tests.
+                return null;
               }
-
-              @Override
-              public DeclaredType visitDeclared(DeclaredType type, Void unused) {
-                return type;
-              }
-
-              @Override
-              public DeclaredType visitError(ErrorType type, Void unused) {
-                return type;
-              }
-            },
-            null /* the Void accumulator */));
+            })
+        .filter(Objects::nonNull)
+        .map(XType::getTypeElement)
+        .collect(toImmutableList());
   }
 
-  /**
-   * Returns the declared type if the received mirror represents a declared type or an array of
-   * declared types, otherwise throws an {@link IllegalStateException}.
-   */
-  public static DeclaredType getDeclaredType(TypeMirror type) {
-    return getOptionalDeclaredType(type)
-        .orElseThrow(() -> new IllegalStateException("Not a declared type: " + type));
+  private static ImmutableList<XAnnotationValue> getOptionalAnnotationValues(
+      XAnnotation annotation, String key) {
+    return annotation.getAnnotationValues().stream()
+        .filter(annotationValue -> annotationValue.getName().equals(key))
+        .collect(toOptional())
+        .map(
+            annotationValue ->
+                (annotationValue.hasListValue()
+                    ? ImmutableList.copyOf(annotationValue.asAnnotationValueList())
+                    : ImmutableList.of(annotationValue)))
+        .orElse(ImmutableList.of());
   }
 
-  /** Gets the values from an annotation value representing a string array. */
-  public static ImmutableList<String> getStringArrayAnnotationValue(AnnotationValue value) {
-    return value.accept(new SimpleAnnotationValueVisitor7<ImmutableList<String>, Void>() {
-      @Override
-      public ImmutableList<String> defaultAction(Object o, Void unused) {
-        throw new IllegalStateException("Expected an array, got instead: " + o);
-      }
-
-      @Override
-      public ImmutableList<String> visitArray(List<? extends AnnotationValue> values,
-          Void unused) {
-        ImmutableList.Builder<String> builder = ImmutableList.builder();
-        for (AnnotationValue value : values) {
-          builder.add(getStringAnnotationValue(value));
-        }
-        return builder.build();
-      }
-    }, /* unused accumulator */ null);
-  }
-
-  /** Gets the values from an annotation value representing an int. */
-  public static Boolean getBooleanAnnotationValue(AnnotationValue value) {
-    return value.accept(
-        new SimpleAnnotationValueVisitor7<Boolean, Void>() {
-          @Override
-          public Boolean defaultAction(Object o, Void unused) {
-            throw new IllegalStateException("Expected a boolean, got instead: " + o);
-          }
-
-          @Override
-          public Boolean visitBoolean(boolean value, Void unused) {
-            return value;
-          }
-        }, /* unused accumulator */
-        null);
-  }
-
-  /** Gets the values from an annotation value representing an int. */
-  public static Integer getIntAnnotationValue(AnnotationValue value) {
-    return value.accept(new SimpleAnnotationValueVisitor7<Integer, Void>() {
-      @Override
-      public Integer defaultAction(Object o, Void unused) {
-        throw new IllegalStateException("Expected an int, got instead: " + o);
-      }
-
-      @Override
-      public Integer visitInt(int value, Void unused) {
-        return value;
-      }
-    }, /* unused accumulator */ null);
-  }
-
-  /** Gets the values from an annotation value representing a long. */
-  public static Long getLongAnnotationValue(AnnotationValue value) {
-    return value.accept(
-        new SimpleAnnotationValueVisitor7<Long, Void>() {
-          @Override
-          public Long defaultAction(Object o, Void unused) {
-            throw new IllegalStateException("Expected an int, got instead: " + o);
-          }
-
-          @Override
-          public Long visitLong(long value, Void unused) {
-            return value;
-          }
-        },
-        null /* unused accumulator */);
-  }
-
-  /** Gets the values from an annotation value representing a string. */
-  public static String getStringAnnotationValue(AnnotationValue value) {
-    return value.accept(new SimpleAnnotationValueVisitor7<String, Void>() {
-      @Override
-      public String defaultAction(Object o, Void unused) {
-        throw new IllegalStateException("Expected a string, got instead: " + o);
-      }
-
-      @Override
-      public String visitString(String value, Void unused) {
-        return value;
-      }
-    }, /* unused accumulator */ null);
-  }
-
-  /** Gets the values from an annotation value representing a DeclaredType. */
-  public static DeclaredType getDeclaredTypeAnnotationValue(AnnotationValue value) {
-    return value.accept(
-        new SimpleAnnotationValueVisitor7<DeclaredType, Void>() {
-          @Override
-          public DeclaredType defaultAction(Object o, Void unused) {
-            throw new IllegalStateException("Expected a TypeMirror, got instead: " + o);
-          }
-
-          @Override
-          public DeclaredType visitType(TypeMirror typeMirror, Void unused) {
-            return MoreTypes.asDeclared(typeMirror);
-          }
-        }, /* unused accumulator */
-        null);
-  }
-
-  private static final SimpleAnnotationValueVisitor7<ImmutableSet<VariableElement>, Void>
-      ENUM_ANNOTATION_VALUE_VISITOR =
-          new SimpleAnnotationValueVisitor7<ImmutableSet<VariableElement>, Void>() {
-            @Override
-            public ImmutableSet<VariableElement> defaultAction(Object o, Void unused) {
-              throw new IllegalStateException(
-                  "Expected an Enum or an Enum array, got instead: " + o);
-            }
-
-            @Override
-            public ImmutableSet<VariableElement> visitArray(
-                List<? extends AnnotationValue> values, Void unused) {
-              ImmutableSet.Builder<VariableElement> builder = ImmutableSet.builder();
-              for (AnnotationValue value : values) {
-                builder.addAll(value.accept(this, null));
-              }
-              return builder.build();
-            }
-
-            @Override
-            public ImmutableSet<VariableElement> visitEnumConstant(
-                VariableElement value, Void unused) {
-              return ImmutableSet.of(value);
-            }
-          };
-
-  /** Gets the values from an annotation value representing a Enum array. */
-  public static ImmutableSet<VariableElement> getEnumArrayAnnotationValue(AnnotationValue value) {
-    return value.accept(ENUM_ANNOTATION_VALUE_VISITOR, /* unused accumulator */ null);
-  }
-
-  /** Converts an annotation value map to be keyed by the attribute name. */
-  public static ImmutableMap<String, AnnotationValue> convertToAttributeNameMap(
-      Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValues) {
-    ImmutableMap.Builder<String, AnnotationValue> builder = ImmutableMap.builder();
-    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
-        : annotationValues.entrySet()) {
-      String attribute = e.getKey().getSimpleName().toString();
-      builder.put(attribute, e.getValue());
-    }
-    return builder.build();
-  }
-
-  /** Returns the given elements containing package element. */
-  public static PackageElement getPackageElement(Element originalElement) {
+  public static XTypeElement getTopLevelType(XElement originalElement) {
     checkNotNull(originalElement);
-    for (Element e = originalElement; e != null; e = e.getEnclosingElement()) {
-      if (e instanceof PackageElement) {
-        return (PackageElement) e;
-      }
-    }
-    throw new IllegalStateException("Cannot find a package for " + originalElement);
-  }
-
-  public static TypeElement getTopLevelType(Element originalElement) {
-    checkNotNull(originalElement);
-    for (Element e = originalElement; e != null; e = e.getEnclosingElement()) {
+    for (XElement e = originalElement; e != null; e = e.getEnclosingElement()) {
       if (isTopLevel(e)) {
-        return MoreElements.asType(e);
+        return XElements.asTypeElement(e);
       }
     }
-    throw new IllegalStateException("Cannot find a top-level type for " + originalElement);
+    throw new IllegalStateException(
+        "Cannot find a top-level type for " + XElements.toStableString(originalElement));
   }
 
-  /** Returns true if the given element is a top-level element. */
-  public static boolean isTopLevel(Element element) {
-    return element.getEnclosingElement().getKind() == ElementKind.PACKAGE;
-  }
-
-  /** Returns true if the given element is annotated with the given annotation. */
-  public static boolean hasAnnotation(Element element, Class<? extends Annotation> annotation) {
-    return element.getAnnotation(annotation) != null;
+  public static boolean isTopLevel(XElement element) {
+    return element.getEnclosingElement() == null;
   }
 
   /** Returns true if the given element has an annotation with the given class name. */
@@ -477,41 +176,27 @@
     return getAnnotationMirrorOptional(element, className).isPresent();
   }
 
-  /** Returns true if the given element has an annotation with the given class name. */
-  public static boolean hasAnnotation(AnnotationMirror mirror, ClassName className) {
-    return hasAnnotation(mirror.getAnnotationType().asElement(), className);
-  }
-
-  /** Returns true if the given element is annotated with the given annotation. */
-  public static boolean hasAnnotation(
-      AnnotationMirror mirror, Class<? extends Annotation> annotation) {
-    return hasAnnotation(mirror.getAnnotationType().asElement(), annotation);
-  }
-
   /** Returns true if the given element has an annotation that is an error kind. */
-  public static boolean hasErrorTypeAnnotation(Element element) {
-    for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
-      if (annotationMirror.getAnnotationType().getKind() == TypeKind.ERROR) {
+  public static boolean hasErrorTypeAnnotation(XElement element) {
+    for (XAnnotation annotation : element.getAllAnnotations()) {
+      if (annotation.getType().isError()) {
         return true;
       }
     }
     return false;
   }
 
-
   /**
-   * Returns all elements in the round that are annotated with at least 1 of the given
-   * annotations.
+   * Returns the annotation mirror from the given element that corresponds to the given class.
+   *
+   * @throws IllegalArgumentException if 2 or more annotations are found.
+   * @return {@link Optional#empty()} if no annotation is found on the element.
    */
-  @SafeVarargs
-  public static ImmutableSet<Element> getElementsAnnotatedWith(RoundEnvironment roundEnv,
-      Class<? extends Annotation>... annotations) {
-    ImmutableSet.Builder<Element> builder = ImmutableSet.builder();
-    for (Class<? extends Annotation> annotation : annotations){
-      builder.addAll(roundEnv.getElementsAnnotatedWith(annotation));
-    }
-
-    return builder.build();
+  static Optional<AnnotationMirror> getAnnotationMirrorOptional(
+      Element element, ClassName className) {
+    return element.getAnnotationMirrors().stream()
+        .filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className))
+        .collect(toOptional());
   }
 
   /**
@@ -522,11 +207,6 @@
     return Joiner.on('_').join(name.simpleNames());
   }
 
-  /** Returns the name of a class. See {@link #getEnclosedName(ClassName)}. */
-  public static String getEnclosedName(TypeElement element) {
-    return getEnclosedName(ClassName.get(element));
-  }
-
   /**
    * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
    * {@code _}.
@@ -539,13 +219,8 @@
    * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
    * {@code _}.
    */
-  public static ClassName getEnclosedClassName(TypeElement typeElement) {
-    return getEnclosedClassName(ClassName.get(typeElement));
-  }
-
-  /** Returns the fully qualified class name, with _ instead of . */
-  public static String getFullyQualifiedEnclosedClassName(ClassName className) {
-    return className.packageName().replace('.', '_') + getEnclosedName(className);
+  public static ClassName getEnclosedClassName(XTypeElement typeElement) {
+    return getEnclosedClassName(typeElement.getClassName());
   }
 
   /**
@@ -553,18 +228,19 @@
    * elements, this continues to append the simple name of elements. For example,
    * foo_bar_Outer_Inner_fooMethod.
    */
-  public static String getFullEnclosedName(Element element) {
+  public static String getFullEnclosedName(XElement element) {
     Preconditions.checkNotNull(element);
     String qualifiedName = "";
     while (element != null) {
-      if (element.getKind().equals(ElementKind.PACKAGE)) {
-        qualifiedName = asPackage(element).getQualifiedName() + qualifiedName;
+      if (element.getEnclosingElement() == null) {
+        qualifiedName =
+            element.getClosestMemberContainer().asClassName().getCanonicalName() + qualifiedName;
       } else {
         // This check is needed to keep the name stable when compiled with jdk8 vs jdk11. jdk11
-        // contains newly added "module" enclosing elements of packages, which adds an addtional "_"
-        // prefix to the name due to an empty module element compared with jdk8.
-        if (!element.getSimpleName().toString().isEmpty()) {
-          qualifiedName = "." + element.getSimpleName() + qualifiedName;
+        // contains newly added "module" enclosing elements of packages, which adds an additional
+        // "_" prefix to the name due to an empty module element compared with jdk8.
+        if (!XElements.getSimpleName(element).isEmpty()) {
+          qualifiedName = "." + XElements.getSimpleName(element) + qualifiedName;
         }
       }
       element = element.getEnclosingElement();
@@ -587,85 +263,53 @@
    *
    * @throws BadInputException if the simple name of {@code type} does not end with {@code suffix}
    */
-  public static ClassName removeNameSuffix(TypeElement type, String suffix) {
-    ClassName originalName = ClassName.get(type);
+  public static ClassName removeNameSuffix(XTypeElement type, String suffix) {
+    ClassName originalName = type.getClassName();
     String originalSimpleName = originalName.simpleName();
-    ProcessorErrors.checkState(originalSimpleName.endsWith(suffix),
-        type, "Name of type %s must end with '%s'", originalName, suffix);
+    ProcessorErrors.checkState(
+        originalSimpleName.endsWith(suffix),
+        type,
+        "Name of type %s must end with '%s'",
+        originalName,
+        suffix);
     String withoutSuffix =
         originalSimpleName.substring(0, originalSimpleName.length() - suffix.length());
     return originalName.peerClass(withoutSuffix);
   }
 
-  /** @see #getAnnotationMirror(Element, ClassName) */
-  public static AnnotationMirror getAnnotationMirror(
-      Element element, Class<? extends Annotation> annotationClass) {
-    return getAnnotationMirror(element, ClassName.get(annotationClass));
-  }
-
-  /**
-   * Returns the annotation mirror from the given element that corresponds to the given class.
-   *
-   * @throws IllegalStateException if the given element isn't annotated with that annotation.
-   */
-  public static AnnotationMirror getAnnotationMirror(Element element, ClassName className) {
-    Optional<AnnotationMirror> annotationMirror = getAnnotationMirrorOptional(element, className);
-    if (annotationMirror.isPresent()) {
-      return annotationMirror.get();
-    } else {
-      throw new IllegalStateException(
-          String.format(
-              "Couldn't find annotation %s on element %s. Found annotations: %s",
-              className, element.getSimpleName(), element.getAnnotationMirrors()));
-    }
-  }
-
-  /**
-   * Returns the annotation mirror from the given element that corresponds to the given class.
-   *
-   * @throws {@link IllegalArgumentException} if 2 or more annotations are found.
-   * @return {@link Optional#empty()} if no annotation is found on the element.
-   */
-  static Optional<AnnotationMirror> getAnnotationMirrorOptional(
-      Element element, ClassName className) {
-    return element.getAnnotationMirrors().stream()
-        .filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className))
-        .collect(toOptional());
-  }
-
-  /** @return true if element inherits directly or indirectly from the className */
-  public static boolean isAssignableFrom(TypeElement element, ClassName className) {
+  /** Returns {@code true} if element inherits directly or indirectly from the className. */
+  public static boolean isAssignableFrom(XTypeElement element, ClassName className) {
     return isAssignableFromAnyOf(element, ImmutableSet.of(className));
   }
 
-  /** @return true if element inherits directly or indirectly from any of the classNames */
-  public static boolean isAssignableFromAnyOf(TypeElement element,
-      ImmutableSet<ClassName> classNames) {
+  /** Returns {@code true} if element inherits directly or indirectly from any of the classNames. */
+  public static boolean isAssignableFromAnyOf(
+      XTypeElement element, ImmutableSet<ClassName> classNames) {
     for (ClassName className : classNames) {
-      if (ClassName.get(element).equals(className)) {
+      if (element.getClassName().equals(className)) {
         return true;
       }
     }
 
-    TypeMirror superClass = element.getSuperclass();
+    XType superClass = element.getSuperClass();
     // None type is returned if this is an interface or Object
     // Error type is returned for classes that are generated by this processor
-    if ((superClass.getKind() != TypeKind.NONE) && (superClass.getKind() != TypeKind.ERROR)) {
-      Preconditions.checkState(superClass.getKind() == TypeKind.DECLARED);
-      if (isAssignableFromAnyOf(MoreTypes.asTypeElement(superClass), classNames)) {
+    if (superClass != null && !superClass.isNone() && !superClass.isError()) {
+      Preconditions.checkState(XTypes.isDeclared(superClass));
+      if (isAssignableFromAnyOf(superClass.getTypeElement(), classNames)) {
         return true;
       }
     }
 
-    for (TypeMirror iface : element.getInterfaces()) {
+    for (XType iface : element.getSuperInterfaces()) {
       // Skip errors and keep looking. This is especially needed for classes generated by this
       // processor.
-      if (iface.getKind() == TypeKind.ERROR) {
+      if (iface.isError()) {
         continue;
       }
-      Preconditions.checkState(iface.getKind() == TypeKind.DECLARED,
-          "Interface type is %s", iface.getKind());
-      if (isAssignableFromAnyOf(MoreTypes.asTypeElement(iface), classNames)) {
+      Preconditions.checkState(
+          XTypes.isDeclared(iface), "Interface type is %s", XTypes.getKindName(iface));
+      if (isAssignableFromAnyOf(iface.getTypeElement(), classNames)) {
         return true;
       }
     }
@@ -673,215 +317,23 @@
     return false;
   }
 
-  /** Returns methods from a given TypeElement, not including constructors. */
-  public static ImmutableList<ExecutableElement> getMethods(TypeElement element) {
-    ImmutableList.Builder<ExecutableElement> builder = ImmutableList.builder();
-    for (Element e : element.getEnclosedElements()) {
-      // Only look for executable elements, not fields, etc
-      if (e instanceof ExecutableElement) {
-        ExecutableElement method = (ExecutableElement) e;
-        if (!method.getSimpleName().contentEquals(CONSTRUCTOR_NAME)
-            && !method.getSimpleName().contentEquals(STATIC_INITIALIZER_NAME)) {
-          builder.add(method);
-        }
-      }
-    }
-    return builder.build();
-  }
-
-  public static ImmutableList<ExecutableElement> getConstructors(TypeElement element) {
-    ImmutableList.Builder<ExecutableElement> builder = ImmutableList.builder();
-    for (Element enclosed : element.getEnclosedElements()) {
-      // Only look for executable elements, not fields, etc
-      if (enclosed instanceof ExecutableElement) {
-        ExecutableElement method = (ExecutableElement) enclosed;
-        if (method.getSimpleName().contentEquals(CONSTRUCTOR_NAME)) {
-          builder.add(method);
-        }
-      }
-    }
-    return builder.build();
-  }
-
-  /**
-   * Returns all transitive methods from a given TypeElement, not including constructors. Also does
-   * not include methods from Object or that override methods on Object.
-   */
-  public static ImmutableList<ExecutableElement> getAllMethods(TypeElement element) {
-    ImmutableList.Builder<ExecutableElement> builder = ImmutableList.builder();
-    builder.addAll(
-        Iterables.filter(
-            getMethods(element),
-            method -> {
-              return !isObjectMethod(method);
-            }));
-    TypeMirror superclass = element.getSuperclass();
-    if (superclass.getKind() != TypeKind.NONE) {
-      TypeElement superclassElement = MoreTypes.asTypeElement(superclass);
-      builder.addAll(getAllMethods(superclassElement));
-    }
-    for (TypeMirror iface : element.getInterfaces()) {
-      builder.addAll(getAllMethods(MoreTypes.asTypeElement(iface)));
-    }
-    return builder.build();
-  }
-
-  /** Checks that the given element is not the error type. */
-  public static void checkForCompilationError(TypeElement e) {
-    ProcessorErrors.checkState(e.asType().getKind() != TypeKind.ERROR, e,
-        "Unable to resolve the type %s. Look for compilation errors above related to this type.",
-        e);
-  }
-
-  private static void addInterfaceMethods(
-      TypeElement type, ImmutableList.Builder<ExecutableElement> interfaceMethods) {
-    for (TypeMirror interfaceMirror : type.getInterfaces()) {
-      TypeElement interfaceElement = MoreTypes.asTypeElement(interfaceMirror);
-      interfaceMethods.addAll(getMethods(interfaceElement));
-      addInterfaceMethods(interfaceElement, interfaceMethods);
-    }
-  }
-
-  /**
-   * Finds methods of interfaces implemented by {@code type}. This method also checks the
-   * superinterfaces of those interfaces. This method does not check the interfaces of any
-   * superclass of {@code type}.
-   */
-  public static ImmutableList<ExecutableElement> methodsOnInterfaces(TypeElement type) {
-    ImmutableList.Builder<ExecutableElement> interfaceMethods = new ImmutableList.Builder<>();
-    addInterfaceMethods(type, interfaceMethods);
-    return interfaceMethods.build();
-  }
-
   /** Returns MapKey annotated annotations found on an element. */
-  public static ImmutableList<AnnotationMirror> getMapKeyAnnotations(Element element) {
-    ImmutableSet<? extends AnnotationMirror> mapKeys =
-        AnnotationMirrors.getAnnotatedAnnotations(element, ClassNames.MAP_KEY.canonicalName());
+  public static ImmutableList<XAnnotation> getMapKeyAnnotations(XElement element) {
     // Normally, we wouldn't need to handle Kotlin metadata because map keys are typically used
     // only on methods. However, with @BindValueIntoMap, this can be used on fields so we need
     // to check annotations on the property as well, just like with qualifiers.
-    KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
-    if (element.getKind() == ElementKind.FIELD
-        // static fields are generally not supported, no need to get map keys from Kotlin metadata
-        && !element.getModifiers().contains(STATIC)
-        && metadataUtil.hasMetadata(element)) {
-      VariableElement fieldElement = asVariable(element);
-      return Stream.concat(
-              mapKeys.stream(),
-              metadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement)
-                  ? Stream.empty()
-                  : metadataUtil
-                      .getSyntheticPropertyAnnotations(fieldElement, ClassNames.MAP_KEY)
-                      .stream())
-          .map(AnnotationMirrors.equivalence()::wrap)
-          .distinct()
-          .map(Wrapper::get)
-          .collect(DaggerStreams.toImmutableList());
-    } else {
-      return ImmutableList.copyOf(mapKeys);
-    }
+    return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.MAP_KEY);
   }
 
   /** Returns Qualifier annotated annotations found on an element. */
-  public static ImmutableList<AnnotationMirror> getQualifierAnnotations(Element element) {
-    // TODO(bcorso): Consolidate this logic with InjectionAnnotations in Dagger
-    ImmutableSet<? extends AnnotationMirror> qualifiers =
-        AnnotationMirrors.getAnnotatedAnnotations(element, ClassNames.QUALIFIER.canonicalName());
-    KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
-    if (element.getKind() == ElementKind.FIELD
-        // static fields are generally not supported, no need to get qualifier from kotlin metadata
-        && !element.getModifiers().contains(STATIC)
-        && metadataUtil.hasMetadata(element)) {
-      VariableElement fieldElement = asVariable(element);
-      return Stream.concat(
-              qualifiers.stream(),
-              metadataUtil.isMissingSyntheticPropertyForAnnotations(fieldElement)
-                  ? Stream.empty()
-                  : metadataUtil
-                      .getSyntheticPropertyAnnotations(fieldElement, ClassNames.QUALIFIER)
-                      .stream())
-          .map(AnnotationMirrors.equivalence()::wrap)
-          .distinct()
-          .map(Wrapper::get)
-          .collect(DaggerStreams.toImmutableList());
-    } else {
-      return ImmutableList.copyOf(qualifiers);
-    }
+  public static ImmutableList<XAnnotation> getQualifierAnnotations(XElement element) {
+    return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.QUALIFIER);
   }
 
   /** Returns Scope annotated annotations found on an element. */
-  public static ImmutableList<AnnotationMirror> getScopeAnnotations(Element element) {
-    return getAnnotationsAnnotatedWith(element, ClassNames.SCOPE);
-  }
-
-  /** Returns annotations of element that are annotated with subAnnotation */
-  public static ImmutableList<AnnotationMirror> getAnnotationsAnnotatedWith(
-      Element element, ClassName subAnnotation) {
-    ImmutableList.Builder<AnnotationMirror> builder = ImmutableList.builder();
-    element.getAnnotationMirrors().stream()
-        .filter(annotation -> hasAnnotation(annotation, subAnnotation))
-        .forEach(builder::add);
-    return builder.build();
-  }
-
-  /** Returns true if there are any annotations of element that are annotated with subAnnotation */
-  public static boolean hasAnnotationsAnnotatedWith(Element element, ClassName subAnnotation) {
-    return !getAnnotationsAnnotatedWith(element, subAnnotation).isEmpty();
-  }
-
-  /**
-   * Returns true iff the given {@code method} is one of the public or protected methods on {@link
-   * Object}, or an overridden version thereof.
-   *
-   * <p>This method ignores the return type of the given method, but this is generally fine since
-   * two methods which only differ by their return type will cause a compiler error. (e.g. a
-   * non-static method with the signature {@code int equals(Object)})
-   */
-  public static boolean isObjectMethod(ExecutableElement method) {
-    // First check if this method is directly defined on Object
-    Element enclosingElement = method.getEnclosingElement();
-    if (enclosingElement.getKind() == ElementKind.CLASS
-        && TypeName.get(enclosingElement.asType()).equals(TypeName.OBJECT)) {
-      return true;
-    }
-
-    if (method.getModifiers().contains(Modifier.STATIC)) {
-      return false;
-    }
-    switch (method.getSimpleName().toString()) {
-      case "equals":
-        return (method.getParameters().size() == 1)
-            && (method.getParameters().get(0).asType().toString().equals("java.lang.Object"));
-      case "hashCode":
-      case "toString":
-      case "clone":
-      case "getClass":
-      case "notify":
-      case "notifyAll":
-      case "finalize":
-        return method.getParameters().isEmpty();
-      case "wait":
-        if (method.getParameters().isEmpty()) {
-          return true;
-        } else if ((method.getParameters().size() == 1)
-            && (method.getParameters().get(0).asType().toString().equals("long"))) {
-          return true;
-        } else if ((method.getParameters().size() == 2)
-            && (method.getParameters().get(0).asType().toString().equals("long"))
-            && (method.getParameters().get(1).asType().toString().equals("int"))) {
-          return true;
-        }
-        return false;
-      default:
-        return false;
-    }
-  }
-
-  public static ParameterSpec parameterSpecFromVariableElement(VariableElement element) {
-    return ParameterSpec.builder(
-        ClassName.get(element.asType()),
-        upperToLowerCamel(element.getSimpleName().toString()))
-        .build();
+  public static ImmutableList<XAnnotation> getScopeAnnotations(XElement element) {
+    return ImmutableList.copyOf(
+        element.getAnnotationsAnnotatedWith(ClassNames.SCOPE));
   }
 
   /**
@@ -914,78 +366,62 @@
         .addTypeVariables(methodSpec.typeVariables);
   }
 
-  /** @return A method spec for an empty constructor (useful for abstract Dagger modules). */
-  public static MethodSpec privateEmptyConstructor() {
-    return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();
-  }
-
   /**
-   * Returns true if the given method is annotated with one of the annotations Dagger recognizes
-   * for abstract methods (e.g. @Binds).
+   * Returns true if the given method is annotated with one of the annotations Dagger recognizes for
+   * abstract methods (e.g. @Binds).
    */
-  public static boolean hasDaggerAbstractMethodAnnotation(ExecutableElement method) {
-    return hasAnnotation(method, ClassNames.BINDS)
-        || hasAnnotation(method, ClassNames.BINDS_OPTIONAL_OF)
-        || hasAnnotation(method, ClassNames.MULTIBINDS)
-        || hasAnnotation(method, ClassNames.CONTRIBUTES_ANDROID_INJECTOR);
+  public static boolean hasDaggerAbstractMethodAnnotation(XExecutableElement method) {
+    return method.hasAnnotation(ClassNames.BINDS)
+        || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF)
+        || method.hasAnnotation(ClassNames.MULTIBINDS)
+        || method.hasAnnotation(ClassNames.CONTRIBUTES_ANDROID_INJECTOR);
   }
 
-  public static ImmutableSet<TypeElement> toTypeElements(Elements elements, String[] classes) {
-    return FluentIterable.from(classes).transform(elements::getTypeElement).toSet();
-  }
-
-  public static ImmutableSet<ClassName> toClassNames(Iterable<TypeElement> elements) {
-    return FluentIterable.from(elements).transform(ClassName::get).toSet();
-  }
-
-  public static boolean requiresModuleInstance(Elements elements, TypeElement module) {
+  public static boolean requiresModuleInstance(XTypeElement module) {
     // Binding methods that lack ABSTRACT or STATIC require module instantiation.
     // Required by Dagger.  See b/31489617.
-    return ElementFilter.methodsIn(elements.getAllMembers(module)).stream()
-        .filter(Processors::isBindingMethod)
-        .map(ExecutableElement::getModifiers)
-        .anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC))
-        // TODO(erichang): Getting a new KotlinMetadataUtil each time isn't great here, but until
-        // we have some sort of dependency management it will be difficult to share the instance.
-        && !KotlinMetadataUtils.getMetadataUtil().isObjectOrCompanionObjectClass(module);
+    return module.getDeclaredMethods().stream()
+            .filter(Processors::isBindingMethod)
+            .anyMatch(method -> !method.isAbstract() && !method.isStatic())
+        && !module.isKotlinObject();
   }
 
-  public static boolean hasVisibleEmptyConstructor(TypeElement type) {
-    List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
+  public static boolean hasVisibleEmptyConstructor(XTypeElement type) {
+    List<XConstructorElement> constructors = type.getConstructors();
     return constructors.isEmpty()
         || constructors.stream()
             .filter(constructor -> constructor.getParameters().isEmpty())
             .anyMatch(
                 constructor ->
-                    !constructor.getModifiers().contains(Modifier.PRIVATE)
+                    !constructor.isPrivate()
                         );
   }
 
-  private static boolean isBindingMethod(ExecutableElement method) {
-    return hasAnnotation(method, ClassNames.PROVIDES)
-        || hasAnnotation(method, ClassNames.BINDS)
-        || hasAnnotation(method, ClassNames.BINDS_OPTIONAL_OF)
-        || hasAnnotation(method, ClassNames.MULTIBINDS);
+  private static boolean isBindingMethod(XExecutableElement method) {
+    return method.hasAnnotation(ClassNames.PROVIDES)
+        || method.hasAnnotation(ClassNames.BINDS)
+        || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF)
+        || method.hasAnnotation(ClassNames.MULTIBINDS);
   }
 
   public static void addGeneratedAnnotation(
-      TypeSpec.Builder typeSpecBuilder, ProcessingEnvironment env, Class<?> generatorClass) {
+      TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, Class<?> generatorClass) {
     addGeneratedAnnotation(typeSpecBuilder, env, generatorClass.getName());
   }
 
   public static void addGeneratedAnnotation(
-      TypeSpec.Builder typeSpecBuilder, ProcessingEnvironment env, String generatorClass) {
-    GeneratedAnnotations.generatedAnnotation(env.getElementUtils(), env.getSourceVersion())
-        .ifPresent(
-            annotation ->
-                typeSpecBuilder.addAnnotation(
-                    AnnotationSpec.builder(ClassName.get(annotation))
-                        .addMember("value", "$S", generatorClass)
-                        .build()));
+      TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, String generatorClass) {
+    XTypeElement annotation = env.findGeneratedAnnotation();
+    if (annotation != null) {
+      typeSpecBuilder.addAnnotation(
+          AnnotationSpec.builder(annotation.getClassName())
+              .addMember("value", "$S", generatorClass)
+              .build());
+    }
   }
 
-  public static AnnotationSpec getOriginatingElementAnnotation(TypeElement element) {
-    TypeName rawType = rawTypeName(ClassName.get(getTopLevelType(element)));
+  public static AnnotationSpec getOriginatingElementAnnotation(XTypeElement element) {
+    TypeName rawType = rawTypeName(getTopLevelType(element).getClassName());
     return AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
         .addMember("topLevelClass", "$T.class", rawType)
         .build();
@@ -1001,27 +437,29 @@
         : typeName;
   }
 
-  public static Optional<TypeElement> getOriginatingTestElement(
-      Element element, Elements elements) {
-    TypeElement topLevelType = getOriginatingTopLevelType(element, elements);
-    return hasAnnotation(topLevelType, ClassNames.HILT_ANDROID_TEST)
-        ? Optional.of(asType(topLevelType))
+  public static Optional<XTypeElement> getOriginatingTestElement(XElement element) {
+    XTypeElement topLevelType = getOriginatingTopLevelType(element);
+    return topLevelType.hasAnnotation(ClassNames.HILT_ANDROID_TEST)
+        ? Optional.of(topLevelType)
         : Optional.empty();
   }
 
-  private static TypeElement getOriginatingTopLevelType(Element element, Elements elements) {
-    TypeElement topLevelType = getTopLevelType(element);
-    if (hasAnnotation(topLevelType, ClassNames.ORIGINATING_ELEMENT)) {
+  private static XTypeElement getOriginatingTopLevelType(XElement element) {
+    XTypeElement topLevelType = getTopLevelType(element);
+    if (topLevelType.hasAnnotation(ClassNames.ORIGINATING_ELEMENT)) {
       return getOriginatingTopLevelType(
-          getAnnotationClassValue(
-              elements,
-              getAnnotationMirror(topLevelType, ClassNames.ORIGINATING_ELEMENT),
-              "topLevelClass"),
-          elements);
+          XAnnotations.getAsTypeElement(
+              topLevelType.getAnnotation(ClassNames.ORIGINATING_ELEMENT), "topLevelClass"));
     }
-
     return topLevelType;
   }
 
+  public static boolean hasJavaPackagePrivateVisibility(XHasModifiers element) {
+    return !element.isPrivate()
+        && !element.isProtected()
+        && !element.isInternal()
+        && !element.isPublic();
+  }
+
   private Processors() {}
 }
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java
index 84fb5a1..4068ac7 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsGenerator.java
@@ -16,14 +16,12 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
 import java.util.Optional;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 
 /**
  * Generates the @AggregatedDeps annotated class used to pass information
@@ -35,30 +33,27 @@
       ClassName.get("dagger.hilt.processor.internal.aggregateddeps", "AggregatedDeps");
 
   private final String dependencyType;
-  private final TypeElement dependency;
+  private final XTypeElement dependency;
   private final Optional<ClassName> testName;
   private final ImmutableSet<ClassName> components;
   private final ImmutableSet<ClassName> replacedDependencies;
-  private final ProcessingEnvironment processingEnv;
 
   AggregatedDepsGenerator(
       String dependencyType,
-      TypeElement dependency,
+      XTypeElement dependency,
       Optional<ClassName> testName,
       ImmutableSet<ClassName> components,
-      ImmutableSet<ClassName> replacedDependencies,
-      ProcessingEnvironment processingEnv) {
+      ImmutableSet<ClassName> replacedDependencies) {
     this.dependencyType = dependencyType;
     this.dependency = dependency;
     this.testName = testName;
     this.components = components;
     this.replacedDependencies = replacedDependencies;
-    this.processingEnv = processingEnv;
   }
 
-  void generate() throws IOException {
+  void generate() {
     Processors.generateAggregatingClass(
-        AGGREGATING_PACKAGE, aggregatedDepsAnnotation(), dependency, getClass(), processingEnv);
+        AGGREGATING_PACKAGE, aggregatedDepsAnnotation(), dependency, getClass());
   }
 
   private AnnotationSpec aggregatedDepsAnnotation() {
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java
index bc7a4d2..840ba9c 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsMetadata.java
@@ -16,26 +16,24 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 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 dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XAnnotationValue;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.AggregatedElements;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr;
 import java.util.Optional;
 import java.util.stream.Collectors;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /**
  * A class that represents the values stored in an {@link
@@ -52,97 +50,93 @@
   }
 
   /** Returns the aggregating element */
-  public abstract TypeElement aggregatingElement();
+  public abstract XTypeElement aggregatingElement();
 
-  public abstract Optional<TypeElement> testElement();
+  public abstract Optional<XTypeElement> testElement();
 
-  public abstract ImmutableSet<TypeElement> componentElements();
+  public abstract ImmutableSet<XTypeElement> componentElements();
 
   abstract DependencyType dependencyType();
 
-  public abstract TypeElement dependency();
+  public abstract XTypeElement dependency();
 
-  public abstract ImmutableSet<TypeElement> replacedDependencies();
+  public abstract ImmutableSet<XTypeElement> replacedDependencies();
 
   public boolean isModule() {
     return dependencyType() == DependencyType.MODULE;
   }
 
   /** Returns metadata for all aggregated elements in the aggregating package. */
-  public static ImmutableSet<AggregatedDepsMetadata> from(Elements elements) {
-    return from(
-        AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, elements),
-        elements);
+  public static ImmutableSet<AggregatedDepsMetadata> from(XProcessingEnv env) {
+    return from(AggregatedElements.from(AGGREGATED_DEPS_PACKAGE, ClassNames.AGGREGATED_DEPS, env));
   }
 
   /** Returns metadata for each aggregated element. */
   public static ImmutableSet<AggregatedDepsMetadata> from(
-      ImmutableSet<TypeElement> aggregatedElements, Elements elements) {
+      ImmutableSet<XTypeElement> aggregatedElements) {
     return aggregatedElements.stream()
-        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .map(aggregatedElement -> create(aggregatedElement, getProcessingEnv(aggregatedElement)))
         .collect(toImmutableSet());
   }
 
   public static AggregatedDepsIr toIr(AggregatedDepsMetadata metadata) {
     return new AggregatedDepsIr(
-        ClassName.get(metadata.aggregatingElement()),
+        metadata.aggregatingElement().getClassName(),
         metadata.componentElements().stream()
-            .map(ClassName::get)
+            .map(XTypeElement::getClassName)
             .map(ClassName::canonicalName)
             .collect(Collectors.toList()),
-        metadata.testElement()
-            .map(ClassName::get)
+        metadata
+            .testElement()
+            .map(XTypeElement::getClassName)
             .map(ClassName::canonicalName)
             .orElse(null),
         metadata.replacedDependencies().stream()
-            .map(ClassName::get)
+            .map(XTypeElement::getClassName)
             .map(ClassName::canonicalName)
             .collect(Collectors.toList()),
         metadata.dependencyType() == DependencyType.MODULE
-            ? ClassName.get(metadata.dependency()).canonicalName()
+            ? metadata.dependency().getClassName().canonicalName()
             : null,
         metadata.dependencyType() == DependencyType.ENTRY_POINT
-            ? ClassName.get(metadata.dependency()).canonicalName()
+            ? metadata.dependency().getClassName().canonicalName()
             : null,
         metadata.dependencyType() == DependencyType.COMPONENT_ENTRY_POINT
-            ? ClassName.get(metadata.dependency()).canonicalName()
+            ? metadata.dependency().getClassName().canonicalName()
             : null);
   }
 
-  private static AggregatedDepsMetadata create(TypeElement element, Elements elements) {
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_DEPS);
-
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(elements, annotationMirror);
+  private static AggregatedDepsMetadata create(XTypeElement element, XProcessingEnv env) {
+    XAnnotation annotation = element.getAnnotation(ClassNames.AGGREGATED_DEPS);
 
     return new AutoValue_AggregatedDepsMetadata(
         element,
-        getTestElement(values.get("test"), elements),
-        getComponents(values.get("components"), elements),
+        getTestElement(annotation.getAnnotationValue("test"), env),
+        getComponents(annotation.getAnnotationValue("components"), env),
         getDependencyType(
-            values.get("modules"), values.get("entryPoints"), values.get("componentEntryPoints")),
+            annotation.getAnnotationValue("modules"),
+            annotation.getAnnotationValue("entryPoints"),
+            annotation.getAnnotationValue("componentEntryPoints")),
         getDependency(
-            values.get("modules"),
-            values.get("entryPoints"),
-            values.get("componentEntryPoints"),
-            elements),
-        getReplacedDependencies(values.get("replaces"), elements));
+            annotation.getAnnotationValue("modules"),
+            annotation.getAnnotationValue("entryPoints"),
+            annotation.getAnnotationValue("componentEntryPoints"),
+            env),
+        getReplacedDependencies(annotation.getAnnotationValue("replaces"), env));
   }
 
-  private static Optional<TypeElement> getTestElement(
-      AnnotationValue testValue, Elements elements) {
+  private static Optional<XTypeElement> getTestElement(
+      XAnnotationValue testValue, XProcessingEnv env) {
     checkNotNull(testValue);
-    String test = AnnotationValues.getString(testValue);
-    return test.isEmpty() ? Optional.empty() : Optional.of(elements.getTypeElement(test));
+    String test = testValue.asString();
+    return test.isEmpty() ? Optional.empty() : Optional.of(env.findTypeElement(test));
   }
 
-  private static ImmutableSet<TypeElement> getComponents(
-      AnnotationValue componentsValue, Elements elements) {
+  private static ImmutableSet<XTypeElement> getComponents(
+      XAnnotationValue componentsValue, XProcessingEnv env) {
     checkNotNull(componentsValue);
-    ImmutableSet<TypeElement> componentNames =
-        AnnotationValues.getAnnotationValues(componentsValue).stream()
-            .map(AnnotationValues::getString)
+    ImmutableSet<XTypeElement> componentNames =
+        componentsValue.asStringList().stream()
             .map(
                 // This is a temporary hack to map the old ApplicationComponent to the new
                 // SingletonComponent. Technically, this is only needed for backwards compatibility
@@ -153,71 +147,70 @@
                             "dagger.hilt.android.components.ApplicationComponent")
                         ? ClassNames.SINGLETON_COMPONENT.canonicalName()
                         : componentName)
-            .map(elements::getTypeElement)
+            .map(env::requireTypeElement)
             .collect(toImmutableSet());
     checkState(!componentNames.isEmpty());
     return componentNames;
   }
 
   private static DependencyType getDependencyType(
-      AnnotationValue modulesValue,
-      AnnotationValue entryPointsValue,
-      AnnotationValue componentEntryPointsValue) {
+      XAnnotationValue modulesValue,
+      XAnnotationValue entryPointsValue,
+      XAnnotationValue componentEntryPointsValue) {
     checkNotNull(modulesValue);
     checkNotNull(entryPointsValue);
     checkNotNull(componentEntryPointsValue);
 
     ImmutableSet.Builder<DependencyType> dependencyTypes = ImmutableSet.builder();
-    if (!AnnotationValues.getAnnotationValues(modulesValue).isEmpty()) {
+    if (!modulesValue.asAnnotationValueList().isEmpty()) {
       dependencyTypes.add(DependencyType.MODULE);
     }
-    if (!AnnotationValues.getAnnotationValues(entryPointsValue).isEmpty()) {
+    if (!entryPointsValue.asAnnotationValueList().isEmpty()) {
       dependencyTypes.add(DependencyType.ENTRY_POINT);
     }
-    if (!AnnotationValues.getAnnotationValues(componentEntryPointsValue).isEmpty()) {
+    if (!componentEntryPointsValue.asAnnotationValueList().isEmpty()) {
       dependencyTypes.add(DependencyType.COMPONENT_ENTRY_POINT);
     }
     return getOnlyElement(dependencyTypes.build());
   }
 
-  private static TypeElement getDependency(
-      AnnotationValue modulesValue,
-      AnnotationValue entryPointsValue,
-      AnnotationValue componentEntryPointsValue,
-      Elements elements) {
+  private static XTypeElement getDependency(
+      XAnnotationValue modulesValue,
+      XAnnotationValue entryPointsValue,
+      XAnnotationValue componentEntryPointsValue,
+      XProcessingEnv env) {
     checkNotNull(modulesValue);
     checkNotNull(entryPointsValue);
     checkNotNull(componentEntryPointsValue);
 
     String dependencyName =
-        AnnotationValues.getString(
-            getOnlyElement(
-                ImmutableSet.<AnnotationValue>builder()
-                    .addAll(AnnotationValues.getAnnotationValues(modulesValue))
-                    .addAll(AnnotationValues.getAnnotationValues(entryPointsValue))
-                    .addAll(AnnotationValues.getAnnotationValues(componentEntryPointsValue))
-                    .build()));
-    TypeElement dependency = elements.getTypeElement(dependencyName);
+        getOnlyElement(
+                ImmutableSet.<XAnnotationValue>builder()
+                    .addAll(modulesValue.asAnnotationValueList())
+                    .addAll(entryPointsValue.asAnnotationValueList())
+                    .addAll(componentEntryPointsValue.asAnnotationValueList())
+                    .build())
+            .asString();
+    XTypeElement dependency = env.findTypeElement(dependencyName);
     checkNotNull(dependency, "Could not get element for %s", dependencyName);
     return dependency;
   }
 
-  private static ImmutableSet<TypeElement> getReplacedDependencies(
-      AnnotationValue replacedDependenciesValue, Elements elements) {
+  private static ImmutableSet<XTypeElement> getReplacedDependencies(
+      XAnnotationValue replacedDependenciesValue, XProcessingEnv env) {
     // Allow null values to support libraries using a Hilt version before @TestInstallIn was added
     return replacedDependenciesValue == null
         ? ImmutableSet.of()
-        : AnnotationValues.getAnnotationValues(replacedDependenciesValue).stream()
-            .map(AnnotationValues::getString)
-            .map(elements::getTypeElement)
-            .map(replacedDep -> getPublicDependency(replacedDep, elements))
+        : replacedDependenciesValue.asStringList().stream()
+            .map(env::requireTypeElement)
+            .map(replacedDep -> getPublicDependency(replacedDep, env))
             .collect(toImmutableSet());
   }
 
   /** Returns the public Hilt wrapper module, or the module itself if its already public. */
-  private static TypeElement getPublicDependency(TypeElement dependency, Elements elements) {
-    return PkgPrivateMetadata.of(elements, dependency, ClassNames.MODULE)
-        .map(metadata -> elements.getTypeElement(metadata.generatedClassName().toString()))
+  private static XTypeElement getPublicDependency(XTypeElement dependency, XProcessingEnv env) {
+    return PkgPrivateMetadata.of(dependency, ClassNames.MODULE)
+        .map(metadata -> env.requireTypeElement(metadata.generatedClassName().toString()))
         .orElse(dependency);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessingStep.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessingStep.java
new file mode 100644
index 0000000..f19ec74
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessingStep.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2019 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.aggregateddeps;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XElementKt;
+import androidx.room.compiler.processing.XExecutableElement;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Components;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.extension.DaggerStreams;
+import dagger.internal.codegen.xprocessing.XElements;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+/** Processor that outputs dummy files to propagate information through multiple javac runs. */
+public final class AggregatedDepsProcessingStep extends BaseProcessingStep {
+
+  private static final ImmutableSet<ClassName> ENTRY_POINT_ANNOTATIONS =
+      ImmutableSet.of(
+          ClassNames.ENTRY_POINT,
+          ClassNames.EARLY_ENTRY_POINT,
+          ClassNames.GENERATED_ENTRY_POINT,
+          ClassNames.COMPONENT_ENTRY_POINT);
+
+  private static final ImmutableSet<ClassName> MODULE_ANNOTATIONS =
+      ImmutableSet.of(
+          ClassNames.MODULE);
+
+  private static final ImmutableSet<ClassName> INSTALL_IN_ANNOTATIONS =
+      ImmutableSet.of(ClassNames.INSTALL_IN, ClassNames.TEST_INSTALL_IN);
+
+  private final Set<XElement> seen = new HashSet<>();
+
+  public AggregatedDepsProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.<ClassName>builder()
+        .addAll(INSTALL_IN_ANNOTATIONS)
+        .addAll(MODULE_ANNOTATIONS)
+        .addAll(ENTRY_POINT_ANNOTATIONS)
+        .build();
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) throws Exception {
+    if (!seen.add(element)) {
+      return;
+    }
+
+    Optional<ClassName> installInAnnotation = getAnnotation(element, INSTALL_IN_ANNOTATIONS);
+    Optional<ClassName> entryPointAnnotation = getAnnotation(element, ENTRY_POINT_ANNOTATIONS);
+    Optional<ClassName> moduleAnnotation = getAnnotation(element, MODULE_ANNOTATIONS);
+
+    boolean hasInstallIn = installInAnnotation.isPresent();
+    boolean isEntryPoint = entryPointAnnotation.isPresent();
+    boolean isModule = moduleAnnotation.isPresent();
+
+    ProcessorErrors.checkState(
+        !hasInstallIn || isEntryPoint || isModule,
+        element,
+        "@%s-annotated classes must also be annotated with @Module or @EntryPoint: %s",
+        installInAnnotation.map(ClassName::simpleName).orElse("@InstallIn"),
+        XElements.toStableString(element));
+
+    ProcessorErrors.checkState(
+        !(isEntryPoint && isModule),
+        element,
+        "@%s and @%s cannot be used on the same interface: %s",
+        moduleAnnotation.map(ClassName::simpleName).orElse("@Module"),
+        entryPointAnnotation.map(ClassName::simpleName).orElse("@EntryPoint"),
+        XElements.toStableString(element));
+
+    if (isModule) {
+      processModule(element, installInAnnotation, moduleAnnotation.get());
+    } else if (isEntryPoint) {
+      processEntryPoint(element, installInAnnotation, entryPointAnnotation.get());
+    } else {
+      throw new AssertionError();
+    }
+  }
+
+  private void processModule(
+      XElement element, Optional<ClassName> installInAnnotation, ClassName moduleAnnotation)
+      throws Exception {
+    ProcessorErrors.checkState(
+        installInAnnotation.isPresent()
+            || isDaggerGeneratedModule(element)
+            || installInCheckDisabled(element),
+        element,
+        "%s is missing an @InstallIn annotation. If this was intentional, see"
+            + " https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this"
+            + " check.",
+        XElements.toStableString(element));
+
+    if (!installInAnnotation.isPresent()) {
+      // Modules without @InstallIn or @TestInstallIn annotations don't need to be processed further
+      return;
+    }
+
+    ProcessorErrors.checkState(
+        XElementKt.isTypeElement(element),
+        element,
+        "Only classes and interfaces can be annotated with @Module: %s",
+        XElements.toStableString(element));
+
+    XTypeElement module = XElements.asTypeElement(element);
+
+    ProcessorErrors.checkState(
+        module.isClass() || module.isInterface() || module.isKotlinObject(),
+        module,
+        "Only classes and interfaces can be annotated with @Module: %s",
+        XElements.toStableString(module));
+
+    ProcessorErrors.checkState(
+        Processors.isTopLevel(module)
+            || module.isStatic()
+            || module.isAbstract()
+            || module.getEnclosingElement().hasAnnotation(ClassNames.HILT_ANDROID_TEST),
+        module,
+        "Nested @%s modules must be static unless they are directly nested within a test. "
+            + "Found: %s",
+        installInAnnotation.get().simpleName(),
+        XElements.toStableString(module));
+
+    // Check that if Dagger needs an instance of the module, Hilt can provide it automatically by
+    // calling a visible empty constructor.
+    ProcessorErrors.checkState(
+        // Skip ApplicationContextModule, since Hilt manages this module internally.
+        ClassNames.APPLICATION_CONTEXT_MODULE.equals(module.getClassName())
+            || !Processors.requiresModuleInstance(module)
+            || Processors.hasVisibleEmptyConstructor(module),
+        module,
+        "Modules that need to be instantiated by Hilt must have a visible, empty constructor.");
+
+    // TODO(b/28989613): This should really be fixed in Dagger. Remove once Dagger bug is fixed.
+    ImmutableList<XExecutableElement> abstractMethodsWithMissingBinds =
+        module.getDeclaredMethods().stream()
+            .filter(XMethodElement::isAbstract)
+            .filter(method -> !Processors.hasDaggerAbstractMethodAnnotation(method))
+            .collect(toImmutableList());
+    ProcessorErrors.checkState(
+        abstractMethodsWithMissingBinds.isEmpty(),
+        module,
+        "Found unimplemented abstract methods, %s, in an abstract module, %s. "
+            + "Did you forget to add a Dagger binding annotation (e.g. @Binds)?",
+        abstractMethodsWithMissingBinds.stream()
+            .map(XElements::toStableString)
+            .collect(DaggerStreams.toImmutableList()),
+        XElements.toStableString(module));
+
+    ImmutableList<XTypeElement> replacedModules = ImmutableList.of();
+    if (module.hasAnnotation(ClassNames.TEST_INSTALL_IN)) {
+      Optional<XTypeElement> originatingTestElement = Processors.getOriginatingTestElement(module);
+      ProcessorErrors.checkState(
+          !originatingTestElement.isPresent(),
+          // TODO(b/152801981): this should really error on the annotation value
+          module,
+          "@TestInstallIn modules cannot be nested in (or originate from) a "
+              + "@HiltAndroidTest-annotated class:  %s",
+          originatingTestElement.map(XTypeElement::getQualifiedName).orElse(""));
+
+      XAnnotation testInstallIn = module.getAnnotation(ClassNames.TEST_INSTALL_IN);
+      replacedModules = Processors.getAnnotationClassValues(testInstallIn, "replaces");
+
+      ProcessorErrors.checkState(
+          !replacedModules.isEmpty(),
+          // TODO(b/152801981): this should really error on the annotation value
+          module,
+          "@TestInstallIn#replaces() cannot be empty. Use @InstallIn instead.");
+
+      ImmutableList<XTypeElement> nonInstallInModules =
+          replacedModules.stream()
+              .filter(replacedModule -> !replacedModule.hasAnnotation(ClassNames.INSTALL_IN))
+              .collect(toImmutableList());
+
+      ProcessorErrors.checkState(
+          nonInstallInModules.isEmpty(),
+          // TODO(b/152801981): this should really error on the annotation value
+          module,
+          "@TestInstallIn#replaces() can only contain @InstallIn modules, but found: %s",
+          nonInstallInModules.stream()
+              .map(XElements::toStableString)
+              .collect(DaggerStreams.toImmutableList()));
+
+      ImmutableList<XTypeElement> hiltWrapperModules =
+          replacedModules.stream()
+              .filter(
+                  replacedModule ->
+                      replacedModule.getClassName().simpleName().startsWith("HiltWrapper_"))
+              .collect(toImmutableList());
+
+      ProcessorErrors.checkState(
+          hiltWrapperModules.isEmpty(),
+          // TODO(b/152801981): this should really error on the annotation value
+          module,
+          "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper modules, "
+              + "but found: %s. ",
+          hiltWrapperModules.stream()
+              .map(XElements::toStableString)
+              .collect(DaggerStreams.toImmutableList()));
+
+      if (!module.getPackageName().startsWith("dagger.hilt")) {
+        // Prevent external users from overriding Hilt's internal modules. Techincally, except for
+        // ApplicationContextModule, making all modules pkg-private should be enough but this is an
+        // extra measure of precaution.
+        ImmutableList<XTypeElement> hiltInternalModules =
+            replacedModules.stream()
+                .filter(replacedModule -> replacedModule.getPackageName().startsWith("dagger.hilt"))
+                .collect(toImmutableList());
+
+        ProcessorErrors.checkState(
+            hiltInternalModules.isEmpty(),
+            // TODO(b/152801981): this should really error on the annotation value
+            module,
+            "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: %s. ",
+            hiltInternalModules.stream()
+                .map(XElements::toStableString)
+                .collect(DaggerStreams.toImmutableList()));
+      }
+
+      // Prevent users from uninstalling test-specific @InstallIn modules.
+      ImmutableList<XTypeElement> replacedTestSpecificInstallIn =
+          replacedModules.stream()
+              .filter(
+                  replacedModule ->
+                      Processors.getOriginatingTestElement(replacedModule).isPresent())
+              .collect(toImmutableList());
+
+      ProcessorErrors.checkState(
+          replacedTestSpecificInstallIn.isEmpty(),
+          // TODO(b/152801981): this should really error on the annotation value
+          module,
+          "@TestInstallIn#replaces() cannot replace test specific @InstallIn modules, but found: "
+              + "%s. Please remove the @InstallIn module manually rather than replacing it.",
+          replacedTestSpecificInstallIn.stream()
+              .map(XElements::toStableString)
+              .collect(DaggerStreams.toImmutableList()));
+    }
+
+    generateAggregatedDeps(
+        "modules",
+        module,
+        moduleAnnotation,
+        replacedModules.stream().map(XTypeElement::getClassName).collect(toImmutableSet()));
+  }
+
+  private void processEntryPoint(
+      XElement element, Optional<ClassName> installInAnnotation, ClassName entryPointAnnotation)
+      throws Exception {
+    ProcessorErrors.checkState(
+        installInAnnotation.isPresent() ,
+        element,
+        "@%s %s must also be annotated with @InstallIn",
+        entryPointAnnotation.simpleName(),
+        XElements.toStableString(element));
+
+    ProcessorErrors.checkState(
+        !element.hasAnnotation(ClassNames.TEST_INSTALL_IN),
+        element,
+        "@TestInstallIn can only be used with modules");
+
+    ProcessorErrors.checkState(
+        XElementKt.isTypeElement(element) && XElements.asTypeElement(element).isInterface(),
+        element,
+        "Only interfaces can be annotated with @%s: %s",
+        entryPointAnnotation.simpleName(),
+        XElements.toStableString(element));
+    XTypeElement entryPoint = XElements.asTypeElement(element);
+
+    if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) {
+      ImmutableSet<ClassName> components = Components.getComponents(element);
+      ProcessorErrors.checkState(
+          components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)),
+          element,
+          "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s",
+          components);
+
+      Optional<XTypeElement> optionalTestElement = Processors.getOriginatingTestElement(element);
+      ProcessorErrors.checkState(
+          !optionalTestElement.isPresent(),
+          element,
+          "@EarlyEntryPoint-annotated entry point, %s, cannot be nested in (or originate from) "
+              + "a @HiltAndroidTest-annotated class, %s. This requirement is to avoid confusion "
+              + "with other, test-specific entry points.",
+          entryPoint.getQualifiedName(),
+          optionalTestElement.map(testElement -> testElement.getQualifiedName()).orElse(""));
+    }
+
+    generateAggregatedDeps(
+        entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT)
+            ? "componentEntryPoints"
+            : "entryPoints",
+        entryPoint,
+        entryPointAnnotation,
+        ImmutableSet.of());
+  }
+
+  private void generateAggregatedDeps(
+      String key,
+      XTypeElement element,
+      ClassName annotation,
+      ImmutableSet<ClassName> replacedModules)
+      throws Exception {
+    // Get @InstallIn components here to catch errors before skipping user's pkg-private element.
+    ImmutableSet<ClassName> components = Components.getComponents(element);
+
+    if (isValidKind(element)) {
+      Optional<PkgPrivateMetadata> pkgPrivateMetadata = PkgPrivateMetadata.of(element, annotation);
+      if (pkgPrivateMetadata.isPresent()) {
+        if (key.contentEquals("modules")) {
+          new PkgPrivateModuleGenerator(processingEnv(), pkgPrivateMetadata.get()).generate();
+        } else {
+          new PkgPrivateEntryPointGenerator(processingEnv(), pkgPrivateMetadata.get()).generate();
+        }
+      } else {
+        Optional<ClassName> testName =
+            Processors.getOriginatingTestElement(element).map(XTypeElement::getClassName);
+        new AggregatedDepsGenerator(key, element, testName, components, replacedModules).generate();
+      }
+    }
+  }
+
+  private static Optional<ClassName> getAnnotation(
+      XElement element, ImmutableSet<ClassName> annotations) {
+    ImmutableSet<ClassName> usedAnnotations =
+        annotations.stream().filter(element::hasAnnotation).collect(toImmutableSet());
+
+    if (usedAnnotations.isEmpty()) {
+      return Optional.empty();
+    }
+
+    ProcessorErrors.checkState(
+        usedAnnotations.size() == 1,
+        element,
+        "Only one of the following annotations can be used on %s: %s",
+        XElements.toStableString(element),
+        usedAnnotations);
+
+    return Optional.of(getOnlyElement(usedAnnotations));
+  }
+
+  private static boolean isValidKind(XElement element) {
+    // don't go down the rabbit hole of analyzing undefined types. N.B. we don't issue
+    // an error here because javac already has and we don't want to spam the user.
+    return !XElements.asTypeElement(element).getType().isError();
+  }
+
+  private boolean installInCheckDisabled(XElement element) {
+    return isModuleInstallInCheckDisabled(processingEnv())
+        || element.hasAnnotation(ClassNames.DISABLE_INSTALL_IN_CHECK);
+  }
+
+  /**
+   * When using Dagger Producers, don't process generated modules. They will not have the expected
+   * annotations.
+   */
+  private static boolean isDaggerGeneratedModule(XElement element) {
+    if (!element.hasAnnotation(ClassNames.MODULE)) {
+      return false;
+    }
+    return element.getAllAnnotations().stream()
+        .filter(annotation -> isGenerated(annotation))
+        .map(annotation -> getOnlyElement(annotation.getAsStringList("value")))
+        .anyMatch(value -> value.startsWith("dagger"));
+  }
+
+  private static boolean isGenerated(XAnnotation annotation) {
+    String name = annotation.getTypeElement().getQualifiedName();
+
+    return name.equals("javax.annotation.Generated")
+        || name.equals("javax.annotation.processing.Generated");
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java
index 184894a..73cbc59 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessor.java
@@ -16,431 +16,20 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
-import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
-import static com.google.auto.common.MoreElements.asType;
-import static com.google.auto.common.MoreElements.getPackage;
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static dagger.hilt.processor.internal.HiltCompilerOptions.isModuleInstallInCheckDisabled;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-import static javax.lang.model.element.ElementKind.CLASS;
-import static javax.lang.model.element.ElementKind.INTERFACE;
-import static javax.lang.model.element.Modifier.ABSTRACT;
-import static javax.lang.model.element.Modifier.STATIC;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Components;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-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.Name;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.util.ElementFilter;
-import javax.lang.model.util.SimpleAnnotationValueVisitor8;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Processor that outputs dummy files to propagate information through multiple javac runs. */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class AggregatedDepsProcessor extends BaseProcessor {
-
-  private static final ImmutableSet<ClassName> ENTRY_POINT_ANNOTATIONS =
-      ImmutableSet.of(
-          ClassNames.ENTRY_POINT,
-          ClassNames.EARLY_ENTRY_POINT,
-          ClassNames.GENERATED_ENTRY_POINT,
-          ClassNames.COMPONENT_ENTRY_POINT);
-
-  private static final ImmutableSet<ClassName> MODULE_ANNOTATIONS =
-      ImmutableSet.of(
-          ClassNames.MODULE);
-
-  private static final ImmutableSet<ClassName> INSTALL_IN_ANNOTATIONS =
-      ImmutableSet.of(ClassNames.INSTALL_IN, ClassNames.TEST_INSTALL_IN);
-
-  private final Set<Element> seen = new HashSet<>();
-
+public final class AggregatedDepsProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public Set<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.builder()
-        .addAll(INSTALL_IN_ANNOTATIONS)
-        .addAll(MODULE_ANNOTATIONS)
-        .addAll(ENTRY_POINT_ANNOTATIONS)
-        .build()
-        .stream()
-        .map(Object::toString)
-        .collect(toImmutableSet());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    if (!seen.add(element)) {
-      return;
-    }
-
-    Optional<ClassName> installInAnnotation = getAnnotation(element, INSTALL_IN_ANNOTATIONS);
-    Optional<ClassName> entryPointAnnotation = getAnnotation(element, ENTRY_POINT_ANNOTATIONS);
-    Optional<ClassName> moduleAnnotation = getAnnotation(element, MODULE_ANNOTATIONS);
-
-    boolean hasInstallIn = installInAnnotation.isPresent();
-    boolean isEntryPoint = entryPointAnnotation.isPresent();
-    boolean isModule = moduleAnnotation.isPresent();
-
-    ProcessorErrors.checkState(
-        !hasInstallIn || isEntryPoint || isModule,
-        element,
-        "@%s-annotated classes must also be annotated with @Module or @EntryPoint: %s",
-        installInAnnotation.map(ClassName::simpleName).orElse("@InstallIn"),
-        element);
-
-    ProcessorErrors.checkState(
-        !(isEntryPoint && isModule),
-        element,
-        "@%s and @%s cannot be used on the same interface: %s",
-        moduleAnnotation.map(ClassName::simpleName).orElse("@Module"),
-        entryPointAnnotation.map(ClassName::simpleName).orElse("@EntryPoint"),
-        element);
-
-    if (isModule) {
-      processModule(element, installInAnnotation, moduleAnnotation.get());
-    } else if (isEntryPoint) {
-      processEntryPoint(element, installInAnnotation, entryPointAnnotation.get());
-    } else {
-      throw new AssertionError();
-    }
-  }
-
-  private void processModule(
-      Element element, Optional<ClassName> installInAnnotation, ClassName moduleAnnotation)
-      throws Exception {
-    ProcessorErrors.checkState(
-        installInAnnotation.isPresent()
-            || isDaggerGeneratedModule(element)
-            || installInCheckDisabled(element),
-        element,
-        "%s is missing an @InstallIn annotation. If this was intentional, see"
-            + " https://dagger.dev/hilt/flags#disable-install-in-check for how to disable this"
-            + " check.",
-        element);
-
-    if (!installInAnnotation.isPresent()) {
-      // Modules without @InstallIn or @TestInstallIn annotations don't need to be processed further
-      return;
-    }
-
-    ProcessorErrors.checkState(
-        element.getKind() == CLASS || element.getKind() == INTERFACE,
-        element,
-        "Only classes and interfaces can be annotated with @Module: %s",
-        element);
-    TypeElement module = asType(element);
-
-    ProcessorErrors.checkState(
-        Processors.isTopLevel(module)
-            || module.getModifiers().contains(STATIC)
-            || module.getModifiers().contains(ABSTRACT)
-            || Processors.hasAnnotation(module.getEnclosingElement(), ClassNames.HILT_ANDROID_TEST),
-        module,
-        "Nested @%s modules must be static unless they are directly nested within a test. "
-            + "Found: %s",
-        installInAnnotation.get().simpleName(),
-        module);
-
-    // Check that if Dagger needs an instance of the module, Hilt can provide it automatically by
-    // calling a visible empty constructor.
-    ProcessorErrors.checkState(
-        // Skip ApplicationContextModule, since Hilt manages this module internally.
-        ClassNames.APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module))
-            || !Processors.requiresModuleInstance(getElementUtils(), module)
-            || Processors.hasVisibleEmptyConstructor(module),
-        module,
-        "Modules that need to be instantiated by Hilt must have a visible, empty constructor.");
-
-    // TODO(b/28989613): This should really be fixed in Dagger. Remove once Dagger bug is fixed.
-    ImmutableList<ExecutableElement> abstractMethodsWithMissingBinds =
-        ElementFilter.methodsIn(module.getEnclosedElements()).stream()
-            .filter(method -> method.getModifiers().contains(ABSTRACT))
-            .filter(method -> !Processors.hasDaggerAbstractMethodAnnotation(method))
-            .collect(toImmutableList());
-    ProcessorErrors.checkState(
-        abstractMethodsWithMissingBinds.isEmpty(),
-        module,
-        "Found unimplemented abstract methods, %s, in an abstract module, %s. "
-            + "Did you forget to add a Dagger binding annotation (e.g. @Binds)?",
-        abstractMethodsWithMissingBinds,
-        module);
-
-    ImmutableList<TypeElement> replacedModules = ImmutableList.of();
-    if (Processors.hasAnnotation(module, ClassNames.TEST_INSTALL_IN)) {
-      Optional<TypeElement> originatingTestElement =
-          Processors.getOriginatingTestElement(module, getElementUtils());
-      ProcessorErrors.checkState(
-          !originatingTestElement.isPresent(),
-          // TODO(b/152801981): this should really error on the annotation value
-          module,
-          "@TestInstallIn modules cannot be nested in (or originate from) a "
-              + "@HiltAndroidTest-annotated class:  %s",
-          originatingTestElement
-              .map(testElement -> testElement.getQualifiedName().toString())
-              .orElse(""));
-
-      AnnotationMirror testInstallIn =
-          Processors.getAnnotationMirror(module, ClassNames.TEST_INSTALL_IN);
-      replacedModules =
-          Processors.getAnnotationClassValues(getElementUtils(), testInstallIn, "replaces");
-
-      ProcessorErrors.checkState(
-          !replacedModules.isEmpty(),
-          // TODO(b/152801981): this should really error on the annotation value
-          module,
-          "@TestInstallIn#replaces() cannot be empty. Use @InstallIn instead.");
-
-      ImmutableList<TypeElement> nonInstallInModules =
-          replacedModules.stream()
-              .filter(
-                  replacedModule ->
-                      !Processors.hasAnnotation(replacedModule, ClassNames.INSTALL_IN))
-              .collect(toImmutableList());
-
-      ProcessorErrors.checkState(
-          nonInstallInModules.isEmpty(),
-          // TODO(b/152801981): this should really error on the annotation value
-          module,
-          "@TestInstallIn#replaces() can only contain @InstallIn modules, but found: %s",
-          nonInstallInModules);
-
-      ImmutableList<TypeElement> hiltWrapperModules =
-          replacedModules.stream()
-              .filter(
-                  replacedModule ->
-                      replacedModule.getSimpleName().toString().startsWith("HiltWrapper_"))
-              .collect(toImmutableList());
-
-      ProcessorErrors.checkState(
-          hiltWrapperModules.isEmpty(),
-          // TODO(b/152801981): this should really error on the annotation value
-          module,
-          "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper modules, "
-              + "but found: %s. ",
-          hiltWrapperModules);
-
-      if (!getPackage(module).getQualifiedName().toString().startsWith("dagger.hilt")) {
-        // Prevent external users from overriding Hilt's internal modules. Techincally, except for
-        // ApplicationContextModule, making all modules pkg-private should be enough but this is an
-        // extra measure of precaution.
-        ImmutableList<TypeElement> hiltInternalModules =
-            replacedModules.stream()
-                .filter(
-                    replacedModule ->
-                        getPackage(replacedModule)
-                            .getQualifiedName()
-                            .toString()
-                            .startsWith("dagger.hilt"))
-                .collect(toImmutableList());
-
-        ProcessorErrors.checkState(
-            hiltInternalModules.isEmpty(),
-            // TODO(b/152801981): this should really error on the annotation value
-            module,
-            "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: %s. ",
-            hiltInternalModules);
-      }
-
-      // Prevent users from uninstalling test-specific @InstallIn modules.
-      ImmutableList<TypeElement> replacedTestSpecificInstallIn =
-          replacedModules.stream()
-              .filter(
-                  replacedModule ->
-                      Processors.getOriginatingTestElement(replacedModule, getElementUtils())
-                          .isPresent())
-              .collect(toImmutableList());
-
-      ProcessorErrors.checkState(
-          replacedTestSpecificInstallIn.isEmpty(),
-          // TODO(b/152801981): this should really error on the annotation value
-          module,
-          "@TestInstallIn#replaces() cannot replace test specific @InstallIn modules, but found: "
-              + "%s. Please remove the @InstallIn module manually rather than replacing it.",
-          replacedTestSpecificInstallIn);
-    }
-
-    generateAggregatedDeps(
-        "modules",
-        module,
-        moduleAnnotation,
-        replacedModules.stream().map(ClassName::get).collect(toImmutableSet()));
-  }
-
-  private void processEntryPoint(
-      Element element, Optional<ClassName> installInAnnotation, ClassName entryPointAnnotation)
-      throws Exception {
-    ProcessorErrors.checkState(
-        installInAnnotation.isPresent() ,
-        element,
-        "@%s %s must also be annotated with @InstallIn",
-        entryPointAnnotation.simpleName(),
-        element);
-
-    ProcessorErrors.checkState(
-        !Processors.hasAnnotation(element, ClassNames.TEST_INSTALL_IN),
-        element,
-        "@TestInstallIn can only be used with modules");
-
-    ProcessorErrors.checkState(
-        element.getKind() == INTERFACE,
-        element,
-        "Only interfaces can be annotated with @%s: %s",
-        entryPointAnnotation.simpleName(),
-        element);
-    TypeElement entryPoint = asType(element);
-
-    if (entryPointAnnotation.equals(ClassNames.EARLY_ENTRY_POINT)) {
-      ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element);
-      ProcessorErrors.checkState(
-          components.equals(ImmutableSet.of(ClassNames.SINGLETON_COMPONENT)),
-          element,
-          "@EarlyEntryPoint can only be installed into the SingletonComponent. Found: %s",
-          components);
-
-      Optional<TypeElement> optionalTestElement =
-          Processors.getOriginatingTestElement(element, getElementUtils());
-      ProcessorErrors.checkState(
-          !optionalTestElement.isPresent(),
-          element,
-          "@EarlyEntryPoint-annotated entry point, %s, cannot be nested in (or originate from) "
-              + "a @HiltAndroidTest-annotated class, %s. This requirement is to avoid confusion "
-              + "with other, test-specific entry points.",
-          asType(element).getQualifiedName().toString(),
-          optionalTestElement
-              .map(testElement -> testElement.getQualifiedName().toString())
-              .orElse(""));
-    }
-
-    generateAggregatedDeps(
-        entryPointAnnotation.equals(ClassNames.COMPONENT_ENTRY_POINT)
-            ? "componentEntryPoints"
-            : "entryPoints",
-        entryPoint,
-        entryPointAnnotation,
-        ImmutableSet.of());
-  }
-
-  private void generateAggregatedDeps(
-      String key,
-      TypeElement element,
-      ClassName annotation,
-      ImmutableSet<ClassName> replacedModules)
-      throws Exception {
-    // Get @InstallIn components here to catch errors before skipping user's pkg-private element.
-    ImmutableSet<ClassName> components = Components.getComponents(getElementUtils(), element);
-
-    if (isValidKind(element)) {
-      Optional<PkgPrivateMetadata> pkgPrivateMetadata =
-          PkgPrivateMetadata.of(getElementUtils(), element, annotation);
-      if (pkgPrivateMetadata.isPresent()) {
-        if (key.contentEquals("modules")) {
-          new PkgPrivateModuleGenerator(getProcessingEnv(), pkgPrivateMetadata.get()).generate();
-        } else {
-          new PkgPrivateEntryPointGenerator(getProcessingEnv(), pkgPrivateMetadata.get())
-              .generate();
-        }
-      } else {
-        Optional<ClassName> testName =
-            Processors.getOriginatingTestElement(element, getElementUtils()).map(ClassName::get);
-        new AggregatedDepsGenerator(
-                key, element, testName, components, replacedModules, getProcessingEnv())
-            .generate();
-      }
-    }
-  }
-
-  private static Optional<ClassName> getAnnotation(
-      Element element, ImmutableSet<ClassName> annotations) {
-    ImmutableSet<ClassName> usedAnnotations =
-        annotations.stream()
-            .filter(annotation -> Processors.hasAnnotation(element, annotation))
-            .collect(toImmutableSet());
-
-    if (usedAnnotations.isEmpty()) {
-      return Optional.empty();
-    }
-
-    ProcessorErrors.checkState(
-        usedAnnotations.size() == 1,
-        element,
-        "Only one of the following annotations can be used on %s: %s",
-        element,
-        usedAnnotations);
-
-    return Optional.of(getOnlyElement(usedAnnotations));
-  }
-
-  private static boolean isValidKind(Element element) {
-    // don't go down the rabbit hole of analyzing undefined types. N.B. we don't issue
-    // an error here because javac already has and we don't want to spam the user.
-    return element.asType().getKind() != TypeKind.ERROR;
-  }
-
-  private boolean installInCheckDisabled(Element element) {
-    return isModuleInstallInCheckDisabled(getProcessingEnv())
-        || Processors.hasAnnotation(element, ClassNames.DISABLE_INSTALL_IN_CHECK);
-  }
-
-  /**
-   * When using Dagger Producers, don't process generated modules. They will not have the expected
-   * annotations.
-   */
-  private static boolean isDaggerGeneratedModule(Element element) {
-    if (!Processors.hasAnnotation(element, ClassNames.MODULE)) {
-      return false;
-    }
-    return element.getAnnotationMirrors().stream()
-        .filter(mirror -> isGenerated(mirror))
-        .map(mirror -> asString(getOnlyElement(asList(getAnnotationValue(mirror, "value")))))
-        .anyMatch(value -> value.startsWith("dagger"));
-  }
-
-  private static List<? extends AnnotationValue> asList(AnnotationValue value) {
-    return value.accept(
-        new SimpleAnnotationValueVisitor8<List<? extends AnnotationValue>, Void>() {
-          @Override
-          public List<? extends AnnotationValue> visitArray(
-              List<? extends AnnotationValue> value, Void unused) {
-            return value;
-          }
-        },
-        null);
-  }
-
-  private static String asString(AnnotationValue value) {
-    return value.accept(
-        new SimpleAnnotationValueVisitor8<String, Void>() {
-          @Override
-          public String visitString(String value, Void unused) {
-            return value;
-          }
-        },
-        null);
-  }
-
-  private static boolean isGenerated(AnnotationMirror annotationMirror) {
-    Name name = asType(annotationMirror.getAnnotationType().asElement()).getQualifiedName();
-    return name.contentEquals("javax.annotation.Generated")
-        || name.contentEquals("javax.annotation.processing.Generated");
+  protected BaseProcessingStep processingStep() {
+    return new AggregatedDepsProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD
index 257e312..2d1bd92 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/BUILD
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/BUILD
@@ -37,7 +37,9 @@
     name = "processor_lib",
     srcs = [
         "AggregatedDepsGenerator.java",
+        "AggregatedDepsProcessingStep.java",
         "AggregatedDepsProcessor.java",
+        "KspAggregatedDepsProcessor.java",
         "PkgPrivateEntryPointGenerator.java",
         "PkgPrivateModuleGenerator.java",
     ],
@@ -50,11 +52,12 @@
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -64,10 +67,10 @@
     deps = [
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processors",
-        "//java/dagger/hilt/processor/internal/kotlin",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -87,6 +90,7 @@
         "//java/dagger/hilt/processor/internal/root/ir",
         "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java
index ed71c98..14ae03d 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/ComponentDependencies.java
@@ -19,6 +19,8 @@
 import static com.google.common.base.Preconditions.checkState;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
@@ -27,8 +29,6 @@
 import dagger.hilt.processor.internal.ComponentDescriptor;
 import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
 import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /** Represents information needed to create a component (i.e. modules, entry points, etc) */
 @AutoValue
@@ -38,21 +38,21 @@
   }
 
   /** Returns the modules for a component, without any filtering. */
-  public abstract ImmutableSetMultimap<ClassName, TypeElement> modules();
+  public abstract ImmutableSetMultimap<ClassName, XTypeElement> modules();
 
   /** Returns the entry points associated with the given a component. */
-  public abstract ImmutableSetMultimap<ClassName, TypeElement> entryPoints();
+  public abstract ImmutableSetMultimap<ClassName, XTypeElement> entryPoints();
 
   /** Returns the component entry point associated with the given a component. */
-  public abstract ImmutableSetMultimap<ClassName, TypeElement> componentEntryPoints();
+  public abstract ImmutableSetMultimap<ClassName, XTypeElement> componentEntryPoints();
 
   @AutoValue.Builder
   abstract static class Builder {
-    abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> modulesBuilder();
+    abstract ImmutableSetMultimap.Builder<ClassName, XTypeElement> modulesBuilder();
 
-    abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> entryPointsBuilder();
+    abstract ImmutableSetMultimap.Builder<ClassName, XTypeElement> entryPointsBuilder();
 
-    abstract ImmutableSetMultimap.Builder<ClassName, TypeElement> componentEntryPointsBuilder();
+    abstract ImmutableSetMultimap.Builder<ClassName, XTypeElement> componentEntryPointsBuilder();
 
     abstract ComponentDependencies build();
   }
@@ -63,16 +63,16 @@
       ImmutableSet<AggregatedDepsMetadata> aggregatedDepsMetadata,
       ImmutableSet<AggregatedUninstallModulesMetadata> aggregatedUninstallModulesMetadata,
       ImmutableSet<AggregatedEarlyEntryPointMetadata> aggregatedEarlyEntryPointMetadata,
-      Elements elements) {
-    ImmutableSet<TypeElement> uninstalledModules =
-        ImmutableSet.<TypeElement>builder()
+      XProcessingEnv env) {
+    ImmutableSet<XTypeElement> uninstalledModules =
+        ImmutableSet.<XTypeElement>builder()
             .addAll(
                 aggregatedUninstallModulesMetadata.stream()
                     .flatMap(metadata -> metadata.uninstallModuleElements().stream())
                     // @AggregatedUninstallModules always references the user module, so convert to
                     // the generated public wrapper if needed.
                     // TODO(bcorso): Consider converting this to the public module in the processor.
-                    .map(module -> PkgPrivateMetadata.publicModule(module, elements))
+                    .map(module -> PkgPrivateMetadata.publicModule(module))
                     .collect(toImmutableSet()))
             .addAll(
                 aggregatedDepsMetadata.stream()
@@ -84,8 +84,8 @@
     ImmutableSet<ClassName> componentNames =
         descriptors.stream().map(ComponentDescriptor::component).collect(toImmutableSet());
     for (AggregatedDepsMetadata metadata : aggregatedDepsMetadata) {
-      for (TypeElement componentElement : metadata.componentElements()) {
-        ClassName componentName = ClassName.get(componentElement);
+      for (XTypeElement componentElement : metadata.componentElements()) {
+        ClassName componentName = componentElement.getClassName();
         checkState(
             componentNames.contains(componentName), "%s is not a valid Component.", componentName);
         switch (metadata.dependencyType()) {
@@ -115,7 +115,7 @@
                 // @AggregatedEarlyEntryPointMetadata always references the user module, so convert
                 // to the generated public wrapper if needed.
                 // TODO(bcorso): Consider converting this to the public module in the processor.
-                .map(entryPoint -> PkgPrivateMetadata.publicEarlyEntryPoint(entryPoint, elements))
+                .map(PkgPrivateMetadata::publicEarlyEntryPoint)
                 .collect(toImmutableSet()));
 
     return componentDependencies.build();
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/KspAggregatedDepsProcessor.java b/java/dagger/hilt/processor/internal/aggregateddeps/KspAggregatedDepsProcessor.java
new file mode 100644
index 0000000..32e450c
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/KspAggregatedDepsProcessor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.aggregateddeps;
+
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Processor that outputs dummy files to propagate information through multiple javac runs. */
+public final class KspAggregatedDepsProcessor extends KspBaseProcessingStepProcessor {
+
+  public KspAggregatedDepsProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new AggregatedDepsProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspAggregatedDepsProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspAggregatedDepsProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateEntryPointGenerator.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateEntryPointGenerator.java
index 6dac59f..d02bdfe 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateEntryPointGenerator.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateEntryPointGenerator.java
@@ -16,12 +16,14 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
-import com.squareup.javapoet.AnnotationSpec;
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /**
@@ -29,10 +31,10 @@
  * user's entrypoint to use pkg-private visibility to hide from external packages.
  */
 final class PkgPrivateEntryPointGenerator {
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final PkgPrivateMetadata metadata;
 
-  PkgPrivateEntryPointGenerator(ProcessingEnvironment env, PkgPrivateMetadata metadata) {
+  PkgPrivateEntryPointGenerator(XProcessingEnv env, PkgPrivateMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
   }
@@ -55,8 +57,9 @@
   void generate() throws IOException {
 
     TypeSpec.Builder entryPointInterfaceBuilder =
-        TypeSpec.interfaceBuilder(metadata.generatedClassName().simpleName())
-            .addOriginatingElement(metadata.getTypeElement())
+        JavaPoetExtKt.addOriginatingElement(
+                TypeSpec.interfaceBuilder(metadata.generatedClassName().simpleName()),
+                metadata.getTypeElement())
             .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.getTypeElement()))
             .addModifiers(Modifier.PUBLIC)
             .addSuperinterface(metadata.baseClassName())
@@ -64,14 +67,16 @@
 
     Processors.addGeneratedAnnotation(entryPointInterfaceBuilder, env, getClass());
 
-    if (metadata.getOptionalInstallInAnnotationMirror().isPresent()) {
+    if (metadata.getOptionalInstallInAnnotation().isPresent()) {
       entryPointInterfaceBuilder.addAnnotation(
-          AnnotationSpec.get(metadata.getOptionalInstallInAnnotationMirror().get()));
+          XAnnotations.getAnnotationSpec(metadata.getOptionalInstallInAnnotation().get()));
     }
 
-    JavaFile.builder(
-            metadata.generatedClassName().packageName(), entryPointInterfaceBuilder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(
+                    metadata.generatedClassName().packageName(), entryPointInterfaceBuilder.build())
+                .build(),
+            Mode.Isolating);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java
index f88a089..3956853 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateMetadata.java
@@ -16,45 +16,41 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
-import static com.google.auto.common.Visibility.effectiveVisibilityOfElement;
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 
-import com.google.auto.common.MoreElements;
-import com.google.auto.common.Visibility;
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeName;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils;
+import dagger.internal.codegen.xprocessing.XTypeElements;
 import java.util.Optional;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /** PkgPrivateModuleMetadata contains a set of utilities for processing package private modules. */
 @AutoValue
 public abstract class PkgPrivateMetadata {
   /** Returns the public Hilt wrapped type or the type itself if it is already public. */
-  public static TypeElement publicModule(TypeElement element, Elements elements) {
-    return publicDep(element, elements, ClassNames.MODULE);
+  public static XTypeElement publicModule(XTypeElement element) {
+    return publicDep(element, ClassNames.MODULE);
   }
 
   /** Returns the public Hilt wrapped type or the type itself if it is already public. */
-  public static TypeElement publicEarlyEntryPoint(TypeElement element, Elements elements) {
-    return publicDep(element, elements, ClassNames.EARLY_ENTRY_POINT);
+  public static XTypeElement publicEntryPoint(XTypeElement element) {
+    return publicDep(element, ClassNames.ENTRY_POINT);
   }
 
   /** Returns the public Hilt wrapped type or the type itself if it is already public. */
-  public static TypeElement publicEntryPoint(TypeElement element, Elements elements) {
-    return publicDep(element, elements, ClassNames.ENTRY_POINT);
+  public static XTypeElement publicEarlyEntryPoint(XTypeElement element) {
+    return publicDep(element, ClassNames.EARLY_ENTRY_POINT);
   }
 
-  private static TypeElement publicDep(
-      TypeElement element, Elements elements, ClassName annotation) {
-    return of(elements, element, annotation)
+  private static XTypeElement publicDep(XTypeElement element, ClassName annotation) {
+    return of(element, annotation)
         .map(PkgPrivateMetadata::generatedClassName)
         .map(ClassName::canonicalName)
-        .map(elements::getTypeElement)
+        .map(getProcessingEnv(element)::requireTypeElement)
         .orElse(element);
   }
 
@@ -62,44 +58,42 @@
 
   /** Returns the base class name of the elemenet. */
   TypeName baseClassName() {
-    return TypeName.get(getTypeElement().asType());
+    return getTypeElement().getClassName();
   }
 
   /** Returns TypeElement for the module element the metadata object represents */
-  abstract TypeElement getTypeElement();
+  abstract XTypeElement getTypeElement();
 
   /**
    * Returns an optional @InstallIn AnnotationMirror for the module element the metadata object
    * represents
    */
-  abstract Optional<AnnotationMirror> getOptionalInstallInAnnotationMirror();
+  abstract Optional<XAnnotation> getOptionalInstallInAnnotation();
 
   /** Return the Type of this package private element. */
   abstract ClassName getAnnotation();
 
-  /** Returns the expected genenerated classname for the element the metadata object represents */
+  /** Returns the expected generated classname for the element the metadata object represents */
   final ClassName generatedClassName() {
     return Processors.prepend(
-        Processors.getEnclosedClassName(ClassName.get(getTypeElement())), PREFIX);
+        Processors.getEnclosedClassName(getTypeElement().getClassName()), PREFIX);
   }
 
   /**
    * Returns an Optional PkgPrivateMetadata requiring Hilt processing, otherwise returns an empty
    * Optional.
    */
-  static Optional<PkgPrivateMetadata> of(
-      Elements elements, TypeElement element, ClassName annotation) {
+  static Optional<PkgPrivateMetadata> of(XTypeElement element, ClassName annotation) {
     // If this is a public element no wrapping is needed
-    if (effectiveVisibilityOfElement(element) == Visibility.PUBLIC
-        && !KotlinMetadataUtils.getMetadataUtil().isVisibilityInternal(element)) {
+    if (XTypeElements.isEffectivelyPublic(element) && !element.isInternal()) {
       return Optional.empty();
     }
 
-    Optional<AnnotationMirror> installIn;
-    if (Processors.hasAnnotation(element, ClassNames.INSTALL_IN)) {
-      installIn = Optional.of(Processors.getAnnotationMirror(element, ClassNames.INSTALL_IN));
-    } else if (Processors.hasAnnotation(element, ClassNames.TEST_INSTALL_IN)) {
-      installIn = Optional.of(Processors.getAnnotationMirror(element, ClassNames.TEST_INSTALL_IN));
+    Optional<XAnnotation> installIn;
+    if (element.hasAnnotation(ClassNames.INSTALL_IN)) {
+      installIn = Optional.of(element.getAnnotation(ClassNames.INSTALL_IN));
+    } else if (element.hasAnnotation(ClassNames.TEST_INSTALL_IN)) {
+      installIn = Optional.of(element.getAnnotation(ClassNames.TEST_INSTALL_IN));
     } else {
       throw new IllegalStateException(
           "Expected element to be annotated with @InstallIn: " + element);
@@ -112,11 +106,10 @@
       // error more confusing for users since they probably aren't aware of the wrapper. When
       // skipped, if the root is in a different package, the error will instead just be on the
       // generated Hilt component.
-      if (Processors.requiresModuleInstance(elements, MoreElements.asType(element))) {
+      if (Processors.requiresModuleInstance(element)) {
         return Optional.empty();
       }
     }
-    return Optional.of(
-        new AutoValue_PkgPrivateMetadata(MoreElements.asType(element), installIn, annotation));
+    return Optional.of(new AutoValue_PkgPrivateMetadata(element, installIn, annotation));
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateModuleGenerator.java b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateModuleGenerator.java
index 18ff0bd..3225c0b 100644
--- a/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateModuleGenerator.java
+++ b/java/dagger/hilt/processor/internal/aggregateddeps/PkgPrivateModuleGenerator.java
@@ -16,12 +16,15 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /**
@@ -30,10 +33,10 @@
  * install the module when the component is created in another package.
  */
 final class PkgPrivateModuleGenerator {
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final PkgPrivateMetadata metadata;
 
-  PkgPrivateModuleGenerator(ProcessingEnvironment env, PkgPrivateMetadata metadata) {
+  PkgPrivateModuleGenerator(XProcessingEnv env, PkgPrivateMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
   }
@@ -53,21 +56,22 @@
   void generate() throws IOException {
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(metadata.generatedClassName().simpleName())
-            .addOriginatingElement(metadata.getTypeElement())
             .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.getTypeElement()))
             .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
             // generated @InstallIn is exactly the same as the module being processed
             .addAnnotation(
-                AnnotationSpec.get(metadata.getOptionalInstallInAnnotationMirror().get()))
+                XAnnotations.getAnnotationSpec(metadata.getOptionalInstallInAnnotation().get()))
             .addAnnotation(
                 AnnotationSpec.builder(metadata.getAnnotation())
-                    .addMember("includes", "$T.class", metadata.getTypeElement())
+                    .addMember("includes", "$T.class", metadata.getTypeElement().getClassName())
                     .build());
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.getTypeElement());
 
     Processors.addGeneratedAnnotation(builder, env, getClass());
 
-    JavaFile.builder(metadata.generatedClassName().packageName(), builder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(metadata.generatedClassName().packageName(), builder.build()).build(),
+            Mode.Isolating);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessingStep.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessingStep.java
new file mode 100644
index 0000000..ad54223
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessingStep.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.aliasof;
+
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.internal.codegen.extension.DaggerStreams;
+import dagger.internal.codegen.xprocessing.XElements;
+
+/** Processes the annotations annotated with {@link dagger.hilt.migration.AliasOf} */
+public final class AliasOfProcessingStep extends BaseProcessingStep {
+
+  public AliasOfProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  public ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.ALIAS_OF);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    ProcessorErrors.checkState(
+        element.hasAnnotation(ClassNames.SCOPE),
+        element,
+        "%s should only be used on scopes." + " However, it was found annotating %s",
+        annotation.simpleName(),
+        XElements.toStableString(element));
+
+    XAnnotation xAnnotation = element.getAnnotation(ClassNames.ALIAS_OF);
+
+    ImmutableList<XTypeElement> defineComponentScopes =
+        xAnnotation.getAsTypeList("value").stream()
+            .map(XType::getTypeElement)
+            .collect(DaggerStreams.toImmutableList());
+
+    ProcessorErrors.checkState(
+        defineComponentScopes.size() >= 1,
+        element,
+        "@AliasOf annotation %s must declare at least one scope to alias.",
+        xAnnotation.getClassName());
+
+    new AliasOfPropagatedDataGenerator(XElements.asTypeElement(element), defineComponentScopes)
+        .generate();
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java
index 6c6a734..aeab7a1 100644
--- a/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfProcessor.java
@@ -16,54 +16,19 @@
 
 package dagger.hilt.processor.internal.aliasof;
 
-import static com.google.auto.common.MoreElements.asType;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
-import java.util.Set;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Processes the annotations annotated with {@link dagger.hilt.migration.AliasOf} */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class AliasOfProcessor extends BaseProcessor {
+public final class AliasOfProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public Set<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.ALIAS_OF.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    ProcessorErrors.checkState(
-        Processors.hasAnnotation(element, ClassNames.SCOPE),
-        element,
-        "%s should only be used on scopes." + " However, it was found annotating %s",
-        annotation,
-        element);
-
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF);
-
-    ImmutableList<TypeElement> defineComponentScopes =
-        Processors.getAnnotationClassValues(getElementUtils(), annotationMirror, "value");
-
-    ProcessorErrors.checkState(
-        defineComponentScopes.size() >= 1,
-        element,
-        "@AliasOf annotation %s must declare at least one scope to alias.",
-        annotationMirror);
-
-    new AliasOfPropagatedDataGenerator(getProcessingEnv(), asType(element), defineComponentScopes)
-        .generate();
+  public AliasOfProcessingStep processingStep() {
+    return new AliasOfProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java
index a2441f9..62c69b4 100644
--- a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataGenerator.java
@@ -16,45 +16,39 @@
 
 package dagger.hilt.processor.internal.aliasof;
 
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.AnnotationSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 
 /** Generates resource files for {@link dagger.hilt.migration.AliasOf}. */
 final class AliasOfPropagatedDataGenerator {
 
-  private final ProcessingEnvironment processingEnv;
-  private final TypeElement aliasScope;
-  private final ImmutableList<TypeElement> defineComponentScopes;
+  private final XTypeElement aliasScope;
+  private final ImmutableList<XTypeElement> defineComponentScopes;
 
   AliasOfPropagatedDataGenerator(
-      ProcessingEnvironment processingEnv,
-      TypeElement aliasScope,
-      ImmutableList<TypeElement> defineComponentScopes) {
-    this.processingEnv = processingEnv;
+      XTypeElement aliasScope,
+      ImmutableList<XTypeElement> defineComponentScopes) {
     this.aliasScope = aliasScope;
     this.defineComponentScopes = defineComponentScopes;
   }
 
-  void generate() throws IOException {
+  void generate() {
     Processors.generateAggregatingClass(
         ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE,
         propagatedDataAnnotation(),
         aliasScope,
-        getClass(),
-        processingEnv);
+        getClass());
   }
 
   private AnnotationSpec propagatedDataAnnotation() {
     AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassNames.ALIAS_OF_PROPAGATED_DATA);
-    for (TypeElement defineComponentScope : defineComponentScopes) {
-      builder.addMember("defineComponentScopes", "$T.class", defineComponentScope);
+    for (XTypeElement defineComponentScope : defineComponentScopes) {
+      builder.addMember("defineComponentScopes", "$T.class", defineComponentScope.getClassName());
     }
-    builder.addMember("alias", "$T.class", aliasScope);
+    builder.addMember("alias", "$T.class", aliasScope.getClassName());
     return builder.build();
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java
index d0c89db..c1a7bb0 100644
--- a/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfPropagatedDataMetadata.java
@@ -19,21 +19,20 @@
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XAnnotationValue;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.AggregatedElements;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.BadInputException;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 
 /**
  * A class that represents the values stored in an {@link
@@ -43,62 +42,58 @@
 public abstract class AliasOfPropagatedDataMetadata {
 
   /** Returns the aggregating element */
-  public abstract TypeElement aggregatingElement();
+  public abstract XTypeElement aggregatingElement();
 
-  abstract ImmutableList<TypeElement> defineComponentScopeElements();
+  abstract ImmutableList<XTypeElement> defineComponentScopeElements();
 
-  abstract TypeElement aliasElement();
+  abstract XTypeElement aliasElement();
 
   /** Returns metadata for all aggregated elements in the aggregating package. */
-  public static ImmutableSet<AliasOfPropagatedDataMetadata> from(Elements elements) {
+  public static ImmutableSet<AliasOfPropagatedDataMetadata> from(XProcessingEnv env) {
     return from(
         AggregatedElements.from(
-            ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE,
-            ClassNames.ALIAS_OF_PROPAGATED_DATA,
-            elements),
-        elements);
+            ClassNames.ALIAS_OF_PROPAGATED_DATA_PACKAGE, ClassNames.ALIAS_OF_PROPAGATED_DATA, env));
   }
 
   /** Returns metadata for each aggregated element. */
   public static ImmutableSet<AliasOfPropagatedDataMetadata> from(
-      ImmutableSet<TypeElement> aggregatedElements, Elements elements) {
+      ImmutableSet<XTypeElement> aggregatedElements) {
     return aggregatedElements.stream()
-        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .map(AliasOfPropagatedDataMetadata::create)
         .collect(toImmutableSet());
   }
 
   public static AliasOfPropagatedDataIr toIr(AliasOfPropagatedDataMetadata metadata) {
     return new AliasOfPropagatedDataIr(
-        ClassName.get(metadata.aggregatingElement()),
+        metadata.aggregatingElement().getClassName(),
         metadata.defineComponentScopeElements().stream()
-            .map(ClassName::get)
+            .map(XTypeElement::getClassName)
             .collect(toImmutableList()),
-        ClassName.get(metadata.aliasElement()));
+        metadata.aliasElement().getClassName());
   }
 
-  private static AliasOfPropagatedDataMetadata create(TypeElement element, Elements elements) {
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.ALIAS_OF_PROPAGATED_DATA);
+  private static AliasOfPropagatedDataMetadata create(XTypeElement element) {
+    XAnnotation annotation = element.getAnnotation(ClassNames.ALIAS_OF_PROPAGATED_DATA);
 
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(elements, annotationMirror);
+    // TODO(kuanyingchou) We can remove this once we have
+    // `XAnnotation.hasAnnotationValue(methodName: String)`.
+    ImmutableMap<String, XAnnotationValue> values = Processors.getAnnotationValues(annotation);
 
-    ImmutableList<TypeElement> defineComponentScopes;
+    ImmutableList<XTypeElement> defineComponentScopes;
+
     if (values.containsKey("defineComponentScopes")) {
       defineComponentScopes =
-          ImmutableList.copyOf(
-              AnnotationValues.getTypeElements(values.get("defineComponentScopes")));
+          XAnnotations.getAsTypeElementList(annotation, "defineComponentScopes");
     } else if (values.containsKey("defineComponentScope")) {
       // Older version of AliasOfPropagatedData only passed a single defineComponentScope class
       // value. Fall back on reading the single value if we get old propagated data.
-      defineComponentScopes =
-          ImmutableList.of(AnnotationValues.getTypeElement(values.get("defineComponentScope")));
+      defineComponentScopes = XAnnotations.getAsTypeElementList(annotation, "defineComponentScope");
     } else {
       throw new BadInputException(
           "AliasOfPropagatedData is missing defineComponentScopes", element);
     }
 
     return new AutoValue_AliasOfPropagatedDataMetadata(
-        element, defineComponentScopes, AnnotationValues.getTypeElement(values.get("alias")));
+        element, defineComponentScopes, annotation.getAsType("alias").getTypeElement());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java
index 1fee2a3..fb45597 100644
--- a/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java
+++ b/java/dagger/hilt/processor/internal/aliasof/AliasOfs.java
@@ -40,12 +40,12 @@
     ImmutableSetMultimap.Builder<ClassName, ClassName> builder = ImmutableSetMultimap.builder();
     metadatas.forEach(
         metadata -> {
-          ClassName aliasScopeName = ClassName.get(metadata.aliasElement());
+          ClassName aliasScopeName = metadata.aliasElement().getClassName();
           metadata
               .defineComponentScopeElements()
               .forEach(
                   defineComponentScope -> {
-                    ClassName defineComponentScopeName = ClassName.get(defineComponentScope);
+                    ClassName defineComponentScopeName = defineComponentScope.getClassName();
                     ProcessorErrors.checkState(
                         defineComponentScopes.contains(defineComponentScopeName),
                         metadata.aliasElement(),
diff --git a/java/dagger/hilt/processor/internal/aliasof/BUILD b/java/dagger/hilt/processor/internal/aliasof/BUILD
index d589cb2..3c72e71 100644
--- a/java/dagger/hilt/processor/internal/aliasof/BUILD
+++ b/java/dagger/hilt/processor/internal/aliasof/BUILD
@@ -27,19 +27,23 @@
 java_library(
     name = "processor_lib",
     srcs = [
+        "AliasOfProcessingStep.java",
         "AliasOfProcessor.java",
         "AliasOfPropagatedDataGenerator.java",
+        "KspAliasOfProcessor.java",
     ],
     deps = [
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -57,6 +61,7 @@
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/root/ir",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
diff --git a/java/dagger/hilt/processor/internal/aliasof/KspAliasOfProcessor.java b/java/dagger/hilt/processor/internal/aliasof/KspAliasOfProcessor.java
new file mode 100644
index 0000000..083783e
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/aliasof/KspAliasOfProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.aliasof;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Processes the annotations annotated with {@link dagger.hilt.migration.AliasOf} */
+public final class KspAliasOfProcessor extends KspBaseProcessingStepProcessor {
+  public KspAliasOfProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new AliasOfProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspAliasOfProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspAliasOfProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/definecomponent/BUILD b/java/dagger/hilt/processor/internal/definecomponent/BUILD
index 9701a06..a259b72 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/BUILD
+++ b/java/dagger/hilt/processor/internal/definecomponent/BUILD
@@ -30,29 +30,50 @@
 java_library(
     name = "processor_lib",
     srcs = [
+        "DefineComponentProcessingStep.java",
         "DefineComponentProcessor.java",
+        "KspDefineComponentProcessor.java",
     ],
     deps = [
-        ":define_components",
+        ":metadatas",
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
+    ],
+)
+
+java_library(
+    name = "metadatas",
+    srcs = [
+        "DefineComponentBuilderMetadatas.java",
+        "DefineComponentMetadatas.java",
+    ],
+    deps = [
+        "//java/dagger/hilt/processor/internal:classnames",
+        "//java/dagger/hilt/processor/internal:processor_errors",
+        "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
+        "//third_party/java/auto:value",
+        "//third_party/java/guava/collect",
+        "//third_party/java/javapoet",
     ],
 )
 
 java_library(
     name = "define_components",
     srcs = [
-        "DefineComponentBuilderMetadatas.java",
         "DefineComponentClassesMetadata.java",
-        "DefineComponentMetadatas.java",
         "DefineComponents.java",
     ],
     deps = [
+        ":metadatas",
         "//java/dagger/hilt/processor/internal:aggregated_elements",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:component_descriptor",
@@ -60,7 +81,7 @@
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/root/ir",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentBuilderMetadatas.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentBuilderMetadatas.java
index 0f10f39..f4bebf9 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentBuilderMetadatas.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentBuilderMetadatas.java
@@ -16,29 +16,26 @@
 
 package dagger.hilt.processor.internal.definecomponent;
 
-import static javax.lang.model.element.Modifier.STATIC;
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
+import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
 
-import com.google.auto.common.MoreElements;
-import com.google.auto.common.MoreTypes;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XFieldElement;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
-import com.squareup.javapoet.ClassName;
-import com.squareup.javapoet.TypeName;
+import com.google.common.collect.ImmutableList;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
+import dagger.internal.codegen.xprocessing.XTypes;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.ElementFilter;
 
 /** Metadata for types annotated with {@link dagger.hilt.DefineComponent.Builder}. */
 final class DefineComponentBuilderMetadatas {
@@ -46,115 +43,125 @@
     return new DefineComponentBuilderMetadatas(componentMetadatas);
   }
 
-  private final Map<Element, DefineComponentBuilderMetadata> builderMetadatas = new HashMap<>();
+  private final Map<XElement, DefineComponentBuilderMetadata> builderMetadatas = new HashMap<>();
   private final DefineComponentMetadatas componentMetadatas;
 
   private DefineComponentBuilderMetadatas(DefineComponentMetadatas componentMetadatas) {
     this.componentMetadatas = componentMetadatas;
   }
 
-  DefineComponentBuilderMetadata get(Element element) {
+  DefineComponentBuilderMetadata get(XElement element) {
     if (!builderMetadatas.containsKey(element)) {
       builderMetadatas.put(element, getUncached(element));
     }
     return builderMetadatas.get(element);
   }
 
-  private DefineComponentBuilderMetadata getUncached(Element element) {
+  private DefineComponentBuilderMetadata getUncached(XElement element) {
     ProcessorErrors.checkState(
-        Processors.hasAnnotation(element, ClassNames.DEFINE_COMPONENT_BUILDER),
+        element.hasAnnotation(ClassNames.DEFINE_COMPONENT_BUILDER),
         element,
         "%s, expected to be annotated with @DefineComponent.Builder. Found: %s",
-        element,
-        element.getAnnotationMirrors());
+        XElements.toStableString(element),
+        element.getAllAnnotations().stream()
+            .map(XAnnotations::toStableString)
+            .collect(toImmutableList()));
 
     // TODO(bcorso): Allow abstract classes?
     ProcessorErrors.checkState(
-        element.getKind().equals(ElementKind.INTERFACE),
+        isTypeElement(element) && asTypeElement(element).isInterface(),
         element,
         "@DefineComponent.Builder is only allowed on interfaces. Found: %s",
-        element);
-    TypeElement builder = MoreElements.asType(element);
+        XElements.toStableString(element));
+    XTypeElement builder = asTypeElement(element);
 
     // TODO(bcorso): Allow extending interfaces?
     ProcessorErrors.checkState(
-        builder.getInterfaces().isEmpty(),
+        builder.getSuperInterfaces().isEmpty(),
         builder,
         "@DefineComponent.Builder %s, cannot extend a super class or interface. Found: %s",
-        builder,
-        builder.getInterfaces());
+        XElements.toStableString(builder),
+        builder.getSuperInterfaces().stream()
+            .map(XTypes::toStableString)
+            .collect(toImmutableList()));
 
     // TODO(bcorso): Allow type parameters?
     ProcessorErrors.checkState(
         builder.getTypeParameters().isEmpty(),
         builder,
         "@DefineComponent.Builder %s, cannot have type parameters.",
-        builder.asType());
+        XTypes.toStableString(builder.getType()));
 
-    List<VariableElement> nonStaticFields =
-        ElementFilter.fieldsIn(builder.getEnclosedElements()).stream()
-            .filter(method -> !method.getModifiers().contains(STATIC))
-            .collect(Collectors.toList());
+    ImmutableList<XFieldElement> nonStaticFields =
+        builder.getDeclaredFields().stream()
+            .filter(field -> !field.isStatic())
+            .collect(toImmutableList());
+
     ProcessorErrors.checkState(
         nonStaticFields.isEmpty(),
         builder,
         "@DefineComponent.Builder %s, cannot have non-static fields. Found: %s",
-        builder,
-        nonStaticFields);
+        XElements.toStableString(builder),
+        nonStaticFields.stream()
+            .map(XElements::toStableString)
+            .collect(toImmutableList()));
 
-    List<ExecutableElement> buildMethods =
-        ElementFilter.methodsIn(builder.getEnclosedElements()).stream()
-            .filter(method -> !method.getModifiers().contains(STATIC))
+    ImmutableList<XMethodElement> buildMethods =
+        builder.getDeclaredMethods().stream()
+            .filter(method -> !method.isStatic())
             .filter(method -> method.getParameters().isEmpty())
-            .collect(Collectors.toList());
+            .collect(toImmutableList());
 
     ProcessorErrors.checkState(
         buildMethods.size() == 1,
         builder,
         "@DefineComponent.Builder %s, must have exactly 1 build method that takes no parameters. "
             + "Found: %s",
-        builder,
-        buildMethods);
+        XElements.toStableString(builder),
+        buildMethods.stream()
+            .map(XElements::toStableString)
+            .collect(toImmutableList()));
 
-    ExecutableElement buildMethod = buildMethods.get(0);
-    TypeMirror component = buildMethod.getReturnType();
+    XMethodElement buildMethod = buildMethods.get(0);
+    XType componentType = buildMethod.getReturnType();
     ProcessorErrors.checkState(
-        buildMethod.getReturnType().getKind().equals(TypeKind.DECLARED)
-            && Processors.hasAnnotation(
-                MoreTypes.asTypeElement(component), ClassNames.DEFINE_COMPONENT),
+        isDeclared(componentType)
+            && componentType.getTypeElement().hasAnnotation(ClassNames.DEFINE_COMPONENT),
         builder,
         "@DefineComponent.Builder method, %s#%s, must return a @DefineComponent type. Found: %s",
-        builder,
-        buildMethod,
-        component);
+        XElements.toStableString(builder),
+        XElements.toStableString(buildMethod),
+        XTypes.toStableString(componentType));
 
-    List<ExecutableElement> nonStaticNonBuilderMethods =
-        ElementFilter.methodsIn(builder.getEnclosedElements()).stream()
-            .filter(method -> !method.getModifiers().contains(STATIC))
+    ImmutableList<XMethodElement> nonStaticNonBuilderMethods =
+        builder.getDeclaredMethods().stream()
+            .filter(method -> !method.isStatic())
             .filter(method -> !method.equals(buildMethod))
-            .filter(method -> !TypeName.get(method.getReturnType()).equals(ClassName.get(builder)))
-            .collect(Collectors.toList());
+            .filter(method -> !method.getReturnType().getTypeName().equals(builder.getClassName()))
+            .collect(toImmutableList());
 
-    ProcessorErrors.checkState(
+    ProcessorErrors.checkStateX(
         nonStaticNonBuilderMethods.isEmpty(),
         nonStaticNonBuilderMethods,
         "@DefineComponent.Builder %s, all non-static methods must return %s or %s. Found: %s",
-        builder,
-        builder,
-        component,
-        nonStaticNonBuilderMethods);
+        XElements.toStableString(builder),
+        XElements.toStableString(builder),
+        XTypes.toStableString(componentType),
+        nonStaticNonBuilderMethods.stream()
+            .map(XElements::toStableString)
+            .collect(toImmutableList()));
 
     return new AutoValue_DefineComponentBuilderMetadatas_DefineComponentBuilderMetadata(
         builder,
         buildMethod,
-        componentMetadatas.get(MoreTypes.asTypeElement(component)));
+        componentMetadatas.get(componentType.getTypeElement()));
   }
 
   @AutoValue
   abstract static class DefineComponentBuilderMetadata {
-    abstract TypeElement builder();
+    abstract XTypeElement builder();
 
-    abstract ExecutableElement buildMethod();
+    abstract XMethodElement buildMethod();
 
     abstract DefineComponentMetadata componentMetadata();
   }
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java
index 280269f..da3277d 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentClassesMetadata.java
@@ -16,22 +16,18 @@
 
 package dagger.hilt.processor.internal.definecomponent;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.AggregatedElements;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /**
  * A class that represents the values stored in an {@link
@@ -41,13 +37,13 @@
 public abstract class DefineComponentClassesMetadata {
 
   /** Returns the aggregating element */
-  public abstract TypeElement aggregatingElement();
+  public abstract XTypeElement aggregatingElement();
 
   /**
    * Returns the element annotated with {@code dagger.hilt.internal.definecomponent.DefineComponent}
    * or {@code dagger.hilt.internal.definecomponent.DefineComponent.Builder}.
    */
-  public abstract TypeElement element();
+  public abstract XTypeElement element();
 
   /** Returns {@code true} if this element represents a component. */
   abstract boolean isComponent();
@@ -58,32 +54,25 @@
   }
 
   /** Returns metadata for all aggregated elements in the aggregating package. */
-  public static ImmutableSet<DefineComponentClassesMetadata> from(Elements elements) {
+  public static ImmutableSet<DefineComponentClassesMetadata> from(XProcessingEnv env) {
     return from(
         AggregatedElements.from(
-            ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE,
-            ClassNames.DEFINE_COMPONENT_CLASSES,
-            elements),
-        elements);
+            ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE, ClassNames.DEFINE_COMPONENT_CLASSES, env));
   }
 
   /** Returns metadata for each aggregated element. */
   public static ImmutableSet<DefineComponentClassesMetadata> from(
-      ImmutableSet<TypeElement> aggregatedElements, Elements elements) {
+      ImmutableSet<XTypeElement> aggregatedElements) {
     return aggregatedElements.stream()
-        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .map(aggregatedElement -> create(aggregatedElement))
         .collect(toImmutableSet());
   }
 
-  private static DefineComponentClassesMetadata create(TypeElement element, Elements elements) {
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.DEFINE_COMPONENT_CLASSES);
+  private static DefineComponentClassesMetadata create(XTypeElement element) {
+    XAnnotation annotation = element.getAnnotation(ClassNames.DEFINE_COMPONENT_CLASSES);
 
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(elements, annotationMirror);
-
-    String componentName = AnnotationValues.getString(values.get("component"));
-    String builderName = AnnotationValues.getString(values.get("builder"));
+    String componentName = annotation.getAsString("component");
+    String builderName = annotation.getAsString("builder");
 
     ProcessorErrors.checkState(
         !(componentName.isEmpty() && builderName.isEmpty()),
@@ -97,7 +86,8 @@
 
     boolean isComponent = !componentName.isEmpty();
     String componentOrBuilderName = isComponent ? componentName : builderName;
-    TypeElement componentOrBuilderElement = elements.getTypeElement(componentOrBuilderName);
+    XTypeElement componentOrBuilderElement =
+        getProcessingEnv(element).findTypeElement(componentOrBuilderName);
     ProcessorErrors.checkState(
         componentOrBuilderElement != null,
         componentOrBuilderElement,
@@ -111,7 +101,7 @@
 
   public static DefineComponentClassesIr toIr(DefineComponentClassesMetadata metadata) {
     return new DefineComponentClassesIr(
-        ClassName.get(metadata.aggregatingElement()),
-        ClassName.get(metadata.element()).canonicalName());
+        metadata.aggregatingElement().getClassName(),
+        metadata.element().getClassName().canonicalName());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java
index 60864c2..0381e6c 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentMetadatas.java
@@ -16,34 +16,29 @@
 
 package dagger.hilt.processor.internal.definecomponent;
 
-import static com.google.auto.common.AnnotationMirrors.getAnnotationElementAndValue;
-import static com.google.auto.common.MoreElements.asType;
-import static com.google.auto.common.MoreTypes.asTypeElement;
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
 import static java.util.stream.Collectors.joining;
-import static javax.lang.model.element.Modifier.STATIC;
 
-import com.google.auto.common.MoreTypes;
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XAnnotationValue;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XExecutableElement;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
+import dagger.internal.codegen.xprocessing.XTypes;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.stream.Collectors;
-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.TypeElement;
-import javax.lang.model.util.ElementFilter;
 
 /** Metadata for types annotated with {@link dagger.hilt.DefineComponent}. */
 final class DefineComponentMetadatas {
@@ -51,16 +46,16 @@
     return new DefineComponentMetadatas();
   }
 
-  private final Map<Element, DefineComponentMetadata> metadatas = new HashMap<>();
+  private final Map<XElement, DefineComponentMetadata> metadatas = new HashMap<>();
 
   private DefineComponentMetadatas() {}
 
   /** Returns the metadata for an element annotated with {@link dagger.hilt.DefineComponent}. */
-  DefineComponentMetadata get(Element element) {
+  DefineComponentMetadata get(XElement element) {
     return get(element, new LinkedHashSet<>());
   }
 
-  private DefineComponentMetadata get(Element element, LinkedHashSet<Element> childPath) {
+  private DefineComponentMetadata get(XElement element, LinkedHashSet<XElement> childPath) {
     if (!metadatas.containsKey(element)) {
       metadatas.put(element, getUncached(element, childPath));
     }
@@ -68,102 +63,106 @@
   }
 
   private DefineComponentMetadata getUncached(
-      Element element, LinkedHashSet<Element> childPath) {
+      XElement element, LinkedHashSet<XElement> childPath) {
     ProcessorErrors.checkState(
         childPath.add(element),
         element,
         "@DefineComponent cycle: %s -> %s",
-        childPath.stream().map(Object::toString).collect(joining(" -> ")),
-        element);
+        childPath.stream().map(XElements::toStableString).collect(joining(" -> ")),
+        XElements.toStableString(element));
 
     ProcessorErrors.checkState(
-        Processors.hasAnnotation(element, ClassNames.DEFINE_COMPONENT),
+        element.hasAnnotation(ClassNames.DEFINE_COMPONENT),
         element,
         "%s, expected to be annotated with @DefineComponent. Found: %s",
-        element,
-        element.getAnnotationMirrors());
+        XElements.toStableString(element),
+        element.getAllAnnotations().stream()
+            .map(XAnnotations::toStableString)
+            .collect(toImmutableList()));
 
     // TODO(bcorso): Allow abstract classes?
     ProcessorErrors.checkState(
-        element.getKind().equals(ElementKind.INTERFACE),
+        isTypeElement(element) && asTypeElement(element).isInterface(),
         element,
         "@DefineComponent is only allowed on interfaces. Found: %s",
-        element);
-    TypeElement component = asType(element);
+        XElements.toStableString(element));
+    XTypeElement component = asTypeElement(element);
 
     // TODO(bcorso): Allow extending interfaces?
     ProcessorErrors.checkState(
-        component.getInterfaces().isEmpty(),
+        component.getSuperInterfaces().isEmpty(),
         component,
         "@DefineComponent %s, cannot extend a super class or interface. Found: %s",
-        component,
-        component.getInterfaces());
+        XElements.toStableString(component),
+        component.getSuperInterfaces().stream()
+            .map(XTypes::toStableString)
+            .collect(toImmutableList()));
 
     // TODO(bcorso): Allow type parameters?
     ProcessorErrors.checkState(
         component.getTypeParameters().isEmpty(),
         component,
         "@DefineComponent %s, cannot have type parameters.",
-        component.asType());
+        XTypes.toStableString(component.getType()));
 
     // TODO(bcorso): Allow non-static abstract methods (aka EntryPoints)?
-    List<ExecutableElement> nonStaticMethods =
-        ElementFilter.methodsIn(component.getEnclosedElements()).stream()
-            .filter(method -> !method.getModifiers().contains(STATIC))
-            .collect(Collectors.toList());
+    ImmutableList<XExecutableElement> nonStaticMethods =
+        component.getDeclaredMethods().stream()
+            .filter(method -> !method.isStatic())
+            .collect(toImmutableList());
+
     ProcessorErrors.checkState(
         nonStaticMethods.isEmpty(),
         component,
         "@DefineComponent %s, cannot have non-static methods. Found: %s",
-        component,
-        nonStaticMethods);
+        XElements.toStableString(component),
+        nonStaticMethods.stream()
+            .map(XElements::toStableString)
+            .collect(toImmutableList()));
 
     // No need to check non-static fields since interfaces can't have them.
 
-    ImmutableList<TypeElement> scopes =
+    ImmutableList<XTypeElement> scopes =
         Processors.getScopeAnnotations(component).stream()
-            .map(AnnotationMirror::getAnnotationType)
-            .map(MoreTypes::asTypeElement)
+            .map(XAnnotation::getTypeElement)
             .collect(toImmutableList());
 
-    ImmutableList<AnnotationMirror> aliasScopes =
-        Processors.getAnnotationsAnnotatedWith(component, ClassNames.ALIAS_OF);
+    ImmutableList<XAnnotation> aliasScopes =
+        ImmutableList.copyOf(component.getAnnotationsAnnotatedWith(ClassNames.ALIAS_OF));
     ProcessorErrors.checkState(
         aliasScopes.isEmpty(),
         component,
         "@DefineComponent %s, references invalid scope(s) annotated with @AliasOf. "
             + "@DefineComponent scopes cannot be aliases of other scopes: %s",
-        component,
-        aliasScopes);
+        XElements.toStableString(component),
+        aliasScopes.stream().map(XAnnotations::toStableString).collect(toImmutableList()));
 
-    AnnotationMirror mirror =
-        Processors.getAnnotationMirror(component, ClassNames.DEFINE_COMPONENT);
-    AnnotationValue parentValue = getAnnotationElementAndValue(mirror, "parent").getValue();
+    XAnnotation annotation = component.getAnnotation(ClassNames.DEFINE_COMPONENT);
+    XAnnotationValue parentValue = annotation.getAnnotationValue("parent");
 
     ProcessorErrors.checkState(
-        // TODO(bcorso): Contribute a check to auto/common AnnotationValues.
         !"<error>".contentEquals(parentValue.getValue().toString()),
         component,
         "@DefineComponent %s, references an invalid parent type: %s",
-        component,
-        mirror);
+        XElements.toStableString(component),
+        XAnnotations.toStableString(annotation));
 
-    TypeElement parent = asTypeElement(AnnotationValues.getTypeMirror(parentValue));
+    XTypeElement parent = parentValue.asType().getTypeElement();
 
     ProcessorErrors.checkState(
-        ClassName.get(parent).equals(ClassNames.DEFINE_COMPONENT_NO_PARENT)
-            || Processors.hasAnnotation(parent, ClassNames.DEFINE_COMPONENT),
+        parent.getClassName().equals(ClassNames.DEFINE_COMPONENT_NO_PARENT)
+            || parent.hasAnnotation(ClassNames.DEFINE_COMPONENT),
         component,
         "@DefineComponent %s, references a type not annotated with @DefineComponent: %s",
-        component,
-        parent);
+        XElements.toStableString(component),
+        XElements.toStableString(parent));
 
     Optional<DefineComponentMetadata> parentComponent =
-        ClassName.get(parent).equals(ClassNames.DEFINE_COMPONENT_NO_PARENT)
+        parent.getClassName().equals(ClassNames.DEFINE_COMPONENT_NO_PARENT)
             ? Optional.empty()
             : Optional.of(get(parent, childPath));
 
-    ClassName componentClassName = ClassName.get(component);
+    ClassName componentClassName = component.getClassName();
 
     ProcessorErrors.checkState(
         parentComponent.isPresent()
@@ -172,7 +171,7 @@
         "@DefineComponent %s is missing a parent declaration.\n"
             + "Please declare the parent, for example: @DefineComponent(parent ="
             + " SingletonComponent.class)",
-        component);
+        XElements.toStableString(component));
 
     ProcessorErrors.checkState(
         componentClassName.equals(ClassNames.SINGLETON_COMPONENT)
@@ -180,7 +179,7 @@
         component,
         "Cannot have a component with the same simple name as the reserved %s: %s",
         ClassNames.SINGLETON_COMPONENT.simpleName(),
-        componentClassName);
+        componentClassName.canonicalName());
 
     return new AutoValue_DefineComponentMetadatas_DefineComponentMetadata(
         component, scopes, parentComponent);
@@ -190,10 +189,10 @@
   abstract static class DefineComponentMetadata {
 
     /** Returns the component annotated with {@link dagger.hilt.DefineComponent}. */
-    abstract TypeElement component();
+    abstract XTypeElement component();
 
     /** Returns the scopes of the component. */
-    abstract ImmutableList<TypeElement> scopes();
+    abstract ImmutableList<XTypeElement> scopes();
 
     /** Returns the parent component, if one exists. */
     abstract Optional<DefineComponentMetadata> parentMetadata();
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessingStep.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessingStep.java
new file mode 100644
index 0000000..fe21227
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessingStep.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.definecomponent;
+
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.AnnotationSpec;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.Processors;
+import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata;
+import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata;
+
+/**
+ * A processor for {@link dagger.hilt.DefineComponent} and {@link
+ * dagger.hilt.DefineComponent.Builder}.
+ */
+public final class DefineComponentProcessingStep extends BaseProcessingStep {
+  // Note: these caches should be cleared between rounds.
+  private DefineComponentMetadatas componentMetadatas;
+  private DefineComponentBuilderMetadatas componentBuilderMetadatas;
+
+  public DefineComponentProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  public void preProcess(XProcessingEnv env, XRoundEnv round) {
+    componentMetadatas = DefineComponentMetadatas.create();
+    componentBuilderMetadatas = DefineComponentBuilderMetadatas.create(componentMetadatas);
+  }
+
+  @Override
+  public void postProcess(XProcessingEnv env, XRoundEnv round) {
+    componentMetadatas = null;
+    componentBuilderMetadatas = null;
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.DEFINE_COMPONENT, ClassNames.DEFINE_COMPONENT_BUILDER);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    if (annotation.equals(ClassNames.DEFINE_COMPONENT)) {
+      // TODO(bcorso): For cycles we currently process each element in the cycle. We should skip
+      // processing of subsequent elements in a cycle, but this requires ensuring that the first
+      // element processed is always the same so that our failure tests are stable.
+      DefineComponentMetadata metadata = componentMetadatas.get(element);
+      generateFile("component", metadata.component());
+    } else if (annotation.equals(ClassNames.DEFINE_COMPONENT_BUILDER)) {
+      DefineComponentBuilderMetadata metadata = componentBuilderMetadatas.get(element);
+      generateFile("builder", metadata.builder());
+    } else {
+      throw new AssertionError("Unhandled annotation type: " + annotation.canonicalName());
+    }
+  }
+
+  private void generateFile(String member, XTypeElement typeElement) {
+    Processors.generateAggregatingClass(
+        ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE,
+        AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES)
+            .addMember(member, "$S", typeElement.getQualifiedName())
+            .build(),
+        typeElement,
+        getClass());
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java
index f7e54a0..81bc43a 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessor.java
@@ -19,19 +19,9 @@
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.AnnotationSpec;
-import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
-import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata;
-import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata;
-import java.io.IOException;
-import java.util.Set;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /**
@@ -40,41 +30,9 @@
  */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class DefineComponentProcessor extends BaseProcessor {
-  private final DefineComponentMetadatas componentMetadatas = DefineComponentMetadatas.create();
-  private final DefineComponentBuilderMetadatas componentBuilderMetadatas =
-      DefineComponentBuilderMetadatas.create(componentMetadatas);
-
+public final class DefineComponentProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public Set<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(
-        ClassNames.DEFINE_COMPONENT.toString(), ClassNames.DEFINE_COMPONENT_BUILDER.toString());
-  }
-
-  @Override
-  protected void processEach(TypeElement annotation, Element element) throws Exception {
-    if (ClassName.get(annotation).equals(ClassNames.DEFINE_COMPONENT)) {
-      // TODO(bcorso): For cycles we currently process each element in the cycle. We should skip
-      // processing of subsequent elements in a cycle, but this requires ensuring that the first
-      // element processed is always the same so that our failure tests are stable.
-      DefineComponentMetadata metadata = componentMetadatas.get(element);
-      generateFile("component", metadata.component());
-    } else if (ClassName.get(annotation).equals(ClassNames.DEFINE_COMPONENT_BUILDER)) {
-      DefineComponentBuilderMetadata metadata = componentBuilderMetadatas.get(element);
-      generateFile("builder", metadata.builder());
-    } else {
-      throw new AssertionError("Unhandled annotation type: " + annotation);
-    }
-  }
-
-  private void generateFile(String member, TypeElement typeElement) throws IOException {
-    Processors.generateAggregatingClass(
-        ClassNames.DEFINE_COMPONENT_CLASSES_PACKAGE,
-        AnnotationSpec.builder(ClassNames.DEFINE_COMPONENT_CLASSES)
-            .addMember(member, "$S", typeElement.getQualifiedName())
-            .build(),
-        typeElement,
-        getClass(),
-        getProcessingEnv());
+  protected BaseProcessingStep processingStep() {
+    return new DefineComponentProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java
index e9200d0..b5b41af 100644
--- a/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java
+++ b/java/dagger/hilt/processor/internal/definecomponent/DefineComponents.java
@@ -19,63 +19,34 @@
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
-import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ComponentDescriptor;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.definecomponent.DefineComponentBuilderMetadatas.DefineComponentBuilderMetadata;
 import dagger.hilt.processor.internal.definecomponent.DefineComponentMetadatas.DefineComponentMetadata;
-import java.util.HashMap;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 
 /**
  * A utility class for getting {@link DefineComponentMetadata} and {@link
  * DefineComponentBuilderMetadata}.
  */
 public final class DefineComponents {
-
   public static DefineComponents create() {
     return new DefineComponents();
   }
 
-  private final Map<Element, ComponentDescriptor> componentDescriptors = new HashMap<>();
   private final DefineComponentMetadatas componentMetadatas = DefineComponentMetadatas.create();
   private final DefineComponentBuilderMetadatas componentBuilderMetadatas =
       DefineComponentBuilderMetadatas.create(componentMetadatas);
 
   private DefineComponents() {}
 
-  /** Returns the {@link ComponentDescriptor} for the given component element. */
-  // TODO(b/144940889): This descriptor doesn't contain the "creator" or the "installInName".
-  public ComponentDescriptor componentDescriptor(Element element) {
-    if (!componentDescriptors.containsKey(element)) {
-      componentDescriptors.put(element, uncachedComponentDescriptor(element));
-    }
-    return componentDescriptors.get(element);
-  }
-
-  private ComponentDescriptor uncachedComponentDescriptor(Element element) {
-    DefineComponentMetadata metadata = componentMetadatas.get(element);
-    ComponentDescriptor.Builder builder =
-        ComponentDescriptor.builder()
-            .component(ClassName.get(metadata.component()))
-            .scopes(metadata.scopes().stream().map(ClassName::get).collect(toImmutableSet()));
-
-
-    metadata.parentMetadata()
-        .map(DefineComponentMetadata::component)
-        .map(this::componentDescriptor)
-        .ifPresent(builder::parent);
-
-    return builder.build();
-  }
-
   /** Returns the set of aggregated {@link ComponentDescriptor}s. */
   public ImmutableSet<ComponentDescriptor> getComponentDescriptors(
       ImmutableSet<DefineComponentClassesMetadata> aggregatedMetadatas) {
@@ -99,17 +70,17 @@
 
     // Check that there are not multiple builders per component
     for (DefineComponentMetadata componentMetadata : builderMultimap.keySet()) {
-      TypeElement component = componentMetadata.component();
+      XTypeElement component = componentMetadata.component();
       ProcessorErrors.checkState(
           builderMultimap.get(componentMetadata).size() <= 1,
           component,
           "Multiple @%s declarations are not allowed for @%s type, %s. Found: %s",
           ClassNames.DEFINE_COMPONENT_BUILDER,
           ClassNames.DEFINE_COMPONENT,
-          component,
+          XElements.toStableString(component),
           builderMultimap.get(componentMetadata).stream()
               .map(DefineComponentBuilderMetadata::builder)
-              .map(TypeElement::toString)
+              .map(XTypeElement::getQualifiedName)
               .sorted()
               .collect(toImmutableList()));
     }
@@ -128,13 +99,15 @@
       Map<DefineComponentMetadata, DefineComponentBuilderMetadata> builderMap) {
     ComponentDescriptor.Builder builder =
         ComponentDescriptor.builder()
-            .component(ClassName.get(componentMetadata.component()))
+            .component(componentMetadata.component().getClassName())
             .scopes(
-                componentMetadata.scopes().stream().map(ClassName::get).collect(toImmutableSet()));
+                componentMetadata.scopes().stream()
+                    .map(XTypeElement::getClassName)
+                    .collect(toImmutableSet()));
 
 
     if (builderMap.containsKey(componentMetadata)) {
-      builder.creator(ClassName.get(builderMap.get(componentMetadata).builder()));
+      builder.creator(builderMap.get(componentMetadata).builder().getClassName());
     }
 
     componentMetadata
diff --git a/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentProcessor.java b/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentProcessor.java
new file mode 100644
index 0000000..497c1aa
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentProcessor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.definecomponent;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/**
+ * A processor for {@link dagger.hilt.DefineComponent} and {@link
+ * dagger.hilt.DefineComponent.Builder}.
+ */
+public final class KspDefineComponentProcessor extends KspBaseProcessingStepProcessor {
+  public KspDefineComponentProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new DefineComponentProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspDefineComponentProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspDefineComponentProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentValidationProcessor.java b/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentValidationProcessor.java
new file mode 100644
index 0000000..d46921e
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/definecomponent/KspDefineComponentValidationProcessor.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.definecomponent;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/**
+ * A processor for {@link dagger.hilt.DefineComponent} and {@link
+ * dagger.hilt.DefineComponent.Builder}.
+ */
+public final class KspDefineComponentValidationProcessor extends KspBaseProcessingStepProcessor {
+  public KspDefineComponentValidationProcessor(
+      SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new DefineComponentValidationProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspDefineComponentValidationProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspDefineComponentValidationProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD b/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD
index c06f9fe..b9caf58 100644
--- a/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD
+++ b/java/dagger/hilt/processor/internal/disableinstallincheck/BUILD
@@ -30,16 +30,20 @@
 java_library(
     name = "processor_lib",
     srcs = [
+        "DisableInstallInCheckProcessingStep.java",
         "DisableInstallInCheckProcessor.java",
+        "KspDisableInstallInCheckProcessor.java",
     ],
     deps = [
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processor_errors",
-        "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
+        "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
diff --git a/java/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessingStep.java b/java/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessingStep.java
new file mode 100644
index 0000000..591889c
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessingStep.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.disableinstallincheck;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.internal.codegen.xprocessing.XElements;
+
+/** Processes the annotations annotated with {@link dagger.hilt.migration.DisableInstallInCheck} */
+public final class DisableInstallInCheckProcessingStep extends BaseProcessingStep {
+  public DisableInstallInCheckProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.DISABLE_INSTALL_IN_CHECK);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    ProcessorErrors.checkState(
+        element.hasAnnotation(ClassNames.MODULE),
+        element,
+        "@DisableInstallInCheck should only be used on modules. However, it was found annotating"
+            + " %s",
+        XElements.toStableString(element));
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessor.java b/java/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessor.java
index c51f350..873e049 100644
--- a/java/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessor.java
+++ b/java/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessor.java
@@ -19,32 +19,16 @@
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableSet;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Processes the annotations annotated with {@link dagger.hilt.migration.DisableInstallInCheck} */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class DisableInstallInCheckProcessor extends BaseProcessor {
+public final class DisableInstallInCheckProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public ImmutableSet<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.DISABLE_INSTALL_IN_CHECK.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) {
-    ProcessorErrors.checkState(
-        Processors.hasAnnotation(element, ClassNames.MODULE),
-        element,
-        "@DisableInstallInCheck should only be used on modules. However, it was found annotating"
-            + " %s",
-        element);
+  public DisableInstallInCheckProcessingStep processingStep() {
+    return new DisableInstallInCheckProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/disableinstallincheck/KspDisableInstallInCheckProcessor.java b/java/dagger/hilt/processor/internal/disableinstallincheck/KspDisableInstallInCheckProcessor.java
new file mode 100644
index 0000000..98ff17a
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/disableinstallincheck/KspDisableInstallInCheckProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.disableinstallincheck;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Processes the annotations annotated with {@link dagger.hilt.migration.DisableInstallInCheck} */
+public final class KspDisableInstallInCheckProcessor extends KspBaseProcessingStepProcessor {
+  public KspDisableInstallInCheckProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  public DisableInstallInCheckProcessingStep processingStep() {
+    return new DisableInstallInCheckProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspDisableInstallInCheckProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspDisableInstallInCheckProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java
index ae34118..bfe11a7 100644
--- a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointGenerator.java
@@ -16,35 +16,29 @@
 
 package dagger.hilt.processor.internal.earlyentrypoint;
 
+import androidx.room.compiler.processing.XTypeElement;
 import com.squareup.javapoet.AnnotationSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 
 /**
  * Generates an {@link dagger.hilt.android.internal.earlyentrypoint.AggregatedEarlyEntryPoint}
  * annotation.
  */
 final class AggregatedEarlyEntryPointGenerator {
+  private final XTypeElement earlyEntryPoint;
 
-  private final ProcessingEnvironment env;
-  private final TypeElement earlyEntryPoint;
-
-  AggregatedEarlyEntryPointGenerator(TypeElement earlyEntryPoint, ProcessingEnvironment env) {
+  AggregatedEarlyEntryPointGenerator(XTypeElement earlyEntryPoint) {
     this.earlyEntryPoint = earlyEntryPoint;
-    this.env = env;
   }
 
-  void generate() throws IOException {
+  void generate() {
     Processors.generateAggregatingClass(
         ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE,
         AnnotationSpec.builder(ClassNames.AGGREGATED_EARLY_ENTRY_POINT)
             .addMember("earlyEntryPoint", "$S", earlyEntryPoint.getQualifiedName())
             .build(),
         earlyEntryPoint,
-        getClass(),
-        env);
+        getClass());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java
index cf88757..35d3720 100644
--- a/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/AggregatedEarlyEntryPointMetadata.java
@@ -16,21 +16,17 @@
 
 package dagger.hilt.processor.internal.earlyentrypoint;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.AggregatedElements;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /**
  * A class that represents the values stored in an {@link
@@ -40,44 +36,39 @@
 public abstract class AggregatedEarlyEntryPointMetadata {
 
   /** Returns the aggregating element */
-  public abstract TypeElement aggregatingElement();
+  public abstract XTypeElement aggregatingElement();
 
   /** Returns the element annotated with {@link dagger.hilt.android.EarlyEntryPoint}. */
-  public abstract TypeElement earlyEntryPoint();
+  public abstract XTypeElement earlyEntryPoint();
 
   /** Returns metadata for all aggregated elements in the aggregating package. */
-  public static ImmutableSet<AggregatedEarlyEntryPointMetadata> from(Elements elements) {
+  public static ImmutableSet<AggregatedEarlyEntryPointMetadata> from(XProcessingEnv env) {
     return from(
         AggregatedElements.from(
             ClassNames.AGGREGATED_EARLY_ENTRY_POINT_PACKAGE,
             ClassNames.AGGREGATED_EARLY_ENTRY_POINT,
-            elements),
-        elements);
+            env));
   }
 
   /** Returns metadata for each aggregated element. */
   public static ImmutableSet<AggregatedEarlyEntryPointMetadata> from(
-      ImmutableSet<TypeElement> aggregatedElements, Elements elements) {
+      ImmutableSet<XTypeElement> aggregatedElements) {
     return aggregatedElements.stream()
-        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .map(aggregatedElement -> create(aggregatedElement, getProcessingEnv(aggregatedElement)))
         .collect(toImmutableSet());
   }
 
   public static AggregatedEarlyEntryPointIr toIr(AggregatedEarlyEntryPointMetadata metadata) {
     return new AggregatedEarlyEntryPointIr(
-        ClassName.get(metadata.aggregatingElement()),
-        ClassName.get(metadata.earlyEntryPoint()).canonicalName());
+        metadata.aggregatingElement().getClassName(),
+        metadata.earlyEntryPoint().getClassName().canonicalName());
   }
 
-  private static AggregatedEarlyEntryPointMetadata create(TypeElement element, Elements elements) {
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_EARLY_ENTRY_POINT);
-
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(elements, annotationMirror);
+  private static AggregatedEarlyEntryPointMetadata create(
+      XTypeElement element, XProcessingEnv env) {
+    XAnnotation annotation = element.getAnnotation(ClassNames.AGGREGATED_EARLY_ENTRY_POINT);
 
     return new AutoValue_AggregatedEarlyEntryPointMetadata(
-        element,
-        elements.getTypeElement(AnnotationValues.getString(values.get("earlyEntryPoint"))));
+        element, env.requireTypeElement(annotation.getAsString("earlyEntryPoint")));
   }
 }
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD
index 7bbc90c..81607e9 100644
--- a/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/BUILD
@@ -28,17 +28,20 @@
     name = "processor_lib",
     srcs = [
         "AggregatedEarlyEntryPointGenerator.java",
+        "EarlyEntryPointProcessingStep.java",
         "EarlyEntryPointProcessor.java",
+        "KspEarlyEntryPointProcessor.java",
     ],
     deps = [
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processors",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -53,6 +56,7 @@
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/root/ir",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessingStep.java b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessingStep.java
new file mode 100644
index 0000000..f2fa9a0
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessingStep.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.earlyentrypoint;
+
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+
+/** Validates {@link dagger.hilt.android.EarlyEntryPoint} usages. */
+public final class EarlyEntryPointProcessingStep extends BaseProcessingStep {
+  public EarlyEntryPointProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  public ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.EARLY_ENTRY_POINT);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    new AggregatedEarlyEntryPointGenerator(asTypeElement(element)).generate();
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java
index 848fa31..8454f6c 100644
--- a/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/EarlyEntryPointProcessor.java
@@ -16,30 +16,19 @@
 
 package dagger.hilt.processor.internal.earlyentrypoint;
 
-import static com.google.auto.common.MoreElements.asType;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableSet;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Validates {@link dagger.hilt.android.EarlyEntryPoint} usages. */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class EarlyEntryPointProcessor extends BaseProcessor {
-
+public final class EarlyEntryPointProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public ImmutableSet<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.EARLY_ENTRY_POINT.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    new AggregatedEarlyEntryPointGenerator(asType(element), getProcessingEnv()).generate();
+  public EarlyEntryPointProcessingStep processingStep() {
+    return new EarlyEntryPointProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/earlyentrypoint/KspEarlyEntryPointProcessor.java b/java/dagger/hilt/processor/internal/earlyentrypoint/KspEarlyEntryPointProcessor.java
new file mode 100644
index 0000000..04c93da
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/earlyentrypoint/KspEarlyEntryPointProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.earlyentrypoint;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Validates {@link dagger.hilt.android.EarlyEntryPoint} usages. */
+public final class KspEarlyEntryPointProcessor extends KspBaseProcessingStepProcessor {
+
+  public KspEarlyEntryPointProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  public EarlyEntryPointProcessingStep processingStep() {
+    return new EarlyEntryPointProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspEarlyEntryPointProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspEarlyEntryPointProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/generatesrootinput/BUILD b/java/dagger/hilt/processor/internal/generatesrootinput/BUILD
index 2b56408..afd611f 100644
--- a/java/dagger/hilt/processor/internal/generatesrootinput/BUILD
+++ b/java/dagger/hilt/processor/internal/generatesrootinput/BUILD
@@ -27,8 +27,10 @@
 java_library(
     name = "processor_lib",
     srcs = [
+        "GeneratesRootInputProcessingStep.java",
         "GeneratesRootInputProcessor.java",
         "GeneratesRootInputPropagatedDataGenerator.java",
+        "KspGeneratesRootInputProcessor.java",
     ],
     deps = [
         ":generates_root_inputs",
@@ -36,10 +38,12 @@
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -53,6 +57,7 @@
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
diff --git a/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessingStep.java b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessingStep.java
new file mode 100644
index 0000000..f464dc4
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessingStep.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.generatesrootinput;
+
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.internal.codegen.xprocessing.XElements;
+
+/**
+ * Processes the annotations annotated with {@link dagger.hilt.GeneratesRootInput} which generate
+ * input for components and should be processed before component creation.
+ */
+public final class GeneratesRootInputProcessingStep extends BaseProcessingStep {
+  public GeneratesRootInputProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.GENERATES_ROOT_INPUT);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    ProcessorErrors.checkState(
+        isTypeElement(element) && asTypeElement(element).isAnnotationClass(),
+        element,
+        "%s should only annotate other annotations. However, it was found annotating %s",
+        annotation.simpleName(),
+        XElements.toStableString(element));
+
+    new GeneratesRootInputPropagatedDataGenerator(processingEnv(), asTypeElement(element))
+        .generate();
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessor.java b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessor.java
index 6588e09..afd4cba 100644
--- a/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessor.java
+++ b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessor.java
@@ -19,15 +19,8 @@
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableSet;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import java.util.Set;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /**
@@ -36,22 +29,9 @@
  */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class GeneratesRootInputProcessor extends BaseProcessor {
-
+public final class GeneratesRootInputProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public Set<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.GENERATES_ROOT_INPUT.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    ProcessorErrors.checkState(
-        element.getKind().equals(ElementKind.ANNOTATION_TYPE),
-        element,
-        "%s should only annotate other annotations. However, it was found annotating %s",
-        annotation,
-        element);
-
-    new GeneratesRootInputPropagatedDataGenerator(this.getProcessingEnv(), element).generate();
+  public GeneratesRootInputProcessingStep processingStep() {
+    return new GeneratesRootInputProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputPropagatedDataGenerator.java b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputPropagatedDataGenerator.java
index 5c53894..f0c08e0 100644
--- a/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputPropagatedDataGenerator.java
+++ b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputPropagatedDataGenerator.java
@@ -16,41 +16,40 @@
 
 package dagger.hilt.processor.internal.generatesrootinput;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
 
 /** Generates resource files for {@link GeneratesRootInputs}. */
 final class GeneratesRootInputPropagatedDataGenerator {
-  private final ProcessingEnvironment processingEnv;
-  private final Element element;
+  private final XProcessingEnv processingEnv;
+  private final XTypeElement element;
 
-  GeneratesRootInputPropagatedDataGenerator(ProcessingEnvironment processingEnv, Element element) {
+  GeneratesRootInputPropagatedDataGenerator(XProcessingEnv processingEnv, XTypeElement element) {
     this.processingEnv = processingEnv;
     this.element = element;
   }
 
-  void generate() throws IOException {
-    TypeSpec.Builder generator =
-        TypeSpec.classBuilder(Processors.getFullEnclosedName(element))
-            .addOriginatingElement(element)
-            .addAnnotation(
-                AnnotationSpec.builder(ClassNames.GENERATES_ROOT_INPUT_PROPAGATED_DATA)
-                    .addMember("value", "$T.class", element)
-                    .build())
-            .addJavadoc(
-                "Generated class to"
-                    + "get the list of annotations that generate input for root.\n");
+  void generate() {
+    TypeSpec.Builder generator = TypeSpec.classBuilder(Processors.getFullEnclosedName(element));
+
+    JavaPoetExtKt.addOriginatingElement(generator, element)
+        .addAnnotation(
+            AnnotationSpec.builder(ClassNames.GENERATES_ROOT_INPUT_PROPAGATED_DATA)
+                .addMember("value", "$T.class", element.getClassName())
+                .build())
+        .addJavadoc(
+            "Generated class to get the list of annotations that generate input for root.\n");
 
     Processors.addGeneratedAnnotation(generator, processingEnv, getClass());
-
-    JavaFile.builder(GeneratesRootInputs.AGGREGATING_PACKAGE, generator.build())
-        .build()
-        .writeTo(processingEnv.getFiler());
+    JavaFile javaFile =
+        JavaFile.builder(GeneratesRootInputs.AGGREGATING_PACKAGE, generator.build()).build();
+    processingEnv.getFiler().write(javaFile, Mode.Isolating);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputs.java b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputs.java
index 139592e..eb1a8ee 100644
--- a/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputs.java
+++ b/java/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputs.java
@@ -16,87 +16,79 @@
 
 package dagger.hilt.processor.internal.generatesrootinput;
 
-import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Suppliers.memoize;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 import java.util.List;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.PackageElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /** Extracts the list of annotations annotated with {@link dagger.hilt.GeneratesRootInput}. */
 public final class GeneratesRootInputs {
   static final String AGGREGATING_PACKAGE =
       GeneratesRootInputs.class.getPackage().getName() + ".codegen";
 
-  private final Elements elements;
+  private final XProcessingEnv env;
   private final Supplier<ImmutableList<ClassName>> generatesRootInputAnnotations =
       memoize(() -> getAnnotationList());
 
-  public GeneratesRootInputs(ProcessingEnvironment processingEnvironment) {
-    this.elements = processingEnvironment.getElementUtils();
+  public GeneratesRootInputs(XProcessingEnv processingEnvironment) {
+    this.env = processingEnvironment;
   }
 
-  public ImmutableSet<Element> getElementsToWaitFor(RoundEnvironment roundEnv) {
+  public ImmutableSet<XElement> getElementsToWaitFor(XRoundEnv roundEnv) {
     // Processing can only take place after all dependent annotations have been processed
     // Note: We start with ClassName rather than TypeElement because jdk8 does not treat type
     // elements as equal across rounds. Thus, in order for RoundEnvironment#getElementsAnnotatedWith
     // to work properly, we get new elements to ensure it works across rounds (See b/148693284).
     return generatesRootInputAnnotations.get().stream()
-        .map(className -> elements.getTypeElement(className.toString()))
+        .map(className -> env.findTypeElement(className.toString()))
         .filter(element -> element != null)
-        .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream())
+        .flatMap(
+            annotation -> roundEnv.getElementsAnnotatedWith(annotation.getQualifiedName()).stream())
         .collect(toImmutableSet());
   }
 
   private ImmutableList<ClassName> getAnnotationList() {
-    PackageElement packageElement = elements.getPackageElement(AGGREGATING_PACKAGE);
-
-    if (packageElement == null) {
-      return ImmutableList.of();
-    }
-
-    List<? extends Element> annotationElements = packageElement.getEnclosedElements();
-    checkState(!annotationElements.isEmpty(), "No elements Found in package %s.", packageElement);
+    List<? extends XTypeElement> annotationElements =
+        env.getTypeElementsFromPackage(AGGREGATING_PACKAGE);
 
     ImmutableList.Builder<ClassName> builder = ImmutableList.builder();
-    for (Element element : annotationElements) {
+    for (XTypeElement element : annotationElements) {
       ProcessorErrors.checkState(
-          element.getKind() == ElementKind.CLASS,
+          element.isClass(),
           element,
           "Only classes may be in package %s. Did you add custom code in the package?",
-          packageElement);
+          AGGREGATING_PACKAGE);
 
-      AnnotationMirror annotationMirror =
-          Processors.getAnnotationMirror(element, ClassNames.GENERATES_ROOT_INPUT_PROPAGATED_DATA);
+      XAnnotation annotation =
+          element.getAnnotation(ClassNames.GENERATES_ROOT_INPUT_PROPAGATED_DATA);
       ProcessorErrors.checkState(
-          annotationMirror != null,
+          annotation != null,
           element,
           "Classes in package %s must be annotated with @%s: %s."
               + " Found: %s. Files in this package are generated, did you add custom code in the"
               + " package? ",
-          packageElement,
+          AGGREGATING_PACKAGE,
           ClassNames.GENERATES_ROOT_INPUT_PROPAGATED_DATA,
-          element.getSimpleName(),
-          element.getAnnotationMirrors());
+          element.getClassName().simpleName(),
+          element.getAllAnnotations().stream()
+              .map(XAnnotations::toStableString)
+              .collect(toImmutableSet()));
 
-      TypeElement annotation =
-          Processors.getAnnotationClassValue(elements, annotationMirror, "value");
+      XTypeElement value = annotation.getAsType("value").getTypeElement();
 
-      builder.add(ClassName.get(annotation));
+      builder.add(value.getClassName());
     }
     // This annotation was on Dagger so it couldn't be annotated with @GeneratesRootInput to be
     // cultivated later. We have to manually add it to the list.
diff --git a/java/dagger/hilt/processor/internal/generatesrootinput/KspGeneratesRootInputProcessor.java b/java/dagger/hilt/processor/internal/generatesrootinput/KspGeneratesRootInputProcessor.java
new file mode 100644
index 0000000..e9ffee5
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/generatesrootinput/KspGeneratesRootInputProcessor.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.generatesrootinput;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/**
+ * Processes the annotations annotated with {@link dagger.hilt.GeneratesRootInput} which generate
+ * input for components and should be processed before component creation.
+ */
+public final class KspGeneratesRootInputProcessor extends KspBaseProcessingStepProcessor {
+  public KspGeneratesRootInputProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected GeneratesRootInputProcessingStep processingStep() {
+    return new GeneratesRootInputProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspGeneratesRootInputProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspGeneratesRootInputProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/kotlin/BUILD b/java/dagger/hilt/processor/internal/kotlin/BUILD
index 5705393..437ea0f 100644
--- a/java/dagger/hilt/processor/internal/kotlin/BUILD
+++ b/java/dagger/hilt/processor/internal/kotlin/BUILD
@@ -25,9 +25,8 @@
     deps = [
         "//:dagger_with_compiler",
         "//java/dagger/hilt/processor/internal:classnames",
-        "//java/dagger/hilt/processor/internal:element_descriptors",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
diff --git a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadata.java b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadata.java
index 4d7c1fc..e98b2b3 100644
--- a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadata.java
+++ b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadata.java
@@ -16,34 +16,27 @@
 
 package dagger.hilt.processor.internal.kotlin;
 
-import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
-import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults;
-import static com.google.auto.common.AnnotationValues.getAnnotationValues;
-import static com.google.auto.common.MoreElements.getAnnotationMirror;
-import static dagger.hilt.processor.internal.ElementDescriptors.getMethodDescriptor;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
 import static kotlinx.metadata.Flag.ValueParameter.DECLARES_DEFAULT_VALUE;
 
-import com.google.auto.common.AnnotationValues;
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XFieldElement;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ElementDescriptors;
 import dagger.internal.codegen.extension.DaggerCollectors;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
 import javax.annotation.Nullable;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.util.ElementFilter;
 import kotlin.Metadata;
 import kotlinx.metadata.Flag;
 import kotlinx.metadata.KmClass;
@@ -65,21 +58,21 @@
   private static final String DELEGATED_PROPERTY_NAME_SUFFIX = "$delegate";
 
   // Map that associates field elements with its Kotlin synthetic method for annotations.
-  private final Map<VariableElement, Optional<MethodForAnnotations>>
-      elementFieldAnnotationMethodMap = new HashMap<>();
-
-  // Map that associates field elements with its Kotlin getter method.
-  private final Map<VariableElement, Optional<ExecutableElement>> elementFieldGetterMethodMap =
+  private final Map<XFieldElement, Optional<MethodForAnnotations>> elementFieldAnnotationMethodMap =
       new HashMap<>();
 
-  abstract TypeElement typeElement();
+  // Map that associates field elements with its Kotlin getter method.
+  private final Map<XFieldElement, Optional<XMethodElement>> elementFieldGetterMethodMap =
+      new HashMap<>();
+
+  abstract XTypeElement typeElement();
 
   abstract ClassMetadata classMetadata();
 
   @Memoized
-  ImmutableMap<String, ExecutableElement> methodDescriptors() {
-    return ElementFilter.methodsIn(typeElement().getEnclosedElements()).stream()
-        .collect(toImmutableMap(ElementDescriptors::getMethodDescriptor, Function.identity()));
+  ImmutableMap<String, XMethodElement> methodDescriptors() {
+    return typeElement().getDeclaredMethods().stream()
+        .collect(toImmutableMap(XMethodElement::getJvmDescriptor, Function.identity()));
   }
 
   /** Returns true if any constructor of the defined a default parameter. */
@@ -91,7 +84,7 @@
   }
 
   /** Gets the synthetic method for annotations of a given field element. */
-  Optional<ExecutableElement> getSyntheticAnnotationMethod(VariableElement fieldElement) {
+  Optional<XMethodElement> getSyntheticAnnotationMethod(XFieldElement fieldElement) {
     return getAnnotationMethod(fieldElement)
         .map(
             methodForAnnotations -> {
@@ -99,29 +92,16 @@
                 throw new IllegalStateException(
                     "Method for annotations is missing for " + fieldElement);
               }
-              return methodForAnnotations.method();
+              return XElements.asMethod(methodForAnnotations.method());
             });
   }
 
-  /**
-   * Returns true if the synthetic method for annotations is missing. This can occur when inspecting
-   * the Kotlin metadata of a property from another compilation unit.
-   */
-  boolean isMissingSyntheticAnnotationMethod(VariableElement fieldElement) {
-    return getAnnotationMethod(fieldElement)
-        .map(methodForAnnotations -> methodForAnnotations == MethodForAnnotations.MISSING)
-        // This can be missing if there was no property annotation at all (e.g. no annotations or
-        // the qualifier is already properly attached to the field). For these cases, it isn't
-        // considered missing since there was no method to look for in the first place.
-        .orElse(false);
-  }
-
-  private Optional<MethodForAnnotations> getAnnotationMethod(VariableElement fieldElement) {
+  private Optional<MethodForAnnotations> getAnnotationMethod(XFieldElement fieldElement) {
     return elementFieldAnnotationMethodMap.computeIfAbsent(
         fieldElement, this::getAnnotationMethodUncached);
   }
 
-  private Optional<MethodForAnnotations> getAnnotationMethodUncached(VariableElement fieldElement) {
+  private Optional<MethodForAnnotations> getAnnotationMethodUncached(XFieldElement fieldElement) {
     return findProperty(fieldElement)
         .methodForAnnotationsSignature()
         .map(
@@ -134,19 +114,19 @@
   }
 
   /** Gets the getter method of a given field element corresponding to a property. */
-  Optional<ExecutableElement> getPropertyGetter(VariableElement fieldElement) {
+  Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) {
     return elementFieldGetterMethodMap.computeIfAbsent(
         fieldElement, this::getPropertyGetterUncached);
   }
 
-  private Optional<ExecutableElement> getPropertyGetterUncached(VariableElement fieldElement) {
+  private Optional<XMethodElement> getPropertyGetterUncached(XFieldElement fieldElement) {
     return findProperty(fieldElement)
         .getterSignature()
         .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature)));
   }
 
-  private PropertyMetadata findProperty(VariableElement field) {
-    String fieldDescriptor = ElementDescriptors.getFieldDescriptor(field);
+  private PropertyMetadata findProperty(XFieldElement field) {
+    String fieldDescriptor = field.getJvmDescriptor();
     if (classMetadata().propertiesByFieldSignature().containsKey(fieldDescriptor)) {
       return classMetadata().propertiesByFieldSignature().get(fieldDescriptor);
     } else {
@@ -158,8 +138,8 @@
     }
   }
 
-  private static String getPropertyNameFromField(VariableElement field) {
-    String name = field.getSimpleName().toString();
+  private static String getPropertyNameFromField(XFieldElement field) {
+    String name = XElements.getSimpleName(field);
     if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) {
       return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length());
     } else {
@@ -167,27 +147,22 @@
     }
   }
 
-  FunctionMetadata getFunctionMetadata(ExecutableElement method) {
-    return classMetadata().functionsBySignature().get(getMethodDescriptor(method));
-  }
-
   /** Parse Kotlin class metadata from a given type element. */
-  static KotlinMetadata from(TypeElement typeElement) {
+  static KotlinMetadata from(XTypeElement typeElement) {
     return new AutoValue_KotlinMetadata(typeElement, ClassMetadata.create(metadataOf(typeElement)));
   }
 
-  private static KotlinClassMetadata.Class metadataOf(TypeElement typeElement) {
-    AnnotationMirror annotationMirror =
-        getAnnotationMirror(typeElement, ClassNames.KOTLIN_METADATA.canonicalName()).get();
+  private static KotlinClassMetadata.Class metadataOf(XTypeElement typeElement) {
+    XAnnotation annotation = typeElement.getAnnotation(ClassNames.KOTLIN_METADATA);
     Metadata metadataAnnotation =
         JvmMetadataUtil.Metadata(
-            getIntValue(annotationMirror, "k"),
-            getIntArrayValue(annotationMirror, "mv"),
-            getStringArrayValue(annotationMirror, "d1"),
-            getStringArrayValue(annotationMirror, "d2"),
-            getStringValue(annotationMirror, "xs"),
-            getOptionalStringValue(annotationMirror, "pn").orElse(null),
-            getOptionalIntValue(annotationMirror, "xi").orElse(null));
+            annotation.getAsInt("k"),
+            annotation.getAsIntList("mv").stream().mapToInt(Integer::intValue).toArray(),
+            annotation.getAsStringList("d1").toArray(new String[0]),
+            annotation.getAsStringList("d2").toArray(new String[0]),
+            annotation.getAsString("xs"),
+            getOptionalStringValue(annotation, "pn").orElse(null),
+            getOptionalIntValue(annotation, "xi").orElse(null));
     KotlinClassMetadata metadata = KotlinClassMetadata.read(metadataAnnotation);
     if (metadata == null) {
       // Can happen if Kotlin < 1.0 or if metadata version is not supported, i.e.
@@ -376,52 +351,30 @@
 
   @AutoValue
   abstract static class MethodForAnnotations {
-    static MethodForAnnotations create(ExecutableElement method) {
+    static MethodForAnnotations create(XMethodElement method) {
       return new AutoValue_KotlinMetadata_MethodForAnnotations(method);
     }
 
     static final MethodForAnnotations MISSING = MethodForAnnotations.create(null);
 
     @Nullable
-    abstract ExecutableElement method();
+    abstract XMethodElement method();
   }
 
-  private static int getIntValue(AnnotationMirror annotation, String valueName) {
-    return AnnotationValues.getInt(getAnnotationValue(annotation, valueName));
-  }
-
-  private static Optional<Integer> getOptionalIntValue(
-      AnnotationMirror annotation, String valueName) {
+  private static Optional<Integer> getOptionalIntValue(XAnnotation annotation, String valueName) {
     return isValuePresent(annotation, valueName)
-        ? Optional.of(getIntValue(annotation, valueName))
+        ? Optional.of(annotation.getAsInt(valueName))
         : Optional.empty();
   }
 
-  private static int[] getIntArrayValue(AnnotationMirror annotation, String valueName) {
-    return getAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
-        .mapToInt(AnnotationValues::getInt)
-        .toArray();
-  }
-
-  private static String getStringValue(AnnotationMirror annotation, String valueName) {
-    return AnnotationValues.getString(getAnnotationValue(annotation, valueName));
-  }
-
-  private static Optional<String> getOptionalStringValue(
-      AnnotationMirror annotation, String valueName) {
+  private static Optional<String> getOptionalStringValue(XAnnotation annotation, String valueName) {
     return isValuePresent(annotation, valueName)
-        ? Optional.of(getStringValue(annotation, valueName))
+        ? Optional.of(annotation.getAsString(valueName))
         : Optional.empty();
   }
 
-  private static String[] getStringArrayValue(AnnotationMirror annotation, String valueName) {
-    return getAnnotationValues(getAnnotationValue(annotation, valueName)).stream()
-        .map(AnnotationValues::getString)
-        .toArray(String[]::new);
-  }
-
-  private static boolean isValuePresent(AnnotationMirror annotation, String valueName) {
-    return getAnnotationValuesWithDefaults(annotation).keySet().stream()
-        .anyMatch(member -> member.getSimpleName().contentEquals(valueName));
+  private static boolean isValuePresent(XAnnotation annotation, String valueName) {
+    return annotation.getAnnotationValues().stream()
+        .anyMatch(member -> member.getName().equals(valueName));
   }
 }
diff --git a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataFactory.java b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataFactory.java
index 9988340..65138fe 100644
--- a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataFactory.java
+++ b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataFactory.java
@@ -16,15 +16,16 @@
 
 package dagger.hilt.processor.internal.kotlin;
 
-import static com.google.auto.common.MoreElements.isAnnotationPresent;
 
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XTypeElement;
 import dagger.hilt.processor.internal.ClassNames;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.util.HashMap;
 import java.util.Map;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 
 /**
  * Factory creating Kotlin metadata data objects.
@@ -34,7 +35,7 @@
  */
 @Singleton
 public final class KotlinMetadataFactory {
-  private final Map<TypeElement, KotlinMetadata> metadataCache = new HashMap<>();
+  private final Map<XTypeElement, KotlinMetadata> metadataCache = new HashMap<>();
 
   @Inject
   KotlinMetadataFactory() {}
@@ -44,11 +45,11 @@
    *
    * @throws IllegalStateException if the element has no metadata or is not enclosed in a type
    *     element with metadata. To check if an element has metadata use {@link
-   *     KotlinMetadataUtil#hasMetadata(Element)}
+   *     KotlinMetadataUtil#hasMetadata(XElement)}
    */
-  public KotlinMetadata create(Element element) {
-    TypeElement enclosingElement = KotlinMetadataUtil.closestEnclosingTypeElement(element);
-    if (!isAnnotationPresent(enclosingElement, ClassNames.KOTLIN_METADATA.canonicalName())) {
+  public KotlinMetadata create(XElement element) {
+    XTypeElement enclosingElement = XElements.closestEnclosingTypeElement(element);
+    if (!enclosingElement.hasAnnotation(ClassNames.KOTLIN_METADATA)) {
       throw new IllegalStateException("Missing @Metadata for: " + enclosingElement);
     }
     return metadataCache.computeIfAbsent(enclosingElement, KotlinMetadata::from);
diff --git a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java
index b98ce58..771a4b8 100644
--- a/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java
+++ b/java/dagger/hilt/processor/internal/kotlin/KotlinMetadataUtil.java
@@ -16,32 +16,26 @@
 
 package dagger.hilt.processor.internal.kotlin;
 
-import static com.google.auto.common.MoreElements.asType;
-import static com.google.auto.common.MoreElements.isAnnotationPresent;
-import static com.google.auto.common.MoreElements.isType;
-import static com.google.common.base.Preconditions.checkState;
+import static androidx.room.compiler.processing.XElementKt.isField;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
-import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT;
-import static kotlinx.metadata.Flag.Class.IS_DATA;
-import static kotlinx.metadata.Flag.Class.IS_OBJECT;
-import static kotlinx.metadata.Flag.IS_PRIVATE;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.xprocessing.XElements.asField;
+import static dagger.internal.codegen.xprocessing.XElements.isStatic;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XFieldElement;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XTypeElement;
+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 dagger.hilt.processor.internal.kotlin.KotlinMetadata.FunctionMetadata;
-import dagger.internal.codegen.extension.DaggerCollectors;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.util.Optional;
 import javax.inject.Inject;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.element.VariableElement;
-import javax.lang.model.util.ElementFilter;
-import kotlin.Metadata;
-import kotlinx.metadata.Flag;
 
 /** Utility class for interacting with Kotlin Metadata. */
 public final class KotlinMetadataUtil {
@@ -57,8 +51,63 @@
    * Returns {@code true} if this element has the Kotlin Metadata annotation or if it is enclosed in
    * an element that does.
    */
-  public boolean hasMetadata(Element element) {
-    return isAnnotationPresent(closestEnclosingTypeElement(element), Metadata.class);
+  public boolean hasMetadata(XElement element) {
+    return XElements.closestEnclosingTypeElement(element).hasAnnotation(ClassNames.KOTLIN_METADATA);
+  }
+
+  // TODO(kuanyingchou): Consider replacing it with `XAnnotated.getAnnotationsAnnotatedWith()`
+  //  once b/278077018 is resolved.
+  /**
+   * Returns the annotations on the given {@code element} annotated with {@code annotationName}.
+   *
+   * <p>Note: If the given {@code element} is a non-static field this method will return annotations
+   * on both the backing field and the associated synthetic property (if one exists).
+   */
+  public ImmutableList<XAnnotation> getAnnotationsAnnotatedWith(
+      XElement element, ClassName annotationName) {
+    return getAnnotations(element).stream()
+        .filter(annotation -> annotation.getTypeElement().hasAnnotation(annotationName))
+        .collect(toImmutableList());
+  }
+
+  /**
+   * Returns the annotations on the given {@code element} that match the {@code annotationName}.
+   *
+   * <p>Note: If the given {@code element} is a non-static field this method will return annotations
+   * on both the backing field and the associated synthetic property (if one exists).
+   */
+  private ImmutableList<XAnnotation> getAnnotations(XElement element) {
+    ImmutableList<XAnnotation> annotations = ImmutableList.copyOf(element.getAllAnnotations());
+    ImmutableList<XAnnotation> syntheticAnnotations = getSyntheticPropertyAnnotations(element);
+    if (syntheticAnnotations.isEmpty()) {
+      return annotations;
+    }
+    // Dedupe any annotation that appears on both the field and the property.
+    // Note: we reduce the number of annotations we have to dedupe by only checking equivalence on
+    // annotations that have the same class name as a synthetic annotation. This avoids hitting
+    // TypeNotPresentException on annotation values with error types unless it has the same class
+    // name as a synthetic annotation.
+    ImmutableSet<ClassName> syntheticAnnotationClassNames =
+        syntheticAnnotations.stream()
+            .map(XAnnotations::getClassName)
+            .collect(toImmutableSet());
+    ImmutableSet<Equivalence.Wrapper<XAnnotation>> annotationEquivalenceWrappers =
+        annotations.stream()
+            .filter(annotation -> syntheticAnnotationClassNames.contains(annotation.getClassName()))
+            .map(XAnnotations.equivalence()::wrap)
+            .collect(toImmutableSet());
+    ImmutableList<XAnnotation> uniqueSyntheticAnnotations =
+        syntheticAnnotations.stream()
+            .map(XAnnotations.equivalence()::wrap)
+            .filter(wrapper -> !annotationEquivalenceWrappers.contains(wrapper))
+            .map(Equivalence.Wrapper::get)
+            .collect(toImmutableList());
+    return uniqueSyntheticAnnotations.isEmpty()
+        ? annotations
+        : ImmutableList.<XAnnotation>builder()
+            .addAll(annotations)
+            .addAll(uniqueSyntheticAnnotations)
+            .build();
   }
 
   /**
@@ -67,135 +116,29 @@
    * <p>Note that this method only looks for additional annotations in the synthetic property
    * method, if any, of a Kotlin property and not for annotations in its backing field.
    */
-  public ImmutableList<? extends AnnotationMirror> getSyntheticPropertyAnnotations(
-      VariableElement fieldElement, ClassName annotationType) {
-    return metadataFactory
-        .create(fieldElement)
-        .getSyntheticAnnotationMethod(fieldElement)
-        .map(methodElement -> getAnnotationsAnnotatedWith(methodElement, annotationType))
-        .orElse(ImmutableList.of());
+  private ImmutableList<XAnnotation> getSyntheticPropertyAnnotations(XElement element) {
+    // Currently, we avoid trying to get annotations from properties on object class's (i.e.
+    // properties with static jvm backing fields) due to issues explained in CL/336150864.
+    if (!isField(element) || isStatic(element)) {
+      return ImmutableList.of();
+    }
+    XFieldElement field = asField(element);
+    return hasMetadata(field)
+        ? metadataFactory
+            .create(field)
+            .getSyntheticAnnotationMethod(field)
+            .map(XMethodElement::getAllAnnotations)
+            .map(ImmutableList::copyOf)
+            .orElse(ImmutableList.<XAnnotation>of())
+        : ImmutableList.of();
   }
 
-  /** Returns annotations of element that are annotated with subAnnotation */
-  private static ImmutableList<AnnotationMirror> getAnnotationsAnnotatedWith(
-      Element element, ClassName subAnnotation) {
-    return element.getAnnotationMirrors().stream()
-        .filter(
-            annotation ->
-                isAnnotationPresent(
-                    annotation.getAnnotationType().asElement(), subAnnotation.canonicalName()))
-        .collect(toImmutableList());
-  }
-
-  /**
-   * Returns {@code true} if the synthetic method for annotations is missing. This can occur when
-   * the Kotlin metadata of the property reports that it contains a synthetic method for annotations
-   * but such method is not found since it is synthetic and ignored by the processor.
-   */
-  public boolean isMissingSyntheticPropertyForAnnotations(VariableElement fieldElement) {
-    return metadataFactory.create(fieldElement).isMissingSyntheticAnnotationMethod(fieldElement);
-  }
-
-  /** Returns {@code true} if this type element is a Kotlin Object. */
-  public boolean isObjectClass(TypeElement typeElement) {
-    return hasMetadata(typeElement)
-        && metadataFactory.create(typeElement).classMetadata().flags(IS_OBJECT);
-  }
-
-  /** Returns {@code true} if this type element is a Kotlin data class. */
-  public boolean isDataClass(TypeElement typeElement) {
-    return hasMetadata(typeElement)
-        && metadataFactory.create(typeElement).classMetadata().flags(IS_DATA);
-  }
-
-  /* Returns {@code true} if this type element is a Kotlin Companion Object. */
-  public boolean isCompanionObjectClass(TypeElement typeElement) {
-    return hasMetadata(typeElement)
-        && metadataFactory.create(typeElement).classMetadata().flags(IS_COMPANION_OBJECT);
-  }
-
-  /** Returns {@code true} if this type element is a Kotlin object or companion object. */
-  public boolean isObjectOrCompanionObjectClass(TypeElement typeElement) {
-    return isObjectClass(typeElement) || isCompanionObjectClass(typeElement);
-  }
-
-  /* Returns {@code true} if this type element has a Kotlin Companion Object. */
-  public boolean hasEnclosedCompanionObject(TypeElement typeElement) {
-    return hasMetadata(typeElement)
-        && metadataFactory.create(typeElement).classMetadata().companionObjectName().isPresent();
-  }
-
-  /* Returns the Companion Object element enclosed by the given type element. */
-  public TypeElement getEnclosedCompanionObject(TypeElement typeElement) {
-    return metadataFactory
-        .create(typeElement)
-        .classMetadata()
-        .companionObjectName()
-        .map(
-            companionObjectName ->
-                ElementFilter.typesIn(typeElement.getEnclosedElements()).stream()
-                    .filter(
-                        innerType -> innerType.getSimpleName().contentEquals(companionObjectName))
-                    .collect(DaggerCollectors.onlyElement()))
-        .get();
-  }
-
-  /**
-   * Returns {@code true} if the given type element was declared <code>private</code> in its Kotlin
-   * source.
-   */
-  public boolean isVisibilityPrivate(TypeElement typeElement) {
-    return hasMetadata(typeElement)
-        && metadataFactory.create(typeElement).classMetadata().flags(IS_PRIVATE);
-  }
-
-  /**
-   * Returns {@code true} if the given type element was declared {@code internal} in its Kotlin
-   * source.
-   */
-  public boolean isVisibilityInternal(TypeElement type) {
-    return hasMetadata(type)
-        && metadataFactory.create(type).classMetadata().flags(Flag.IS_INTERNAL);
-  }
-
-  /**
-   * Returns {@code true} if the given executable element was declared {@code internal} in its
-   * Kotlin source.
-   */
-  public boolean isVisibilityInternal(ExecutableElement method) {
-    return hasMetadata(method)
-        && metadataFactory.create(method).getFunctionMetadata(method).flags(Flag.IS_INTERNAL);
-  }
-
-  public Optional<ExecutableElement> getPropertyGetter(VariableElement fieldElement) {
+  public Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) {
     return metadataFactory.create(fieldElement).getPropertyGetter(fieldElement);
   }
 
-  public boolean containsConstructorWithDefaultParam(TypeElement typeElement) {
+  public boolean containsConstructorWithDefaultParam(XTypeElement typeElement) {
     return hasMetadata(typeElement)
         && metadataFactory.create(typeElement).containsConstructorWithDefaultParam();
   }
-
-  /**
-   * Returns a map mapping all method signatures within the given class element, including methods
-   * that it inherits from its ancestors, to their method names.
-   */
-  public ImmutableMap<String, String> getAllMethodNamesBySignature(TypeElement element) {
-    checkState(
-        hasMetadata(element), "Can not call getAllMethodNamesBySignature for non-Kotlin class");
-    return metadataFactory.create(element).classMetadata().functionsBySignature().values().stream()
-        .collect(toImmutableMap(FunctionMetadata::signature, FunctionMetadata::name));
-  }
-
-  /** Returns the argument or the closest enclosing element that is a {@link TypeElement}. */
-  static TypeElement closestEnclosingTypeElement(Element element) {
-    Element current = element;
-    while (current != null) {
-      if (isType(current)) {
-        return asType(current);
-      }
-      current = current.getEnclosingElement();
-    }
-    throw new IllegalStateException("There is no enclosing TypeElement for: " + element);
-  }
 }
diff --git a/java/dagger/hilt/processor/internal/originatingelement/BUILD b/java/dagger/hilt/processor/internal/originatingelement/BUILD
index 8942535..f4a279a 100644
--- a/java/dagger/hilt/processor/internal/originatingelement/BUILD
+++ b/java/dagger/hilt/processor/internal/originatingelement/BUILD
@@ -27,6 +27,8 @@
 java_library(
     name = "processor_lib",
     srcs = [
+        "KspOriginatingElementProcessor.java",
+        "OriginatingElementProcessingStep.java",
         "OriginatingElementProcessor.java",
     ],
     deps = [
@@ -34,10 +36,12 @@
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
+        "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
diff --git a/java/dagger/hilt/processor/internal/originatingelement/KspOriginatingElementProcessor.java b/java/dagger/hilt/processor/internal/originatingelement/KspOriginatingElementProcessor.java
new file mode 100644
index 0000000..51926e1
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/originatingelement/KspOriginatingElementProcessor.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.originatingelement;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/**
+ * Processes the annotations annotated with {@link dagger.hilt.codegen.OriginatingElement} to check
+ * that they're only used on top-level classes and the value passed is also a top-level class.
+ */
+public final class KspOriginatingElementProcessor extends KspBaseProcessingStepProcessor {
+  private KspOriginatingElementProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new OriginatingElementProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspOriginatingElemenetProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspOriginatingElementProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessingStep.java b/java/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessingStep.java
new file mode 100644
index 0000000..d0f0ab9
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessingStep.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.originatingelement;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XElementKt;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XElements;
+
+/**
+ * Processes the annotations annotated with {@link dagger.hilt.codegen.OriginatingElement} to check
+ * that they're only used on top-level classes and the value passed is also a top-level class.
+ */
+public final class OriginatingElementProcessingStep extends BaseProcessingStep {
+
+  public OriginatingElementProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.ORIGINATING_ELEMENT);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    ProcessorErrors.checkState(
+        XElementKt.isTypeElement(element) && Processors.isTopLevel(element),
+        element,
+        "@%s should only be used to annotate top-level types, but found: %s",
+        annotation.simpleName(),
+        XElements.toStableString(element));
+
+    XTypeElement topLevelClassElement =
+        element
+            .getAnnotation(ClassNames.ORIGINATING_ELEMENT)
+            .getAsType("topLevelClass")
+            .getTypeElement();
+
+    // TODO(bcorso): ProcessorErrors should allow us to point to the annotation too.
+    ProcessorErrors.checkState(
+        Processors.isTopLevel(topLevelClassElement),
+        element,
+        "@%s.topLevelClass value should be a top-level class, but found: %s",
+        annotation.simpleName(),
+        topLevelClassElement.getClassName());
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessor.java b/java/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessor.java
index 723cf04..23cc2de 100644
--- a/java/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessor.java
+++ b/java/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessor.java
@@ -18,16 +18,9 @@
 
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
-import com.google.auto.common.MoreElements;
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableSet;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /**
@@ -36,34 +29,9 @@
  */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class OriginatingElementProcessor extends BaseProcessor {
-
+public final class OriginatingElementProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public ImmutableSet<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.ORIGINATING_ELEMENT.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    ProcessorErrors.checkState(
-        MoreElements.isType(element) && Processors.isTopLevel(element),
-        element,
-        "@%s should only be used to annotate top-level types, but found: %s",
-        annotation.getSimpleName(),
-        element);
-
-    TypeElement originatingElementValue =
-        Processors.getAnnotationClassValue(
-            getElementUtils(),
-            Processors.getAnnotationMirror(element, ClassNames.ORIGINATING_ELEMENT),
-            "topLevelClass");
-
-    // TODO(bcorso): ProcessorErrors should allow us to point to the annotation too.
-    ProcessorErrors.checkState(
-        Processors.isTopLevel(originatingElementValue),
-        element,
-        "@%s.topLevelClass value should be a top-level class, but found: %s",
-        annotation.getSimpleName(),
-        originatingElementValue);
+  public OriginatingElementProcessingStep processingStep() {
+    return new OriginatingElementProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java
index 1abe44b..e4794bc 100644
--- a/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/AggregatedRootGenerator.java
@@ -16,50 +16,46 @@
 
 package dagger.hilt.processor.internal.root;
 
+import androidx.room.compiler.processing.XTypeElement;
 import com.squareup.javapoet.AnnotationSpec;
-import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 
 /** Generates an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}. */
 final class AggregatedRootGenerator {
-  private final TypeElement rootElement;
-  private final TypeElement originatingRootElement;
-  private final TypeElement rootAnnotation;
-  private final ProcessingEnvironment processingEnv;
+  private final XTypeElement rootElement;
+  private final XTypeElement originatingRootElement;
+  private final XTypeElement rootAnnotation;
 
   AggregatedRootGenerator(
-      TypeElement rootElement,
-      TypeElement originatingRootElement,
-      TypeElement rootAnnotation,
-      ProcessingEnvironment processingEnv) {
+      XTypeElement rootElement, XTypeElement originatingRootElement, XTypeElement rootAnnotation) {
     this.rootElement = rootElement;
     this.originatingRootElement = originatingRootElement;
     this.rootAnnotation = rootAnnotation;
-    this.processingEnv = processingEnv;
   }
 
-  void generate() throws IOException {
-    AnnotationSpec.Builder aggregatedRootAnnotation = AnnotationSpec.builder(
-        ClassNames.AGGREGATED_ROOT)
+  void generate() {
+    AnnotationSpec.Builder aggregatedRootAnnotation =
+        AnnotationSpec.builder(ClassNames.AGGREGATED_ROOT)
             .addMember("root", "$S", rootElement.getQualifiedName())
-            .addMember("rootPackage", "$S", ClassName.get(rootElement).packageName())
+            .addMember("rootPackage", "$S", rootElement.getClassName().packageName())
             .addMember("originatingRoot", "$S", originatingRootElement.getQualifiedName())
-            .addMember("originatingRootPackage", "$S",
-                ClassName.get(originatingRootElement).packageName())
-            .addMember("rootAnnotation", "$T.class", rootAnnotation);
-    ClassName.get(rootElement).simpleNames().forEach(
-        name -> aggregatedRootAnnotation.addMember("rootSimpleNames", "$S", name));
-    ClassName.get(originatingRootElement).simpleNames().forEach(
-        name -> aggregatedRootAnnotation.addMember("originatingRootSimpleNames", "$S", name));
+            .addMember(
+                "originatingRootPackage", "$S", originatingRootElement.getClassName().packageName())
+            .addMember("rootAnnotation", "$T.class", rootAnnotation.getClassName());
+    rootElement
+        .getClassName()
+        .simpleNames()
+        .forEach(name -> aggregatedRootAnnotation.addMember("rootSimpleNames", "$S", name));
+    originatingRootElement
+        .getClassName()
+        .simpleNames()
+        .forEach(
+            name -> aggregatedRootAnnotation.addMember("originatingRootSimpleNames", "$S", name));
     Processors.generateAggregatingClass(
         ClassNames.AGGREGATED_ROOT_PACKAGE,
         aggregatedRootAnnotation.build(),
         rootElement,
-        getClass(),
-        processingEnv);
+        getClass());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java
index 70ee63f..7788c81 100644
--- a/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java
+++ b/java/dagger/hilt/processor/internal/root/AggregatedRootMetadata.java
@@ -18,20 +18,15 @@
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.AggregatedElements;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.root.ir.AggregatedRootIr;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
 
 /**
  * Represents the values stored in an {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot}.
@@ -40,19 +35,19 @@
 abstract class AggregatedRootMetadata {
 
   /** Returns the aggregating element */
-  public abstract TypeElement aggregatingElement();
+  public abstract XTypeElement aggregatingElement();
 
   /** Returns the element that was annotated with the root annotation. */
-  abstract TypeElement rootElement();
+  abstract XTypeElement rootElement();
 
   /**
-   * Returns the originating root element. In most cases this will be the same as
-   * {@link #rootElement()}.
+   * Returns the originating root element. In most cases this will be the same as {@link
+   * #rootElement()}.
    */
-  abstract TypeElement originatingRootElement();
+  abstract XTypeElement originatingRootElement();
 
   /** Returns the root annotation as an element. */
-  abstract TypeElement rootAnnotation();
+  abstract XTypeElement rootAnnotation();
 
   /** Returns whether this root can use a shared component. */
   abstract boolean allowsSharingComponent();
@@ -62,16 +57,16 @@
     return RootType.of(rootElement());
   }
 
-  static ImmutableSet<AggregatedRootMetadata> from(ProcessingEnvironment env) {
+  static ImmutableSet<AggregatedRootMetadata> from(XProcessingEnv env) {
     return from(
         AggregatedElements.from(
-            ClassNames.AGGREGATED_ROOT_PACKAGE, ClassNames.AGGREGATED_ROOT, env.getElementUtils()),
+            ClassNames.AGGREGATED_ROOT_PACKAGE, ClassNames.AGGREGATED_ROOT, env),
         env);
   }
 
   /** Returns metadata for each aggregated element. */
   public static ImmutableSet<AggregatedRootMetadata> from(
-      ImmutableSet<TypeElement> aggregatedElements, ProcessingEnvironment env) {
+      ImmutableSet<XTypeElement> aggregatedElements, XProcessingEnv env) {
     return aggregatedElements.stream()
         .map(aggregatedElement -> create(aggregatedElement, env))
         .collect(toImmutableSet());
@@ -79,29 +74,23 @@
 
   public static AggregatedRootIr toIr(AggregatedRootMetadata metadata) {
     return new AggregatedRootIr(
-        ClassName.get(metadata.aggregatingElement()),
-        ClassName.get(metadata.rootElement()),
-        ClassName.get(metadata.originatingRootElement()),
-        ClassName.get(metadata.rootAnnotation()),
+        metadata.aggregatingElement().getClassName(),
+        metadata.rootElement().getClassName(),
+        metadata.originatingRootElement().getClassName(),
+        metadata.rootAnnotation().getClassName(),
         metadata.allowsSharingComponent());
   }
 
-  private static AggregatedRootMetadata create(TypeElement element, ProcessingEnvironment env) {
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_ROOT);
+  private static AggregatedRootMetadata create(XTypeElement element, XProcessingEnv env) {
+    XAnnotation annotation = element.getAnnotation(ClassNames.AGGREGATED_ROOT);
 
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(env.getElementUtils(), annotationMirror);
-
-    TypeElement rootElement =
-        env.getElementUtils().getTypeElement(AnnotationValues.getString(values.get("root")));
+    XTypeElement rootElement = env.requireTypeElement(annotation.getAsString("root"));
     boolean allowSharingComponent = true;
     return new AutoValue_AggregatedRootMetadata(
         element,
         rootElement,
-        env.getElementUtils()
-            .getTypeElement(AnnotationValues.getString(values.get("originatingRoot"))),
-        AnnotationValues.getTypeElement(values.get("rootAnnotation")),
+        env.requireTypeElement(annotation.getAsString("originatingRoot")),
+        annotation.getAsType("rootAnnotation").getTypeElement(),
         allowSharingComponent);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/BUILD b/java/dagger/hilt/processor/internal/root/BUILD
index 0402d91..a132b1c 100644
--- a/java/dagger/hilt/processor/internal/root/BUILD
+++ b/java/dagger/hilt/processor/internal/root/BUILD
@@ -30,8 +30,10 @@
     name = "component_tree_deps_processor_lib",
     srcs = [
         "ComponentGenerator.java",
+        "ComponentTreeDepsProcessingStep.java",
         "ComponentTreeDepsProcessor.java",
         "EarlySingletonComponentCreatorGenerator.java",
+        "KspComponentTreeDepsProcessor.java",
         "RootFileFormatter.java",
         "RootGenerator.java",
         "TestComponentDataGenerator.java",
@@ -53,13 +55,15 @@
         "//java/dagger/hilt/processor/internal/earlyentrypoint:aggregated_early_entry_point_metadata",
         "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
+        "//third_party/java/error_prone:annotations",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/guava/graph",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -77,7 +81,9 @@
     srcs = [
         "AggregatedRootGenerator.java",
         "ComponentTreeDepsGenerator.java",
+        "KspRootProcessor.java",
         "ProcessedRootSentinelGenerator.java",
+        "RootProcessingStep.java",
         "RootProcessor.java",
         "TestInjectorGenerator.java",
     ],
@@ -100,12 +106,13 @@
         "//java/dagger/hilt/processor/internal/root/ir",
         "//java/dagger/hilt/processor/internal/uninstallmodules:aggregated_uninstall_modules_metadata",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -132,6 +139,7 @@
         "//java/dagger/hilt/processor/internal/kotlin",
         "//java/dagger/hilt/processor/internal/root/ir",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:common",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
@@ -147,6 +155,7 @@
     deps = [
         "//java/dagger/hilt/processor/internal:classnames",
         "//java/dagger/hilt/processor/internal:processors",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/javapoet",
     ],
 )
diff --git a/java/dagger/hilt/processor/internal/root/ComponentGenerator.java b/java/dagger/hilt/processor/internal/root/ComponentGenerator.java
index 0e8d0ad..500b93b 100644
--- a/java/dagger/hilt/processor/internal/root/ComponentGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/ComponentGenerator.java
@@ -19,6 +19,8 @@
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static java.util.Comparator.comparing;
 
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.google.common.base.Joiner;
 import com.google.common.base.Utf8;
 import com.google.common.collect.ImmutableCollection;
@@ -36,7 +38,6 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
 
 /** Generates a Dagger component or subcomponent interface. */
@@ -47,7 +48,7 @@
           .thenComparing(ClassName::compareTo);
   private static final Comparator<TypeName> TYPE_NAME_SORTER = comparing(TypeName::toString);
 
-  private final ProcessingEnvironment processingEnv;
+  private final XProcessingEnv processingEnv;
   private final ClassName name;
   private final Optional<ClassName> superclass;
   private final ImmutableList<ClassName> modules;
@@ -58,7 +59,7 @@
   private final Optional<TypeSpec> componentBuilder;
 
   public ComponentGenerator(
-      ProcessingEnvironment processingEnv,
+      XProcessingEnv processingEnv,
       ClassName name,
       Optional<ClassName> superclass,
       Set<? extends ClassName> modules,
@@ -161,7 +162,9 @@
 
     Processors.addGeneratedAnnotation(builder, processingEnv, ClassNames.ROOT_PROCESSOR.toString());
 
-    JavaFile.builder(name.packageName(), builder.build()).build().writeTo(processingEnv.getFiler());
+    processingEnv
+        .getFiler()
+        .write(JavaFile.builder(name.packageName(), builder.build()).build(), Mode.Isolating);
     return partitionName;
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsGenerator.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsGenerator.java
index 34c586a..bddc41d 100644
--- a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsGenerator.java
@@ -18,6 +18,9 @@
 
 import static javax.lang.model.element.Modifier.PUBLIC;
 
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
@@ -30,18 +33,18 @@
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 
 /** Generates an {@link dagger.hilt.internal.componenttreedeps.ComponentTreeDeps}. */
 final class ComponentTreeDepsGenerator {
   // Keeps track of already generated proxies. For correctness, this same instance of
   // ComponentTreeDepsGenerator must be used for a given round.
   private final Set<ClassName> generatedProxies = new HashSet<>();
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
+  private final Mode mode;
 
-  ComponentTreeDepsGenerator(ProcessingEnvironment env) {
+  ComponentTreeDepsGenerator(XProcessingEnv env, Mode mode) {
     this.env = env;
+    this.mode = mode;
   }
 
   void generate(ComponentTreeDepsMetadata metadata) throws IOException {
@@ -53,7 +56,7 @@
 
     Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString());
 
-    JavaFile.builder(name.packageName(), builder.build()).build().writeTo(env.getFiler());
+    env.getFiler().write(JavaFile.builder(name.packageName(), builder.build()).build(), mode);
   }
 
   AnnotationSpec componentTreeDepsAnnotation(ComponentTreeDepsMetadata metadata)
@@ -68,9 +71,9 @@
     return builder.build();
   }
 
-  private void addDeps(AnnotationSpec.Builder builder, ImmutableSet<TypeElement> deps, String name)
+  private void addDeps(AnnotationSpec.Builder builder, ImmutableSet<XTypeElement> deps, String name)
       throws IOException {
-    for (TypeElement dep : deps) {
+    for (XTypeElement dep : deps) {
       builder.addMember(name, "$T.class", maybeWrapInPublicProxy(dep));
     }
   }
@@ -85,33 +88,32 @@
    * <p>Note: The public proxy is needed because Hilt versions < 2.35 generated package-private
    * aggregating elements, which can't be referenced directly in the {@code @ComponentTreeDeps}.
    */
-  private ClassName maybeWrapInPublicProxy(TypeElement dep) throws IOException {
+  private ClassName maybeWrapInPublicProxy(XTypeElement dep) {
     Optional<ClassName> proxyName = AggregatedElements.aggregatedElementProxyName(dep);
     if (proxyName.isPresent()) {
       // Check the set of already generated proxies to ensure we don't regenerate the proxy in
       // this round. Also check that the element doesn't already exist to ensure we don't regenerate
       // a proxy generated in a previous round.
       if (generatedProxies.add(proxyName.get())
-          && env.getElementUtils().getTypeElement(proxyName.get().canonicalName()) == null) {
+          && env.findTypeElement(proxyName.get().canonicalName()) == null) {
         generateProxy(dep, proxyName.get());
       }
       return proxyName.get();
     }
-    return ClassName.get(dep);
+    return dep.getClassName();
   }
 
-  private void generateProxy(TypeElement dep, ClassName proxyName) throws IOException {
+  private void generateProxy(XTypeElement dep, ClassName proxyName) {
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(proxyName)
             .addModifiers(PUBLIC)
             // No originating element since this is generated by the aggregating processor.
             .addAnnotation(
                 AnnotationSpec.builder(ClassNames.AGGREGATED_ELEMENT_PROXY)
-                    .addMember("value", "$T.class", dep)
+                    .addMember("value", "$T.class", dep.getClassName())
                     .build());
 
     Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString());
-
-    JavaFile.builder(proxyName.packageName(), builder.build()).build().writeTo(env.getFiler());
+    env.getFiler().write(JavaFile.builder(proxyName.packageName(), builder.build()).build(), mode);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsMetadata.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsMetadata.java
index bcd56c4..a84d7da 100644
--- a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsMetadata.java
+++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsMetadata.java
@@ -18,21 +18,18 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static dagger.hilt.processor.internal.AggregatedElements.unwrapProxies;
-import static dagger.hilt.processor.internal.AnnotationValues.getTypeElements;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata;
 import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIr;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 
 /**
  * Represents the values stored in an {@link
@@ -50,75 +47,71 @@
   abstract ClassName name();
 
   /** Returns the {@link dagger.hilt.internal.aggregatedroot.AggregatedRoot} deps. */
-  abstract ImmutableSet<TypeElement> aggregatedRootDeps();
+  abstract ImmutableSet<XTypeElement> aggregatedRootDeps();
 
   /** Returns the {@link dagger.hilt.internal.definecomponent.DefineComponentClasses} deps. */
-  abstract ImmutableSet<TypeElement> defineComponentDeps();
+  abstract ImmutableSet<XTypeElement> defineComponentDeps();
 
   /** Returns the {@link dagger.hilt.internal.aliasof.AliasOfPropagatedData} deps. */
-  abstract ImmutableSet<TypeElement> aliasOfDeps();
+  abstract ImmutableSet<XTypeElement> aliasOfDeps();
 
   /** Returns the {@link dagger.hilt.internal.aggregateddeps.AggregatedDeps} deps. */
-  abstract ImmutableSet<TypeElement> aggregatedDeps();
+  abstract ImmutableSet<XTypeElement> aggregatedDeps();
 
   /** Returns the {@link dagger.hilt.android.uninstallmodules.AggregatedUninstallModules} deps. */
-  abstract ImmutableSet<TypeElement> aggregatedUninstallModulesDeps();
+  abstract ImmutableSet<XTypeElement> aggregatedUninstallModulesDeps();
 
   /** Returns the {@link dagger.hilt.android.earlyentrypoint.AggregatedEarlyEntryPoint} deps. */
-  abstract ImmutableSet<TypeElement> aggregatedEarlyEntryPointDeps();
+  abstract ImmutableSet<XTypeElement> aggregatedEarlyEntryPointDeps();
 
-  static ComponentTreeDepsMetadata from(TypeElement element, Elements elements) {
-    checkArgument(Processors.hasAnnotation(element, ClassNames.COMPONENT_TREE_DEPS));
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.COMPONENT_TREE_DEPS);
-
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(elements, annotationMirror);
+  static ComponentTreeDepsMetadata from(XTypeElement element, XProcessingEnv env) {
+    checkArgument(element.hasAnnotation(ClassNames.COMPONENT_TREE_DEPS));
+    XAnnotation annotation = element.getAnnotation(ClassNames.COMPONENT_TREE_DEPS);
 
     return create(
-        ClassName.get(element),
-        unwrapProxies(getTypeElements(values.get("rootDeps")), elements),
-        unwrapProxies(getTypeElements(values.get("defineComponentDeps")), elements),
-        unwrapProxies(getTypeElements(values.get("aliasOfDeps")), elements),
-        unwrapProxies(getTypeElements(values.get("aggregatedDeps")), elements),
-        unwrapProxies(getTypeElements(values.get("uninstallModulesDeps")), elements),
-        unwrapProxies(getTypeElements(values.get("earlyEntryPointDeps")), elements));
+        element.getClassName(),
+        unwrapProxies(XAnnotations.getAsTypeElementList(annotation, "rootDeps")),
+        unwrapProxies(XAnnotations.getAsTypeElementList(annotation, "defineComponentDeps")),
+        unwrapProxies(XAnnotations.getAsTypeElementList(annotation, "aliasOfDeps")),
+        unwrapProxies(XAnnotations.getAsTypeElementList(annotation, "aggregatedDeps")),
+        unwrapProxies(XAnnotations.getAsTypeElementList(annotation, "uninstallModulesDeps")),
+        unwrapProxies(XAnnotations.getAsTypeElementList(annotation, "earlyEntryPointDeps")));
   }
 
-  static ComponentTreeDepsMetadata from(ComponentTreeDepsIr ir, Elements elements) {
+  static ComponentTreeDepsMetadata from(ComponentTreeDepsIr ir, XProcessingEnv env) {
     return create(
         ir.getName(),
         ir.getRootDeps().stream()
-            .map(it -> elements.getTypeElement(it.canonicalName()))
+            .map(it -> env.requireTypeElement(it.canonicalName()))
             .collect(toImmutableSet()),
         ir.getDefineComponentDeps().stream()
-            .map(it -> elements.getTypeElement(it.canonicalName()))
+            .map(it -> env.requireTypeElement(it.canonicalName()))
             .collect(toImmutableSet()),
         ir.getAliasOfDeps().stream()
-            .map(it -> elements.getTypeElement(it.canonicalName()))
+            .map(it -> env.requireTypeElement(it.canonicalName()))
             .collect(toImmutableSet()),
         ir.getAggregatedDeps().stream()
-            .map(it -> elements.getTypeElement(it.canonicalName()))
+            .map(it -> env.requireTypeElement(it.canonicalName()))
             .collect(toImmutableSet()),
         ir.getUninstallModulesDeps().stream()
-            .map(it -> elements.getTypeElement(it.canonicalName()))
+            .map(it -> env.requireTypeElement(it.canonicalName()))
             .collect(toImmutableSet()),
         ir.getEarlyEntryPointDeps().stream()
-            .map(it -> elements.getTypeElement(it.canonicalName()))
+            .map(it -> env.requireTypeElement(it.canonicalName()))
             .collect(toImmutableSet()));
   }
 
   /** Returns all modules included in a component tree deps. */
-  public ImmutableSet<TypeElement> modules(Elements elements) {
-    return AggregatedDepsMetadata.from(aggregatedDeps(), elements).stream()
+  public ImmutableSet<XTypeElement> modules() {
+    return AggregatedDepsMetadata.from(aggregatedDeps()).stream()
         .filter(AggregatedDepsMetadata::isModule)
         .map(AggregatedDepsMetadata::dependency)
         .collect(toImmutableSet());
   }
 
   /** Returns all entry points included in a component tree deps. */
-  public ImmutableSet<TypeElement> entrypoints(Elements elements) {
-    return AggregatedDepsMetadata.from(aggregatedDeps(), elements).stream()
+  public ImmutableSet<XTypeElement> entrypoints() {
+    return AggregatedDepsMetadata.from(aggregatedDeps()).stream()
         .filter(dependency -> !dependency.isModule())
         .map(AggregatedDepsMetadata::dependency)
         .collect(toImmutableSet());
@@ -126,12 +119,12 @@
 
   static ComponentTreeDepsMetadata create(
       ClassName name,
-      ImmutableSet<TypeElement> aggregatedRootDeps,
-      ImmutableSet<TypeElement> defineComponentDeps,
-      ImmutableSet<TypeElement> aliasOfDeps,
-      ImmutableSet<TypeElement> aggregatedDeps,
-      ImmutableSet<TypeElement> aggregatedUninstallModulesDeps,
-      ImmutableSet<TypeElement> aggregatedEarlyEntryPointDeps) {
+      ImmutableSet<XTypeElement> aggregatedRootDeps,
+      ImmutableSet<XTypeElement> defineComponentDeps,
+      ImmutableSet<XTypeElement> aliasOfDeps,
+      ImmutableSet<XTypeElement> aggregatedDeps,
+      ImmutableSet<XTypeElement> aggregatedUninstallModulesDeps,
+      ImmutableSet<XTypeElement> aggregatedEarlyEntryPointDeps) {
     return new AutoValue_ComponentTreeDepsMetadata(
         name,
         aggregatedRootDeps,
diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessingStep.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessingStep.java
new file mode 100644
index 0000000..295a779
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessingStep.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.root;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata;
+import dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ComponentDescriptor;
+import dagger.hilt.processor.internal.ComponentNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.hilt.processor.internal.Processors;
+import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata;
+import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
+import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata;
+import dagger.hilt.processor.internal.aliasof.AliasOfs;
+import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata;
+import dagger.hilt.processor.internal.definecomponent.DefineComponents;
+import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
+import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
+import dagger.internal.codegen.xprocessing.XElements;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Processor that outputs dagger components based on transitive build deps. */
+public final class ComponentTreeDepsProcessingStep extends BaseProcessingStep {
+  private final Set<ClassName> componentTreeDepNames = new HashSet<>();
+  private final Set<ClassName> processed = new HashSet<>();
+
+  public ComponentTreeDepsProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.COMPONENT_TREE_DEPS);
+  }
+
+  @Override
+  protected void processEach(ClassName annotation, XElement element) {
+    componentTreeDepNames.add(XElements.asTypeElement(element).getClassName());
+  }
+
+  @Override
+  public void postProcess(XProcessingEnv env, XRoundEnv roundEnv) throws Exception {
+    ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsToProcess =
+        componentTreeDepNames.stream()
+            .filter(className -> !processed.contains(className))
+            .map(className -> processingEnv().requireTypeElement(className))
+            .map(element -> ComponentTreeDepsMetadata.from(element, processingEnv()))
+            .collect(toImmutableSet());
+
+    DefineComponents defineComponents = DefineComponents.create();
+    for (ComponentTreeDepsMetadata metadata : componentTreeDepsToProcess) {
+      processComponentTreeDeps(metadata, defineComponents);
+    }
+  }
+
+  private void processComponentTreeDeps(
+      ComponentTreeDepsMetadata metadata, DefineComponents defineComponents) throws IOException {
+    XTypeElement metadataElement = processingEnv().requireTypeElement(metadata.name());
+    try {
+      // We choose a name for the generated components/wrapper based off of the originating element
+      // annotated with @ComponentTreeDeps. This is close to but isn't necessarily a "real" name of
+      // a root, since with shared test components, even for single roots, the component tree deps
+      // will be moved to a shared package with a deduped name.
+      ClassName renamedRoot = Processors.removeNameSuffix(metadataElement, "_ComponentTreeDeps");
+      ComponentNames componentNames = ComponentNames.withRenaming(rootName -> renamedRoot);
+
+      boolean isDefaultRoot = ClassNames.DEFAULT_ROOT.equals(renamedRoot);
+      ImmutableSet<Root> roots =
+          AggregatedRootMetadata.from(metadata.aggregatedRootDeps(), processingEnv()).stream()
+              .map(AggregatedRootMetadata::rootElement)
+              .map(rootElement -> Root.create(rootElement, processingEnv()))
+              .collect(toImmutableSet());
+
+      // TODO(bcorso): For legacy reasons, a lot of the generating code requires a "root" as input
+      // since we used to assume 1 root per component tree. Now that each ComponentTreeDeps may
+      // represent multiple roots, we should refactor this logic.
+      Root root =
+          isDefaultRoot
+              ? Root.createDefaultRoot(processingEnv())
+              // Non-default roots should only ever be associated with one root element
+              : getOnlyElement(roots);
+
+      ImmutableSet<ComponentDescriptor> componentDescriptors =
+          defineComponents.getComponentDescriptors(
+              DefineComponentClassesMetadata.from(metadata.defineComponentDeps()));
+      ComponentTree tree = ComponentTree.from(componentDescriptors);
+      ComponentDependencies deps =
+          ComponentDependencies.from(
+              componentDescriptors,
+              AggregatedDepsMetadata.from(metadata.aggregatedDeps()),
+              AggregatedUninstallModulesMetadata.from(metadata.aggregatedUninstallModulesDeps()),
+              AggregatedEarlyEntryPointMetadata.from(metadata.aggregatedEarlyEntryPointDeps()),
+              processingEnv());
+      AliasOfs aliasOfs =
+          AliasOfs.create(
+              AliasOfPropagatedDataMetadata.from(metadata.aliasOfDeps()), componentDescriptors);
+      RootMetadata rootMetadata = RootMetadata.create(root, tree, deps, aliasOfs, processingEnv());
+
+      generateComponents(metadata, rootMetadata, componentNames);
+
+        // Generate a creator for the early entry point if there is a default component available
+        // and there are early entry points.
+        if (isDefaultRoot && !metadata.aggregatedEarlyEntryPointDeps().isEmpty()) {
+          EarlySingletonComponentCreatorGenerator.generate(processingEnv());
+        }
+
+        if (root.isTestRoot()) {
+          // Generate test related classes for each test root that uses this component.
+          ImmutableList<RootMetadata> rootMetadatas =
+              roots.stream()
+                  .map(test -> RootMetadata.create(test, tree, deps, aliasOfs, processingEnv()))
+                  .collect(toImmutableList());
+          generateTestComponentData(metadataElement, rootMetadatas, componentNames);
+        } else {
+          generateApplication(root.element());
+        }
+
+      setProcessingState(metadata, root);
+    } catch (Exception e) {
+      processed.add(metadata.name());
+      throw e;
+    }
+  }
+
+  private void setProcessingState(ComponentTreeDepsMetadata metadata, Root root) {
+    processed.add(metadata.name());
+  }
+
+  private void generateComponents(
+      ComponentTreeDepsMetadata metadata, RootMetadata rootMetadata, ComponentNames componentNames)
+      throws IOException {
+    RootGenerator.generate(metadata, rootMetadata, componentNames, processingEnv());
+  }
+
+  private void generateTestComponentData(
+      XTypeElement metadataElement,
+      ImmutableList<RootMetadata> rootMetadatas,
+      ComponentNames componentNames)
+      throws IOException {
+    for (RootMetadata rootMetadata : rootMetadatas) {
+      // TODO(bcorso): Consider moving this check earlier into processEach.
+      XTypeElement testElement = rootMetadata.testRootMetadata().testElement();
+      ProcessorErrors.checkState(
+          testElement.isPublic(),
+          testElement,
+          "Hilt tests must be public, but found: %s",
+          XElements.toStableString(testElement));
+      new TestComponentDataGenerator(processingEnv(), metadataElement, rootMetadata, componentNames)
+          .generate();
+    }
+  }
+
+  private void generateApplication(XTypeElement rootElement) throws IOException {
+    // The generated application references the generated component so they must be generated
+    // in the same build unit. Thus, we only generate the application here if we're using the
+    // Hilt Gradle plugin's aggregating task. If we're using the aggregating processor, we need
+    // to generate the application within AndroidEntryPointProcessor instead.
+    if (!useAggregatingRootProcessor(processingEnv())) {
+      AndroidEntryPointMetadata metadata = AndroidEntryPointMetadata.of(rootElement);
+      new ApplicationGenerator(processingEnv(), metadata).generate();
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java
index 85eb59a..08c0e8b 100644
--- a/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java
+++ b/java/dagger/hilt/processor/internal/root/ComponentTreeDepsProcessor.java
@@ -16,189 +16,20 @@
 
 package dagger.hilt.processor.internal.root;
 
-import static com.google.auto.common.MoreElements.asType;
-import static com.google.common.collect.Iterables.getOnlyElement;
-import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-import static javax.lang.model.element.Modifier.PUBLIC;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.ClassName;
-import dagger.hilt.android.processor.internal.androidentrypoint.AndroidEntryPointMetadata;
-import dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ComponentDescriptor;
-import dagger.hilt.processor.internal.ComponentNames;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
-import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata;
-import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
-import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata;
-import dagger.hilt.processor.internal.aliasof.AliasOfs;
-import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata;
-import dagger.hilt.processor.internal.definecomponent.DefineComponents;
-import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
-import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Processor that outputs dagger components based on transitive build deps. */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class ComponentTreeDepsProcessor extends BaseProcessor {
-  private final Set<ClassName> componentTreeDepNames = new HashSet<>();
-  private final Set<ClassName> processed = new HashSet<>();
-  private final DefineComponents defineComponents = DefineComponents.create();
-
+public final class ComponentTreeDepsProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public ImmutableSet<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.COMPONENT_TREE_DEPS.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) {
-    componentTreeDepNames.add(ClassName.get(asType(element)));
-  }
-
-  @Override
-  public void postRoundProcess(RoundEnvironment roundEnv) throws Exception {
-    ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsToProcess =
-        componentTreeDepNames.stream()
-            .filter(className -> !processed.contains(className))
-            .map(className -> getElementUtils().getTypeElement(className.canonicalName()))
-            .map(element -> ComponentTreeDepsMetadata.from(element, getElementUtils()))
-            .collect(toImmutableSet());
-
-    for (ComponentTreeDepsMetadata metadata : componentTreeDepsToProcess) {
-      processComponentTreeDeps(metadata);
-    }
-  }
-
-  private void processComponentTreeDeps(ComponentTreeDepsMetadata metadata) throws IOException {
-    TypeElement metadataElement = getElementUtils().getTypeElement(metadata.name().canonicalName());
-    try {
-      // We choose a name for the generated components/wrapper based off of the originating element
-      // annotated with @ComponentTreeDeps. This is close to but isn't necessarily a "real" name of
-      // a root, since with shared test components, even for single roots, the component tree deps
-      // will be moved to a shared package with a deduped name.
-      ClassName renamedRoot = Processors.removeNameSuffix(metadataElement, "_ComponentTreeDeps");
-      ComponentNames componentNames = ComponentNames.withRenaming(rootName -> renamedRoot);
-
-      boolean isDefaultRoot = ClassNames.DEFAULT_ROOT.equals(renamedRoot);
-      ImmutableSet<Root> roots =
-          AggregatedRootMetadata.from(metadata.aggregatedRootDeps(), processingEnv).stream()
-              .map(AggregatedRootMetadata::rootElement)
-              .map(rootElement -> Root.create(rootElement, getProcessingEnv()))
-              .collect(toImmutableSet());
-
-      // TODO(bcorso): For legacy reasons, a lot of the generating code requires a "root" as input
-      // since we used to assume 1 root per component tree. Now that each ComponentTreeDeps may
-      // represent multiple roots, we should refactor this logic.
-      Root root =
-          isDefaultRoot
-              ? Root.createDefaultRoot(getProcessingEnv())
-              // Non-default roots should only ever be associated with one root element
-              : getOnlyElement(roots);
-
-      ImmutableSet<ComponentDescriptor> componentDescriptors =
-          defineComponents.getComponentDescriptors(
-              DefineComponentClassesMetadata.from(
-                  metadata.defineComponentDeps(), getElementUtils()));
-      ComponentTree tree = ComponentTree.from(componentDescriptors);
-      ComponentDependencies deps =
-          ComponentDependencies.from(
-              componentDescriptors,
-              AggregatedDepsMetadata.from(metadata.aggregatedDeps(), getElementUtils()),
-              AggregatedUninstallModulesMetadata.from(
-                  metadata.aggregatedUninstallModulesDeps(), getElementUtils()),
-              AggregatedEarlyEntryPointMetadata.from(
-                  metadata.aggregatedEarlyEntryPointDeps(), getElementUtils()),
-              getElementUtils());
-      AliasOfs aliasOfs =
-          AliasOfs.create(
-              AliasOfPropagatedDataMetadata.from(metadata.aliasOfDeps(), getElementUtils()),
-              componentDescriptors);
-      RootMetadata rootMetadata =
-          RootMetadata.create(root, tree, deps, aliasOfs, getProcessingEnv());
-
-      generateComponents(metadata, rootMetadata, componentNames);
-
-        // Generate a creator for the early entry point if there is a default component available
-        // and there are early entry points.
-        if (isDefaultRoot && !metadata.aggregatedEarlyEntryPointDeps().isEmpty()) {
-          EarlySingletonComponentCreatorGenerator.generate(getProcessingEnv());
-        }
-
-        if (root.isTestRoot()) {
-          // Generate test related classes for each test root that uses this component.
-          ImmutableList<RootMetadata> rootMetadatas =
-              roots.stream()
-                  .map(test -> RootMetadata.create(test, tree, deps, aliasOfs, getProcessingEnv()))
-                  .collect(toImmutableList());
-          generateTestComponentData(metadataElement, rootMetadatas, componentNames);
-        } else {
-          generateApplication(root.element());
-        }
-
-      setProcessingState(metadata, root);
-    } catch (Exception e) {
-      processed.add(metadata.name());
-      throw e;
-    }
-  }
-
-  private void setProcessingState(ComponentTreeDepsMetadata metadata, Root root) {
-    processed.add(metadata.name());
-  }
-
-  private void generateComponents(
-      ComponentTreeDepsMetadata metadata, RootMetadata rootMetadata, ComponentNames componentNames)
-      throws IOException {
-    RootGenerator.generate(metadata, rootMetadata, componentNames, getProcessingEnv());
-  }
-
-  private void generateTestComponentData(
-      TypeElement metadataElement,
-      ImmutableList<RootMetadata> rootMetadatas,
-      ComponentNames componentNames)
-      throws IOException {
-    for (RootMetadata rootMetadata : rootMetadatas) {
-      // TODO(bcorso): Consider moving this check earlier into processEach.
-      TypeElement testElement = rootMetadata.testRootMetadata().testElement();
-      ProcessorErrors.checkState(
-          testElement.getModifiers().contains(PUBLIC),
-          testElement,
-          "Hilt tests must be public, but found: %s",
-          testElement);
-      new TestComponentDataGenerator(
-              getProcessingEnv(), metadataElement, rootMetadata, componentNames)
-          .generate();
-    }
-  }
-
-  private void generateApplication(TypeElement rootElement) throws IOException {
-    // The generated application references the generated component so they must be generated
-    // in the same build unit. Thus, we only generate the application here if we're using the
-    // Hilt Gradle plugin's aggregating task. If we're using the aggregating processor, we need
-    // to generate the application within AndroidEntryPointProcessor instead.
-    if (!useAggregatingRootProcessor(getProcessingEnv())) {
-      AndroidEntryPointMetadata metadata =
-          AndroidEntryPointMetadata.of(getProcessingEnv(), rootElement);
-      new ApplicationGenerator(
-              getProcessingEnv(),
-              metadata)
-          .generate();
-    }
+  protected BaseProcessingStep processingStep() {
+    return new ComponentTreeDepsProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java
index f0066e1..a0fc70b 100644
--- a/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/EarlySingletonComponentCreatorGenerator.java
@@ -16,14 +16,14 @@
 
 package dagger.hilt.processor.internal.root;
 
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.MethodSpec;
 import com.squareup.javapoet.TypeSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 
 /** Generator for the {@code EarlySingletonComponentCreator}. */
 final class EarlySingletonComponentCreatorGenerator {
@@ -36,7 +36,7 @@
       ClassName.get(
           "dagger.hilt.android.internal.testing.root", "DaggerDefault_HiltComponents_SingletonC");
 
-  static void generate(ProcessingEnvironment env) throws IOException {
+  static void generate(XProcessingEnv env) {
     TypeSpec.Builder builder =
         TypeSpec.classBuilder(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL)
             .superclass(EARLY_SINGLETON_COMPONENT_CREATOR)
@@ -54,9 +54,11 @@
 
     Processors.addGeneratedAnnotation(builder, env, ClassNames.ROOT_PROCESSOR.toString());
 
-    JavaFile.builder(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL.packageName(), builder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(EARLY_SINGLETON_COMPONENT_CREATOR_IMPL.packageName(), builder.build())
+                .build(),
+            Mode.Isolating);
   }
 
   private EarlySingletonComponentCreatorGenerator() {}
diff --git a/java/dagger/hilt/processor/internal/root/KspComponentTreeDepsProcessor.java b/java/dagger/hilt/processor/internal/root/KspComponentTreeDepsProcessor.java
new file mode 100644
index 0000000..5b51abd
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/KspComponentTreeDepsProcessor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.root;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Processor that outputs dagger components based on transitive build deps. */
+public final class KspComponentTreeDepsProcessor extends KspBaseProcessingStepProcessor {
+
+  public KspComponentTreeDepsProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new ComponentTreeDepsProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspComponentTreeDepsProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspComponentTreeDepsProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/KspRootProcessor.java b/java/dagger/hilt/processor/internal/root/KspRootProcessor.java
new file mode 100644
index 0000000..ef823ce
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/KspRootProcessor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.root;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Processor that outputs dagger components based on transitive build deps. */
+public final class KspRootProcessor extends KspBaseProcessingStepProcessor {
+
+  public KspRootProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new RootProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspRootProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspRootProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java
index 87efa20..d2e3a93 100644
--- a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelGenerator.java
@@ -16,21 +16,21 @@
 
 package dagger.hilt.processor.internal.root;
 
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XTypeElement;
 import com.squareup.javapoet.AnnotationSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 
 /** Generates an {@link dagger.hilt.internal.processedrootsentinel.ProcessedRootSentinel}. */
 final class ProcessedRootSentinelGenerator {
-  private final TypeElement processedRoot;
-  private final ProcessingEnvironment processingEnv;
+  private final XTypeElement processedRoot;
+  private final Mode mode;
 
-  ProcessedRootSentinelGenerator(TypeElement processedRoot, ProcessingEnvironment processingEnv) {
+  ProcessedRootSentinelGenerator(XTypeElement processedRoot, Mode mode) {
     this.processedRoot = processedRoot;
-    this.processingEnv = processingEnv;
+    this.mode = mode;
   }
 
   void generate() throws IOException {
@@ -41,6 +41,6 @@
             .build(),
         processedRoot,
         getClass(),
-        processingEnv);
+        mode);
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java
index ffc67f4..ba0be42 100644
--- a/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java
+++ b/java/dagger/hilt/processor/internal/root/ProcessedRootSentinelMetadata.java
@@ -18,20 +18,16 @@
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.AggregatedElements;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr;
 import java.util.stream.Collectors;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /**
  * Represents the values stored in an {@link
@@ -41,42 +37,35 @@
 abstract class ProcessedRootSentinelMetadata {
 
   /** Returns the aggregating element */
-  public abstract TypeElement aggregatingElement();
+  public abstract XTypeElement aggregatingElement();
 
   /** Returns the processed root elements. */
-  abstract ImmutableSet<TypeElement> rootElements();
+  abstract ImmutableSet<XTypeElement> rootElements();
 
-  static ImmutableSet<ProcessedRootSentinelMetadata> from(Elements elements) {
+  static ImmutableSet<ProcessedRootSentinelMetadata> from(XProcessingEnv env) {
     return AggregatedElements.from(
-            ClassNames.PROCESSED_ROOT_SENTINEL_PACKAGE,
-            ClassNames.PROCESSED_ROOT_SENTINEL,
-            elements)
+            ClassNames.PROCESSED_ROOT_SENTINEL_PACKAGE, ClassNames.PROCESSED_ROOT_SENTINEL, env)
         .stream()
-        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .map(aggregatedElement -> create(aggregatedElement, env))
         .collect(toImmutableSet());
   }
 
   static ProcessedRootSentinelIr toIr(ProcessedRootSentinelMetadata metadata) {
     return new ProcessedRootSentinelIr(
-        ClassName.get(metadata.aggregatingElement()),
+        metadata.aggregatingElement().getClassName(),
         metadata.rootElements().stream()
-            .map(ClassName::get)
+            .map(XTypeElement::getClassName)
             .map(ClassName::canonicalName)
-            .collect(Collectors.toList())
-    );
+            .collect(Collectors.toList()));
   }
 
-  private static ProcessedRootSentinelMetadata create(TypeElement element, Elements elements) {
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.PROCESSED_ROOT_SENTINEL);
-
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(elements, annotationMirror);
+  private static ProcessedRootSentinelMetadata create(XTypeElement element, XProcessingEnv env) {
+    XAnnotation annotationMirror = element.getAnnotation(ClassNames.PROCESSED_ROOT_SENTINEL);
 
     return new AutoValue_ProcessedRootSentinelMetadata(
         element,
-        AnnotationValues.getStrings(values.get("roots")).stream()
-            .map(elements::getTypeElement)
+        annotationMirror.getAsStringList("roots").stream()
+            .map(env::requireTypeElement)
             .collect(toImmutableSet()));
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/Root.java b/java/dagger/hilt/processor/internal/root/Root.java
index 00e17ca..86b8159 100644
--- a/java/dagger/hilt/processor/internal/root/Root.java
+++ b/java/dagger/hilt/processor/internal/root/Root.java
@@ -16,13 +16,13 @@
 
 package dagger.hilt.processor.internal.root;
 
-import com.google.auto.common.MoreElements;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
+import dagger.internal.codegen.xprocessing.XElements;
 
 /** Metadata for a root element that can trigger the {@link RootProcessor}. */
 @AutoValue
@@ -40,41 +40,40 @@
    *   <li>To inject tests that only depend on global dependencies
    * </ul>
    */
-  static Root createDefaultRoot(ProcessingEnvironment env) {
-    TypeElement rootElement =
-        env.getElementUtils().getTypeElement(ClassNames.DEFAULT_ROOT.canonicalName());
+  static Root createDefaultRoot(XProcessingEnv env) {
+    XTypeElement rootElement = env.requireTypeElement(ClassNames.DEFAULT_ROOT.canonicalName());
     return new AutoValue_Root(rootElement, rootElement, /*isTestRoot=*/ true);
   }
 
   /** Creates a {@plainlink Root root} for the given {@plainlink Element element}. */
-  static Root create(Element element, ProcessingEnvironment env) {
-    TypeElement rootElement = MoreElements.asType(element);
-    if (ClassNames.DEFAULT_ROOT.equals(ClassName.get(rootElement))) {
+  static Root create(XElement element, XProcessingEnv env) {
+    XTypeElement rootElement = XElements.asTypeElement(element);
+    if (ClassNames.DEFAULT_ROOT.equals(rootElement.getClassName())) {
       return createDefaultRoot(env);
     }
     return new AutoValue_Root(rootElement, rootElement, RootType.of(rootElement).isTestRoot());
   }
 
   /** Returns the root element that should be used with processing. */
-  abstract TypeElement element();
+  abstract XTypeElement element();
 
   /**
    * Returns the originating root element. In most cases this will be the same as {@link
    * #element()}.
    */
-  abstract TypeElement originatingRootElement();
+  abstract XTypeElement originatingRootElement();
 
   /** Returns {@code true} if this is a test root. */
   abstract boolean isTestRoot();
 
   /** Returns the class name of the root element. */
   ClassName classname() {
-    return ClassName.get(element());
+    return element().getClassName();
   }
 
   /** Returns the class name of the originating root element. */
   ClassName originatingRootClassname() {
-    return ClassName.get(originatingRootElement());
+    return originatingRootElement().getClassName();
   }
 
   @Override
diff --git a/java/dagger/hilt/processor/internal/root/RootFileFormatter.java b/java/dagger/hilt/processor/internal/root/RootFileFormatter.java
index 45cdd22..0f22604 100644
--- a/java/dagger/hilt/processor/internal/root/RootFileFormatter.java
+++ b/java/dagger/hilt/processor/internal/root/RootFileFormatter.java
@@ -16,15 +16,23 @@
 
 package dagger.hilt.processor.internal.root;
 
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.compat.XConverters;
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.JavaFile;
+import java.io.BufferedWriter;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.Writer;
 import java.util.regex.MatchResult;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import javax.annotation.processing.Filer;
-import javax.lang.model.element.Element;
-import javax.tools.JavaFileObject;
 
 /**
  * Typically we would just use {@code JavaFile#writeTo()} to write files. However, this formatter
@@ -38,28 +46,25 @@
   private static final Pattern CLASS_PATERN = Pattern.compile("(\\h*)(.*class.*implements)(.*\\{)");
 
   /** Formats the {@link JavaFile} java source file. */
-  static void write(JavaFile javaFile, Filer filer) throws IOException {
-    String fileName =
-        javaFile.packageName.isEmpty()
-            ? javaFile.typeSpec.name
-            : javaFile.packageName + "." + javaFile.typeSpec.name;
-
-    Element[] originatingElements = javaFile.typeSpec.originatingElements.toArray(new Element[0]);
+  static void write(JavaFile javaFile, XProcessingEnv env) throws IOException {
+    ImmutableList<XElement> originatingElements =
+        javaFile.typeSpec.originatingElements.stream()
+            .map(element -> XConverters.toXProcessing(element, env))
+            .collect(toImmutableList());
 
     StringBuilder sb = new StringBuilder("");
     javaFile.writeTo(sb);
     String fileContent = formatInterfaces(sb.toString(), CLASS_PATERN);
 
-    JavaFileObject filerSourceFile = filer.createSourceFile(fileName, originatingElements);
-    try (Writer writer = filerSourceFile.openWriter()) {
+    try (OutputStream outputStream = env.getFiler()
+            .writeSource(
+                javaFile.packageName,
+                javaFile.typeSpec.name,
+                "java",
+                originatingElements,
+                Mode.Isolating);
+        Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) {
       writer.write(fileContent);
-    } catch (Exception e) {
-      try {
-        filerSourceFile.delete();
-      } catch (Exception ignored) {
-        // Nothing to do.
-      }
-      throw e;
     }
   }
 
diff --git a/java/dagger/hilt/processor/internal/root/RootGenerator.java b/java/dagger/hilt/processor/internal/root/RootGenerator.java
index 732c6ea..2868f7c 100644
--- a/java/dagger/hilt/processor/internal/root/RootGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/RootGenerator.java
@@ -16,14 +16,17 @@
 
 package dagger.hilt.processor.internal.root;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
-import static dagger.hilt.processor.internal.Processors.toClassNames;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static javax.lang.model.element.Modifier.ABSTRACT;
 import static javax.lang.model.element.Modifier.PUBLIC;
 import static javax.lang.model.element.Modifier.STATIC;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XProcessingEnv.Backend;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -43,9 +46,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
 
 /** Generates components and any other classes needed for a root. */
 final class RootGenerator {
@@ -54,7 +55,7 @@
       ComponentTreeDepsMetadata componentTreeDepsMetadata,
       RootMetadata metadata,
       ComponentNames componentNames,
-      ProcessingEnvironment env)
+      XProcessingEnv env)
       throws IOException {
     new RootGenerator(
             componentTreeDepsMetadata,
@@ -64,9 +65,9 @@
         .generateComponents();
   }
 
-  private final TypeElement originatingElement;
+  private final XTypeElement originatingElement;
   private final RootMetadata metadata;
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final Root root;
   private final Map<String, Integer> simpleComponentNamesToDedupeSuffix = new HashMap<>();
   private final Map<ComponentDescriptor, ClassName> componentNameMap = new HashMap<>();
@@ -76,10 +77,8 @@
       ComponentTreeDepsMetadata componentTreeDepsMetadata,
       RootMetadata metadata,
       ComponentNames componentNames,
-      ProcessingEnvironment env) {
-    this.originatingElement =
-        checkNotNull(
-            env.getElementUtils().getTypeElement(componentTreeDepsMetadata.name().toString()));
+      XProcessingEnv env) {
+    this.originatingElement = env.requireTypeElement(componentTreeDepsMetadata.name().toString());
     this.metadata = metadata;
     this.componentNames = componentNames;
     this.env = env;
@@ -104,7 +103,10 @@
     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
       ImmutableSet<ClassName> modules =
           ImmutableSet.<ClassName>builder()
-              .addAll(toClassNames(metadata.modules(componentDescriptor.component())))
+              .addAll(
+                  metadata.modules(componentDescriptor.component()).stream()
+                      .map(XTypeElement::getClassName)
+                      .collect(toImmutableSet()))
               .addAll(
                   componentTree.childrenOf(componentDescriptor).stream()
                       .map(subcomponentBuilderModules::get)
@@ -127,10 +129,15 @@
               .build());
     }
 
-    RootFileFormatter.write(
+    JavaFile componentsWrapperJavaFile =
         JavaFile.builder(componentsWrapperClassName.packageName(), componentsWrapper.build())
-            .build(),
-        env.getFiler());
+            .build();
+    // TODO(danysantiago): Support formatting in KSP: b/288572563
+    if (env.getBackend() == Backend.KSP) {
+      env.getFiler().write(componentsWrapperJavaFile, Mode.Isolating);
+    } else {
+      RootFileFormatter.write(componentsWrapperJavaFile, env);
+    }
   }
 
   private static ComponentTree filterDescriptors(ComponentTree componentTree) {
@@ -178,7 +185,6 @@
       ClassName componentName, ClassName builderName, ClassName moduleName) {
     TypeSpec.Builder subcomponentBuilderModule =
         TypeSpec.interfaceBuilder(moduleName)
-            .addOriginatingElement(originatingElement)
             .addModifiers(ABSTRACT)
             .addAnnotation(
                 AnnotationSpec.builder(ClassNames.MODULE)
@@ -192,7 +198,7 @@
                     .returns(builderName)
                     .addParameter(componentName.nestedClass("Builder"), "builder")
                     .build());
-
+    JavaPoetExtKt.addOriginatingElement(subcomponentBuilderModule, originatingElement);
     Processors.addGeneratedAnnotation(
         subcomponentBuilderModule, env, ClassNames.ROOT_PROCESSOR.toString());
 
@@ -203,13 +209,15 @@
     return descriptor
         .creator()
         .map(
-            creator ->
-                TypeSpec.interfaceBuilder("Builder")
-                    .addOriginatingElement(originatingElement)
-                    .addModifiers(STATIC, ABSTRACT)
-                    .addSuperinterface(creator)
-                    .addAnnotation(componentBuilderAnnotation(descriptor))
-                    .build());
+            creator -> {
+              TypeSpec.Builder builder =
+                  TypeSpec.interfaceBuilder("Builder")
+                      .addModifiers(STATIC, ABSTRACT)
+                      .addSuperinterface(creator)
+                      .addAnnotation(componentBuilderAnnotation(descriptor));
+              JavaPoetExtKt.addOriginatingElement(builder, originatingElement);
+              return builder.build();
+            });
   }
 
   private ClassName componentAnnotation(ComponentDescriptor componentDescriptor) {
diff --git a/java/dagger/hilt/processor/internal/root/RootMetadata.java b/java/dagger/hilt/processor/internal/root/RootMetadata.java
index 05fd078..8166d66 100644
--- a/java/dagger/hilt/processor/internal/root/RootMetadata.java
+++ b/java/dagger/hilt/processor/internal/root/RootMetadata.java
@@ -19,10 +19,11 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Suppliers.memoize;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-import static javax.lang.model.element.Modifier.ABSTRACT;
-import static javax.lang.model.element.Modifier.PRIVATE;
-import static javax.lang.model.element.Modifier.STATIC;
 
+import androidx.room.compiler.processing.XConstructorElement;
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
@@ -33,14 +34,8 @@
 import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.aggregateddeps.ComponentDependencies;
 import dagger.hilt.processor.internal.aliasof.AliasOfs;
-import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtil;
-import dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils;
 import java.util.List;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.ElementFilter;
-import javax.lang.model.util.Elements;
 import javax.tools.Diagnostic;
 
 /** Contains metadata about the given hilt root. */
@@ -53,7 +48,7 @@
       ComponentTree componentTree,
       ComponentDependencies deps,
       AliasOfs aliasOfs,
-      ProcessingEnvironment env) {
+      XProcessingEnv env) {
     return createInternal(root, componentTree, deps, aliasOfs, env);
   }
 
@@ -66,15 +61,14 @@
       ComponentTree componentTree,
       ComponentDependencies deps,
       AliasOfs aliasOfs,
-      ProcessingEnvironment env) {
+      XProcessingEnv env) {
     RootMetadata metadata = new RootMetadata(root, componentTree, deps, aliasOfs, env);
     metadata.validate();
     return metadata;
   }
 
   private final Root root;
-  private final ProcessingEnvironment env;
-  private final Elements elements;
+  private final XProcessingEnv env;
   private final ComponentTree componentTree;
   private final ComponentDependencies deps;
   private final AliasOfs aliasOfs;
@@ -88,10 +82,9 @@
       ComponentTree componentTree,
       ComponentDependencies deps,
       AliasOfs aliasOfs,
-      ProcessingEnvironment env) {
+      XProcessingEnv env) {
     this.root = root;
     this.env = env;
-    this.elements = env.getElementUtils();
     this.componentTree = componentTree;
     this.deps = deps;
     this.aliasOfs = aliasOfs;
@@ -109,15 +102,15 @@
     return deps;
   }
 
-  public ImmutableSet<TypeElement> modules(ClassName componentName) {
-    return deps.modules().get(componentName);
+  public ImmutableSet<XTypeElement> modules(ClassName componentName) {
+    return deps.modules().get(componentName).stream().collect(toImmutableSet());
   }
 
   public ImmutableSet<TypeName> entryPoints(ClassName componentName) {
     return ImmutableSet.<TypeName>builder()
         .addAll(
             deps.entryPoints().get(componentName).stream()
-                .map(ClassName::get)
+                .map(XTypeElement::getClassName)
                 .collect(toImmutableSet()))
         .add(
             root.isTestRoot() && componentName.equals(ClassNames.SINGLETON_COMPONENT)
@@ -138,10 +131,10 @@
    * filters out framework modules directly referenced by the codegen, since those are already known
    * about and are specifically handled in the codegen.
    */
-  public ImmutableSet<TypeElement> modulesThatDaggerCannotConstruct(ClassName componentName) {
+  public ImmutableSet<XTypeElement> modulesThatDaggerCannotConstruct(ClassName componentName) {
     return modules(componentName).stream()
         .filter(module -> !daggerCanConstruct(module))
-        .filter(module -> !APPLICATION_CONTEXT_MODULE.equals(ClassName.get(module)))
+        .filter(module -> !APPLICATION_CONTEXT_MODULE.equals(module.getClassName()))
         .collect(toImmutableSet());
   }
 
@@ -167,7 +160,7 @@
     // Only test modules in the application component can be missing default constructor
     for (ComponentDescriptor componentDescriptor : componentTree.getComponentDescriptors()) {
       ClassName componentName = componentDescriptor.component();
-      for (TypeElement extraModule : modulesThatDaggerCannotConstruct(componentName)) {
+      for (XTypeElement extraModule : modulesThatDaggerCannotConstruct(componentName)) {
         if (root.isTestRoot() && !componentName.equals(ClassNames.SINGLETON_COMPONENT)) {
           env.getMessager()
               .printMessage(
@@ -201,11 +194,8 @@
     return builder.build();
   }
 
-  private static boolean daggerCanConstruct(TypeElement type) {
-    KotlinMetadataUtil metadataUtil = KotlinMetadataUtils.getMetadataUtil();
-    boolean isKotlinObject =
-        metadataUtil.isObjectClass(type) || metadataUtil.isCompanionObjectClass(type);
-    if (isKotlinObject) {
+  private static boolean daggerCanConstruct(XTypeElement type) {
+    if (type.isKotlinObject()) {
       // Treat Kotlin object modules as if Dagger can construct them (it technically can't, but it
       // doesn't need to as it can use them since all their provision methods are static).
       return true;
@@ -216,29 +206,29 @@
         && (hasOnlyStaticProvides(type) || hasVisibleEmptyConstructor(type));
   }
 
-  private static boolean isInnerClass(TypeElement type) {
-    return type.getNestingKind().isNested() && !type.getModifiers().contains(STATIC);
+  private static boolean isInnerClass(XTypeElement type) {
+    return type.isNested() && !type.isStatic();
   }
 
-  private static boolean hasNonDaggerAbstractMethod(TypeElement type) {
+  private static boolean hasNonDaggerAbstractMethod(XTypeElement type) {
     // TODO(erichang): Actually this isn't really supported b/28989613
-    return ElementFilter.methodsIn(type.getEnclosedElements()).stream()
-        .filter(method -> method.getModifiers().contains(ABSTRACT))
+    return type.getDeclaredMethods().stream()
+        .filter(XMethodElement::isAbstract)
         .anyMatch(method -> !Processors.hasDaggerAbstractMethodAnnotation(method));
   }
 
-  private static boolean hasOnlyStaticProvides(TypeElement type) {
+  private static boolean hasOnlyStaticProvides(XTypeElement type) {
     // TODO(erichang): Check for @Produces too when we have a producers story
-    return ElementFilter.methodsIn(type.getEnclosedElements()).stream()
-        .filter(method -> Processors.hasAnnotation(method, ClassNames.PROVIDES))
-        .allMatch(method -> method.getModifiers().contains(STATIC));
+    return type.getDeclaredMethods().stream()
+        .filter(method -> method.hasAnnotation(ClassNames.PROVIDES))
+        .allMatch(XMethodElement::isStatic);
   }
 
-  private static boolean hasVisibleEmptyConstructor(TypeElement type) {
-    List<ExecutableElement> constructors = ElementFilter.constructorsIn(type.getEnclosedElements());
+  private static boolean hasVisibleEmptyConstructor(XTypeElement type) {
+    List<XConstructorElement> constructors = type.getConstructors();
     return constructors.isEmpty()
         || constructors.stream()
             .filter(constructor -> constructor.getParameters().isEmpty())
-            .anyMatch(constructor -> !constructor.getModifiers().contains(PRIVATE));
+            .anyMatch(constructor -> !constructor.isPrivate());
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/RootProcessingStep.java b/java/dagger/hilt/processor/internal/root/RootProcessingStep.java
new file mode 100644
index 0000000..f281995
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/root/RootProcessingStep.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2019 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.root;
+
+import static com.google.common.base.Preconditions.checkState;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled;
+import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static java.util.Arrays.stream;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BadInputException;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata;
+import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata;
+import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata;
+import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
+import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs;
+import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr;
+import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr;
+import dagger.hilt.processor.internal.root.ir.AggregatedRootIr;
+import dagger.hilt.processor.internal.root.ir.AggregatedRootIrValidator;
+import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr;
+import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr;
+import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIr;
+import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIrCreator;
+import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr;
+import dagger.hilt.processor.internal.root.ir.InvalidRootsException;
+import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr;
+import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
+import dagger.internal.codegen.xprocessing.XElements;
+import java.util.Set;
+
+/** Processor that outputs dagger components based on transitive build deps. */
+public final class RootProcessingStep extends BaseProcessingStep {
+
+  private boolean processed;
+  private GeneratesRootInputs generatesRootInputs;
+
+  public RootProcessingStep(XProcessingEnv env) {
+    super(env);
+    generatesRootInputs = new GeneratesRootInputs(processingEnv());
+  }
+
+  private Mode getMode() {
+    return useAggregatingRootProcessor(processingEnv()) ? Mode.Aggregating : Mode.Isolating;
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return stream(RootType.values()).map(RootType::className).collect(toImmutableSet());
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) throws Exception {
+    XTypeElement rootElement = XElements.asTypeElement(element);
+    // TODO(bcorso): Move this logic into a separate isolating processor to avoid regenerating it
+    // for unrelated changes in Gradle.
+    RootType rootType = RootType.of(rootElement);
+    if (rootType.isTestRoot()) {
+      new TestInjectorGenerator(processingEnv(), TestRootMetadata.of(processingEnv(), rootElement))
+          .generate();
+    }
+
+    XTypeElement originatingRootElement =
+        Root.create(rootElement, processingEnv()).originatingRootElement();
+    new AggregatedRootGenerator(
+            rootElement, originatingRootElement, processingEnv().requireTypeElement(annotation))
+        .generate();
+  }
+
+  @Override
+  protected void postProcess(XProcessingEnv env, XRoundEnv roundEnv) throws Exception {
+    if (!useAggregatingRootProcessor(processingEnv())) {
+      return;
+    }
+    ImmutableSet<XElement> newElements =
+        generatesRootInputs.getElementsToWaitFor(roundEnv).stream().collect(toImmutableSet());
+    if (processed) {
+      checkState(
+          newElements.isEmpty(),
+          "Found extra modules after compilation: %s\n"
+              + "(If you are adding an annotation processor that generates root input for hilt, "
+              + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)",
+          newElements.stream().map(XElements::toStableString).collect(toImmutableList()));
+    } else if (newElements.isEmpty()) {
+      processed = true;
+
+      ImmutableSet<AggregatedRootIr> rootsToProcess = rootsToProcess();
+      if (rootsToProcess.isEmpty()) {
+        return;
+      }
+      // Generate an @ComponentTreeDeps for each unique component tree.
+      ComponentTreeDepsGenerator componentTreeDepsGenerator =
+          new ComponentTreeDepsGenerator(processingEnv(), getMode());
+      for (ComponentTreeDepsMetadata metadata : componentTreeDepsMetadatas(rootsToProcess)) {
+        componentTreeDepsGenerator.generate(metadata);
+      }
+
+      // Generate a sentinel for all processed roots.
+      for (AggregatedRootIr ir : rootsToProcess) {
+        XTypeElement rootElement = processingEnv().requireTypeElement(ir.getRoot().canonicalName());
+        new ProcessedRootSentinelGenerator(rootElement, getMode()).generate();
+      }
+    }
+  }
+
+  private ImmutableSet<AggregatedRootIr> rootsToProcess() {
+    ImmutableSet<ProcessedRootSentinelIr> processedRoots =
+        ProcessedRootSentinelMetadata.from(processingEnv()).stream()
+            .map(ProcessedRootSentinelMetadata::toIr)
+            .collect(toImmutableSet());
+    ImmutableSet<AggregatedRootIr> aggregatedRoots =
+        AggregatedRootMetadata.from(processingEnv()).stream()
+            .map(AggregatedRootMetadata::toIr)
+            .collect(toImmutableSet());
+
+    boolean isCrossCompilationRootValidationDisabled =
+        isCrossCompilationRootValidationDisabled(
+            aggregatedRoots.stream()
+                .map(ir -> processingEnv().requireTypeElement(ir.getRoot().canonicalName()))
+                .collect(toImmutableSet()),
+            processingEnv());
+    try {
+      return ImmutableSet.copyOf(
+          AggregatedRootIrValidator.rootsToProcess(
+              isCrossCompilationRootValidationDisabled, processedRoots, aggregatedRoots));
+    } catch (InvalidRootsException ex) {
+      throw new BadInputException(ex.getMessage());
+    }
+  }
+
+  private ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsMetadatas(
+      ImmutableSet<AggregatedRootIr> aggregatedRoots) {
+    ImmutableSet<DefineComponentClassesIr> defineComponentDeps =
+        DefineComponentClassesMetadata.from(processingEnv()).stream()
+            .map(DefineComponentClassesMetadata::toIr)
+            .collect(toImmutableSet());
+    ImmutableSet<AliasOfPropagatedDataIr> aliasOfDeps =
+        AliasOfPropagatedDataMetadata.from(processingEnv()).stream()
+            .map(AliasOfPropagatedDataMetadata::toIr)
+            .collect(toImmutableSet());
+    ImmutableSet<AggregatedDepsIr> aggregatedDeps =
+        AggregatedDepsMetadata.from(processingEnv()).stream()
+            .map(AggregatedDepsMetadata::toIr)
+            .collect(toImmutableSet());
+    ImmutableSet<AggregatedUninstallModulesIr> aggregatedUninstallModulesDeps =
+        AggregatedUninstallModulesMetadata.from(processingEnv()).stream()
+            .map(AggregatedUninstallModulesMetadata::toIr)
+            .collect(toImmutableSet());
+    ImmutableSet<AggregatedEarlyEntryPointIr> aggregatedEarlyEntryPointDeps =
+        AggregatedEarlyEntryPointMetadata.from(processingEnv()).stream()
+            .map(AggregatedEarlyEntryPointMetadata::toIr)
+            .collect(toImmutableSet());
+
+    // We should be guaranteed that there are no mixed roots, so check if this is prod or test.
+    boolean isTest = aggregatedRoots.stream().anyMatch(AggregatedRootIr::isTestRoot);
+    Set<ComponentTreeDepsIr> componentTreeDeps =
+        ComponentTreeDepsIrCreator.components(
+            isTest,
+            isSharedTestComponentsEnabled(processingEnv()),
+            aggregatedRoots,
+            defineComponentDeps,
+            aliasOfDeps,
+            aggregatedDeps,
+            aggregatedUninstallModulesDeps,
+            aggregatedEarlyEntryPointDeps);
+    return componentTreeDeps.stream()
+        .map(it -> ComponentTreeDepsMetadata.from(it, processingEnv()))
+        .collect(toImmutableSet());
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/root/RootProcessor.java b/java/dagger/hilt/processor/internal/root/RootProcessor.java
index 93ab9d9..cc2e224 100644
--- a/java/dagger/hilt/processor/internal/root/RootProcessor.java
+++ b/java/dagger/hilt/processor/internal/root/RootProcessor.java
@@ -16,192 +16,31 @@
 
 package dagger.hilt.processor.internal.root;
 
-import static com.google.common.base.Preconditions.checkState;
-import static dagger.hilt.processor.internal.HiltCompilerOptions.isCrossCompilationRootValidationDisabled;
-import static dagger.hilt.processor.internal.HiltCompilerOptions.isSharedTestComponentsEnabled;
 import static dagger.hilt.processor.internal.HiltCompilerOptions.useAggregatingRootProcessor;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.DYNAMIC;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
-import com.google.auto.common.MoreElements;
 import com.google.auto.service.AutoService;
 import com.google.common.collect.ImmutableSet;
-import dagger.hilt.processor.internal.BadInputException;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.aggregateddeps.AggregatedDepsMetadata;
-import dagger.hilt.processor.internal.aliasof.AliasOfPropagatedDataMetadata;
-import dagger.hilt.processor.internal.definecomponent.DefineComponentClassesMetadata;
-import dagger.hilt.processor.internal.earlyentrypoint.AggregatedEarlyEntryPointMetadata;
-import dagger.hilt.processor.internal.generatesrootinput.GeneratesRootInputs;
-import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr;
-import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr;
-import dagger.hilt.processor.internal.root.ir.AggregatedRootIr;
-import dagger.hilt.processor.internal.root.ir.AggregatedRootIrValidator;
-import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr;
-import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr;
-import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIr;
-import dagger.hilt.processor.internal.root.ir.ComponentTreeDepsIrCreator;
-import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr;
-import dagger.hilt.processor.internal.root.ir.InvalidRootsException;
-import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr;
-import dagger.hilt.processor.internal.uninstallmodules.AggregatedUninstallModulesMetadata;
-import java.util.Arrays;
-import java.util.Set;
-import javax.annotation.processing.ProcessingEnvironment;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.annotation.processing.RoundEnvironment;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Processor that outputs dagger components based on transitive build deps. */
 @IncrementalAnnotationProcessor(DYNAMIC)
 @AutoService(Processor.class)
-public final class RootProcessor extends BaseProcessor {
-
-  private boolean processed;
-  private GeneratesRootInputs generatesRootInputs;
-
+public final class RootProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public synchronized void init(ProcessingEnvironment processingEnvironment) {
-    super.init(processingEnvironment);
-    generatesRootInputs = new GeneratesRootInputs(processingEnvironment);
+  protected BaseProcessingStep processingStep() {
+    return new RootProcessingStep(getXProcessingEnv());
   }
 
   @Override
   public ImmutableSet<String> additionalProcessingOptions() {
-    return useAggregatingRootProcessor(getProcessingEnv())
+    return useAggregatingRootProcessor(getXProcessingEnv())
         ? ImmutableSet.of(AGGREGATING.getProcessorOption())
         : ImmutableSet.of(ISOLATING.getProcessorOption());
   }
-
-  @Override
-  public ImmutableSet<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.<String>builder()
-        .addAll(
-            Arrays.stream(RootType.values())
-                .map(rootType -> rootType.className().toString())
-                .collect(toImmutableSet()))
-        .build();
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    TypeElement rootElement = MoreElements.asType(element);
-    // TODO(bcorso): Move this logic into a separate isolating processor to avoid regenerating it
-    // for unrelated changes in Gradle.
-    RootType rootType = RootType.of(rootElement);
-    if (rootType.isTestRoot()) {
-      new TestInjectorGenerator(
-              getProcessingEnv(), TestRootMetadata.of(getProcessingEnv(), rootElement))
-          .generate();
-    }
-
-    TypeElement originatingRootElement =
-        Root.create(rootElement, getProcessingEnv()).originatingRootElement();
-    new AggregatedRootGenerator(rootElement, originatingRootElement, annotation, getProcessingEnv())
-        .generate();
-  }
-
-  @Override
-  public void postRoundProcess(RoundEnvironment roundEnv) throws Exception {
-    if (!useAggregatingRootProcessor(getProcessingEnv())) {
-      return;
-    }
-    ImmutableSet<Element> newElements = generatesRootInputs.getElementsToWaitFor(roundEnv);
-    if (processed) {
-      checkState(
-          newElements.isEmpty(),
-          "Found extra modules after compilation: %s\n"
-              + "(If you are adding an annotation processor that generates root input for hilt, "
-              + "the annotation must be annotated with @dagger.hilt.GeneratesRootInput.\n)",
-          newElements);
-    } else if (newElements.isEmpty()) {
-      processed = true;
-
-      ImmutableSet<AggregatedRootIr> rootsToProcess = rootsToProcess();
-      if (rootsToProcess.isEmpty()) {
-        return;
-      }
-      // Generate an @ComponentTreeDeps for each unique component tree.
-      ComponentTreeDepsGenerator componentTreeDepsGenerator =
-          new ComponentTreeDepsGenerator(getProcessingEnv());
-      for (ComponentTreeDepsMetadata metadata : componentTreeDepsMetadatas(rootsToProcess)) {
-        componentTreeDepsGenerator.generate(metadata);
-      }
-
-      // Generate a sentinel for all processed roots.
-      for (AggregatedRootIr ir : rootsToProcess) {
-        TypeElement rootElement = getElementUtils().getTypeElement(ir.getRoot().canonicalName());
-        new ProcessedRootSentinelGenerator(rootElement, getProcessingEnv()).generate();
-      }
-    }
-  }
-
-  private ImmutableSet<AggregatedRootIr> rootsToProcess() {
-    ImmutableSet<ProcessedRootSentinelIr> processedRoots =
-        ProcessedRootSentinelMetadata.from(getElementUtils()).stream()
-            .map(ProcessedRootSentinelMetadata::toIr)
-            .collect(toImmutableSet());
-    ImmutableSet<AggregatedRootIr> aggregatedRoots =
-        AggregatedRootMetadata.from(processingEnv).stream()
-            .map(AggregatedRootMetadata::toIr)
-            .collect(toImmutableSet());
-
-    boolean isCrossCompilationRootValidationDisabled =
-        isCrossCompilationRootValidationDisabled(
-            aggregatedRoots.stream()
-                .map(ir -> getElementUtils().getTypeElement(ir.getRoot().canonicalName()))
-                .collect(toImmutableSet()),
-            processingEnv);
-    try {
-      return ImmutableSet.copyOf(
-          AggregatedRootIrValidator.rootsToProcess(
-              isCrossCompilationRootValidationDisabled, processedRoots, aggregatedRoots));
-    } catch (InvalidRootsException ex) {
-      throw new BadInputException(ex.getMessage());
-    }
-  }
-
-  private ImmutableSet<ComponentTreeDepsMetadata> componentTreeDepsMetadatas(
-      ImmutableSet<AggregatedRootIr> aggregatedRoots) {
-    ImmutableSet<DefineComponentClassesIr> defineComponentDeps =
-        DefineComponentClassesMetadata.from(getElementUtils()).stream()
-            .map(DefineComponentClassesMetadata::toIr)
-            .collect(toImmutableSet());
-    ImmutableSet<AliasOfPropagatedDataIr> aliasOfDeps =
-        AliasOfPropagatedDataMetadata.from(getElementUtils()).stream()
-            .map(AliasOfPropagatedDataMetadata::toIr)
-            .collect(toImmutableSet());
-    ImmutableSet<AggregatedDepsIr> aggregatedDeps =
-        AggregatedDepsMetadata.from(getElementUtils()).stream()
-            .map(AggregatedDepsMetadata::toIr)
-            .collect(toImmutableSet());
-    ImmutableSet<AggregatedUninstallModulesIr> aggregatedUninstallModulesDeps =
-        AggregatedUninstallModulesMetadata.from(getElementUtils()).stream()
-            .map(AggregatedUninstallModulesMetadata::toIr)
-            .collect(toImmutableSet());
-    ImmutableSet<AggregatedEarlyEntryPointIr> aggregatedEarlyEntryPointDeps =
-        AggregatedEarlyEntryPointMetadata.from(getElementUtils()).stream()
-            .map(AggregatedEarlyEntryPointMetadata::toIr)
-            .collect(toImmutableSet());
-
-    // We should be guaranteed that there are no mixed roots, so check if this is prod or test.
-    boolean isTest = aggregatedRoots.stream().anyMatch(AggregatedRootIr::isTestRoot);
-    Set<ComponentTreeDepsIr> componentTreeDeps =
-        ComponentTreeDepsIrCreator.components(
-            isTest,
-            isSharedTestComponentsEnabled(processingEnv),
-            aggregatedRoots,
-            defineComponentDeps,
-            aliasOfDeps,
-            aggregatedDeps,
-            aggregatedUninstallModulesDeps,
-            aggregatedEarlyEntryPointDeps);
-    return componentTreeDeps.stream()
-        .map(it -> ComponentTreeDepsMetadata.from(it, getElementUtils()))
-        .collect(toImmutableSet());
-  }
 }
diff --git a/java/dagger/hilt/processor/internal/root/RootType.java b/java/dagger/hilt/processor/internal/root/RootType.java
index a6efc84..28bc12f 100644
--- a/java/dagger/hilt/processor/internal/root/RootType.java
+++ b/java/dagger/hilt/processor/internal/root/RootType.java
@@ -16,10 +16,9 @@
 
 package dagger.hilt.processor.internal.root;
 
+import androidx.room.compiler.processing.XTypeElement;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
-import javax.lang.model.element.TypeElement;
 
 /** The valid root types for Hilt applications. */
 // TODO(erichang): Fix this class so we don't have to have placeholders
@@ -46,12 +45,12 @@
     return annotation;
   }
 
-  public static RootType of(TypeElement element) {
-    if (Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_APP)) {
+  public static RootType of(XTypeElement element) {
+    if (element.hasAnnotation(ClassNames.HILT_ANDROID_APP)) {
       return ROOT;
-    } else if (Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_TEST)) {
+    } else if (element.hasAnnotation(ClassNames.HILT_ANDROID_TEST)) {
       return TEST_ROOT;
-    } else if (Processors.hasAnnotation(element, ClassNames.INTERNAL_TEST_ROOT)) {
+    } else if (element.hasAnnotation(ClassNames.INTERNAL_TEST_ROOT)) {
       return TEST_ROOT;
     }
     throw new IllegalStateException("Unknown root type: " + element);
diff --git a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java
index 19145f4..689dae7 100644
--- a/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/TestComponentDataGenerator.java
@@ -23,8 +23,12 @@
 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 static javax.lang.model.util.ElementFilter.constructorsIn;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XConstructorElement;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
@@ -37,21 +41,18 @@
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
 import java.util.List;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.ExecutableElement;
-import javax.lang.model.element.TypeElement;
 
 /** Generates an implementation of {@link dagger.hilt.android.internal.TestComponentData}. */
 public final class TestComponentDataGenerator {
-  private final ProcessingEnvironment processingEnv;
-  private final TypeElement originatingElement;
+  private final XProcessingEnv processingEnv;
+  private final XTypeElement originatingElement;
   private final RootMetadata rootMetadata;
   private final ClassName name;
   private final ComponentNames componentNames;
 
   public TestComponentDataGenerator(
-      ProcessingEnvironment processingEnv,
-      TypeElement originatingElement,
+      XProcessingEnv processingEnv,
+      XTypeElement originatingElement,
       RootMetadata rootMetadata,
       ComponentNames componentNames) {
     this.processingEnv = processingEnv;
@@ -92,28 +93,29 @@
   public void generate() throws IOException {
     TypeSpec.Builder generator =
         TypeSpec.classBuilder(name)
-            .addOriginatingElement(originatingElement)
             .superclass(ClassNames.TEST_COMPONENT_DATA_SUPPLIER)
             .addModifiers(PUBLIC, FINAL)
             .addMethod(getMethod())
             .addMethod(getTestInjectInternalMethod());
 
+    JavaPoetExtKt.addOriginatingElement(generator, originatingElement);
+
     Processors.addGeneratedAnnotation(
         generator, processingEnv, ClassNames.ROOT_PROCESSOR.toString());
 
-    JavaFile.builder(name.packageName(), generator.build())
-        .build()
-        .writeTo(processingEnv.getFiler());
+    processingEnv
+        .getFiler()
+        .write(JavaFile.builder(name.packageName(), generator.build()).build(), Mode.Isolating);
   }
 
   private MethodSpec getMethod() {
-    TypeElement testElement = rootMetadata.testRootMetadata().testElement();
+    XTypeElement testElement = rootMetadata.testRootMetadata().testElement();
     ClassName component =
         componentNames.generatedComponent(
-            ClassName.get(testElement), ClassNames.SINGLETON_COMPONENT);
-    ImmutableSet<TypeElement> daggerRequiredModules =
+            testElement.getClassName(), ClassNames.SINGLETON_COMPONENT);
+    ImmutableSet<XTypeElement> daggerRequiredModules =
         rootMetadata.modulesThatDaggerCannotConstruct(ClassNames.SINGLETON_COMPONENT);
-    ImmutableSet<TypeElement> hiltRequiredModules =
+    ImmutableSet<XTypeElement> hiltRequiredModules =
         daggerRequiredModules.stream()
             .filter(module -> !canBeConstructedByHilt(module, testElement))
             .collect(toImmutableSet());
@@ -125,7 +127,8 @@
             "return new $T($L, $L, $L, $L, $L)",
             ClassNames.TEST_COMPONENT_DATA,
             rootMetadata.waitForBindValue(),
-            CodeBlock.of("testInstance -> injectInternal(($1T) testInstance)", testElement),
+            CodeBlock.of(
+                "testInstance -> injectInternal(($1T) testInstance)", testElement.getClassName()),
             getElementsListed(daggerRequiredModules),
             getElementsListed(hiltRequiredModules),
             CodeBlock.of(
@@ -157,8 +160,8 @@
    *     : (FooTest.TestModule) modules.get(FooTest.TestModule.class))
    * </code></pre>
    */
-  private static String getAddModuleStatement(TypeElement module, TypeElement testElement) {
-    ClassName className = ClassName.get(module);
+  private static String getAddModuleStatement(XTypeElement module, XTypeElement testElement) {
+    ClassName className = module.getClassName();
     return canBeConstructedByHilt(module, testElement)
         ? CodeBlock.of(
                 ".$1L(autoAddModuleEnabled\n"
@@ -178,21 +181,21 @@
             .toString();
   }
 
-  private static boolean canBeConstructedByHilt(TypeElement module, TypeElement testElement) {
+  private static boolean canBeConstructedByHilt(XTypeElement module, XTypeElement testElement) {
     return hasOnlyAccessibleNoArgConstructor(module)
         && module.getEnclosingElement().equals(testElement);
   }
 
-  private static boolean hasOnlyAccessibleNoArgConstructor(TypeElement module) {
-    List<ExecutableElement> declaredConstructors = constructorsIn(module.getEnclosedElements());
+  private static boolean hasOnlyAccessibleNoArgConstructor(XTypeElement module) {
+    List<XConstructorElement> declaredConstructors = module.getConstructors();
     return declaredConstructors.isEmpty()
         || (declaredConstructors.size() == 1
-            && !declaredConstructors.get(0).getModifiers().contains(PRIVATE)
+            && !declaredConstructors.get(0).isPrivate()
             && declaredConstructors.get(0).getParameters().isEmpty());
   }
 
   /* Arrays.asList(FooTest.TestModule.class, ...) */
-  private static CodeBlock getElementsListed(ImmutableSet<TypeElement> modules) {
+  private static CodeBlock getElementsListed(ImmutableSet<XTypeElement> modules) {
     return modules.isEmpty()
         ? CodeBlock.of("$T.emptySet()", ClassNames.COLLECTIONS)
         : CodeBlock.of(
@@ -200,13 +203,13 @@
             ClassNames.HASH_SET,
             ClassNames.ARRAYS,
             modules.stream()
-                .map(module -> CodeBlock.of("$T.class", module).toString())
+                .map(module -> CodeBlock.of("$T.class", module.getClassName()).toString())
                 .collect(joining(",")));
   }
 
   private MethodSpec getTestInjectInternalMethod() {
-    TypeElement testElement = rootMetadata.testRootMetadata().testElement();
-    ClassName testName = ClassName.get(testElement);
+    XTypeElement testElement = rootMetadata.testRootMetadata().testElement();
+    ClassName testName = testElement.getClassName();
     return MethodSpec.methodBuilder("injectInternal")
         .addModifiers(PRIVATE, STATIC)
         .addParameter(testName, "testInstance")
@@ -218,9 +221,10 @@
         .build();
   }
 
-  private CodeBlock callInjectTest(TypeElement testElement) {
+  private CodeBlock callInjectTest(XTypeElement testElement) {
     return CodeBlock.of(
-        "(($T) (($T) $T.getApplication($T.getApplicationContext())).generatedComponent()).injectTest(testInstance)",
+        "(($T) (($T) $T.getApplication($T.getApplicationContext()))"
+            + ".generatedComponent()).injectTest(testInstance)",
         rootMetadata.testRootMetadata().testInjectorName(),
         ClassNames.GENERATED_COMPONENT_MANAGER,
         ClassNames.CONTEXTS,
diff --git a/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java b/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java
index b7200e7..1c6ddba 100644
--- a/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java
+++ b/java/dagger/hilt/processor/internal/root/TestInjectorGenerator.java
@@ -16,6 +16,10 @@
 
 package dagger.hilt.processor.internal.root;
 
+import androidx.room.compiler.processing.JavaPoetExtKt;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
@@ -24,16 +28,14 @@
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
 import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.TypeElement;
 
 /** Generates an entry point for a test. */
 public final class TestInjectorGenerator {
-  private final ProcessingEnvironment env;
+  private final XProcessingEnv env;
   private final TestRootMetadata metadata;
 
-  TestInjectorGenerator(ProcessingEnvironment env, TestRootMetadata metadata) {
+  TestInjectorGenerator(XProcessingEnv env, TestRootMetadata metadata) {
     this.env = env;
     this.metadata = metadata;
   }
@@ -46,7 +48,6 @@
   public void generate() throws IOException {
     TypeSpec.Builder builder =
         TypeSpec.interfaceBuilder(metadata.testInjectorName())
-            .addOriginatingElement(metadata.testElement())
             .addAnnotation(Processors.getOriginatingElementAnnotation(metadata.testElement()))
             .addAnnotation(ClassNames.GENERATED_ENTRY_POINT)
             .addAnnotation(
@@ -61,15 +62,17 @@
                         metadata.testName(),
                         Processors.upperToLowerCamel(metadata.testName().simpleName()))
                     .build());
+    JavaPoetExtKt.addOriginatingElement(builder, metadata.testElement());
 
     Processors.addGeneratedAnnotation(builder, env, getClass());
 
-    JavaFile.builder(metadata.testInjectorName().packageName(), builder.build())
-        .build()
-        .writeTo(env.getFiler());
+    env.getFiler()
+        .write(
+            JavaFile.builder(metadata.testInjectorName().packageName(), builder.build()).build(),
+            Mode.Isolating);
   }
 
-  private static ClassName installInComponent(TypeElement testElement) {
+  private static ClassName installInComponent(XTypeElement testElement) {
     return ClassNames.SINGLETON_COMPONENT;
   }
 }
diff --git a/java/dagger/hilt/processor/internal/root/TestRootMetadata.java b/java/dagger/hilt/processor/internal/root/TestRootMetadata.java
index 561beb7..5dc6fee 100644
--- a/java/dagger/hilt/processor/internal/root/TestRootMetadata.java
+++ b/java/dagger/hilt/processor/internal/root/TestRootMetadata.java
@@ -16,14 +16,15 @@
 
 package dagger.hilt.processor.internal.root;
 
-import com.google.auto.common.MoreElements;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.ProcessorErrors;
 import dagger.hilt.processor.internal.Processors;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
+import dagger.internal.codegen.xprocessing.XElements;
 import javax.lang.model.element.TypeElement;
 
 /** Metadata class for {@code InternalTestRoot} annotated classes. */
@@ -31,19 +32,19 @@
 abstract class TestRootMetadata {
 
   /** Returns the {@link TypeElement} for the test class. */
-  abstract TypeElement testElement();
+  abstract XTypeElement testElement();
 
   /** Returns the {@link TypeElement} for the base application. */
-  abstract TypeElement baseElement();
+  abstract XTypeElement baseElement();
 
   /** Returns the {@link ClassName} for the test class. */
   ClassName testName() {
-    return ClassName.get(testElement());
+    return testElement().getClassName();
   }
 
   /** Returns the {@link ClassName} for the base application. */
   ClassName baseAppName() {
-    return ClassName.get(baseElement());
+    return baseElement().getClassName();
   }
 
   /** The name of the generated Hilt test application class for the given test name. */
@@ -56,19 +57,18 @@
     return Processors.append(Processors.getEnclosedClassName(testName()), "_GeneratedInjector");
   }
 
-  static TestRootMetadata of(ProcessingEnvironment env, Element element) {
+  static TestRootMetadata of(XProcessingEnv env, XElement element) {
 
-    TypeElement testElement = MoreElements.asType(element);
+    XTypeElement testElement = XElements.asTypeElement(element);
+    XTypeElement baseElement = env.requireTypeElement(ClassNames.MULTI_DEX_APPLICATION);
 
-    TypeElement baseElement =
-        env.getElementUtils().getTypeElement(ClassNames.MULTI_DEX_APPLICATION.toString());
     ProcessorErrors.checkState(
-        !Processors.hasAnnotation(element, ClassNames.ANDROID_ENTRY_POINT),
+        !element.hasAnnotation(ClassNames.ANDROID_ENTRY_POINT),
         element,
         "Tests cannot be annotated with @AndroidEntryPoint. Please use @HiltAndroidTest");
 
     ProcessorErrors.checkState(
-        Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_TEST),
+        element.hasAnnotation(ClassNames.HILT_ANDROID_TEST),
         element,
         "Tests must be annotated with @HiltAndroidTest");
 
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java
index 654a690..5902da0 100644
--- a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesGenerator.java
@@ -16,13 +16,11 @@
 
 package dagger.hilt.processor.internal.uninstallmodules;
 
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.AnnotationSpec;
 import dagger.hilt.processor.internal.ClassNames;
 import dagger.hilt.processor.internal.Processors;
-import java.io.IOException;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.TypeElement;
 
 /**
  * Generates an {@link dagger.hilt.android.internal.uninstallmodules.AggregatedUninstallModules}
@@ -30,26 +28,22 @@
  */
 final class AggregatedUninstallModulesGenerator {
 
-  private final ProcessingEnvironment env;
-  private final TypeElement testElement;
-  private final ImmutableList<TypeElement> uninstallModuleElements;
+  private final XTypeElement testElement;
+  private final ImmutableList<XTypeElement> uninstallModuleElements;
 
   AggregatedUninstallModulesGenerator(
-      TypeElement testElement,
-      ImmutableList<TypeElement> uninstallModuleElements,
-      ProcessingEnvironment env) {
+      XTypeElement testElement,
+      ImmutableList<XTypeElement> uninstallModuleElements) {
     this.testElement = testElement;
     this.uninstallModuleElements = uninstallModuleElements;
-    this.env = env;
   }
 
-  void generate() throws IOException {
+  void generate() {
     Processors.generateAggregatingClass(
         ClassNames.AGGREGATED_UNINSTALL_MODULES_PACKAGE,
         aggregatedUninstallModulesAnnotation(),
         testElement,
-        getClass(),
-        env);
+        getClass());
   }
 
   private AnnotationSpec aggregatedUninstallModulesAnnotation() {
@@ -57,7 +51,7 @@
         AnnotationSpec.builder(ClassNames.AGGREGATED_UNINSTALL_MODULES);
     builder.addMember("test", "$S", testElement.getQualifiedName());
     uninstallModuleElements.stream()
-        .map(TypeElement::getQualifiedName)
+        .map(XTypeElement::getQualifiedName)
         .forEach(uninstallModule -> builder.addMember("uninstallModules", "$S", uninstallModule));
     return builder.build();
   }
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java
index c41dcf0..6f71552 100644
--- a/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/AggregatedUninstallModulesMetadata.java
@@ -16,24 +16,21 @@
 
 package dagger.hilt.processor.internal.uninstallmodules;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
 import dagger.hilt.processor.internal.AggregatedElements;
-import dagger.hilt.processor.internal.AnnotationValues;
 import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.Processors;
 import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr;
 import java.util.stream.Collectors;
-import javax.lang.model.element.AnnotationMirror;
-import javax.lang.model.element.AnnotationValue;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
 
 /**
  * A class that represents the values stored in an
@@ -43,57 +40,52 @@
 public abstract class AggregatedUninstallModulesMetadata {
 
   /** Returns the aggregating element */
-  public abstract TypeElement aggregatingElement();
+  public abstract XTypeElement aggregatingElement();
 
   /** Returns the test annotated with {@link dagger.hilt.android.testing.UninstallModules}. */
-  public abstract TypeElement testElement();
+  public abstract XTypeElement testElement();
 
   /**
    * Returns the list of uninstall modules in {@link dagger.hilt.android.testing.UninstallModules}.
    */
-  public abstract ImmutableList<TypeElement> uninstallModuleElements();
+  public abstract ImmutableList<XTypeElement> uninstallModuleElements();
 
   /** Returns metadata for all aggregated elements in the aggregating package. */
-  public static ImmutableSet<AggregatedUninstallModulesMetadata> from(Elements elements) {
+  public static ImmutableSet<AggregatedUninstallModulesMetadata> from(XProcessingEnv env) {
     return from(
         AggregatedElements.from(
             ClassNames.AGGREGATED_UNINSTALL_MODULES_PACKAGE,
             ClassNames.AGGREGATED_UNINSTALL_MODULES,
-            elements),
-        elements);
+            env));
   }
 
   /** Returns metadata for each aggregated element. */
   public static ImmutableSet<AggregatedUninstallModulesMetadata> from(
-      ImmutableSet<TypeElement> aggregatedElements, Elements elements) {
+      ImmutableSet<XTypeElement> aggregatedElements) {
     return aggregatedElements.stream()
-        .map(aggregatedElement -> create(aggregatedElement, elements))
+        .map(aggregatedElement -> create(aggregatedElement, getProcessingEnv(aggregatedElement)))
         .collect(toImmutableSet());
   }
 
   public static AggregatedUninstallModulesIr toIr(AggregatedUninstallModulesMetadata metadata) {
     return new AggregatedUninstallModulesIr(
-        ClassName.get(metadata.aggregatingElement()),
-        ClassName.get(metadata.testElement()).canonicalName(),
+        metadata.aggregatingElement().getClassName(),
+        metadata.testElement().getClassName().canonicalName(),
         metadata.uninstallModuleElements().stream()
-            .map(ClassName::get)
+            .map(XTypeElement::getClassName)
             .map(ClassName::canonicalName)
             .collect(Collectors.toList()));
   }
 
-  private static AggregatedUninstallModulesMetadata create(TypeElement element, Elements elements) {
-    AnnotationMirror annotationMirror =
-        Processors.getAnnotationMirror(element, ClassNames.AGGREGATED_UNINSTALL_MODULES);
-
-    ImmutableMap<String, AnnotationValue> values =
-        Processors.getAnnotationValues(elements, annotationMirror);
+  private static AggregatedUninstallModulesMetadata create(
+      XTypeElement element, XProcessingEnv env) {
+    XAnnotation annotationMirror = element.getAnnotation(ClassNames.AGGREGATED_UNINSTALL_MODULES);
 
     return new AutoValue_AggregatedUninstallModulesMetadata(
         element,
-        elements.getTypeElement(AnnotationValues.getString(values.get("test"))),
-        AnnotationValues.getAnnotationValues(values.get("uninstallModules")).stream()
-            .map(AnnotationValues::getString)
-            .map(elements::getTypeElement)
+        env.requireTypeElement(annotationMirror.getAsString("test")),
+        annotationMirror.getAsStringList("uninstallModules").stream()
+            .map(env::requireTypeElement)
             .collect(toImmutableList()));
   }
 }
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/BUILD b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD
index 876c473..818abbc 100644
--- a/java/dagger/hilt/processor/internal/uninstallmodules/BUILD
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/BUILD
@@ -28,6 +28,8 @@
     name = "processor_lib",
     srcs = [
         "AggregatedUninstallModulesGenerator.java",
+        "KspUninstallModulesProcessor.java",
+        "UninstallModulesProcessingStep.java",
         "UninstallModulesProcessor.java",
     ],
     deps = [
@@ -36,11 +38,12 @@
         "//java/dagger/hilt/processor/internal:processor_errors",
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/internal/codegen/extension",
-        "//third_party/java/auto:common",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:service",
         "//third_party/java/guava/collect",
         "//third_party/java/incap",
         "//third_party/java/javapoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
 
@@ -55,6 +58,7 @@
         "//java/dagger/hilt/processor/internal:processors",
         "//java/dagger/hilt/processor/internal/root/ir",
         "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
         "//third_party/java/auto:value",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/KspUninstallModulesProcessor.java b/java/dagger/hilt/processor/internal/uninstallmodules/KspUninstallModulesProcessor.java
new file mode 100644
index 0000000..ab7d6f4
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/KspUninstallModulesProcessor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.uninstallmodules;
+
+import com.google.auto.service.AutoService;
+import com.google.devtools.ksp.processing.SymbolProcessor;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import com.google.devtools.ksp.processing.SymbolProcessorProvider;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.KspBaseProcessingStepProcessor;
+
+/** Validates {@link dagger.hilt.android.testing.UninstallModules} usages. */
+public final class KspUninstallModulesProcessor extends KspBaseProcessingStepProcessor {
+
+  public KspUninstallModulesProcessor(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+    super(symbolProcessorEnvironment);
+  }
+
+  @Override
+  protected BaseProcessingStep processingStep() {
+    return new UninstallModulesProcessingStep(getXProcessingEnv());
+  }
+
+  /** Provides the {@link KspUninstallModulesProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
+  public static final class Provider implements SymbolProcessorProvider {
+    @Override
+    public SymbolProcessor create(SymbolProcessorEnvironment symbolProcessorEnvironment) {
+      return new KspUninstallModulesProcessor(symbolProcessorEnvironment);
+    }
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessingStep.java b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessingStep.java
new file mode 100644
index 0000000..6a20244
--- /dev/null
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessingStep.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 dagger.hilt.processor.internal.uninstallmodules;
+
+import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.javapoet.ClassName;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.ClassNames;
+import dagger.hilt.processor.internal.ProcessorErrors;
+import dagger.hilt.processor.internal.Processors;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
+
+/** Validates {@link dagger.hilt.android.testing.UninstallModules} usages. */
+public final class UninstallModulesProcessingStep extends BaseProcessingStep {
+
+  public UninstallModulesProcessingStep(XProcessingEnv env) {
+    super(env);
+  }
+
+  @Override
+  protected ImmutableSet<ClassName> annotationClassNames() {
+    return ImmutableSet.of(ClassNames.UNINSTALL_MODULES);
+  }
+
+  @Override
+  public void processEach(ClassName annotation, XElement element) {
+    // TODO(bcorso): Consider using RootType to check this?
+    // TODO(bcorso): Loosen this restriction to allow defining sets of ignored modules in libraries.
+    ProcessorErrors.checkState(
+        isTypeElement(element) && element.hasAnnotation(ClassNames.HILT_ANDROID_TEST),
+        element,
+        "@%s should only be used on test classes annotated with @%s, but found: %s",
+        annotation.simpleName(),
+        ClassNames.HILT_ANDROID_TEST.simpleName(),
+        XElements.toStableString(element));
+
+    XTypeElement testElement = XElements.asTypeElement(element);
+    ImmutableList<XTypeElement> uninstallModules =
+        XAnnotations.getAsTypeElementList(
+            testElement.getAnnotation(ClassNames.UNINSTALL_MODULES), "value");
+
+    checkModulesHaveInstallIn(testElement, uninstallModules);
+    checkModulesDontOriginateFromTest(testElement, uninstallModules);
+
+    new AggregatedUninstallModulesGenerator(testElement, uninstallModules).generate();
+  }
+
+  private void checkModulesHaveInstallIn(
+      XTypeElement testElement, ImmutableList<XTypeElement> uninstallModules) {
+    ImmutableList<XTypeElement> invalidModules =
+        uninstallModules.stream()
+            .filter(
+                module ->
+                    !(module.hasAnnotation(ClassNames.MODULE)
+                        && module.hasAnnotation(ClassNames.INSTALL_IN)))
+            .collect(toImmutableList());
+
+    ProcessorErrors.checkState(
+        invalidModules.isEmpty(),
+        // TODO(b/152801981): Point to the annotation value rather than the annotated element.
+        testElement,
+        "@UninstallModules should only include modules annotated with both @Module and @InstallIn, "
+            + "but found: %s.",
+        invalidModules.stream().map(XElements::toStableString).collect(toImmutableList()));
+  }
+
+  private void checkModulesDontOriginateFromTest(
+      XTypeElement testElement, ImmutableList<XTypeElement> uninstallModules) {
+    ImmutableList<ClassName> invalidModules =
+        uninstallModules.stream()
+            .filter(module -> Processors.getOriginatingTestElement(module).isPresent())
+            .map(XTypeElement::getClassName)
+            .collect(toImmutableList());
+
+    ProcessorErrors.checkState(
+        invalidModules.isEmpty(),
+        // TODO(b/152801981): Point to the annotation value rather than the annotated element.
+        testElement,
+        "@UninstallModules should not contain test modules, but found: %s",
+        invalidModules);
+  }
+}
diff --git a/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java
index c7e528d..c13ac4a 100644
--- a/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java
+++ b/java/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessor.java
@@ -16,95 +16,20 @@
 
 package dagger.hilt.processor.internal.uninstallmodules;
 
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
 
-import com.google.auto.common.MoreElements;
 import com.google.auto.service.AutoService;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.squareup.javapoet.ClassName;
-import dagger.hilt.processor.internal.BaseProcessor;
-import dagger.hilt.processor.internal.ClassNames;
-import dagger.hilt.processor.internal.ProcessorErrors;
-import dagger.hilt.processor.internal.Processors;
-import java.util.Set;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.hilt.processor.internal.JavacBaseProcessingStepProcessor;
 import javax.annotation.processing.Processor;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.TypeElement;
 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
 
 /** Validates {@link dagger.hilt.android.testing.UninstallModules} usages. */
 @IncrementalAnnotationProcessor(ISOLATING)
 @AutoService(Processor.class)
-public final class UninstallModulesProcessor extends BaseProcessor {
-
+public final class UninstallModulesProcessor extends JavacBaseProcessingStepProcessor {
   @Override
-  public Set<String> getSupportedAnnotationTypes() {
-    return ImmutableSet.of(ClassNames.UNINSTALL_MODULES.toString());
-  }
-
-  @Override
-  public void processEach(TypeElement annotation, Element element) throws Exception {
-    // TODO(bcorso): Consider using RootType to check this?
-    // TODO(bcorso): Loosen this restriction to allow defining sets of ignored modules in libraries.
-    ProcessorErrors.checkState(
-        MoreElements.isType(element)
-            && Processors.hasAnnotation(element, ClassNames.HILT_ANDROID_TEST),
-        element,
-        "@%s should only be used on test classes annotated with @%s, but found: %s",
-        annotation.getSimpleName(),
-        ClassNames.HILT_ANDROID_TEST.simpleName(),
-        element);
-
-    TypeElement testElement = MoreElements.asType(element);
-    ImmutableList<TypeElement> uninstallModules =
-        Processors.getAnnotationClassValues(
-            getElementUtils(),
-            Processors.getAnnotationMirror(testElement, ClassNames.UNINSTALL_MODULES),
-            "value");
-
-    checkModulesHaveInstallIn(testElement, uninstallModules);
-    checkModulesDontOriginateFromTest(testElement, uninstallModules);
-
-    new AggregatedUninstallModulesGenerator(testElement, uninstallModules, getProcessingEnv())
-        .generate();
-  }
-
-  private void checkModulesHaveInstallIn(
-      TypeElement testElement, ImmutableList<TypeElement> uninstallModules) {
-    ImmutableList<TypeElement> invalidModules =
-        uninstallModules.stream()
-            .filter(
-                module ->
-                    !(Processors.hasAnnotation(module, ClassNames.MODULE)
-                        && Processors.hasAnnotation(module, ClassNames.INSTALL_IN)))
-            .collect(toImmutableList());
-
-    ProcessorErrors.checkState(
-        invalidModules.isEmpty(),
-        // TODO(b/152801981): Point to the annotation value rather than the annotated element.
-        testElement,
-        "@UninstallModules should only include modules annotated with both @Module and @InstallIn, "
-            + "but found: %s.",
-        invalidModules);
-  }
-
-  private void checkModulesDontOriginateFromTest(
-      TypeElement testElement, ImmutableList<TypeElement> uninstallModules) {
-    ImmutableList<ClassName> invalidModules =
-        uninstallModules.stream()
-            .filter(
-                module ->
-                    Processors.getOriginatingTestElement(module, getElementUtils()).isPresent())
-            .map(ClassName::get)
-            .collect(toImmutableList());
-
-    ProcessorErrors.checkState(
-        invalidModules.isEmpty(),
-        // TODO(b/152801981): Point to the annotation value rather than the annotated element.
-        testElement,
-        "@UninstallModules should not contain test modules, but found: %s",
-        invalidModules);
+  protected BaseProcessingStep processingStep() {
+    return new UninstallModulesProcessingStep(getXProcessingEnv());
   }
 }
diff --git a/java/dagger/internal/codegen/BUILD b/java/dagger/internal/codegen/BUILD
index 1df0313..a3868b5 100644
--- a/java/dagger/internal/codegen/BUILD
+++ b/java/dagger/internal/codegen/BUILD
@@ -43,6 +43,7 @@
         "//java/dagger/internal/codegen/compileroption",
         "//java/dagger/internal/codegen/componentgenerator",
         "//java/dagger/internal/codegen/kotlin",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/processingstep",
         "//java/dagger/internal/codegen/validation",
         "//java/dagger/internal/codegen/writing",
@@ -80,12 +81,13 @@
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/processingstep",
         "//java/dagger/internal/codegen/validation",
         "//java/dagger/internal/codegen/writing",
+        "//java/dagger/internal/codegen/xprocessing",
     ],
     artifact_target_maven_deps = [
-        "com.google.auto:auto-common",
         "com.google.code.findbugs:jsr305",
         "com.google.dagger:dagger-producers",
         "com.google.dagger:dagger-spi",
@@ -100,7 +102,6 @@
         "net.ltgt.gradle.incap:incap",
         "org.checkerframework:checker-compat-qual",
         "org.jetbrains.kotlin:kotlin-stdlib",
-        "org.jetbrains.kotlinx:kotlinx-metadata-jvm",
     ],
     javadoc_root_packages = ["dagger.internal.codegen"],
     # The javadocs should only include ComponentProcessor.java, since that is the only class used
diff --git a/java/dagger/internal/codegen/KspComponentProcessor.java b/java/dagger/internal/codegen/KspComponentProcessor.java
index 2b6d0f7..8b07f93 100644
--- a/java/dagger/internal/codegen/KspComponentProcessor.java
+++ b/java/dagger/internal/codegen/KspComponentProcessor.java
@@ -20,6 +20,7 @@
 import androidx.room.compiler.processing.XProcessingStep;
 import androidx.room.compiler.processing.XRoundEnv;
 import androidx.room.compiler.processing.ksp.KspBasicAnnotationProcessor;
+import com.google.auto.service.AutoService;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.ksp.processing.SymbolProcessor;
@@ -63,6 +64,7 @@
   }
 
   /** Provides the {@link KspComponentProcessor}. */
+  @AutoService(SymbolProcessorProvider.class)
   public static final class Provider implements SymbolProcessorProvider {
     /**
      * Creates a component processor that uses given {@link BindingGraphPlugin}s instead of loading
diff --git a/java/dagger/internal/codegen/base/BUILD b/java/dagger/internal/codegen/base/BUILD
index 56bf7f3..8d5fd5e 100644
--- a/java/dagger/internal/codegen/base/BUILD
+++ b/java/dagger/internal/codegen/base/BUILD
@@ -38,8 +38,8 @@
         "//java/dagger/internal/codegen/extension",
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/xprocessing",
-        "//java/dagger/spi",
         "//third_party/java/auto:value",
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
diff --git a/java/dagger/internal/codegen/base/DaggerSuperficialValidation.java b/java/dagger/internal/codegen/base/DaggerSuperficialValidation.java
index fc54c96..208da2b 100644
--- a/java/dagger/internal/codegen/base/DaggerSuperficialValidation.java
+++ b/java/dagger/internal/codegen/base/DaggerSuperficialValidation.java
@@ -56,10 +56,10 @@
 import androidx.room.compiler.processing.compat.XConverters;
 import com.google.common.base.Ascii;
 import com.google.common.collect.ImmutableList;
-import com.google.devtools.ksp.symbol.ClassKind;
 import com.squareup.javapoet.ClassName;
 import dagger.Reusable;
 import dagger.internal.codegen.compileroption.CompilerOptions;
+import dagger.internal.codegen.javapoet.TypeNames;
 import dagger.internal.codegen.xprocessing.XAnnotationValues;
 import dagger.internal.codegen.xprocessing.XAnnotations;
 import dagger.internal.codegen.xprocessing.XElements;
@@ -127,14 +127,7 @@
       // In XProcessing, there is no generic way to get an element "asType" so we break this down
       // differently for different element kinds.
       if (isTypeElement(element)) {
-        XTypeElement typeElement = asTypeElement(element);
-        // TODO(b/247828057): Due to a bug in XProcessing, enum entry types are sometimes
-        // represented by XTypeElement rather than XEnumEntry in KSP which leads to failures later
-        // on. Thus, skip validation in these cases until this bug is fixed.
-        if (!(processingEnv.getBackend() == Backend.KSP
-                && XConverters.toKS(typeElement).getClassKind() == ClassKind.ENUM_ENTRY)) {
-          validateType(Ascii.toLowerCase(getKindName(element)), typeElement.getType());
-        }
+        validateType(Ascii.toLowerCase(getKindName(element)), asTypeElement(element).getType());
       } else if (isVariableElement(element)) {
         validateType(
             Ascii.toLowerCase(getKindName(element)) + " type", asVariable(element).getType());
@@ -279,7 +272,18 @@
         if (typeElement.getSuperType() != null) {
           validateType("superclass", typeElement.getSuperType());
         }
-        validateElements(typeElement.getEnclosedElements());
+        // TODO (b/286313067) move the logic to ComponentValidator once the validation logic is
+        // split into individual validators to satisfy different needs.
+        // Dagger doesn't use components' static method, therefore, they shouldn't be validated to
+        // be able to stop component generation.
+        if (typeElement.hasAnnotation(TypeNames.COMPONENT)) {
+          validateElements(
+              typeElement.getEnclosedElements().stream()
+                  .filter(member -> !XElements.isStatic(member))
+                  .collect(toImmutableList()));
+        } else {
+          validateElements(typeElement.getEnclosedElements());
+        }
       } else if (isExecutable(element)) {
         if (isMethod(element)) {
           validateType("return type", asMethod(element).getReturnType());
@@ -400,10 +404,22 @@
   private void validateAnnotation(XAnnotation annotation) {
     try {
       validateType("annotation type", annotation.getType());
-      validateAnnotationValues(getDefaultValues(annotation));
-      validateAnnotationValues(annotation.getAnnotationValues());
+      try {
+        // Note: We separate this into its own try-catch since there's a bug where we could get an
+        // error when getting the annotation values due to b/264089557. This way we will at least
+        // report the name of the annotation in the error message.
+        validateAnnotationValues(getDefaultValues(annotation));
+        validateAnnotationValues(annotation.getAnnotationValues());
+      } catch (RuntimeException exception) {
+        throw ValidationException.from(exception).append(annotation);
+      }
     } catch (RuntimeException exception) {
-      throw ValidationException.from(exception).append(annotation);
+      throw ValidationException.from(exception)
+          .append(
+              "annotation type: "
+                  + (annotation.getType().isError()
+                      ? annotation.getName() // SUPPRESS_GET_NAME_CHECK
+                      : annotation.getClassName().canonicalName()));
     }
   }
 
diff --git a/java/dagger/internal/codegen/base/Keys.java b/java/dagger/internal/codegen/base/Keys.java
index dc061fe..0a29875 100644
--- a/java/dagger/internal/codegen/base/Keys.java
+++ b/java/dagger/internal/codegen/base/Keys.java
@@ -24,8 +24,8 @@
 import androidx.room.compiler.processing.XAnnotation;
 import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.Key;
 import java.util.Optional;
 
 /** Utility methods related to {@link Key}s. */
diff --git a/java/dagger/internal/codegen/base/MapType.java b/java/dagger/internal/codegen/base/MapType.java
index 00bea13..00401ed 100644
--- a/java/dagger/internal/codegen/base/MapType.java
+++ b/java/dagger/internal/codegen/base/MapType.java
@@ -26,8 +26,8 @@
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeName;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.xprocessing.XTypes;
-import dagger.spi.model.Key;
 
 /** Information about a {@link java.util.Map} type. */
 @AutoValue
diff --git a/java/dagger/internal/codegen/base/OptionalType.java b/java/dagger/internal/codegen/base/OptionalType.java
index feef652..79b638d 100644
--- a/java/dagger/internal/codegen/base/OptionalType.java
+++ b/java/dagger/internal/codegen/base/OptionalType.java
@@ -30,7 +30,7 @@
 import com.squareup.javapoet.ParameterizedTypeName;
 import com.squareup.javapoet.TypeName;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.Key;
 
 /**
  * Information about an {@code Optional} type.
diff --git a/java/dagger/internal/codegen/base/RequestKinds.java b/java/dagger/internal/codegen/base/RequestKinds.java
index d66cd71..a9c7e83 100644
--- a/java/dagger/internal/codegen/base/RequestKinds.java
+++ b/java/dagger/internal/codegen/base/RequestKinds.java
@@ -23,15 +23,15 @@
 import static dagger.internal.codegen.javapoet.TypeNames.producedOf;
 import static dagger.internal.codegen.javapoet.TypeNames.producerOf;
 import static dagger.internal.codegen.javapoet.TypeNames.providerOf;
+import static dagger.internal.codegen.model.RequestKind.LAZY;
+import static dagger.internal.codegen.model.RequestKind.PRODUCED;
+import static dagger.internal.codegen.model.RequestKind.PRODUCER;
+import static dagger.internal.codegen.model.RequestKind.PROVIDER;
 import static dagger.internal.codegen.xprocessing.XProcessingEnvs.wrapType;
 import static dagger.internal.codegen.xprocessing.XTypes.checkTypePresent;
 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;
 import static dagger.internal.codegen.xprocessing.XTypes.unwrapType;
-import static dagger.spi.model.RequestKind.LAZY;
-import static dagger.spi.model.RequestKind.PRODUCED;
-import static dagger.spi.model.RequestKind.PRODUCER;
-import static dagger.spi.model.RequestKind.PROVIDER;
 
 import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XType;
@@ -39,7 +39,7 @@
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeName;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.RequestKind;
 
 /** Utility methods for {@link RequestKind}s. */
 public final class RequestKinds {
@@ -142,7 +142,7 @@
 
   /**
    * A dagger- or {@code javax.inject}-defined class for {@code requestKind} that that can wrap
-   * another type but share the same {@link dagger.spi.model.Key}.
+   * another type but share the same {@link dagger.internal.codegen.model.Key}.
    *
    * <p>For example, {@code Provider<String>} and {@code Lazy<String>} can both be requested if a
    * key exists for {@code String}; they all share the same key.
diff --git a/java/dagger/internal/codegen/base/Scopes.java b/java/dagger/internal/codegen/base/Scopes.java
index b4cc323..935fe97 100644
--- a/java/dagger/internal/codegen/base/Scopes.java
+++ b/java/dagger/internal/codegen/base/Scopes.java
@@ -19,8 +19,8 @@
 import static dagger.internal.codegen.base.DiagnosticFormatting.stripCommonTypePrefixes;
 
 import androidx.room.compiler.processing.XProcessingEnv;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.Scope;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.Scope;
 
 /** Common names and convenience methods for {@link Scope}s. */
 public final class Scopes {
diff --git a/java/dagger/internal/codegen/base/SetType.java b/java/dagger/internal/codegen/base/SetType.java
index 875eb4f..ab2ea91 100644
--- a/java/dagger/internal/codegen/base/SetType.java
+++ b/java/dagger/internal/codegen/base/SetType.java
@@ -25,8 +25,8 @@
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeName;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.xprocessing.XTypes;
-import dagger.spi.model.Key;
 
 /** Information about a {@link java.util.Set} type. */
 @AutoValue
diff --git a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java
index 54ca6ce..a8bca09 100644
--- a/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java
+++ b/java/dagger/internal/codegen/binding/AssistedInjectionAnnotations.java
@@ -43,9 +43,9 @@
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.BindingKind;
 import dagger.internal.codegen.xprocessing.XTypeElements;
 import dagger.internal.codegen.xprocessing.XTypes;
-import dagger.spi.model.BindingKind;
 import java.util.List;
 import java.util.Optional;
 
diff --git a/java/dagger/internal/codegen/binding/BUILD b/java/dagger/internal/codegen/binding/BUILD
index b65233d..e606eb4 100644
--- a/java/dagger/internal/codegen/binding/BUILD
+++ b/java/dagger/internal/codegen/binding/BUILD
@@ -32,9 +32,9 @@
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/xprocessing",
         "//java/dagger/producers",
-        "//java/dagger/spi",
         "//third_party/java/auto:common",
         "//third_party/java/auto:value",
         "//third_party/java/error_prone:annotations",
diff --git a/java/dagger/internal/codegen/binding/Binding.java b/java/dagger/internal/codegen/binding/Binding.java
index c66bdaf..250e608 100644
--- a/java/dagger/internal/codegen/binding/Binding.java
+++ b/java/dagger/internal/codegen/binding/Binding.java
@@ -23,9 +23,9 @@
 import com.google.common.base.Supplier;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Scope;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Scope;
 import java.util.Optional;
 
 /**
diff --git a/java/dagger/internal/codegen/binding/BindingDeclaration.java b/java/dagger/internal/codegen/binding/BindingDeclaration.java
index 9647134..dd24c98 100644
--- a/java/dagger/internal/codegen/binding/BindingDeclaration.java
+++ b/java/dagger/internal/codegen/binding/BindingDeclaration.java
@@ -23,9 +23,9 @@
 
 import androidx.room.compiler.processing.XElement;
 import androidx.room.compiler.processing.XTypeElement;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.xprocessing.XElements;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.Key;
 import java.util.Comparator;
 import java.util.Optional;
 
diff --git a/java/dagger/internal/codegen/binding/BindingFactory.java b/java/dagger/internal/codegen/binding/BindingFactory.java
index 6593e23..11fa591 100644
--- a/java/dagger/internal/codegen/binding/BindingFactory.java
+++ b/java/dagger/internal/codegen/binding/BindingFactory.java
@@ -24,28 +24,27 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static dagger.internal.codegen.binding.ComponentDescriptor.isComponentProductionMethod;
-import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType;
 import static dagger.internal.codegen.binding.MapKeys.getMapKey;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.model.BindingKind.ASSISTED_FACTORY;
+import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION;
+import static dagger.internal.codegen.model.BindingKind.BOUND_INSTANCE;
+import static dagger.internal.codegen.model.BindingKind.COMPONENT;
+import static dagger.internal.codegen.model.BindingKind.COMPONENT_DEPENDENCY;
+import static dagger.internal.codegen.model.BindingKind.COMPONENT_PRODUCTION;
+import static dagger.internal.codegen.model.BindingKind.COMPONENT_PROVISION;
+import static dagger.internal.codegen.model.BindingKind.DELEGATE;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
+import static dagger.internal.codegen.model.BindingKind.MEMBERS_INJECTOR;
+import static dagger.internal.codegen.model.BindingKind.OPTIONAL;
+import static dagger.internal.codegen.model.BindingKind.PRODUCTION;
+import static dagger.internal.codegen.model.BindingKind.PROVISION;
+import static dagger.internal.codegen.model.BindingKind.SUBCOMPONENT_CREATOR;
 import static dagger.internal.codegen.xprocessing.XElements.asMethod;
 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
 import static dagger.internal.codegen.xprocessing.XElements.asVariable;
 import static dagger.internal.codegen.xprocessing.XTypes.erasedTypeName;
 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
-import static dagger.spi.model.BindingKind.ASSISTED_FACTORY;
-import static dagger.spi.model.BindingKind.ASSISTED_INJECTION;
-import static dagger.spi.model.BindingKind.BOUND_INSTANCE;
-import static dagger.spi.model.BindingKind.COMPONENT;
-import static dagger.spi.model.BindingKind.COMPONENT_DEPENDENCY;
-import static dagger.spi.model.BindingKind.COMPONENT_PRODUCTION;
-import static dagger.spi.model.BindingKind.COMPONENT_PROVISION;
-import static dagger.spi.model.BindingKind.DELEGATE;
-import static dagger.spi.model.BindingKind.INJECTION;
-import static dagger.spi.model.BindingKind.MEMBERS_INJECTOR;
-import static dagger.spi.model.BindingKind.OPTIONAL;
-import static dagger.spi.model.BindingKind.PRODUCTION;
-import static dagger.spi.model.BindingKind.PROVISION;
-import static dagger.spi.model.BindingKind.SUBCOMPONENT_CREATOR;
 
 import androidx.room.compiler.processing.XConstructorElement;
 import androidx.room.compiler.processing.XConstructorType;
@@ -53,7 +52,6 @@
 import androidx.room.compiler.processing.XExecutableParameterElement;
 import androidx.room.compiler.processing.XMethodElement;
 import androidx.room.compiler.processing.XMethodType;
-import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
 import androidx.room.compiler.processing.XVariableElement;
@@ -69,12 +67,11 @@
 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
 import dagger.internal.codegen.binding.ProductionBinding.ProductionKind;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DaggerType;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.RequestKind;
 import java.util.Optional;
 import java.util.function.BiFunction;
 import javax.inject.Inject;
@@ -88,7 +85,6 @@
 
   @Inject
   BindingFactory(
-      XProcessingEnv processingEnv,
       KeyFactory keyFactory,
       DependencyRequestFactory dependencyRequestFactory,
       InjectionSiteFactory injectionSiteFactory,
@@ -100,7 +96,7 @@
   }
 
   /**
-   * Returns an {@link dagger.spi.model.BindingKind#INJECTION} binding.
+   * Returns an {@link dagger.internal.codegen.model.BindingKind#INJECTION} binding.
    *
    * @param constructorElement the {@code @Inject}-annotated constructor
    * @param resolvedType the parameterized type if the constructor is for a generic class and the
@@ -165,12 +161,12 @@
     XMethodType factoryMethodType = factoryMethod.asMemberOf(factoryType);
     return ProvisionBinding.builder()
         .contributionType(ContributionType.UNIQUE)
-        .key(Key.builder(DaggerType.from(factoryType)).build())
+        .key(keyFactory.forType(factoryType))
         .bindingElement(factory)
         .provisionDependencies(
             ImmutableSet.of(
                 DependencyRequest.builder()
-                    .key(Key.builder(DaggerType.from(factoryMethodType.getReturnType())).build())
+                    .key(keyFactory.forType(factoryMethodType.getReturnType()))
                     .kind(RequestKind.PROVIDER)
                     .build()))
         .kind(ASSISTED_FACTORY)
@@ -178,7 +174,7 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#PROVISION} binding for a
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#PROVISION} binding for a
    * {@code @Provides}-annotated method.
    *
    * @param contributedBy the installed module that declares or inherits the method
@@ -193,12 +189,12 @@
             this::providesMethodBinding)
         .kind(PROVISION)
         .scope(injectionAnnotations.getScope(providesMethod))
-        .nullableType(getNullableType(providesMethod))
+        .nullability(Nullability.of(providesMethod))
         .build();
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#PRODUCTION} binding for a
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#PRODUCTION} binding for a
    * {@code @Produces}-annotated method.
    *
    * @param contributedBy the installed module that declares or inherits the method
@@ -245,9 +241,9 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#MULTIBOUND_MAP} or {@link
-   * dagger.spi.model.BindingKind#MULTIBOUND_SET} binding given a set of multibinding contribution
-   * bindings.
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#MULTIBOUND_MAP} or {@link
+   * dagger.internal.codegen.model.BindingKind#MULTIBOUND_SET} binding given a set of multibinding
+   * contribution bindings.
    *
    * @param key a key that may be satisfied by a multibinding
    */
@@ -291,7 +287,10 @@
         multibindingContributions, binding -> binding.bindingType().equals(BindingType.PRODUCTION));
   }
 
-  /** Returns a {@link dagger.spi.model.BindingKind#COMPONENT} binding for the component. */
+  /**
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#COMPONENT} binding for the
+   * component.
+   */
   public ProvisionBinding componentBinding(XTypeElement componentDefinitionType) {
     checkNotNull(componentDefinitionType);
     return ProvisionBinding.builder()
@@ -303,8 +302,8 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#COMPONENT_DEPENDENCY} binding for a component's
-   * dependency.
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#COMPONENT_DEPENDENCY} binding for a
+   * component's dependency.
    */
   public ProvisionBinding componentDependencyBinding(ComponentRequirement dependency) {
     checkNotNull(dependency);
@@ -317,9 +316,9 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#COMPONENT_PROVISION} or {@link
-   * dagger.spi.model.BindingKind#COMPONENT_PRODUCTION} binding for a method on a component's
-   * dependency.
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#COMPONENT_PROVISION} or {@link
+   * dagger.internal.codegen.model.BindingKind#COMPONENT_PRODUCTION} binding for a method on a
+   * component's dependency.
    *
    * @param componentDescriptor the component with the dependency, not the dependency that has the
    *     method
@@ -338,7 +337,7 @@
       builder =
           ProvisionBinding.builder()
               .key(keyFactory.forComponentMethod(dependencyMethod))
-              .nullableType(getNullableType(dependencyMethod))
+              .nullability(Nullability.of(dependencyMethod))
               .kind(COMPONENT_PROVISION)
               .scope(injectionAnnotations.getScope(dependencyMethod));
     }
@@ -349,7 +348,7 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#BOUND_INSTANCE} binding for a
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#BOUND_INSTANCE} binding for a
    * {@code @BindsInstance}-annotated builder setter method or factory method parameter.
    */
   ProvisionBinding boundInstanceBinding(ComponentRequirement requirement, XElement element) {
@@ -362,14 +361,14 @@
         .contributionType(ContributionType.UNIQUE)
         .bindingElement(element)
         .key(requirement.key().get())
-        .nullableType(getNullableType(parameterElement))
+        .nullability(Nullability.of(parameterElement))
         .kind(BOUND_INSTANCE)
         .build();
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared by a
-   * component method that returns a subcomponent builder. Use {{@link
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#SUBCOMPONENT_CREATOR} binding
+   * declared by a component method that returns a subcomponent builder. Use {{@link
    * #subcomponentCreatorBinding(ImmutableSet)}} for bindings declared using {@link
    * Module#subcomponents()}.
    *
@@ -389,8 +388,8 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#SUBCOMPONENT_CREATOR} binding declared using
-   * {@link Module#subcomponents()}.
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#SUBCOMPONENT_CREATOR} binding
+   * declared using {@link Module#subcomponents()}.
    */
   ProvisionBinding subcomponentCreatorBinding(
       ImmutableSet<SubcomponentDeclaration> subcomponentDeclarations) {
@@ -403,7 +402,7 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#DELEGATE} binding.
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#DELEGATE} binding.
    *
    * @param delegateDeclaration the {@code @Binds}-annotated declaration
    * @param actualBinding the binding that satisfies the {@code @Binds} declaration
@@ -413,7 +412,7 @@
     switch (actualBinding.bindingType()) {
       case PRODUCTION:
         return buildDelegateBinding(
-            ProductionBinding.builder().nullableType(actualBinding.nullableType()),
+            ProductionBinding.builder().nullability(actualBinding.nullability()),
             delegateDeclaration,
             TypeNames.PRODUCER);
 
@@ -421,7 +420,7 @@
         return buildDelegateBinding(
             ProvisionBinding.builder()
                 .scope(injectionAnnotations.getScope(delegateDeclaration.bindingElement().get()))
-                .nullableType(actualBinding.nullableType()),
+                .nullability(actualBinding.nullability()),
             delegateDeclaration,
             TypeNames.PROVIDER);
 
@@ -431,8 +430,8 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#DELEGATE} binding used when there is no binding
-   * that satisfies the {@code @Binds} declaration.
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#DELEGATE} binding used when there is
+   * no binding that satisfies the {@code @Binds} declaration.
    */
   public ContributionBinding unresolvedDelegateBinding(DelegateDeclaration delegateDeclaration) {
     return buildDelegateBinding(
@@ -458,7 +457,7 @@
   }
 
   /**
-   * Returns an {@link dagger.spi.model.BindingKind#OPTIONAL} binding for {@code key}.
+   * Returns an {@link dagger.internal.codegen.model.BindingKind#OPTIONAL} binding for {@code key}.
    *
    * @param requestKind the kind of request for the optional binding
    * @param underlyingKeyBindings the possibly empty set of bindings that exist in the component for
@@ -490,7 +489,7 @@
         .build();
   }
 
-  /** Returns a {@link dagger.spi.model.BindingKind#MEMBERS_INJECTOR} binding. */
+  /** Returns a {@link dagger.internal.codegen.model.BindingKind#MEMBERS_INJECTOR} binding. */
   public ProvisionBinding membersInjectorBinding(
       Key key, MembersInjectionBinding membersInjectionBinding) {
     return ProvisionBinding.builder()
@@ -504,7 +503,7 @@
   }
 
   /**
-   * Returns a {@link dagger.spi.model.BindingKind#MEMBERS_INJECTION} binding.
+   * Returns a {@link dagger.internal.codegen.model.BindingKind#MEMBERS_INJECTION} binding.
    *
    * @param resolvedType if {@code declaredType} is a generic class and {@code resolvedType} is a
    *     parameterization of that type, the returned binding will be for the resolved type
diff --git a/java/dagger/internal/codegen/binding/BindingGraph.java b/java/dagger/internal/codegen/binding/BindingGraph.java
index 460c483..8dfe74f 100644
--- a/java/dagger/internal/codegen/binding/BindingGraph.java
+++ b/java/dagger/internal/codegen/binding/BindingGraph.java
@@ -42,15 +42,15 @@
 import com.google.common.graph.ImmutableNetwork;
 import com.google.common.graph.Traverser;
 import dagger.internal.codegen.base.TarjanSCCs;
-import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.Edge;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.DaggerTypeElement;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.Edge;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.DaggerTypeElement;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -71,7 +71,8 @@
    * their bindings.
    */
   @AutoValue
-  public abstract static class TopLevelBindingGraph extends dagger.spi.model.BindingGraph {
+  public abstract static class TopLevelBindingGraph
+      extends dagger.internal.codegen.model.BindingGraph {
     static TopLevelBindingGraph create(
         ImmutableNetwork<Node, Edge> network, boolean isFullBindingGraph) {
       TopLevelBindingGraph topLevelBindingGraph =
@@ -106,7 +107,8 @@
 
     TopLevelBindingGraph() {}
 
-    // This overrides dagger.spi.model.BindingGraph with a more efficient implementation.
+    // This overrides dagger.internal.codegen.model.BindingGraph with a more efficient
+    // implementation.
     @Override
     public Optional<ComponentNode> componentNode(ComponentPath componentPath) {
       return componentNodes.containsKey(componentPath)
@@ -162,9 +164,10 @@
     }
 
     private static ImmutableSet<Binding> frameworkRequestBindingSet(
-        ImmutableNetwork<Node, Edge> network, ImmutableSet<dagger.spi.model.Binding> bindings) {
+        ImmutableNetwork<Node, Edge> network,
+        ImmutableSet<dagger.internal.codegen.model.Binding> bindings) {
       Set<Binding> frameworkRequestBindings = new HashSet<>();
-      for (dagger.spi.model.Binding binding : bindings) {
+      for (dagger.internal.codegen.model.Binding binding : bindings) {
         ImmutableList<DependencyEdge> edges =
             network.inEdges(binding).stream()
                 .flatMap(instancesOf(DependencyEdge.class))
diff --git a/java/dagger/internal/codegen/binding/BindingGraphConverter.java b/java/dagger/internal/codegen/binding/BindingGraphConverter.java
index 717e429..5928b8f 100644
--- a/java/dagger/internal/codegen/binding/BindingGraphConverter.java
+++ b/java/dagger/internal/codegen/binding/BindingGraphConverter.java
@@ -20,7 +20,7 @@
 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
 import static dagger.internal.codegen.extension.DaggerGraphs.unreachableNodes;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
-import static dagger.spi.model.BindingKind.SUBCOMPONENT_CREATOR;
+import static dagger.internal.codegen.model.BindingKind.SUBCOMPONENT_CREATOR;
 
 import androidx.room.compiler.processing.XMethodElement;
 import androidx.room.compiler.processing.XType;
@@ -37,16 +37,16 @@
 import com.google.common.graph.NetworkBuilder;
 import dagger.internal.codegen.binding.BindingGraph.TopLevelBindingGraph;
 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.Edge;
-import dagger.spi.model.BindingGraph.MissingBinding;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.DaggerExecutableElement;
-import dagger.spi.model.DaggerTypeElement;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.Edge;
+import dagger.internal.codegen.model.BindingGraph.MissingBinding;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.DaggerExecutableElement;
+import dagger.internal.codegen.model.DaggerTypeElement;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.HashMap;
@@ -55,7 +55,7 @@
 import java.util.Set;
 import javax.inject.Inject;
 
-/** Converts {@link BindingGraph}s to {@link dagger.spi.model.BindingGraph}s. */
+/** Converts {@link BindingGraph}s to {@link dagger.internal.codegen.model.BindingGraph}s. */
 final class BindingGraphConverter {
   private final BindingDeclarationFormatter bindingDeclarationFormatter;
 
@@ -65,8 +65,8 @@
   }
 
   /**
-   * Creates the external {@link dagger.spi.model.BindingGraph} representing the given internal
-   * {@link BindingGraph}.
+   * Creates the external {@link dagger.internal.codegen.model.BindingGraph} representing the given
+   * internal {@link BindingGraph}.
    */
   BindingGraph convert(LegacyBindingGraph legacyBindingGraph, boolean isFullBindingGraph) {
     MutableNetwork<Node, Edge> network = asNetwork(legacyBindingGraph);
@@ -276,8 +276,8 @@
     }
 
     /**
-     * Adds a {@link dagger.spi.model.BindingGraph.DependencyEdge} from a node to the binding(s)
-     * that satisfy a dependency request.
+     * Adds a {@link dagger.internal.codegen.model.BindingGraph.DependencyEdge} from a node to the
+     * binding(s) that satisfy a dependency request.
      */
     private void addDependencyEdges(Node source, DependencyRequest dependencyRequest) {
       ResolvedBindings dependencies = resolvedDependencies(source, dependencyRequest);
diff --git a/java/dagger/internal/codegen/binding/BindingGraphFactory.java b/java/dagger/internal/codegen/binding/BindingGraphFactory.java
index fe01431..ba1157d 100644
--- a/java/dagger/internal/codegen/binding/BindingGraphFactory.java
+++ b/java/dagger/internal/codegen/binding/BindingGraphFactory.java
@@ -22,15 +22,15 @@
 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedFactoryType;
 import static dagger.internal.codegen.binding.SourceFiles.generatedMonitoringModuleName;
+import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION;
+import static dagger.internal.codegen.model.BindingKind.DELEGATE;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
+import static dagger.internal.codegen.model.BindingKind.OPTIONAL;
+import static dagger.internal.codegen.model.BindingKind.SUBCOMPONENT_CREATOR;
+import static dagger.internal.codegen.model.RequestKind.MEMBERS_INJECTION;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;
-import static dagger.spi.model.BindingKind.ASSISTED_INJECTION;
-import static dagger.spi.model.BindingKind.DELEGATE;
-import static dagger.spi.model.BindingKind.INJECTION;
-import static dagger.spi.model.BindingKind.OPTIONAL;
-import static dagger.spi.model.BindingKind.SUBCOMPONENT_CREATOR;
-import static dagger.spi.model.RequestKind.MEMBERS_INJECTION;
 import static java.util.function.Predicate.isEqual;
 
 import androidx.room.compiler.processing.XProcessingEnv;
@@ -51,11 +51,11 @@
 import dagger.internal.codegen.base.OptionalType;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.xprocessing.XTypeElements;
 import dagger.producers.internal.ProductionExecutorModule;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.Scope;
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.HashMap;
diff --git a/java/dagger/internal/codegen/binding/BindingNode.java b/java/dagger/internal/codegen/binding/BindingNode.java
index aa0f6cb..7856420 100644
--- a/java/dagger/internal/codegen/binding/BindingNode.java
+++ b/java/dagger/internal/codegen/binding/BindingNode.java
@@ -24,25 +24,25 @@
 import com.google.common.collect.Iterables;
 import dagger.BindsOptionalOf;
 import dagger.Module;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.DaggerElement;
+import dagger.internal.codegen.model.DaggerTypeElement;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.Scope;
 import dagger.multibindings.Multibinds;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.DaggerElement;
-import dagger.spi.model.DaggerTypeElement;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.Scope;
 import java.util.Optional;
 
 /**
- * An implementation of {@link dagger.spi.model.Binding} that also exposes {@link
+ * An implementation of {@link dagger.internal.codegen.model.Binding} that also exposes {@link
  * BindingDeclaration}s associated with the binding.
  */
-// TODO(dpb): Consider a supertype of dagger.spi.model.Binding that
+// TODO(dpb): Consider a supertype of dagger.internal.codegen.model.Binding that
 // dagger.internal.codegen.binding.Binding
 // could also implement.
 @AutoValue
-public abstract class BindingNode implements dagger.spi.model.Binding {
+public abstract class BindingNode implements dagger.internal.codegen.model.Binding {
   public static BindingNode create(
       ComponentPath component,
       Binding delegate,
diff --git a/java/dagger/internal/codegen/binding/BindingRequest.java b/java/dagger/internal/codegen/binding/BindingRequest.java
index 58e72bc..b560aa0 100644
--- a/java/dagger/internal/codegen/binding/BindingRequest.java
+++ b/java/dagger/internal/codegen/binding/BindingRequest.java
@@ -21,9 +21,9 @@
 import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XType;
 import com.google.auto.value.AutoValue;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.RequestKind;
 import java.util.Optional;
 
 /**
diff --git a/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java b/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java
index 30f3588..077f454 100644
--- a/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java
+++ b/java/dagger/internal/codegen/binding/ChildFactoryMethodEdgeImpl.java
@@ -18,8 +18,8 @@
 
 import static dagger.internal.codegen.base.ElementFormatter.elementToString;
 
-import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge;
-import dagger.spi.model.DaggerExecutableElement;
+import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.internal.codegen.model.DaggerExecutableElement;
 
 /** An implementation of {@link ChildFactoryMethodEdge}. */
 public final class ChildFactoryMethodEdgeImpl implements ChildFactoryMethodEdge {
diff --git a/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java b/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java
index a89de70..0bc2209 100644
--- a/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java
+++ b/java/dagger/internal/codegen/binding/ComponentCreatorDescriptor.java
@@ -40,7 +40,7 @@
 import dagger.internal.codegen.base.ComponentCreatorAnnotation;
 import dagger.internal.codegen.base.ComponentCreatorKind;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DependencyRequest;
 import java.util.List;
 
 /**
diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptor.java b/java/dagger/internal/codegen/binding/ComponentDescriptor.java
index 096554f..a105608 100644
--- a/java/dagger/internal/codegen/binding/ComponentDescriptor.java
+++ b/java/dagger/internal/codegen/binding/ComponentDescriptor.java
@@ -46,9 +46,9 @@
 import dagger.Subcomponent;
 import dagger.internal.codegen.base.ComponentAnnotation;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.xprocessing.XAnnotations;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Scope;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
diff --git a/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java b/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java
index 77e9dbf..1d09450 100644
--- a/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java
+++ b/java/dagger/internal/codegen/binding/ComponentDescriptorFactory.java
@@ -43,8 +43,8 @@
 import dagger.internal.codegen.base.DaggerSuperficialValidation;
 import dagger.internal.codegen.base.ModuleAnnotation;
 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.xprocessing.XTypeElements;
-import dagger.spi.model.Scope;
 import java.util.Optional;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/binding/ComponentNodeImpl.java b/java/dagger/internal/codegen/binding/ComponentNodeImpl.java
index 5dbf633..984b7ed 100644
--- a/java/dagger/internal/codegen/binding/ComponentNodeImpl.java
+++ b/java/dagger/internal/codegen/binding/ComponentNodeImpl.java
@@ -20,10 +20,10 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Scope;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Scope;
 
 /** An implementation of {@link ComponentNode} that also exposes the {@link ComponentDescriptor}. */
 @AutoValue
diff --git a/java/dagger/internal/codegen/binding/ComponentRequirement.java b/java/dagger/internal/codegen/binding/ComponentRequirement.java
index 4b985fe..df10577 100644
--- a/java/dagger/internal/codegen/binding/ComponentRequirement.java
+++ b/java/dagger/internal/codegen/binding/ComponentRequirement.java
@@ -32,9 +32,9 @@
 import com.squareup.javapoet.ParameterSpec;
 import com.squareup.javapoet.TypeName;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.xprocessing.XTypeElements;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.Key;
 import java.util.Optional;
 
 /** A type that a component needs an instance of. */
@@ -191,8 +191,7 @@
 
   public static ComponentRequirement forBoundInstance(ContributionBinding binding) {
     checkArgument(binding.kind().equals(BindingKind.BOUND_INSTANCE));
-    return forBoundInstance(
-        binding.key(), binding.nullableType().isPresent(), binding.bindingElement().get());
+    return forBoundInstance(binding.key(), binding.isNullable(), binding.bindingElement().get());
   }
 
   static ComponentRequirement forBoundInstance(
diff --git a/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java b/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java
index 57da79a..a56a549 100644
--- a/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java
+++ b/java/dagger/internal/codegen/binding/ConfigurationAnnotations.java
@@ -20,12 +20,9 @@
 import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotations;
 import static dagger.internal.codegen.base.ComponentCreatorAnnotation.subcomponentCreatorAnnotations;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName;
 import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation;
 
-import androidx.room.compiler.processing.XAnnotation;
 import androidx.room.compiler.processing.XElement;
-import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
@@ -51,17 +48,6 @@
     return hasAnyAnnotation(element, subcomponentCreatorAnnotations());
   }
 
-  /** Returns the first type that specifies this' nullability, or empty if none. */
-  public static Optional<XAnnotation> getNullableAnnotation(XElement element) {
-    return element.getAllAnnotations().stream()
-        .filter(annotation -> getClassName(annotation).simpleName().contentEquals("Nullable"))
-        .findFirst();
-  }
-
-  public static Optional<XType> getNullableType(XElement element) {
-    return getNullableAnnotation(element).map(XAnnotation::getType);
-  }
-
   /** Returns the enclosed types annotated with the given annotation. */
   public static ImmutableSet<XTypeElement> enclosedAnnotatedTypes(
       XTypeElement typeElement, ImmutableSet<ClassName> annotations) {
diff --git a/java/dagger/internal/codegen/binding/ContributionBinding.java b/java/dagger/internal/codegen/binding/ContributionBinding.java
index 796e073..f8950a3 100644
--- a/java/dagger/internal/codegen/binding/ContributionBinding.java
+++ b/java/dagger/internal/codegen/binding/ContributionBinding.java
@@ -29,11 +29,11 @@
 import dagger.internal.codegen.base.ContributionType.HasContributionType;
 import dagger.internal.codegen.base.MapType;
 import dagger.internal.codegen.base.SetType;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.xprocessing.XTypes;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
 import java.util.Optional;
 
 /**
@@ -43,8 +43,8 @@
 @CheckReturnValue
 public abstract class ContributionBinding extends Binding implements HasContributionType {
 
-  /** Returns the type that specifies this' nullability, absent if not nullable. */
-  public abstract Optional<XType> nullableType();
+  /** Returns the nullability of this binding. */
+  public abstract Nullability nullability();
 
   // Note: We're using DaggerAnnotation instead of XAnnotation for its equals/hashcode
   public abstract Optional<DaggerAnnotation> mapKey();
@@ -64,7 +64,7 @@
 
   @Override
   public final boolean isNullable() {
-    return nullableType().isPresent();
+    return nullability().isNullable();
   }
 
   /**
@@ -134,7 +134,7 @@
     public abstract B key(Key key);
 
     @CanIgnoreReturnValue
-    public abstract B nullableType(Optional<XType> nullableType);
+    public abstract B nullability(Nullability nullability);
 
     @CanIgnoreReturnValue
     abstract B mapKey(Optional<DaggerAnnotation> mapKey);
diff --git a/java/dagger/internal/codegen/binding/DelegateDeclaration.java b/java/dagger/internal/codegen/binding/DelegateDeclaration.java
index 835fff4..76d4287 100644
--- a/java/dagger/internal/codegen/binding/DelegateDeclaration.java
+++ b/java/dagger/internal/codegen/binding/DelegateDeclaration.java
@@ -30,8 +30,8 @@
 import dagger.internal.codegen.base.ContributionType;
 import dagger.internal.codegen.base.ContributionType.HasContributionType;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DependencyRequest;
 import java.util.Optional;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java b/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java
index d3bde9d..098049c 100644
--- a/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java
+++ b/java/dagger/internal/codegen/binding/DependencyEdgeImpl.java
@@ -17,9 +17,9 @@
 package dagger.internal.codegen.binding;
 
 import dagger.internal.codegen.base.ElementFormatter;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.DaggerElement;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.DaggerElement;
+import dagger.internal.codegen.model.DependencyRequest;
 
 /** An implementation of {@link DependencyEdge}. */
 final class DependencyEdgeImpl implements DependencyEdge {
diff --git a/java/dagger/internal/codegen/binding/DependencyRequestFactory.java b/java/dagger/internal/codegen/binding/DependencyRequestFactory.java
index 154cb13..a96eeda 100644
--- a/java/dagger/internal/codegen/binding/DependencyRequestFactory.java
+++ b/java/dagger/internal/codegen/binding/DependencyRequestFactory.java
@@ -24,14 +24,13 @@
 import static dagger.internal.codegen.base.RequestKinds.frameworkClassName;
 import static dagger.internal.codegen.base.RequestKinds.getRequestKind;
 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter;
-import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType;
+import static dagger.internal.codegen.model.RequestKind.FUTURE;
+import static dagger.internal.codegen.model.RequestKind.INSTANCE;
+import static dagger.internal.codegen.model.RequestKind.MEMBERS_INJECTION;
+import static dagger.internal.codegen.model.RequestKind.PRODUCER;
+import static dagger.internal.codegen.model.RequestKind.PROVIDER;
 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;
 import static dagger.internal.codegen.xprocessing.XTypes.unwrapType;
-import static dagger.spi.model.RequestKind.FUTURE;
-import static dagger.spi.model.RequestKind.INSTANCE;
-import static dagger.spi.model.RequestKind.MEMBERS_INJECTION;
-import static dagger.spi.model.RequestKind.PRODUCER;
-import static dagger.spi.model.RequestKind.PROVIDER;
 
 import androidx.room.compiler.processing.XAnnotation;
 import androidx.room.compiler.processing.XElement;
@@ -44,10 +43,10 @@
 import dagger.internal.codegen.base.MapType;
 import dagger.internal.codegen.base.OptionalType;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DaggerElement;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.DaggerElement;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.RequestKind;
 import java.util.List;
 import java.util.Optional;
 import javax.inject.Inject;
@@ -214,7 +213,8 @@
         .kind(kind)
         .key(key.get())
         .isNullable(
-            allowsNull(getRequestKind(OptionalType.from(requestKey).valueType()), Optional.empty()))
+            requestKindImplicitlyAllowsNull(
+                getRequestKind(OptionalType.from(requestKey).valueType())))
         .build();
   }
 
@@ -225,17 +225,21 @@
         .kind(requestKind)
         .key(keyFactory.forQualifiedType(qualifier, extractKeyType(type)))
         .requestElement(DaggerElement.from(requestElement))
-        .isNullable(allowsNull(requestKind, getNullableType(requestElement)))
+        .isNullable(allowsNull(requestKind, Nullability.of(requestElement)))
         .build();
   }
 
   /**
    * Returns {@code true} if a given request element allows null values. {@link
-   * RequestKind#INSTANCE} requests must be annotated with {@code @Nullable} in order to allow null
-   * values. All other request kinds implicitly allow null values because they are are wrapped
-   * inside {@link Provider}, {@link Lazy}, etc.
+   * RequestKind#INSTANCE} requests must be nullable in order to allow null values. All other
+   * request kinds implicitly allow null values because they are are wrapped inside {@link
+   * Provider}, {@link Lazy}, etc.
    */
-  private boolean allowsNull(RequestKind kind, Optional<XType> nullableType) {
-    return nullableType.isPresent() || !kind.equals(INSTANCE);
+  private boolean allowsNull(RequestKind kind, Nullability nullability) {
+    return nullability.isNullable() || requestKindImplicitlyAllowsNull(kind);
+  }
+
+  private boolean requestKindImplicitlyAllowsNull(RequestKind kind) {
+    return !kind.equals(INSTANCE);
   }
 }
diff --git a/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java b/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java
index ae04838..80591b1 100644
--- a/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java
+++ b/java/dagger/internal/codegen/binding/DependencyRequestFormatter.java
@@ -27,10 +27,10 @@
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import dagger.Provides;
 import dagger.internal.codegen.base.Formatter;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DependencyRequest;
 import dagger.internal.codegen.xprocessing.XTypes;
 import dagger.producers.Produces;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DependencyRequest;
 import java.util.Optional;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/binding/DependencyVariableNamer.java b/java/dagger/internal/codegen/binding/DependencyVariableNamer.java
index f75c97c..6fa3f39 100644
--- a/java/dagger/internal/codegen/binding/DependencyVariableNamer.java
+++ b/java/dagger/internal/codegen/binding/DependencyVariableNamer.java
@@ -21,7 +21,7 @@
 
 import com.google.common.base.Ascii;
 import com.google.common.base.CaseFormat;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DependencyRequest;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
diff --git a/java/dagger/internal/codegen/binding/FrameworkField.java b/java/dagger/internal/codegen/binding/FrameworkField.java
index e1ea064..b1d1cf9 100644
--- a/java/dagger/internal/codegen/binding/FrameworkField.java
+++ b/java/dagger/internal/codegen/binding/FrameworkField.java
@@ -20,8 +20,8 @@
 import static androidx.room.compiler.processing.XElementKt.isMethod;
 import static androidx.room.compiler.processing.XElementKt.isMethodParameter;
 import static androidx.room.compiler.processing.XElementKt.isTypeElement;
+import static dagger.internal.codegen.model.BindingKind.MEMBERS_INJECTOR;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
-import static dagger.spi.model.BindingKind.MEMBERS_INJECTOR;
 
 import androidx.room.compiler.processing.XElement;
 import androidx.room.compiler.processing.XType;
diff --git a/java/dagger/internal/codegen/binding/FrameworkType.java b/java/dagger/internal/codegen/binding/FrameworkType.java
index afe6389..ce3b149 100644
--- a/java/dagger/internal/codegen/binding/FrameworkType.java
+++ b/java/dagger/internal/codegen/binding/FrameworkType.java
@@ -27,8 +27,8 @@
 import dagger.internal.codegen.base.RequestKinds;
 import dagger.internal.codegen.javapoet.Expression;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.RequestKind;
 import java.util.Optional;
 
 /** One of the core types initialized as fields in a generated component. */
diff --git a/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java b/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java
index 776fac6..0e17d96 100644
--- a/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java
+++ b/java/dagger/internal/codegen/binding/FrameworkTypeMapper.java
@@ -18,8 +18,8 @@
 
 import static dagger.internal.codegen.binding.BindingType.PRODUCTION;
 
+import dagger.internal.codegen.model.RequestKind;
 import dagger.producers.Producer;
-import dagger.spi.model.RequestKind;
 import javax.inject.Provider;
 
 /**
diff --git a/java/dagger/internal/codegen/binding/InjectBindingRegistry.java b/java/dagger/internal/codegen/binding/InjectBindingRegistry.java
index ceb5024..45fea0a 100644
--- a/java/dagger/internal/codegen/binding/InjectBindingRegistry.java
+++ b/java/dagger/internal/codegen/binding/InjectBindingRegistry.java
@@ -24,7 +24,7 @@
 import dagger.Provides;
 import dagger.internal.codegen.base.SourceFileGenerationException;
 import dagger.internal.codegen.base.SourceFileGenerator;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.Key;
 import java.util.Optional;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/binding/InjectionAnnotations.java b/java/dagger/internal/codegen/binding/InjectionAnnotations.java
index eacdd9a..a8adb15 100644
--- a/java/dagger/internal/codegen/binding/InjectionAnnotations.java
+++ b/java/dagger/internal/codegen/binding/InjectionAnnotations.java
@@ -48,8 +48,8 @@
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.TypeNames;
 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.Scope;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.Scope;
 import java.util.Optional;
 import java.util.stream.Stream;
 import javax.inject.Inject;
diff --git a/java/dagger/internal/codegen/binding/KeyFactory.java b/java/dagger/internal/codegen/binding/KeyFactory.java
index e7daf60..d7f78c1 100644
--- a/java/dagger/internal/codegen/binding/KeyFactory.java
+++ b/java/dagger/internal/codegen/binding/KeyFactory.java
@@ -48,14 +48,14 @@
 import dagger.internal.codegen.base.RequestKinds;
 import dagger.internal.codegen.base.SetType;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DaggerExecutableElement;
+import dagger.internal.codegen.model.DaggerType;
+import dagger.internal.codegen.model.DaggerTypeElement;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.xprocessing.XAnnotations;
 import dagger.multibindings.Multibinds;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DaggerExecutableElement;
-import dagger.spi.model.DaggerType;
-import dagger.spi.model.DaggerTypeElement;
-import dagger.spi.model.Key;
-import dagger.spi.model.RequestKind;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Stream;
@@ -105,11 +105,11 @@
       XMethodElement subcomponentCreatorMethod, XType declaredContainer) {
     checkArgument(isDeclared(declaredContainer));
     XMethodType resolvedMethod = subcomponentCreatorMethod.asMemberOf(declaredContainer);
-    return Key.builder(DaggerType.from(resolvedMethod.getReturnType())).build();
+    return forType(resolvedMethod.getReturnType());
   }
 
   public Key forSubcomponentCreator(XType creatorType) {
-    return Key.builder(DaggerType.from(creatorType)).build();
+    return forType(creatorType);
   }
 
   public Key forProvidesMethod(XMethodElement method, XTypeElement contributingModule) {
@@ -234,16 +234,15 @@
   }
 
   public Key forInjectConstructorWithResolvedType(XType type) {
-    return Key.builder(DaggerType.from(type)).build();
+    return forType(type);
   }
 
-  // TODO(ronshapiro): Remove these conveniences which are simple wrappers around Key.Builder
   Key forType(XType type) {
     return Key.builder(DaggerType.from(type)).build();
   }
 
   public Key forMembersInjectedType(XType type) {
-    return Key.builder(DaggerType.from(type)).build();
+    return forType(type);
   }
 
   Key forQualifiedType(Optional<XAnnotation> qualifier, XType type) {
@@ -265,9 +264,7 @@
   }
 
   public Key forProductionComponentMonitor() {
-    return Key.builder(
-            DaggerType.from(processingEnv.requireType(TypeNames.PRODUCTION_COMPONENT_MONITOR)))
-        .build();
+    return forType(processingEnv.requireType(TypeNames.PRODUCTION_COMPONENT_MONITOR));
   }
 
   /**
diff --git a/java/dagger/internal/codegen/binding/KeyVariableNamer.java b/java/dagger/internal/codegen/binding/KeyVariableNamer.java
index cb6a910..c9a1a4b 100644
--- a/java/dagger/internal/codegen/binding/KeyVariableNamer.java
+++ b/java/dagger/internal/codegen/binding/KeyVariableNamer.java
@@ -28,8 +28,8 @@
 import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableSet;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import java.util.Iterator;
 
 /**
@@ -50,7 +50,8 @@
 
   public static String name(Key key) {
     if (key.multibindingContributionIdentifier().isPresent()) {
-      return key.multibindingContributionIdentifier().get().bindingMethod();
+      return getSimpleName(
+          key.multibindingContributionIdentifier().get().bindingMethod().xprocessing());
     }
 
     StringBuilder builder = new StringBuilder();
diff --git a/java/dagger/internal/codegen/binding/LegacyBindingGraph.java b/java/dagger/internal/codegen/binding/LegacyBindingGraph.java
index 2f8d39a..b38697e 100644
--- a/java/dagger/internal/codegen/binding/LegacyBindingGraph.java
+++ b/java/dagger/internal/codegen/binding/LegacyBindingGraph.java
@@ -22,8 +22,8 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimaps;
-import dagger.spi.model.Key;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.RequestKind;
 import java.util.Collection;
 import java.util.Map;
 
diff --git a/java/dagger/internal/codegen/binding/MapKeys.java b/java/dagger/internal/codegen/binding/MapKeys.java
index 8f8da18..c156dbb 100644
--- a/java/dagger/internal/codegen/binding/MapKeys.java
+++ b/java/dagger/internal/codegen/binding/MapKeys.java
@@ -46,8 +46,8 @@
 import dagger.internal.codegen.base.DaggerSuperficialValidation;
 import dagger.internal.codegen.base.MapKeyAccessibility;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DaggerAnnotation;
 import dagger.internal.codegen.xprocessing.XElements;
-import dagger.spi.model.DaggerAnnotation;
 import java.util.NoSuchElementException;
 import java.util.Optional;
 
diff --git a/java/dagger/internal/codegen/binding/MembersInjectionBinding.java b/java/dagger/internal/codegen/binding/MembersInjectionBinding.java
index 302d6e2..b546f6a 100644
--- a/java/dagger/internal/codegen/binding/MembersInjectionBinding.java
+++ b/java/dagger/internal/codegen/binding/MembersInjectionBinding.java
@@ -30,9 +30,9 @@
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import java.util.Optional;
 
 /** Represents the full members injection of a particular type. */
diff --git a/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java b/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java
index 962618b..e98abcc 100644
--- a/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java
+++ b/java/dagger/internal/codegen/binding/MethodSignatureFormatter.java
@@ -22,6 +22,7 @@
 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
+import static java.util.stream.Collectors.joining;
 
 import androidx.room.compiler.processing.XAnnotation;
 import androidx.room.compiler.processing.XExecutableElement;
@@ -32,6 +33,7 @@
 import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
 import androidx.room.compiler.processing.XVariableElement;
+import com.squareup.javapoet.ClassName;
 import dagger.internal.codegen.base.Formatter;
 import dagger.internal.codegen.xprocessing.XAnnotations;
 import dagger.internal.codegen.xprocessing.XTypes;
@@ -42,6 +44,9 @@
 
 /** Formats the signature of an {@link XExecutableElement} suitable for use in error messages. */
 public final class MethodSignatureFormatter extends Formatter<XExecutableElement> {
+  private static final ClassName JET_BRAINS_NOT_NULL =
+      ClassName.get("org.jetbrains.annotations", "NotNull");
+
   private final InjectionAnnotations injectionAnnotations;
 
   @Inject
@@ -103,14 +108,13 @@
     StringBuilder builder = new StringBuilder();
     List<XAnnotation> annotations = method.getAllAnnotations();
     if (!annotations.isEmpty()) {
-      Iterator<XAnnotation> annotationIterator = annotations.iterator();
-      for (int i = 0; annotationIterator.hasNext(); i++) {
-        if (i > 0) {
-          builder.append(' ');
-        }
-        builder.append(formatAnnotation(annotationIterator.next()));
-      }
-      builder.append(' ');
+      builder.append(
+          annotations.stream()
+              // Filter out @NotNull annotations added by KAPT to make error messages consistent
+              .filter(annotation -> !annotation.getClassName().equals(JET_BRAINS_NOT_NULL))
+              .map(MethodSignatureFormatter::formatAnnotation)
+              .collect(joining(" ")))
+          .append(" ");
     }
     if (getSimpleName(method).contentEquals("<init>")) {
       builder.append(container.getQualifiedName());
diff --git a/java/dagger/internal/codegen/binding/ModuleDescriptor.java b/java/dagger/internal/codegen/binding/ModuleDescriptor.java
index 6543639..62cfa7f 100644
--- a/java/dagger/internal/codegen/binding/ModuleDescriptor.java
+++ b/java/dagger/internal/codegen/binding/ModuleDescriptor.java
@@ -26,7 +26,6 @@
 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static dagger.internal.codegen.xprocessing.XElements.asMethod;
-import static dagger.internal.codegen.xprocessing.XElements.getMethodDescriptor;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
 
@@ -47,8 +46,8 @@
 import dagger.internal.codegen.base.DaggerSuperficialValidation;
 import dagger.internal.codegen.base.ModuleKind;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.xprocessing.XTypeElements;
-import dagger.spi.model.Key;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
@@ -182,7 +181,7 @@
         XTypeElement companionModule, ImmutableSet.Builder<ContributionBinding> bindings) {
       ImmutableSet<String> bindingElementDescriptors =
           bindings.build().stream()
-              .map(binding -> getMethodDescriptor(asMethod(binding.bindingElement().get())))
+              .map(binding -> asMethod(binding.bindingElement().get()).getJvmDescriptor())
               .collect(toImmutableSet());
 
       XTypeElements.getAllMethods(companionModule).stream()
@@ -193,7 +192,7 @@
           // @JvmStatic by comparing descriptors. Contributing bindings are the only valid bindings
           // a companion module can declare. See: https://youtrack.jetbrains.com/issue/KT-35104
           // TODO(danysantiago): Checks qualifiers too.
-          .filter(method -> !bindingElementDescriptors.contains(getMethodDescriptor(method)))
+          .filter(method -> !bindingElementDescriptors.contains(method.getJvmDescriptor()))
           .forEach(
               method -> {
                 if (method.hasAnnotation(TypeNames.PROVIDES)) {
diff --git a/java/dagger/internal/codegen/binding/MultibindingDeclaration.java b/java/dagger/internal/codegen/binding/MultibindingDeclaration.java
index 746feaf..54672fc 100644
--- a/java/dagger/internal/codegen/binding/MultibindingDeclaration.java
+++ b/java/dagger/internal/codegen/binding/MultibindingDeclaration.java
@@ -29,8 +29,8 @@
 import dagger.internal.codegen.base.MapType;
 import dagger.internal.codegen.base.SetType;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Key;
 import dagger.multibindings.Multibinds;
-import dagger.spi.model.Key;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
diff --git a/java/dagger/internal/codegen/binding/Nullability.java b/java/dagger/internal/codegen/binding/Nullability.java
new file mode 100644
index 0000000..190227b
--- /dev/null
+++ b/java/dagger/internal/codegen/binding/Nullability.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.binding;
+
+import static androidx.room.compiler.processing.XElementKt.isMethod;
+import static androidx.room.compiler.processing.XElementKt.isVariableElement;
+import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName;
+import static dagger.internal.codegen.xprocessing.XElements.asMethod;
+import static dagger.internal.codegen.xprocessing.XElements.asVariable;
+
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XNullability;
+import androidx.room.compiler.processing.XType;
+import com.google.auto.value.AutoValue;
+import java.util.Optional;
+
+/**
+ * Contains information about the nullability of an element.
+ *
+ * <p>Note that an element can be nullable if either:
+ *
+ * <ul>
+ *   <li>The element is annotated with {@code Nullable} or
+ *   <li>the associated kotlin type is nullable (i.e. {@code T?} types in Kotlin source).
+ * </ul>
+ */
+@AutoValue
+public abstract class Nullability {
+  /** A constant that can represent any non-null element. */
+  public static final Nullability NOT_NULLABLE = new AutoValue_Nullability(false, Optional.empty());
+
+  public static Nullability of(XElement element) {
+    Optional<XAnnotation> nullableAnnotation = getNullableAnnotation(element);
+    boolean isNullable = isKotlinTypeNullable(element) || nullableAnnotation.isPresent();
+    return isNullable ? new AutoValue_Nullability(isNullable, nullableAnnotation) : NOT_NULLABLE;
+  }
+
+  private static boolean isKotlinTypeNullable(XElement element) {
+    if (isMethod(element)) {
+      return isKotlinTypeNullable(asMethod(element).getReturnType());
+    } else if (isVariableElement(element)) {
+      return isKotlinTypeNullable(asVariable(element).getType());
+    } else {
+      return false;
+    }
+  }
+
+  private static boolean isKotlinTypeNullable(XType type) {
+    return type.getNullability() == XNullability.NULLABLE;
+  }
+
+  /** Returns the first type that specifies this' nullability, or empty if none. */
+  private static Optional<XAnnotation> getNullableAnnotation(XElement element) {
+    return element.getAllAnnotations().stream()
+        .filter(annotation -> getClassName(annotation).simpleName().contentEquals("Nullable"))
+        .findFirst();
+  }
+
+  public abstract boolean isNullable();
+
+  public abstract Optional<XAnnotation> nullableAnnotation();
+
+  Nullability() {}
+}
diff --git a/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java b/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java
index d708c0a..60d56bb 100644
--- a/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java
+++ b/java/dagger/internal/codegen/binding/OptionalBindingDeclaration.java
@@ -24,7 +24,7 @@
 import com.google.auto.value.extension.memoized.Memoized;
 import dagger.BindsOptionalOf;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.Key;
 import java.util.Optional;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/binding/ProductionBinding.java b/java/dagger/internal/codegen/binding/ProductionBinding.java
index 8f49abc..0bcf8e7 100644
--- a/java/dagger/internal/codegen/binding/ProductionBinding.java
+++ b/java/dagger/internal/codegen/binding/ProductionBinding.java
@@ -29,8 +29,8 @@
 import com.google.errorprone.annotations.CheckReturnValue;
 import dagger.internal.codegen.base.ContributionType;
 import dagger.internal.codegen.base.SetType;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import java.util.Optional;
 import java.util.stream.Stream;
 
@@ -111,6 +111,7 @@
 
   public static Builder builder() {
     return new AutoValue_ProductionBinding.Builder()
+        .nullability(Nullability.NOT_NULLABLE)
         .explicitDependencies(ImmutableList.<DependencyRequest>of())
         .thrownTypes(ImmutableList.<XType>of());
   }
diff --git a/java/dagger/internal/codegen/binding/ProvisionBinding.java b/java/dagger/internal/codegen/binding/ProvisionBinding.java
index ad61677..caf5904 100644
--- a/java/dagger/internal/codegen/binding/ProvisionBinding.java
+++ b/java/dagger/internal/codegen/binding/ProvisionBinding.java
@@ -17,8 +17,8 @@
 package dagger.internal.codegen.binding;
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-import static dagger.spi.model.BindingKind.COMPONENT_PROVISION;
-import static dagger.spi.model.BindingKind.PROVISION;
+import static dagger.internal.codegen.model.BindingKind.COMPONENT_PROVISION;
+import static dagger.internal.codegen.model.BindingKind.PROVISION;
 
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
@@ -28,10 +28,10 @@
 import com.google.errorprone.annotations.CheckReturnValue;
 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
 import dagger.internal.codegen.compileroption.CompilerOptions;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.Scope;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.Scope;
 import java.util.Optional;
 
 /** A value object representing the mechanism by which a {@link Key} can be provided. */
@@ -82,6 +82,7 @@
 
   public static Builder builder() {
     return new AutoValue_ProvisionBinding.Builder()
+        .nullability(Nullability.NOT_NULLABLE)
         .provisionDependencies(ImmutableSet.of())
         .injectionSites(ImmutableSortedSet.of());
   }
@@ -95,7 +96,7 @@
   public boolean shouldCheckForNull(CompilerOptions compilerOptions) {
     return KINDS_TO_CHECK_FOR_NULL.contains(kind())
         && !contributedPrimitiveType().isPresent()
-        && !nullableType().isPresent()
+        && !isNullable()
         && compilerOptions.doCheckForNulls();
   }
 
diff --git a/java/dagger/internal/codegen/binding/ResolvedBindings.java b/java/dagger/internal/codegen/binding/ResolvedBindings.java
index 2e7eb68..8a35442 100644
--- a/java/dagger/internal/codegen/binding/ResolvedBindings.java
+++ b/java/dagger/internal/codegen/binding/ResolvedBindings.java
@@ -27,7 +27,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Multimap;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.Key;
 
 /**
  * The collection of bindings that have been resolved for a key. For valid graphs, contains exactly
diff --git a/java/dagger/internal/codegen/binding/SourceFiles.java b/java/dagger/internal/codegen/binding/SourceFiles.java
index d108e75..19d316f 100644
--- a/java/dagger/internal/codegen/binding/SourceFiles.java
+++ b/java/dagger/internal/codegen/binding/SourceFiles.java
@@ -34,14 +34,14 @@
 import static dagger.internal.codegen.javapoet.TypeNames.SET_FACTORY;
 import static dagger.internal.codegen.javapoet.TypeNames.SET_OF_PRODUCED_PRODUCER;
 import static dagger.internal.codegen.javapoet.TypeNames.SET_PRODUCER;
+import static dagger.internal.codegen.model.BindingKind.ASSISTED_INJECTION;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_MAP;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_SET;
 import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 import static dagger.internal.codegen.xprocessing.XTypeElements.typeVariableNames;
-import static dagger.spi.model.BindingKind.ASSISTED_INJECTION;
-import static dagger.spi.model.BindingKind.INJECTION;
-import static dagger.spi.model.BindingKind.MULTIBOUND_MAP;
-import static dagger.spi.model.BindingKind.MULTIBOUND_SET;
 import static javax.lang.model.SourceVersion.isName;
 
 import androidx.room.compiler.processing.XExecutableElement;
@@ -62,8 +62,8 @@
 import dagger.internal.codegen.base.MapType;
 import dagger.internal.codegen.base.SetType;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.RequestKind;
 import javax.inject.Inject;
 import javax.lang.model.SourceVersion;
 
diff --git a/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java b/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java
index 5998b98..6790717 100644
--- a/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java
+++ b/java/dagger/internal/codegen/binding/SubcomponentCreatorBindingEdgeImpl.java
@@ -23,8 +23,8 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.squareup.javapoet.ClassName;
-import dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge;
-import dagger.spi.model.DaggerTypeElement;
+import dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge;
+import dagger.internal.codegen.model.DaggerTypeElement;
 
 /** An implementation of {@link SubcomponentCreatorBindingEdge}. */
 public final class SubcomponentCreatorBindingEdgeImpl implements SubcomponentCreatorBindingEdge {
diff --git a/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java b/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java
index dfc2905..5df2dd9 100644
--- a/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java
+++ b/java/dagger/internal/codegen/binding/SubcomponentDeclaration.java
@@ -27,7 +27,7 @@
 import com.google.common.collect.ImmutableSet;
 import dagger.internal.codegen.base.DaggerSuperficialValidation;
 import dagger.internal.codegen.base.ModuleAnnotation;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.Key;
 import java.util.Optional;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/BUILD b/java/dagger/internal/codegen/bindinggraphvalidation/BUILD
index 85c6451..e8a005e 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/BUILD
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/BUILD
@@ -33,9 +33,9 @@
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/validation",
         "//java/dagger/internal/codegen/xprocessing",
-        "//java/dagger/spi",
         "//third_party/java/auto:common",
         "//third_party/java/auto:value",
         "//third_party/java/error_prone:annotations",
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java b/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java
index 7e32bec..c826c3d 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/BindingGraphValidationModule.java
@@ -20,9 +20,9 @@
 import dagger.Module;
 import dagger.Provides;
 import dagger.internal.codegen.compileroption.CompilerOptions;
+import dagger.internal.codegen.model.BindingGraphPlugin;
 import dagger.internal.codegen.validation.Validation;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.BindingGraphPlugin;
 
 /** Binds the set of {@link BindingGraphPlugin}s used to implement Dagger validation. */
 @Module
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/CompositeBindingGraphPlugin.java b/java/dagger/internal/codegen/bindinggraphvalidation/CompositeBindingGraphPlugin.java
index a723800..9d958d7 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/CompositeBindingGraphPlugin.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/CompositeBindingGraphPlugin.java
@@ -27,16 +27,16 @@
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
 import dagger.assisted.AssistedInject;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
+import dagger.internal.codegen.model.BindingGraphPlugin;
+import dagger.internal.codegen.model.DaggerProcessingEnv;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import dagger.internal.codegen.validation.DiagnosticMessageGenerator;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.MaybeBinding;
-import dagger.spi.model.BindingGraphPlugin;
-import dagger.spi.model.DaggerProcessingEnv;
-import dagger.spi.model.DiagnosticReporter;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java
index f42f7ad..a2c53c4 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/DependencyCycleValidator.java
@@ -44,16 +44,16 @@
 import dagger.internal.codegen.base.OptionalType;
 import dagger.internal.codegen.binding.DependencyRequestFormatter;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.DiagnosticReporter;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.DiagnosticReporter;
-import dagger.spi.model.RequestKind;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java
index 4bdff69..ea4d738 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/DependsOnProductionExecutorValidator.java
@@ -21,12 +21,12 @@
 
 import dagger.internal.codegen.binding.KeyFactory;
 import dagger.internal.codegen.compileroption.CompilerOptions;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
+import dagger.internal.codegen.model.DiagnosticReporter;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.MaybeBinding;
-import dagger.spi.model.DiagnosticReporter;
-import dagger.spi.model.Key;
 import javax.inject.Inject;
 
 /**
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java
index 2b11f17..5f4dcfc 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/DuplicateBindingsValidator.java
@@ -16,19 +16,19 @@
 
 package dagger.internal.codegen.bindinggraphvalidation;
 
-import static com.google.common.base.Verify.verify;
-import static com.google.common.collect.Iterables.getOnlyElement;
 import static dagger.internal.codegen.base.Formatter.INDENT;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap;
-import static dagger.spi.model.BindingKind.INJECTION;
-import static dagger.spi.model.BindingKind.MEMBERS_INJECTION;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
+import static dagger.internal.codegen.model.BindingKind.MEMBERS_INJECTION;
 import static java.util.Comparator.comparing;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
 import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.value.AutoValue;
+import com.google.common.base.Equivalence;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
@@ -37,30 +37,32 @@
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
-import com.google.common.collect.Sets;
 import dagger.internal.codegen.base.Formatter;
 import dagger.internal.codegen.binding.BindingDeclaration;
 import dagger.internal.codegen.binding.BindingDeclarationFormatter;
 import dagger.internal.codegen.binding.BindingNode;
 import dagger.internal.codegen.binding.MultibindingDeclaration;
 import dagger.internal.codegen.compileroption.CompilerOptions;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DaggerElement;
+import dagger.internal.codegen.model.DaggerTypeElement;
+import dagger.internal.codegen.model.DiagnosticReporter;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.Key.MultibindingContributionIdentifier;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.DaggerElement;
-import dagger.spi.model.DaggerTypeElement;
-import dagger.spi.model.DiagnosticReporter;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.xprocessing.XTypes;
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Predicate;
 import javax.inject.Inject;
-import javax.tools.Diagnostic.Kind;
+import javax.tools.Diagnostic;
 
 /** Reports errors for conflicting bindings with the same key. */
 final class DuplicateBindingsValidator extends ValidationBindingGraphPlugin {
@@ -89,13 +91,13 @@
     // same two modules, then fixing the error in one subcomponent will uncover the second
     // subcomponent to fix.
     // TODO(ronshapiro): Explore ways to address such underreporting without overreporting.
-    Set<ImmutableSet<BindingElement>> reportedDuplicateBindingSets = new HashSet<>();
+    Set<ImmutableSet<BindingWithoutComponent>> reportedDuplicateBindingSets = new HashSet<>();
     duplicateBindingSets(bindingGraph)
         .forEach(
             duplicateBindings -> {
               // Only report each set of duplicate bindings once, ignoring the installed component.
               if (reportedDuplicateBindingSets.add(duplicateBindings.keySet())) {
-                reportDuplicateBindings(duplicateBindings, bindingGraph, diagnosticReporter);
+                reportErrors(duplicateBindings, bindingGraph, diagnosticReporter);
               }
             });
   }
@@ -107,20 +109,31 @@
    * descendant component because it depends on local multibindings or optional bindings. Hence each
    * "set" is represented as a multimap from binding element (ignoring component path) to binding.
    */
-  private ImmutableSet<ImmutableSetMultimap<BindingElement, Binding>> duplicateBindingSets(
+  private ImmutableSet<ImmutableSetMultimap<BindingWithoutComponent, Binding>> duplicateBindingSets(
       BindingGraph bindingGraph) {
     return groupBindingsByKey(bindingGraph).stream()
         .flatMap(bindings -> mutuallyVisibleSubsets(bindings).stream())
-        .map(BindingElement::index)
+        .map(BindingWithoutComponent::index)
         .filter(duplicates -> duplicates.keySet().size() > 1)
         .collect(toImmutableSet());
   }
 
-  private static ImmutableSet<ImmutableSet<Binding>> groupBindingsByKey(BindingGraph bindingGraph) {
+  private ImmutableSet<ImmutableSet<Binding>> groupBindingsByKey(BindingGraph bindingGraph) {
     return valueSetsForEachKey(
         bindingGraph.bindings().stream()
             .filter(binding -> !binding.kind().equals(MEMBERS_INJECTION))
-            .collect(toImmutableSetMultimap(Binding::key, binding -> binding)));
+            .collect(
+                toImmutableSetMultimap(
+                    binding ->
+                        // If the "ignoreProvisionKeyWildcards" flag is enabled then ignore the
+                        // variance in the key types here so that Foo<Bar> and Foo<? extends Bar>
+                        // get grouped into the same set (i.e. as duplicates).
+                        KeyWithTypeEquivalence.forKey(
+                            binding.key(),
+                            compilerOptions.ignoreProvisionKeyWildcards()
+                                ? XTypes.equivalenceIgnoringVariance()
+                                : XTypes.equivalence()),
+                    binding -> binding)));
   }
 
   /**
@@ -147,8 +160,8 @@
     return valueSetsForEachKey(mutuallyVisibleBindings.build());
   }
 
-  private void reportDuplicateBindings(
-      ImmutableSetMultimap<BindingElement, Binding> duplicateBindings,
+  private void reportErrors(
+      ImmutableSetMultimap<BindingWithoutComponent, Binding> duplicateBindings,
       BindingGraph bindingGraph,
       DiagnosticReporter diagnosticReporter) {
     if (explicitBindingConfictsWithInject(duplicateBindings.keySet())) {
@@ -158,49 +171,33 @@
           .ifPresent(
               diagnosticKind ->
                   reportExplicitBindingConflictsWithInject(
-                      duplicateBindings,
+                      duplicateBindings.values(),
                       diagnosticReporter,
                       diagnosticKind,
                       bindingGraph.rootComponentNode()));
       return;
     }
-    ImmutableSet<Binding> bindings = ImmutableSet.copyOf(duplicateBindings.values());
-    Binding oneBinding = bindings.asList().get(0);
-    String message = bindings.stream().anyMatch(binding -> binding.kind().isMultibinding())
-        ? incompatibleBindingsMessage(oneBinding, bindings, bindingGraph)
-        : duplicateBindingMessage(oneBinding, bindings, bindingGraph);
-    if (compilerOptions.experimentalDaggerErrorMessages()) {
-      diagnosticReporter.reportComponent(
-          ERROR,
-          bindingGraph.rootComponentNode(),
-          message);
-    } else {
-      diagnosticReporter.reportBinding(
-          ERROR,
-          oneBinding,
-          message);
-    }
+
+    reportDuplicateBindings(duplicateBindings.values(), bindingGraph, diagnosticReporter);
   }
 
   /**
    * Returns {@code true} if the bindings contain one {@code @Inject} binding and one that isn't.
    */
   private static boolean explicitBindingConfictsWithInject(
-      ImmutableSet<BindingElement> duplicateBindings) {
+      ImmutableSet<BindingWithoutComponent> duplicateBindings) {
     ImmutableMultiset<BindingKind> bindingKinds =
-        Multimaps.index(duplicateBindings, BindingElement::bindingKind).keys();
+        Multimaps.index(duplicateBindings, BindingWithoutComponent::bindingKind).keys();
     return bindingKinds.count(INJECTION) == 1 && bindingKinds.size() == 2;
   }
 
   private void reportExplicitBindingConflictsWithInject(
-      ImmutableSetMultimap<BindingElement, Binding> duplicateBindings,
+      ImmutableCollection<Binding> duplicateBindings,
       DiagnosticReporter diagnosticReporter,
-      Kind diagnosticKind,
+      Diagnostic.Kind diagnosticKind,
       ComponentNode rootComponent) {
-    Binding injectBinding =
-        rootmostBindingWithKind(k -> k.equals(INJECTION), duplicateBindings.values());
-    Binding explicitBinding =
-        rootmostBindingWithKind(k -> !k.equals(INJECTION), duplicateBindings.values());
+    Binding injectBinding = rootmostBindingWithKind(k -> k.equals(INJECTION), duplicateBindings);
+    Binding explicitBinding = rootmostBindingWithKind(k -> !k.equals(INJECTION), duplicateBindings);
     StringBuilder message =
         new StringBuilder()
             .append(explicitBinding.key())
@@ -226,47 +223,45 @@
         binding.componentPath());
   }
 
-  private String duplicateBindingMessage(
-      Binding oneBinding, ImmutableSet<Binding> duplicateBindings, BindingGraph graph) {
-    StringBuilder message =
-        new StringBuilder().append(oneBinding.key()).append(" is bound multiple times:");
-    formatDeclarations(message, 1, declarations(graph, duplicateBindings));
-    if (compilerOptions.experimentalDaggerErrorMessages()) {
-      message.append(String.format("\n%sin component: [%s]", INDENT, oneBinding.componentPath()));
-    }
-    return message.toString();
-  }
-
-  private String incompatibleBindingsMessage(
-      Binding oneBinding, ImmutableSet<Binding> duplicateBindings, BindingGraph graph) {
-    Key key = oneBinding.key();
-    ImmutableSet<dagger.spi.model.Binding> multibindings =
+  private void reportDuplicateBindings(
+      ImmutableCollection<Binding> duplicateBindings,
+      BindingGraph graph,
+      DiagnosticReporter diagnosticReporter) {
+    StringBuilder message = new StringBuilder();
+    Binding oneBinding = duplicateBindings.asList().get(0);
+    ImmutableSet<Binding> multibindings =
         duplicateBindings.stream()
             .filter(binding -> binding.kind().isMultibinding())
             .collect(toImmutableSet());
-    verify(
-        multibindings.size() == 1, "expected only one multibinding for %s: %s", key, multibindings);
-    StringBuilder message = new StringBuilder();
-    java.util.Formatter messageFormatter = new java.util.Formatter(message);
-    messageFormatter.format("%s has incompatible bindings or declarations:\n", key);
-    message.append(INDENT);
-    dagger.spi.model.Binding multibinding = getOnlyElement(multibindings);
-    messageFormatter.format("%s bindings and declarations:", multibindingTypeString(multibinding));
-    formatDeclarations(message, 2, declarations(graph, multibindings));
+    if (multibindings.isEmpty()) {
+      message.append(oneBinding.key()).append(" is bound multiple times:");
+      formatDeclarations(message, 2, declarations(graph, duplicateBindings));
+    } else {
+      Binding oneMultibinding = multibindings.asList().get(0);
+      message.append(oneMultibinding.key()).append(" has incompatible bindings or declarations:\n");
+      message
+          .append(INDENT)
+          .append(multibindingTypeString(oneMultibinding))
+          .append(" bindings and declarations:");
+      formatDeclarations(message, 2, declarations(graph, multibindings));
+      ImmutableSet<BindingDeclaration> uniqueBindingDeclarations =
+          duplicateBindings.stream()
+              .filter(binding -> !binding.kind().isMultibinding())
+              .flatMap(binding -> declarations(graph, binding).stream())
+              .filter(declaration -> !(declaration instanceof MultibindingDeclaration))
+              .collect(toImmutableSet());
+      if (!uniqueBindingDeclarations.isEmpty()) {
+        message.append('\n').append(INDENT).append("Unique bindings and declarations:");
+        formatDeclarations(message, 2, uniqueBindingDeclarations);
+      }
+    }
 
-    Set<dagger.spi.model.Binding> uniqueBindings =
-        Sets.filter(duplicateBindings, binding -> !binding.equals(multibinding));
-    message.append('\n').append(INDENT).append("Unique bindings and declarations:");
-    formatDeclarations(
-        message,
-        2,
-        Sets.filter(
-            declarations(graph, uniqueBindings),
-            declaration -> !(declaration instanceof MultibindingDeclaration)));
     if (compilerOptions.experimentalDaggerErrorMessages()) {
       message.append(String.format("\n%sin component: [%s]", INDENT, oneBinding.componentPath()));
+      diagnosticReporter.reportComponent(ERROR, graph.rootComponentNode(), message.toString());
+    } else {
+      diagnosticReporter.reportBinding(ERROR, oneBinding, message.toString());
     }
-    return message.toString();
   }
 
   private void formatDeclarations(
@@ -278,7 +273,7 @@
   }
 
   private ImmutableSet<BindingDeclaration> declarations(
-      BindingGraph graph, Set<dagger.spi.model.Binding> bindings) {
+      BindingGraph graph, ImmutableCollection<Binding> bindings) {
     return bindings.stream()
         .flatMap(binding -> declarations(graph, binding).stream())
         .distinct()
@@ -286,8 +281,7 @@
         .collect(toImmutableSet());
   }
 
-  private ImmutableSet<BindingDeclaration> declarations(
-      BindingGraph graph, dagger.spi.model.Binding binding) {
+  private ImmutableSet<BindingDeclaration> declarations(BindingGraph graph, Binding binding) {
     ImmutableSet.Builder<BindingDeclaration> declarations = ImmutableSet.builder();
     BindingNode bindingNode = (BindingNode) binding;
     bindingNode.associatedDeclarations().forEach(declarations::add);
@@ -301,7 +295,7 @@
     return declarations.build();
   }
 
-  private String multibindingTypeString(dagger.spi.model.Binding multibinding) {
+  private String multibindingTypeString(Binding multibinding) {
     switch (multibinding.kind()) {
       case MULTIBOUND_MAP:
         return "Map";
@@ -327,23 +321,45 @@
 
   /** The identifying information about a binding, excluding its {@link Binding#componentPath()}. */
   @AutoValue
-  abstract static class BindingElement {
+  abstract static class BindingWithoutComponent {
 
     abstract BindingKind bindingKind();
 
+    abstract Key bindingKey();
+
     abstract Optional<XElement> bindingElement();
 
     abstract Optional<XTypeElement> contributingModule();
 
-    static ImmutableSetMultimap<BindingElement, Binding> index(Set<Binding> bindings) {
-      return bindings.stream().collect(toImmutableSetMultimap(BindingElement::forBinding, b -> b));
+    static ImmutableSetMultimap<BindingWithoutComponent, Binding> index(Set<Binding> bindings) {
+      return bindings.stream()
+          .collect(toImmutableSetMultimap(BindingWithoutComponent::forBinding, b -> b));
     }
 
-    private static BindingElement forBinding(Binding binding) {
-      return new AutoValue_DuplicateBindingsValidator_BindingElement(
+    private static BindingWithoutComponent forBinding(Binding binding) {
+      return new AutoValue_DuplicateBindingsValidator_BindingWithoutComponent(
           binding.kind(),
+          binding.key(),
           binding.bindingElement().map(DaggerElement::xprocessing),
           binding.contributingModule().map(DaggerTypeElement::xprocessing));
     }
   }
+
+
+  /** The identifying information about a key with the given type equivalence. */
+  @AutoValue
+  abstract static class KeyWithTypeEquivalence {
+    abstract Optional<DaggerAnnotation> qualifier();
+
+    abstract Equivalence.Wrapper<XType> wrappedType();
+
+    abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier();
+
+    private static KeyWithTypeEquivalence forKey(Key key, Equivalence<XType> typeEquivalence) {
+      return new AutoValue_DuplicateBindingsValidator_KeyWithTypeEquivalence(
+          key.qualifier(),
+          typeEquivalence.wrap(key.type().xprocessing()),
+          key.multibindingContributionIdentifier());
+    }
+  }
 }
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java
index 3643870..c1108a8 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/IncompatiblyScopedBindingsValidator.java
@@ -18,9 +18,9 @@
 
 import static dagger.internal.codegen.base.Formatter.INDENT;
 import static dagger.internal.codegen.base.Scopes.getReadableSource;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
 import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
 import static dagger.internal.codegen.xprocessing.XElements.closestEnclosingTypeElement;
-import static dagger.spi.model.BindingKind.INJECTION;
 import static java.util.stream.Collectors.joining;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
@@ -29,12 +29,12 @@
 import dagger.internal.codegen.base.Scopes;
 import dagger.internal.codegen.binding.MethodSignatureFormatter;
 import dagger.internal.codegen.compileroption.CompilerOptions;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import dagger.internal.codegen.validation.DiagnosticMessageGenerator;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.DiagnosticReporter;
 import java.util.Optional;
 import java.util.Set;
 import javax.inject.Inject;
@@ -69,9 +69,9 @@
   public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
     DiagnosticMessageGenerator diagnosticMessageGenerator =
         diagnosticMessageGeneratorFactory.create(bindingGraph);
-    ImmutableSetMultimap.Builder<ComponentNode, dagger.spi.model.Binding> incompatibleBindings =
-        ImmutableSetMultimap.builder();
-    for (dagger.spi.model.Binding binding : bindingGraph.bindings()) {
+    ImmutableSetMultimap.Builder<ComponentNode, dagger.internal.codegen.model.Binding>
+        incompatibleBindings = ImmutableSetMultimap.builder();
+    for (dagger.internal.codegen.model.Binding binding : bindingGraph.bindings()) {
       binding
           .scope()
           .filter(scope -> !scope.isReusable())
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java
index ae153a0..2151295 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/InjectBindingValidator.java
@@ -16,15 +16,15 @@
 
 package dagger.internal.codegen.bindinggraphvalidation;
 
-import static dagger.spi.model.BindingKind.INJECTION;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
 
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import dagger.internal.codegen.validation.InjectValidator;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
 import dagger.internal.codegen.validation.ValidationReport;
 import dagger.internal.codegen.validation.ValidationReport.Item;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.DiagnosticReporter;
 import javax.inject.Inject;
 
 /** Validates bindings from {@code @Inject}-annotated constructors. */
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java
index 0698bef..87e8771 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/MapMultibindingValidator.java
@@ -21,7 +21,7 @@
 import static dagger.internal.codegen.base.Formatter.INDENT;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap;
-import static dagger.spi.model.BindingKind.MULTIBOUND_MAP;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_MAP;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
 import com.google.common.collect.ImmutableList;
@@ -37,11 +37,11 @@
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.binding.KeyFactory;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.DiagnosticReporter;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.DiagnosticReporter;
-import dagger.spi.model.Key;
 import java.util.Set;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java
index 724a980..774ee5d 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java
@@ -16,34 +16,49 @@
 
 package dagger.internal.codegen.bindinggraphvalidation;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static com.google.common.base.Verify.verify;
 import static com.google.common.collect.Iterables.getLast;
+import static com.google.common.collect.Iterables.getOnlyElement;
 import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey;
 import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey;
 import static dagger.internal.codegen.base.RequestKinds.canBeSatisfiedByProductionBinding;
 import static dagger.internal.codegen.binding.DependencyRequestFormatter.DOUBLE_INDENT;
 import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
 import static dagger.internal.codegen.xprocessing.XTypes.isWildcard;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
+import androidx.room.compiler.processing.XType;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.squareup.javapoet.TypeName;
+import com.squareup.javapoet.WildcardTypeName;
 import dagger.internal.codegen.binding.DependencyRequestFormatter;
 import dagger.internal.codegen.binding.InjectBindingRegistry;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.Edge;
+import dagger.internal.codegen.model.BindingGraph.MissingBinding;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DiagnosticReporter;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.validation.DiagnosticMessageGenerator;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.Edge;
-import dagger.spi.model.BindingGraph.MissingBinding;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.DiagnosticReporter;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.xprocessing.XTypes;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Optional;
 import javax.inject.Inject;
 
 /** Reports errors for missing bindings. */
@@ -97,11 +112,32 @@
       BindingGraph prunedGraph,
       BindingGraph fullGraph,
       DiagnosticReporter diagnosticReporter) {
+    ImmutableSet<Binding> wildcardAlternatives =
+        getSimilarTypeBindings(fullGraph, missingBinding.key());
+    String wildcardTypeErrorMessage = "";
+    if (!wildcardAlternatives.isEmpty()) {
+      wildcardTypeErrorMessage =
+          String.format(
+              "\nFound similar bindings:\n    %s",
+              String.join(
+                  "\n    ",
+                  wildcardAlternatives.stream()
+                      .map(
+                          binding -> binding.key().type() + " in [" + binding.componentPath() + "]")
+                      .collect(toImmutableSet())))
+                  + "\n(In Kotlin source, a type like 'Set<Foo>' may"
+                  + " be translated as 'Set<? extends Foo>'. To avoid this implicit"
+                  + " conversion you can add '@JvmSuppressWildcards' on the associated type"
+                  + " argument, e.g. 'Set<@JvmSuppressWildcards Foo>'.)";
+    }
+
     List<ComponentPath> alternativeComponents =
-        fullGraph.bindings(missingBinding.key()).stream()
-            .map(Binding::componentPath)
-            .distinct()
-            .collect(Collectors.toList());
+        wildcardAlternatives.isEmpty()
+            ? fullGraph.bindings(missingBinding.key()).stream()
+                .map(Binding::componentPath)
+                .distinct()
+                .collect(toImmutableList())
+            : ImmutableList.of();
     // Print component name for each binding along the dependency path if the missing binding
     // exists in a different component than expected
     if (alternativeComponents.isEmpty()) {
@@ -113,6 +149,8 @@
           ERROR,
           fullGraph.componentNode(missingBinding.componentPath()).get(),
           missingBindingErrorMessage(missingBinding, fullGraph)
+              + wildcardTypeErrorMessage
+              + "\n\nMissing binding usage:"
               + diagnosticMessageGeneratorFactory.create(prunedGraph).getMessage(missingBinding));
     } else {
       diagnosticReporter.reportComponent(
@@ -123,6 +161,45 @@
     }
   }
 
+  private static ImmutableSet<Binding> getSimilarTypeBindings(
+      BindingGraph graph, Key missingBindingKey) {
+    XType missingBindingType = missingBindingKey.type().xprocessing();
+    Optional<DaggerAnnotation> missingBindingQualifier = missingBindingKey.qualifier();
+    ImmutableList<TypeName> flatMissingBindingType = flattenBindingType(missingBindingType);
+    if (flatMissingBindingType.size() <= 1) {
+      return ImmutableSet.of();
+    }
+    return graph.bindings().stream()
+        .filter(
+            binding ->
+                binding.key().qualifier().equals(missingBindingQualifier)
+                    && isSimilarType(binding.key().type().xprocessing(), flatMissingBindingType))
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Unwraps a parameterized type to a list of TypeNames. e.g. {@code Map<Foo, List<Bar>>} to {@code
+   * [Map, Foo, List, Bar]}.
+   */
+  private static ImmutableList<TypeName> flattenBindingType(XType type) {
+    return ImmutableList.copyOf(new TypeDfsIterator(type));
+  }
+
+  private static boolean isSimilarType(XType type, List<TypeName> flatTypeNames) {
+    return Iterators.elementsEqual(flatTypeNames.iterator(), new TypeDfsIterator(type));
+  }
+
+  private static TypeName getBound(WildcardTypeName wildcardType) {
+    // Note: The javapoet API returns a list to be extensible, but there's currently no way to get
+    // multiple bounds, and it's not really clear what we should do if there were multiple bounds
+    // so we just assume there's only one for now. The javapoet API also guarantees that there will
+    // always be at least one upper bound -- in the absence of an explicit upper bound the Object
+    // type is used (e.g. Set<?> has an upper bound of Object).
+    return !wildcardType.lowerBounds.isEmpty()
+        ? getOnlyElement(wildcardType.lowerBounds)
+        : getOnlyElement(wildcardType.upperBounds);
+  }
+
   private String missingBindingErrorMessage(MissingBinding missingBinding, BindingGraph graph) {
     Key key = missingBinding.key();
     StringBuilder errorMessage = new StringBuilder();
@@ -167,7 +244,7 @@
         getComponentFromDependencyEdge(dependencyTrace.get(0), graph, false);
     boolean hasSameComponentName = false;
     for (ComponentPath component : alternativeComponentPath) {
-      message.append("\nA binding for ").append(missingBinding.key()).append(" exists in ");
+      message.append("\nNote: A binding for ").append(missingBinding.key()).append(" exists in ");
       String currentComponentName = component.currentComponent().className().canonicalName();
       if (currentComponentName.contentEquals(missingComponentName)) {
         hasSameComponentName = true;
@@ -214,11 +291,11 @@
     if (source instanceof ComponentNode) {
       return canBeSatisfiedByProductionBinding(edge.dependencyRequest().kind());
     }
-    if (source instanceof dagger.spi.model.Binding) {
-      return ((dagger.spi.model.Binding) source).isProduction();
+    if (source instanceof Binding) {
+      return ((Binding) source).isProduction();
     }
     throw new IllegalArgumentException(
-        "expected a dagger.spi.model.Binding or ComponentNode: " + source);
+        "expected a dagger.internal.codegen.model.Binding or ComponentNode: " + source);
   }
 
   private boolean typeHasInjectionSites(Key key) {
@@ -239,4 +316,52 @@
   private Node source(Edge edge, BindingGraph graph) {
     return graph.network().incidentNodes(edge).source();
   }
+
+  /**
+   * An iterator over a list of TypeNames produced by flattening a parameterized type. e.g. {@code
+   * Map<Foo, List<Bar>>} to {@code [Map, Foo, List, Bar]}.
+   *
+   * <p>The iterator returns the bound when encounters a wildcard type.
+   */
+  private static class TypeDfsIterator implements Iterator<TypeName> {
+    final Deque<XType> stack = new ArrayDeque<>();
+
+    TypeDfsIterator(XType root) {
+      stack.push(root);
+    }
+
+    @Override
+    public boolean hasNext() {
+      return !stack.isEmpty();
+    }
+
+    @Override
+    public TypeName next() {
+      XType next = stack.pop();
+      if (isDeclared(next)) {
+        if (XTypes.isRawParameterizedType(next)) {
+          XType obj = getProcessingEnv(next).requireType(TypeName.OBJECT);
+          for (int i = 0; i < next.getTypeElement().getType().getTypeArguments().size(); i++) {
+            stack.push(obj);
+          }
+        } else {
+          for (XType arg : Lists.reverse(next.getTypeArguments())) {
+            stack.push(arg);
+          }
+        }
+      }
+      return getBaseTypeName(next);
+    }
+
+    private static TypeName getBaseTypeName(XType type) {
+      if (isDeclared(type)) {
+        return type.getRawType().getTypeName();
+      }
+      TypeName typeName = type.getTypeName();
+      if (typeName instanceof WildcardTypeName) {
+        return getBound((WildcardTypeName) typeName);
+      }
+      return typeName;
+    }
+  }
 }
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java
index bbc026e..e586cc6 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidator.java
@@ -24,11 +24,11 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import dagger.internal.codegen.compileroption.CompilerOptions;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.DiagnosticReporter;
 import javax.inject.Inject;
 
 /**
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java
index 7ad0e9f..d8305df 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/ProvisionDependencyOnProducerBindingValidator.java
@@ -22,12 +22,12 @@
 import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.DiagnosticReporter;
 import java.util.stream.Stream;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java
index 6b7de40..f767da8 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/SetMultibindingValidator.java
@@ -16,8 +16,8 @@
 
 package dagger.internal.codegen.bindinggraphvalidation;
 
-import static dagger.spi.model.BindingKind.DELEGATE;
-import static dagger.spi.model.BindingKind.MULTIBOUND_SET;
+import static dagger.internal.codegen.model.BindingKind.DELEGATE;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_SET;
 import static javax.tools.Diagnostic.Kind.ERROR;
 
 import com.google.common.base.Joiner;
@@ -25,11 +25,11 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.DiagnosticReporter;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.DiagnosticReporter;
-import dagger.spi.model.Key;
 import javax.inject.Inject;
 
 /** Validates that there are not multiple set binding contributions to the same binding. */
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java
index 1ecff19..0196d03 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/SubcomponentFactoryMethodValidator.java
@@ -31,11 +31,11 @@
 import com.google.common.collect.Sets.SetView;
 import dagger.internal.codegen.base.Util;
 import dagger.internal.codegen.binding.ComponentNodeImpl;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.DiagnosticReporter;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
diff --git a/java/dagger/internal/codegen/compileroption/CompilerOptions.java b/java/dagger/internal/codegen/compileroption/CompilerOptions.java
index 18e133e..42599e0 100644
--- a/java/dagger/internal/codegen/compileroption/CompilerOptions.java
+++ b/java/dagger/internal/codegen/compileroption/CompilerOptions.java
@@ -150,4 +150,13 @@
    * eventually become the default and enforced.
    */
   public abstract boolean strictMultibindingValidation();
+
+  /**
+   * Returns {@code true} if we should ignore the variance in provision key types.
+   *
+   * <p>By enabling this flag, Dagger will no longer allow provisioning multiple keys that only
+   * differ by the key type's variance (a.k.a. wildcards). As an example, the provisioning a binding
+   * for {@code Foo<? extends Bar>} and {@code Foo<Bar>} would result in a duplicate binding error.
+   */
+  public abstract boolean ignoreProvisionKeyWildcards();
 }
diff --git a/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java b/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java
index a516b1a..356d55b 100644
--- a/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java
+++ b/java/dagger/internal/codegen/compileroption/ProcessingEnvironmentCompilerOptions.java
@@ -31,6 +31,7 @@
 import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.FORMAT_GENERATED_SOURCE;
 import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.GENERATED_CLASS_EXTENDS_COMPONENT;
 import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.IGNORE_PRIVATE_AND_STATIC_INJECTION_FOR_COMPONENT;
+import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.IGNORE_PROVISION_KEY_WILDCARDS;
 import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.INCLUDE_STACKTRACE_WITH_DEFERRED_ERROR_MESSAGES;
 import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.PLUGINS_VISIT_FULL_BINDING_GRAPHS;
 import static dagger.internal.codegen.compileroption.ProcessingEnvironmentCompilerOptions.Feature.STRICT_MULTIBINDING_VALIDATION;
@@ -213,6 +214,11 @@
   }
 
   @Override
+  public boolean ignoreProvisionKeyWildcards() {
+    return isEnabled(IGNORE_PROVISION_KEY_WILDCARDS);
+  }
+
+  @Override
   public boolean strictMultibindingValidation() {
     return isEnabled(STRICT_MULTIBINDING_VALIDATION);
   }
@@ -353,6 +359,8 @@
 
     GENERATED_CLASS_EXTENDS_COMPONENT,
 
+    IGNORE_PROVISION_KEY_WILDCARDS,
+
     VALIDATE_TRANSITIVE_COMPONENT_DEPENDENCIES(ENABLED)
     ;
 
diff --git a/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java b/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java
index 4d130b6..6e77835 100644
--- a/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java
+++ b/java/dagger/internal/codegen/javac/JavacPluginCompilerOptions.java
@@ -140,4 +140,9 @@
   public boolean generatedClassExtendsComponent() {
     return false;
   }
+
+  @Override
+  public boolean ignoreProvisionKeyWildcards() {
+    return false;
+  }
 }
diff --git a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java
index 0056a72..b27ae91 100644
--- a/java/dagger/internal/codegen/kotlin/KotlinMetadata.java
+++ b/java/dagger/internal/codegen/kotlin/KotlinMetadata.java
@@ -17,7 +17,6 @@
 package dagger.internal.codegen.kotlin;
 
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
-import static dagger.internal.codegen.xprocessing.XElements.getFieldDescriptor;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 
 import androidx.room.compiler.processing.XAnnotation;
@@ -32,7 +31,6 @@
 import com.google.common.collect.ImmutableSet;
 import dagger.internal.codegen.extension.DaggerCollectors;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.internal.codegen.xprocessing.XElements;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -74,7 +72,7 @@
   @Memoized
   ImmutableMap<String, XMethodElement> methodDescriptors() {
     return typeElement().getDeclaredMethods().stream()
-        .collect(toImmutableMap(XElements::getMethodDescriptor, Function.identity()));
+        .collect(toImmutableMap(XMethodElement::getJvmDescriptor, Function.identity()));
   }
 
   /** Gets the synthetic method for annotations of a given field element. */
@@ -133,7 +131,7 @@
   }
 
   private PropertyMetadata findProperty(XFieldElement field) {
-    String fieldDescriptor = getFieldDescriptor(field);
+    String fieldDescriptor = field.getJvmDescriptor();
     if (classMetadata().propertiesByFieldSignature().containsKey(fieldDescriptor)) {
       return classMetadata().propertiesByFieldSignature().get(fieldDescriptor);
     } else {
diff --git a/java/dagger/internal/codegen/kythe/BUILD b/java/dagger/internal/codegen/kythe/BUILD
index b1025f3..9b2e63b 100644
--- a/java/dagger/internal/codegen/kythe/BUILD
+++ b/java/dagger/internal/codegen/kythe/BUILD
@@ -32,9 +32,9 @@
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/validation",
         "//java/dagger/internal/codegen/xprocessing",
-        "//java/dagger/spi",
         "//third_party/java/auto:service",
         "//third_party/java/error_prone:annotations",
         "//third_party/java/guava/collect",
diff --git a/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java b/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java
index 52ff652..3280b4f 100644
--- a/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java
+++ b/java/dagger/internal/codegen/kythe/DaggerKythePlugin.java
@@ -44,12 +44,12 @@
 import dagger.internal.codegen.binding.ModuleDescriptor;
 import dagger.internal.codegen.javac.JavacPluginModule;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.Edge;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.DependencyRequest;
 import dagger.internal.codegen.validation.InjectBindingRegistryModule;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.Edge;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.DependencyRequest;
 import java.util.Optional;
 import java.util.logging.Logger;
 import javax.inject.Inject;
diff --git a/java/dagger/internal/codegen/model/BUILD b/java/dagger/internal/codegen/model/BUILD
new file mode 100644
index 0000000..e6c944b
--- /dev/null
+++ b/java/dagger/internal/codegen/model/BUILD
@@ -0,0 +1,41 @@
+# Copyright (C) 2023 The Dagger Authors.
+#
+# 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.
+
+# Description:
+#   Dagger's core APIs exposed for plugins
+
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(default_visibility = ["//:src"])
+
+java_library(
+    name = "model",
+    srcs = glob(["*.java"]),
+    plugins = ["//java/dagger/internal/codegen/bootstrap"],
+    deps = [
+        "//java/dagger:core",
+        "//java/dagger/internal/codegen/extension",
+        "//java/dagger/internal/codegen/xprocessing",
+        "//java/dagger/producers",
+        "//java/dagger/spi",
+        "//third_party/java/auto:common",
+        "//third_party/java/auto:value",
+        "//third_party/java/guava/base",
+        "//third_party/java/guava/collect",
+        "//third_party/java/guava/graph",
+        "//third_party/java/javapoet",
+        "//third_party/java/jsr330_inject",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
+    ],
+)
diff --git a/java/dagger/internal/codegen/model/Binding.java b/java/dagger/internal/codegen/model/Binding.java
new file mode 100644
index 0000000..f5bf66c
--- /dev/null
+++ b/java/dagger/internal/codegen/model/Binding.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import com.google.common.collect.ImmutableSet;
+import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
+import java.util.Optional;
+
+/**
+ * The association between a {@link Key} and the way in which instances of the key are provided.
+ * Includes any {@linkplain DependencyRequest dependencies} that are needed in order to provide the
+ * instances.
+ *
+ * <p>If a binding is owned by more than one component, there is one {@code Binding} for every
+ * owning component.
+ */
+public interface Binding extends MaybeBinding {
+  @Override
+  ComponentPath componentPath();
+
+  /** @deprecated This always returns {@code Optional.of(this)}. */
+  @Override
+  @Deprecated
+  default Optional<Binding> binding() {
+    return Optional.of(this);
+  }
+  /**
+   * The dependencies of this binding. The order of the dependencies corresponds to the order in
+   * which they will be injected when the binding is requested.
+   */
+  ImmutableSet<DependencyRequest> dependencies();
+
+  /**
+   * The {@link DaggerElement} that declares this binding. Absent for
+   * {@linkplain BindingKind binding kinds} that are not always declared by exactly one element.
+   *
+   * <p>For example, consider {@link BindingKind#MULTIBOUND_SET}. A component with many
+   * {@code @IntoSet} bindings for the same key will have a synthetic binding that depends on all
+   * contributions, but with no identifiying binding element. A {@code @Multibinds} method will also
+   * contribute a synthetic binding, but since multiple {@code @Multibinds} methods can coexist in
+   * the same component (and contribute to one single binding), it has no binding element.
+   */
+  Optional<DaggerElement> bindingElement();
+
+  /**
+   * The {@link DaggerTypeElement} of the module which contributes this binding. Absent for bindings
+   * that have no {@link #bindingElement() binding element}.
+   */
+  Optional<DaggerTypeElement> contributingModule();
+
+  /**
+   * Returns {@code true} if using this binding requires an instance of the {@link
+   * #contributingModule()}.
+   */
+  boolean requiresModuleInstance();
+
+  /** The scope of this binding if it has one. */
+  Optional<Scope> scope();
+
+  /**
+   * Returns {@code true} if this binding may provide {@code null} instead of an instance of {@link
+   * #key()}. Nullable bindings cannot be requested from {@linkplain DependencyRequest#isNullable()
+   * non-nullable dependency requests}.
+   */
+  boolean isNullable();
+
+  /** Returns {@code true} if this is a production binding, e.g. an {@code @Produces} method. */
+  boolean isProduction();
+
+  /** The kind of binding this instance represents. */
+  BindingKind kind();
+
+}
diff --git a/java/dagger/internal/codegen/model/BindingGraph.java b/java/dagger/internal/codegen/model/BindingGraph.java
new file mode 100644
index 0000000..9d8b39e
--- /dev/null
+++ b/java/dagger/internal/codegen/model/BindingGraph.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static com.google.common.collect.Sets.intersection;
+import static com.google.common.graph.Graphs.inducedSubgraph;
+import static com.google.common.graph.Graphs.reachableNodes;
+import static com.google.common.graph.Graphs.transpose;
+import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSetMultimap;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.graph.EndpointPair;
+import com.google.common.graph.ImmutableNetwork;
+import com.google.common.graph.MutableNetwork;
+import com.google.common.graph.Network;
+import com.google.common.graph.NetworkBuilder;
+import dagger.Module;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * A graph of bindings, dependency requests, and components.
+ *
+ * <p>A {@link BindingGraph} represents one of the following:
+ *
+ * <ul>
+ *   <li>an entire component hierarchy rooted at a {@link dagger.Component} or {@link
+ *       dagger.producers.ProductionComponent}
+ *   <li>a partial component hierarchy rooted at a {@link dagger.Subcomponent} or {@link
+ *       dagger.producers.ProductionSubcomponent} (only when the value of {@code
+ *       -Adagger.fullBindingGraphValidation} is not {@code NONE})
+ *   <li>the bindings installed by a {@link Module} or {@link dagger.producers.ProducerModule},
+ *       including all subcomponents generated by {@link Module#subcomponents()} ()} and {@link
+ *       dagger.producers.ProducerModule#subcomponents()} ()}
+ * </ul>
+ *
+ * In the case of a {@link BindingGraph} representing a module, the root {@link ComponentNode} will
+ * actually represent the module type. The graph will also be a {@linkplain #isFullBindingGraph()
+ * full binding graph}, which means it will contain all bindings in all modules, as well as nodes
+ * for their dependencies. Otherwise it will contain only bindings that are reachable from at least
+ * one {@linkplain #entryPointEdges() entry point}.
+ *
+ * <h3>Nodes</h3>
+ *
+ * <p>There is a <b>{@link Binding}</b> for each owned binding in the graph. If a binding is owned
+ * by more than one component, there is one binding object for that binding for every owning
+ * component.
+ *
+ * <p>There is a <b>{@linkplain ComponentNode component node}</b> (without a binding) for each
+ * component in the graph.
+ *
+ * <h3>Edges</h3>
+ *
+ * <p>There is a <b>{@linkplain DependencyEdge dependency edge}</b> for each dependency request in
+ * the graph. Its target node is the binding for the binding that satisfies the request. For entry
+ * point dependency requests, the source node is the component node for the component for which it
+ * is an entry point. For other dependency requests, the source node is the binding for the binding
+ * that contains the request.
+ *
+ * <p>There is a <b>subcomponent edge</b> for each parent-child component relationship in the graph.
+ * The target node is the component node for the child component. For subcomponents defined by a
+ * {@linkplain SubcomponentCreatorBindingEdge subcomponent creator binding} (either a method on the
+ * component or a set of {@code @Module.subcomponents} annotation values), the source node is the
+ * binding for the {@code @Subcomponent.Builder} type. For subcomponents defined by {@linkplain
+ * ChildFactoryMethodEdge subcomponent factory methods}, the source node is the component node for
+ * the parent.
+ *
+ * <p><b>Note that this API is experimental and will change.</b>
+ */
+public abstract class BindingGraph {
+  /** Returns the graph in its {@link Network} representation. */
+  public abstract ImmutableNetwork<Node, Edge> network();
+
+  @Override
+  public String toString() {
+    return network().toString();
+  }
+
+  /**
+   * Returns {@code true} if this graph was constructed from a module for full binding graph
+   * validation.
+   *
+   * @deprecated use {@link #isFullBindingGraph()} to tell if this is a full binding graph, or
+   *     {@link ComponentNode#isRealComponent() rootComponentNode().isRealComponent()} to tell if
+   *     the root component node is really a component or derived from a module. Dagger can generate
+   *     full binding graphs for components and subcomponents as well as modules.
+   */
+  @Deprecated
+  public boolean isModuleBindingGraph() {
+    return !rootComponentNode().isRealComponent();
+  }
+
+  /**
+   * Returns {@code true} if this is a full binding graph, which contains all bindings installed in
+   * the component, or {@code false} if it is a reachable binding graph, which contains only
+   * bindings that are reachable from at least one {@linkplain #entryPointEdges() entry point}.
+   *
+   * @see <a href="https://dagger.dev/compiler-options#full-binding-graph-validation">Full binding
+   *     graph validation</a>
+   */
+  public abstract boolean isFullBindingGraph();
+
+  /**
+   * Returns {@code true} if the {@link #rootComponentNode()} is a subcomponent. This occurs in
+   * when {@code -Adagger.fullBindingGraphValidation} is used in a compilation with a subcomponent.
+   *
+   * @deprecated use {@link ComponentNode#isSubcomponent() rootComponentNode().isSubcomponent()}
+   *     instead
+   */
+  @Deprecated
+  public boolean isPartialBindingGraph() {
+    return rootComponentNode().isSubcomponent();
+  }
+
+  /** Returns the bindings. */
+  public ImmutableSet<Binding> bindings() {
+    return nodes(Binding.class);
+  }
+
+  /** Returns the bindings for a key. */
+  public ImmutableSet<Binding> bindings(Key key) {
+    return nodes(Binding.class).stream()
+        .filter(binding -> binding.key().equals(key))
+        .collect(toImmutableSet());
+  }
+
+  /** Returns the nodes that represent missing bindings. */
+  public ImmutableSet<MissingBinding> missingBindings() {
+    return nodes(MissingBinding.class);
+  }
+
+  /** Returns the component nodes. */
+  public ImmutableSet<ComponentNode> componentNodes() {
+    return nodes(ComponentNode.class);
+  }
+
+  /** Returns the component node for a component. */
+  public Optional<ComponentNode> componentNode(ComponentPath component) {
+    return componentNodes().stream()
+        .filter(node -> node.componentPath().equals(component))
+        .findFirst();
+  }
+
+  /** Returns the component nodes for a component. */
+  public ImmutableSet<ComponentNode> componentNodes(DaggerTypeElement component) {
+    return componentNodes().stream()
+        .filter(node -> node.componentPath().currentComponent().equals(component))
+        .collect(toImmutableSet());
+  }
+
+  /** Returns the component node for the root component. */
+  public ComponentNode rootComponentNode() {
+    return componentNodes().stream()
+        .filter(node -> node.componentPath().atRoot())
+        .findFirst()
+        .get();
+  }
+
+  /** Returns the dependency edges. */
+  public ImmutableSet<DependencyEdge> dependencyEdges() {
+    return dependencyEdgeStream().collect(toImmutableSet());
+  }
+
+  /**
+   * Returns the dependency edges for the dependencies of a binding. For valid graphs, each {@link
+   * DependencyRequest} will map to a single {@link DependencyEdge}. When conflicting bindings exist
+   * for a key, the multimap will have several edges for that {@link DependencyRequest}. Graphs that
+   * have no binding for a key will have an edge whose {@linkplain EndpointPair#target() target
+   * node} is a {@link MissingBinding}.
+   */
+  public ImmutableSetMultimap<DependencyRequest, DependencyEdge> dependencyEdges(
+      Binding binding) {
+    return dependencyEdgeStream(binding)
+        .collect(toImmutableSetMultimap(DependencyEdge::dependencyRequest, edge -> edge));
+  }
+
+  /** Returns the dependency edges for a dependency request. */
+  public ImmutableSet<DependencyEdge> dependencyEdges(DependencyRequest dependencyRequest) {
+    return dependencyEdgeStream()
+        .filter(edge -> edge.dependencyRequest().equals(dependencyRequest))
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Returns the dependency edges for the entry points of a given {@code component}. Each edge's
+   * source node is that component's component node.
+   */
+  public ImmutableSet<DependencyEdge> entryPointEdges(ComponentPath component) {
+    return dependencyEdgeStream(componentNode(component).get()).collect(toImmutableSet());
+  }
+
+  private Stream<DependencyEdge> dependencyEdgeStream(Node node) {
+    return network().outEdges(node).stream().flatMap(instancesOf(DependencyEdge.class));
+  }
+
+  /**
+   * Returns the dependency edges for all entry points for all components and subcomponents. Each
+   * edge's source node is a component node.
+   */
+  public ImmutableSet<DependencyEdge> entryPointEdges() {
+    return entryPointEdgeStream().collect(toImmutableSet());
+  }
+
+  /** Returns the binding or missing binding nodes that directly satisfy entry points. */
+  public ImmutableSet<MaybeBinding> entryPointBindings() {
+    return entryPointEdgeStream()
+        .map(edge -> (MaybeBinding) network().incidentNodes(edge).target())
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Returns the edges for entry points that transitively depend on a binding or missing binding for
+   * a key.
+   */
+  public ImmutableSet<DependencyEdge> entryPointEdgesDependingOnBinding(
+      MaybeBinding binding) {
+    ImmutableNetwork<Node, DependencyEdge> dependencyGraph = dependencyGraph();
+    Network<Node, DependencyEdge> subgraphDependingOnBinding =
+        inducedSubgraph(
+            dependencyGraph, reachableNodes(transpose(dependencyGraph).asGraph(), binding));
+    return intersection(entryPointEdges(), subgraphDependingOnBinding.edges()).immutableCopy();
+  }
+
+  /** Returns the bindings that directly request a given binding as a dependency. */
+  public ImmutableSet<Binding> requestingBindings(MaybeBinding binding) {
+    return network().predecessors(binding).stream()
+        .flatMap(instancesOf(Binding.class))
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Returns the bindings that a given binding directly requests as a dependency. Does not include
+   * any {@link MissingBinding}s.
+   *
+   * @see #requestedMaybeMissingBindings(Binding)
+   */
+  public ImmutableSet<Binding> requestedBindings(Binding binding) {
+    return network().successors(binding).stream()
+        .flatMap(instancesOf(Binding.class))
+        .collect(toImmutableSet());
+  }
+
+  /**
+   * Returns the bindings or missing bindings that a given binding directly requests as a
+   * dependency.
+   *
+   * @see #requestedBindings(Binding)
+   */
+  public ImmutableSet<MaybeBinding> requestedMaybeMissingBindings(Binding binding) {
+    return network().successors(binding).stream()
+        .flatMap(instancesOf(MaybeBinding.class))
+        .collect(toImmutableSet());
+  }
+
+  /** Returns a subnetwork that contains all nodes but only {@link DependencyEdge}s. */
+  // TODO(dpb): Make public. Cache.
+  private ImmutableNetwork<Node, DependencyEdge> dependencyGraph() {
+    MutableNetwork<Node, DependencyEdge> dependencyGraph =
+        NetworkBuilder.from(network())
+            .expectedNodeCount(network().nodes().size())
+            .expectedEdgeCount((int) dependencyEdgeStream().count())
+            .build();
+    network().nodes().forEach(dependencyGraph::addNode); // include disconnected nodes
+    dependencyEdgeStream()
+        .forEach(
+            edge -> {
+              EndpointPair<Node> endpoints = network().incidentNodes(edge);
+              dependencyGraph.addEdge(endpoints.source(), endpoints.target(), edge);
+            });
+    return ImmutableNetwork.copyOf(dependencyGraph);
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  private <N extends Node> ImmutableSet<N> nodes(Class<N> clazz) {
+    return (ImmutableSet) nodesByClass().get(clazz);
+  }
+
+  private static final ImmutableSet<Class<? extends Node>> NODE_TYPES =
+      ImmutableSet.of(Binding.class, MissingBinding.class, ComponentNode.class);
+
+  protected ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() {
+    return network().nodes().stream()
+        .collect(
+            toImmutableSetMultimap(
+                node ->
+                    NODE_TYPES.stream().filter(clazz -> clazz.isInstance(node)).findFirst().get(),
+                node -> node));
+  }
+
+  private Stream<DependencyEdge> dependencyEdgeStream() {
+    return network().edges().stream().flatMap(instancesOf(DependencyEdge.class));
+  }
+
+  private Stream<DependencyEdge> entryPointEdgeStream() {
+    return dependencyEdgeStream().filter(DependencyEdge::isEntryPoint);
+  }
+
+  /**
+   * An edge in the binding graph. Either a {@link DependencyEdge}, a {@link
+   * ChildFactoryMethodEdge}, or a {@link SubcomponentCreatorBindingEdge}.
+   */
+  public interface Edge {}
+
+  /**
+   * An edge that represents a dependency on a binding.
+   *
+   * <p>Because one {@link DependencyRequest} may represent a dependency from two bindings (e.g., a
+   * dependency of {@code Foo<String>} and {@code Foo<Number>} may have the same key and request
+   * element), this class does not override {@link #equals(Object)} to use value semantics.
+   *
+   * <p>For entry points, the source node is the {@link ComponentNode} that contains the entry
+   * point. Otherwise the source node is a {@link Binding}.
+   *
+   * <p>For dependencies on missing bindings, the target node is a {@link MissingBinding}. Otherwise
+   * the target node is a {@link Binding}.
+   */
+  public interface DependencyEdge extends Edge {
+    /** The dependency request. */
+    DependencyRequest dependencyRequest();
+
+    /** Returns {@code true} if this edge represents an entry point. */
+    boolean isEntryPoint();
+  }
+
+  /**
+   * An edge that represents a subcomponent factory method linking a parent component to a child
+   * subcomponent.
+   */
+  public interface ChildFactoryMethodEdge extends Edge {
+    /** The subcomponent factory method element. */
+    DaggerExecutableElement factoryMethod();
+  }
+
+  /**
+   * An edge that represents the link between a parent component and a child subcomponent implied by
+   * a subcomponent creator ({@linkplain dagger.Subcomponent.Builder builder} or {@linkplain
+   * dagger.Subcomponent.Factory factory}) binding.
+   *
+   * <p>The {@linkplain com.google.common.graph.EndpointPair#source() source node} of this edge is a
+   * {@link Binding} for the subcomponent creator {@link Key} and the {@linkplain
+   * com.google.common.graph.EndpointPair#target() target node} is a {@link ComponentNode} for the
+   * child subcomponent.
+   */
+  public interface SubcomponentCreatorBindingEdge extends Edge {
+    /**
+     * The modules that {@linkplain Module#subcomponents() declare the subcomponent} that generated
+     * this edge. Empty if the parent component has a subcomponent creator method and there are no
+     * declaring modules.
+     */
+    ImmutableSet<DaggerTypeElement> declaringModules();
+  }
+
+  /** A node in the binding graph. Either a {@link Binding} or a {@link ComponentNode}. */
+  // TODO(dpb): Make all the node/edge types top-level.
+  public interface Node {
+    /** The component this node belongs to. */
+    ComponentPath componentPath();
+  }
+
+  /** A node in the binding graph that is either a {@link Binding} or a {@link MissingBinding}. */
+  public interface MaybeBinding extends Node {
+
+    /** The component that owns the binding, or in which the binding is missing. */
+    @Override
+    ComponentPath componentPath();
+
+    /** The key of the binding, or for which there is no binding. */
+    Key key();
+
+    /** The binding, or empty if missing. */
+    Optional<Binding> binding();
+  }
+
+  /** A node in the binding graph that represents a missing binding for a key in a component. */
+  public abstract static class MissingBinding implements MaybeBinding {
+    /** The component in which the binding is missing. */
+    @Override
+    public abstract ComponentPath componentPath();
+
+    /** The key for which there is no binding. */
+    @Override
+    public abstract Key key();
+
+    /** @deprecated This always returns {@code Optional.empty()}. */
+    @Override
+    @Deprecated
+    public Optional<Binding> binding() {
+      return Optional.empty();
+    }
+
+    @Override
+    public String toString() {
+      return String.format("missing binding for %s in %s", key(), componentPath());
+    }
+  }
+
+  /**
+   * A <b>component node</b> in the graph. Every entry point {@linkplain DependencyEdge dependency
+   * edge}'s source node is a component node for the component containing the entry point.
+   */
+  public interface ComponentNode extends Node {
+
+    /** The component represented by this node. */
+    @Override
+    ComponentPath componentPath();
+
+    /**
+     * Returns {@code true} if the component is a {@code @Subcomponent} or
+     * {@code @ProductionSubcomponent}.
+     */
+    boolean isSubcomponent();
+
+    /**
+     * Returns {@code true} if the component is a real component, or {@code false} if it is a
+     * fictional component based on a module.
+     */
+    boolean isRealComponent();
+
+    /** The entry points on this component. */
+    ImmutableSet<DependencyRequest> entryPoints();
+
+    /** The scopes declared on this component. */
+    ImmutableSet<Scope> scopes();
+  }
+}
diff --git a/java/dagger/internal/codegen/model/BindingGraphPlugin.java b/java/dagger/internal/codegen/model/BindingGraphPlugin.java
new file mode 100644
index 0000000..5cef92e
--- /dev/null
+++ b/java/dagger/internal/codegen/model/BindingGraphPlugin.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+// TODO(bcorso): Move this into dagger/spi?
+/**
+ * A pluggable visitor for {@link BindingGraph}.
+ *
+ * <p>Note: This is still experimental and will change.
+ */
+public interface BindingGraphPlugin {
+  /**
+   * Called once for each valid root binding graph encountered by the Dagger processor. May report
+   * diagnostics using {@code diagnosticReporter}.
+   */
+  void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter);
+
+  /**
+   * Initializes this plugin with a {@link DaggerProcessingEnv}.
+   *
+   * <p>This will be called once per instance of this plugin, before any graph is
+   * {@linkplain #visitGraph(BindingGraph, DiagnosticReporter) visited}.
+   */
+  default void init(DaggerProcessingEnv processingEnv, Map<String, String> options) {}
+
+  /**
+   * Returns the annotation-processing options that this plugin uses to configure behavior.
+   *
+   * @see javax.annotation.processing.Processor#getSupportedOptions()
+   */
+  default Set<String> supportedOptions() {
+    return Collections.emptySet();
+  }
+
+  /**
+   * A distinguishing name of the plugin that will be used in diagnostics printed to the messager.
+   * By default, the {@linkplain Class#getCanonicalName() fully qualified name} of the plugin is
+   * used.
+   */
+  default String pluginName() {
+    return getClass().getCanonicalName();
+  }
+
+  /**
+   * Perform any extra work after the plugin finished all its visiting. This will be called once per
+   * instance of this plugin, after all graphs were {@linkplain #visitGraph(BindingGraph,
+   * DiagnosticReporter) visited}
+   */
+  default void onPluginEnd() {}
+}
diff --git a/java/dagger/internal/codegen/model/BindingKind.java b/java/dagger/internal/codegen/model/BindingKind.java
new file mode 100644
index 0000000..95dc959
--- /dev/null
+++ b/java/dagger/internal/codegen/model/BindingKind.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+/** Represents the different kinds of {@link Binding}s that can exist in a binding graph. */
+public enum BindingKind {
+  /** A binding for an {@link javax.inject.Inject}-annotated constructor. */
+  INJECTION,
+
+  /** A binding for a {@link dagger.Provides}-annotated method. */
+  PROVISION,
+
+  /**
+   * A binding for an {@link javax.inject.Inject}-annotated constructor that contains at least one
+   * {@link dagger.assisted.Assisted}-annotated parameter.
+   */
+  ASSISTED_INJECTION,
+
+  /** A binding for an {@link dagger.assisted.AssistedFactory}-annotated type. */
+  ASSISTED_FACTORY,
+
+  /**
+   * An implicit binding for a {@link dagger.Component}- or {@link
+   * dagger.producers.ProductionComponent}-annotated type.
+   */
+  COMPONENT,
+
+  /**
+   * A binding for a provision method on a component's {@linkplain dagger.Component#dependencies()
+   * dependency}.
+   */
+  COMPONENT_PROVISION,
+
+  /**
+   * A binding for an instance of a component's {@linkplain dagger.Component#dependencies()
+   * dependency}.
+   */
+  COMPONENT_DEPENDENCY,
+
+  /** A binding for a {@link dagger.MembersInjector} of a type. */
+  MEMBERS_INJECTOR,
+
+  /**
+   * A binding for a subcomponent creator (a {@linkplain dagger.Subcomponent.Builder builder} or
+   * {@linkplain dagger.Subcomponent.Factory factory}).
+   *
+   * @since 2.22 (previously named {@code SUBCOMPONENT_BUILDER})
+   */
+  SUBCOMPONENT_CREATOR,
+
+  /** A binding for a {@link dagger.BindsInstance}-annotated builder method. */
+  BOUND_INSTANCE,
+
+  /** A binding for a {@link dagger.producers.Produces}-annotated method. */
+  PRODUCTION,
+
+  /**
+   * A binding for a production method on a production component's {@linkplain
+   * dagger.producers.ProductionComponent#dependencies()} dependency} that returns a {@link
+   * com.google.common.util.concurrent.ListenableFuture} or {@link
+   * com.google.common.util.concurrent.FluentFuture}. Methods on production component dependencies
+   * that don't return a future are considered {@linkplain #COMPONENT_PROVISION component provision
+   * bindings}.
+   */
+  COMPONENT_PRODUCTION,
+
+  /**
+   * A synthetic binding for a multibound set that depends on individual multibinding {@link
+   * #PROVISION} or {@link #PRODUCTION} contributions.
+   */
+  MULTIBOUND_SET,
+
+  /**
+   * A synthetic binding for a multibound map that depends on the individual multibinding {@link
+   * #PROVISION} or {@link #PRODUCTION} contributions.
+   */
+  MULTIBOUND_MAP,
+
+  /**
+   * A synthetic binding for {@code Optional} of a type or a {@link javax.inject.Provider}, {@link
+   * dagger.Lazy}, or {@code Provider} of {@code Lazy} of a type. Generated by a {@link
+   * dagger.BindsOptionalOf} declaration.
+   */
+  OPTIONAL,
+
+  /**
+   * A binding for {@link dagger.Binds}-annotated method that that delegates from requests for one
+   * key to another.
+   */
+  // TODO(dpb,ronshapiro): This name is confusing and could use work. Not all usages of @Binds
+  // bindings are simple delegations and we should have a name that better reflects that
+  DELEGATE,
+
+  /** A binding for a members injection method on a component. */
+  MEMBERS_INJECTION,
+  ;
+
+  /**
+   * Returns {@code true} if this is a kind of multibinding (not a contribution to a multibinding,
+   * but the multibinding itself).
+   */
+  public boolean isMultibinding() {
+    switch (this) {
+      case MULTIBOUND_MAP:
+      case MULTIBOUND_SET:
+        return true;
+
+      default:
+        return false;
+    }
+  }
+}
diff --git a/java/dagger/internal/codegen/model/CompilerEnvironment.java b/java/dagger/internal/codegen/model/CompilerEnvironment.java
new file mode 100644
index 0000000..aa0d01c
--- /dev/null
+++ b/java/dagger/internal/codegen/model/CompilerEnvironment.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+/** Types for the compiler in use for annotation processing. */
+public enum CompilerEnvironment {
+  JAVA,
+  KSP
+}
diff --git a/java/dagger/internal/codegen/model/ComponentPath.java b/java/dagger/internal/codegen/model/ComponentPath.java
new file mode 100644
index 0000000..da7827d
--- /dev/null
+++ b/java/dagger/internal/codegen/model/ComponentPath.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.Iterables.getLast;
+import static java.util.stream.Collectors.joining;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import com.google.common.collect.ImmutableList;
+import com.squareup.javapoet.ClassName;
+
+/** A path containing a component and all of its ancestor components. */
+@AutoValue
+public abstract class ComponentPath {
+  /** Returns a new {@link ComponentPath} from {@code components}. */
+  public static ComponentPath create(Iterable<DaggerTypeElement> components) {
+    return new AutoValue_ComponentPath(ImmutableList.copyOf(components));
+  }
+
+  /**
+   * Returns the component types, starting from the {@linkplain #rootComponent() root
+   * component} and ending with the {@linkplain #currentComponent() current component}.
+   */
+  public abstract ImmutableList<DaggerTypeElement> components();
+
+  /**
+   * Returns the root {@link dagger.Component}- or {@link
+   * dagger.producers.ProductionComponent}-annotated type
+   */
+  public final DaggerTypeElement rootComponent() {
+    return components().get(0);
+  }
+
+  /** Returns the component at the end of the path. */
+  @Memoized
+  public DaggerTypeElement currentComponent() {
+    return getLast(components());
+  }
+
+  /**
+   * Returns the parent of the {@linkplain #currentComponent()} current component}.
+   *
+   * @throws IllegalStateException if the current graph is the {@linkplain #atRoot() root component}
+   */
+  public final DaggerTypeElement parentComponent() {
+    checkState(!atRoot());
+    return components().reverse().get(1);
+  }
+
+  /**
+   * Returns this path's parent path.
+   *
+   * @throws IllegalStateException if the current graph is the {@linkplain #atRoot() root component}
+   */
+  // TODO(ronshapiro): consider memoizing this
+  public final ComponentPath parent() {
+    checkState(!atRoot());
+    return create(components().subList(0, components().size() - 1));
+  }
+
+  /** Returns the path from the root component to the {@code child} of the current component. */
+  public final ComponentPath childPath(DaggerTypeElement child) {
+    return create(
+        ImmutableList.<DaggerTypeElement>builder().addAll(components()).add(child).build());
+  }
+
+  /**
+   * Returns {@code true} if the {@linkplain #currentComponent()} current component} is the
+   * {@linkplain #rootComponent()} root component}.
+   */
+  public final boolean atRoot() {
+    return components().size() == 1;
+  }
+
+  @Override
+  public final String toString() {
+    return components().stream()
+        .map(DaggerTypeElement::className)
+        .map(ClassName::canonicalName)
+        .collect(joining(" → "));
+  }
+
+  @Memoized
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public abstract boolean equals(Object obj);
+}
diff --git a/java/dagger/internal/codegen/model/DaggerAnnotation.java b/java/dagger/internal/codegen/model/DaggerAnnotation.java
new file mode 100644
index 0000000..4a59871
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DaggerAnnotation.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+
+import androidx.room.compiler.processing.XAnnotation;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Preconditions;
+import com.squareup.javapoet.ClassName;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import javax.lang.model.element.AnnotationMirror;
+
+/** Wrapper type for an annotation. */
+@AutoValue
+public abstract class DaggerAnnotation {
+
+  public static DaggerAnnotation from(XAnnotation annotation) {
+    Preconditions.checkNotNull(annotation);
+    return new AutoValue_DaggerAnnotation(XAnnotations.equivalence().wrap(annotation));
+  }
+
+  abstract Equivalence.Wrapper<XAnnotation> equivalenceWrapper();
+
+  public DaggerTypeElement annotationTypeElement() {
+    return DaggerTypeElement.from(xprocessing().getType().getTypeElement());
+  }
+
+  public ClassName className() {
+    return annotationTypeElement().className();
+  }
+
+  public XAnnotation xprocessing() {
+    return equivalenceWrapper().get();
+  }
+
+  public AnnotationMirror java() {
+    return toJavac(xprocessing());
+  }
+
+  @Override
+  public final String toString() {
+    return XAnnotations.toString(xprocessing());
+  }
+}
diff --git a/java/dagger/internal/codegen/model/DaggerElement.java b/java/dagger/internal/codegen/model/DaggerElement.java
new file mode 100644
index 0000000..cf2ac52
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DaggerElement.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+
+import androidx.room.compiler.processing.XElement;
+import com.google.auto.value.AutoValue;
+import javax.lang.model.element.Element;
+
+/** Wrapper type for an element. */
+@AutoValue
+public abstract class DaggerElement {
+  public static DaggerElement from(XElement element) {
+    return new AutoValue_DaggerElement(element);
+  }
+
+  public abstract XElement xprocessing();
+
+  public Element java() {
+    return toJavac(xprocessing());
+  }
+
+  @Override
+  public final String toString() {
+    return xprocessing().toString();
+  }
+}
diff --git a/java/dagger/internal/codegen/model/DaggerExecutableElement.java b/java/dagger/internal/codegen/model/DaggerExecutableElement.java
new file mode 100644
index 0000000..484e446
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DaggerExecutableElement.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import androidx.room.compiler.processing.XExecutableElement;
+import com.google.auto.value.AutoValue;
+import javax.lang.model.element.ExecutableElement;
+
+/** Wrapper type for an executable element. */
+@AutoValue
+public abstract class DaggerExecutableElement {
+  public static DaggerExecutableElement from(XExecutableElement executableElement) {
+    return new AutoValue_DaggerExecutableElement(checkNotNull(executableElement));
+  }
+
+  public abstract XExecutableElement xprocessing();
+
+  public ExecutableElement java() {
+    return toJavac(xprocessing());
+  }
+
+  @Override
+  public final String toString() {
+    return xprocessing().toString();
+  }
+}
diff --git a/java/dagger/internal/codegen/model/DaggerProcessingEnv.java b/java/dagger/internal/codegen/model/DaggerProcessingEnv.java
new file mode 100644
index 0000000..6645b90
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DaggerProcessingEnv.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+
+import androidx.room.compiler.processing.XProcessingEnv;
+import com.google.auto.value.AutoValue;
+import javax.annotation.processing.ProcessingEnvironment;
+
+/** Wrapper type for an element. */
+@AutoValue
+public abstract class DaggerProcessingEnv {
+  /** Represents a type of backend used for compilation. */
+  public enum Backend { JAVAC, KSP }
+
+  public static DaggerProcessingEnv from(XProcessingEnv processingEnv) {
+    return new AutoValue_DaggerProcessingEnv(processingEnv);
+  }
+
+  public abstract XProcessingEnv xprocessing();
+
+  /** Returns the backend used in this compilation. */
+  public Backend getBackend() {
+    return Backend.valueOf(xprocessing().getBackend().name());
+  }
+
+  public ProcessingEnvironment java() {
+    return toJavac(xprocessing());
+  }
+}
diff --git a/java/dagger/internal/codegen/model/DaggerType.java b/java/dagger/internal/codegen/model/DaggerType.java
new file mode 100644
index 0000000..c606f15
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DaggerType.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+
+import androidx.room.compiler.processing.XType;
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Preconditions;
+import dagger.internal.codegen.xprocessing.XTypes;
+import javax.lang.model.type.TypeMirror;
+
+/** Wrapper type for a type. */
+@AutoValue
+public abstract class DaggerType {
+  public static DaggerType from(XType type) {
+    Preconditions.checkNotNull(type);
+    return new AutoValue_DaggerType(XTypes.equivalence().wrap(type));
+  }
+
+  abstract Equivalence.Wrapper<XType> equivalenceWrapper();
+
+  public XType xprocessing() {
+    return equivalenceWrapper().get();
+  }
+
+  public TypeMirror java() {
+    return toJavac(xprocessing());
+  }
+
+  @Override
+  public final String toString() {
+    // We define our own stable string rather than use XType#toString() here because
+    // XType#toString() is currently not stable across backends. In particular, in javac it returns
+    // the qualified type but in ksp it returns the simple name.
+    // TODO(bcorso): Consider changing XProcessing so that #toString() is stable across backends.
+    return XTypes.toStableString(xprocessing());
+  }
+}
diff --git a/java/dagger/internal/codegen/model/DaggerTypeElement.java b/java/dagger/internal/codegen/model/DaggerTypeElement.java
new file mode 100644
index 0000000..77e0c4f
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DaggerTypeElement.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
+import javax.lang.model.element.TypeElement;
+
+/** Wrapper type for a type element. */
+@AutoValue
+public abstract class DaggerTypeElement {
+  public static DaggerTypeElement from(XTypeElement typeElement) {
+    return new AutoValue_DaggerTypeElement(typeElement);
+  }
+
+  public abstract XTypeElement xprocessing();
+
+  public TypeElement java() {
+    return toJavac(xprocessing());
+  }
+
+  public ClassName className() {
+    return xprocessing().getClassName();
+  }
+
+  @Override
+  public final String toString() {
+    return xprocessing().toString();
+  }
+}
diff --git a/java/dagger/internal/codegen/model/DependencyRequest.java b/java/dagger/internal/codegen/model/DependencyRequest.java
new file mode 100644
index 0000000..aa120c0
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DependencyRequest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import com.google.auto.value.AutoValue;
+import dagger.Provides;
+import java.util.Optional;
+import javax.inject.Inject;
+
+/**
+ * Represents a request for a {@link Key} at an injection point. For example, parameters to {@link
+ * Inject} constructors, {@link Provides} methods, and component methods are all dependency
+ * requests.
+ *
+ * <p id="synthetic">A dependency request is considered to be <em>synthetic</em> if it does not have
+ * an {@link DaggerElement} in code that requests the key directly. For example, an {@link
+ * java.util.concurrent.Executor} is required for all {@code @Produces} methods to run
+ * asynchronously even though it is not directly specified as a parameter to the binding method.
+ */
+@AutoValue
+public abstract class DependencyRequest {
+  /** The kind of this request. */
+  public abstract RequestKind kind();
+
+  /** The key of this request. */
+  public abstract Key key();
+
+  /**
+   * The element that declares this dependency request. Absent for <a href="#synthetic">synthetic
+   * </a> requests.
+   */
+  public abstract Optional<DaggerElement> requestElement();
+
+  /**
+   * Returns {@code true} if this request allows null objects. A request is nullable if it is
+   * has an annotation with "Nullable" as its simple name.
+   */
+  public abstract boolean isNullable();
+
+  /** Returns a new builder of dependency requests. */
+  public static DependencyRequest.Builder builder() {
+    return new AutoValue_DependencyRequest.Builder().isNullable(false);
+  }
+
+  /** A builder of {@link DependencyRequest}s. */
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder kind(RequestKind kind);
+
+    public abstract Builder key(Key key);
+
+    public abstract Builder requestElement(DaggerElement element);
+
+    public abstract Builder isNullable(boolean isNullable);
+
+    public abstract DependencyRequest build();
+  }
+}
diff --git a/java/dagger/internal/codegen/model/DiagnosticReporter.java b/java/dagger/internal/codegen/model/DiagnosticReporter.java
new file mode 100644
index 0000000..b2dda61
--- /dev/null
+++ b/java/dagger/internal/codegen/model/DiagnosticReporter.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static com.google.common.collect.Lists.asList;
+
+import com.google.errorprone.annotations.FormatMethod;
+import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
+import javax.tools.Diagnostic;
+
+// TODO(bcorso): Move this into dagger/spi?
+/**
+ * An object that {@link BindingGraphPlugin}s can use to report diagnostics while visiting a {@link
+ * BindingGraph}.
+ *
+ * <p>Note: This API is still experimental and will change.
+ */
+public abstract class DiagnosticReporter {
+  /**
+   * Reports a diagnostic for a component. For non-root components, includes information about the
+   * path from the root component.
+   */
+  public abstract void reportComponent(
+      Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message);
+
+  /**
+   * Reports a diagnostic for a component. For non-root components, includes information about the
+   * path from the root component.
+   */
+  @FormatMethod
+  public final void reportComponent(
+      Diagnostic.Kind diagnosticKind,
+      ComponentNode componentNode,
+      String messageFormat,
+      Object firstArg,
+      Object... moreArgs) {
+    reportComponent(
+        diagnosticKind, componentNode, formatMessage(messageFormat, firstArg, moreArgs));
+  }
+
+  /**
+   * Reports a diagnostic for a binding or missing binding. Includes information about how the
+   * binding is reachable from entry points.
+   */
+  public abstract void reportBinding(
+      Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message);
+
+  /**
+   * Reports a diagnostic for a binding or missing binding. Includes information about how the
+   * binding is reachable from entry points.
+   */
+  @FormatMethod
+  public final void reportBinding(
+      Diagnostic.Kind diagnosticKind,
+      MaybeBinding binding,
+      String messageFormat,
+      Object firstArg,
+      Object... moreArgs) {
+    reportBinding(diagnosticKind, binding, formatMessage(messageFormat, firstArg, moreArgs));
+  }
+
+  /**
+   * Reports a diagnostic for a dependency. Includes information about how the dependency is
+   * reachable from entry points.
+   */
+  public abstract void reportDependency(
+      Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message);
+
+  /**
+   * Reports a diagnostic for a dependency. Includes information about how the dependency is
+   * reachable from entry points.
+   */
+  @FormatMethod
+  public final void reportDependency(
+      Diagnostic.Kind diagnosticKind,
+      DependencyEdge dependencyEdge,
+      String messageFormat,
+      Object firstArg,
+      Object... moreArgs) {
+    reportDependency(
+        diagnosticKind, dependencyEdge, formatMessage(messageFormat, firstArg, moreArgs));
+  }
+
+  /** Reports a diagnostic for a subcomponent factory method. */
+  public abstract void reportSubcomponentFactoryMethod(
+      Diagnostic.Kind diagnosticKind,
+      ChildFactoryMethodEdge childFactoryMethodEdge,
+      String message);
+
+  /** Reports a diagnostic for a subcomponent factory method. */
+  @FormatMethod
+  public final void reportSubcomponentFactoryMethod(
+      Diagnostic.Kind diagnosticKind,
+      ChildFactoryMethodEdge childFactoryMethodEdge,
+      String messageFormat,
+      Object firstArg,
+      Object... moreArgs) {
+    reportSubcomponentFactoryMethod(
+        diagnosticKind, childFactoryMethodEdge, formatMessage(messageFormat, firstArg, moreArgs));
+  }
+
+  private String formatMessage(String messageFormat, Object firstArg, Object[] moreArgs) {
+    return String.format(messageFormat, asList(firstArg, moreArgs).toArray());
+  }
+}
diff --git a/java/dagger/internal/codegen/model/Key.java b/java/dagger/internal/codegen/model/Key.java
new file mode 100644
index 0000000..585bdea
--- /dev/null
+++ b/java/dagger/internal/codegen/model/Key.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import com.google.common.base.Joiner;
+import dagger.internal.codegen.xprocessing.XAnnotations;
+import dagger.internal.codegen.xprocessing.XElements;
+import java.util.Optional;
+
+/**
+ * A {@linkplain DaggerType type} and an optional {@linkplain javax.inject.Qualifier qualifier} that
+ * is the lookup key for a binding.
+ */
+@AutoValue
+public abstract class Key {
+  /**
+   * A {@link javax.inject.Qualifier} annotation that provides a unique namespace prefix for the
+   * type of this key.
+   */
+  public abstract Optional<DaggerAnnotation> qualifier();
+
+  /** The type represented by this key. */
+  public abstract DaggerType type();
+
+  /**
+   * Distinguishes keys for multibinding contributions that share a {@link #type()} and {@link
+   * #qualifier()}.
+   *
+   * <p>Each multibound map and set has a synthetic multibinding that depends on the specific
+   * contributions to that map or set using keys that identify those multibinding contributions.
+   *
+   * <p>Absent except for multibinding contributions.
+   */
+  public abstract Optional<MultibindingContributionIdentifier> multibindingContributionIdentifier();
+
+  /** Returns a {@link Builder} that inherits the properties of this key. */
+  abstract Builder toBuilder();
+
+  /** Returns a copy of this key with the type replaced with the given type. */
+  public Key withType(DaggerType newType) {
+    return toBuilder().type(newType).build();
+  }
+
+  /**
+   * Returns a copy of this key with the multibinding contribution identifier replaced with the
+   * given multibinding contribution identifier.
+   */
+  public Key withMultibindingContributionIdentifier(
+      DaggerTypeElement contributingModule, DaggerExecutableElement bindingMethod) {
+    return toBuilder()
+        .multibindingContributionIdentifier(contributingModule, bindingMethod)
+        .build();
+  }
+
+  /** Returns a copy of this key with the multibinding contribution identifier, if any, removed. */
+  public Key withoutMultibindingContributionIdentifier() {
+    return toBuilder().multibindingContributionIdentifier(Optional.empty()).build();
+  }
+
+  // The main hashCode/equality bottleneck is in MoreTypes.equivalence(). It's possible that we can
+  // avoid this by tuning that method. Perhaps we can also avoid the issue entirely by interning all
+  // Keys
+  @Memoized
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public abstract boolean equals(Object o);
+
+  @Override
+  public final String toString() {
+    return Joiner.on(' ')
+        .skipNulls()
+        .join(
+            qualifier()
+                .map(DaggerAnnotation::xprocessing)
+                .map(XAnnotations::toStableString)
+                .orElse(null),
+            type(),
+            multibindingContributionIdentifier().orElse(null));
+  }
+
+  /** Returns a builder for {@link Key}s. */
+  public static Builder builder(DaggerType type) {
+    return new AutoValue_Key.Builder().type(type);
+  }
+
+  /** A builder for {@link Key}s. */
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract Builder type(DaggerType type);
+
+    public abstract Builder qualifier(Optional<DaggerAnnotation> qualifier);
+
+    public abstract Builder qualifier(DaggerAnnotation qualifier);
+
+    public final Builder multibindingContributionIdentifier(
+        DaggerTypeElement contributingModule, DaggerExecutableElement bindingMethod) {
+      return multibindingContributionIdentifier(
+          Optional.of(
+              MultibindingContributionIdentifier.create(contributingModule, bindingMethod)));
+    }
+
+    abstract Builder multibindingContributionIdentifier(
+        Optional<MultibindingContributionIdentifier> identifier);
+
+    public abstract Key build();
+  }
+
+  /**
+   * An object that identifies a multibinding contribution method and the module class that
+   * contributes it to the graph.
+   *
+   * @see #multibindingContributionIdentifier()
+   */
+  @AutoValue
+  public abstract static class MultibindingContributionIdentifier {
+    private static MultibindingContributionIdentifier create(
+        DaggerTypeElement contributingModule, DaggerExecutableElement bindingMethod) {
+      return new AutoValue_Key_MultibindingContributionIdentifier(
+          contributingModule, bindingMethod);
+    }
+
+    /** Returns the module containing the multibinding method. */
+    public abstract DaggerTypeElement contributingModule();
+
+    /** Returns the multibinding method that defines teh multibinding contribution. */
+    public abstract DaggerExecutableElement bindingMethod();
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>The returned string is human-readable and distinguishes the keys in the same way as the
+     * whole object.
+     */
+    @Override
+    public String toString() {
+      return String.format(
+          "%s#%s",
+          contributingModule().xprocessing().getQualifiedName(),
+          XElements.getSimpleName(bindingMethod().xprocessing()));
+    }
+  }
+}
diff --git a/java/dagger/internal/codegen/model/MoreAnnotationMirrors.java b/java/dagger/internal/codegen/model/MoreAnnotationMirrors.java
new file mode 100644
index 0000000..9b45b89
--- /dev/null
+++ b/java/dagger/internal/codegen/model/MoreAnnotationMirrors.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults;
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.squareup.javapoet.CodeBlock;
+import java.util.List;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.util.SimpleAnnotationValueVisitor8;
+
+/** Utility class for qualifier transformations */
+final class MoreAnnotationMirrors {
+  /**
+   * Returns a String rendering of an {@link AnnotationMirror} that includes attributes in the order
+   * defined in the annotation type.
+   */
+  public static String toStableString(DaggerAnnotation qualifier) {
+    return stableAnnotationMirrorToString(qualifier.java());
+  }
+
+  /**
+   * Returns a String rendering of an {@link AnnotationMirror} that includes attributes in the order
+   * defined in the annotation type. This will produce the same output for {@linkplain
+   * com.google.auto.common.AnnotationMirrors#equivalence() equal} {@link AnnotationMirror}s even if
+   * default values are omitted or their attributes were written in different orders, e.g.
+   * {@code @A(b = "b", c = "c")} and {@code @A(c = "c", b = "b", attributeWithDefaultValue =
+   * "default value")}.
+   */
+  // TODO(ronshapiro): move this to auto-common
+  private static String stableAnnotationMirrorToString(AnnotationMirror qualifier) {
+    StringBuilder builder = new StringBuilder("@").append(qualifier.getAnnotationType());
+    ImmutableMap<ExecutableElement, AnnotationValue> elementValues =
+        getAnnotationValuesWithDefaults(qualifier);
+    if (!elementValues.isEmpty()) {
+      ImmutableMap.Builder<String, String> namedValuesBuilder = ImmutableMap.builder();
+      elementValues.forEach(
+          (key, value) ->
+              namedValuesBuilder.put(
+                  key.getSimpleName().toString(), stableAnnotationValueToString(value)));
+      ImmutableMap<String, String> namedValues = namedValuesBuilder.build();
+      builder.append('(');
+      if (namedValues.size() == 1 && namedValues.containsKey("value")) {
+        // Omit "value ="
+        builder.append(namedValues.get("value"));
+      } else {
+        builder.append(Joiner.on(", ").withKeyValueSeparator("=").join(namedValues));
+      }
+      builder.append(')');
+    }
+    return builder.toString();
+  }
+
+  private static String stableAnnotationValueToString(AnnotationValue annotationValue) {
+    return annotationValue.accept(
+        new SimpleAnnotationValueVisitor8<String, Void>() {
+          @Override
+          protected String defaultAction(Object value, Void ignore) {
+            return value.toString();
+          }
+
+          @Override
+          public String visitString(String value, Void ignore) {
+            return CodeBlock.of("$S", value).toString();
+          }
+
+          @Override
+          public String visitAnnotation(AnnotationMirror value, Void ignore) {
+            return stableAnnotationMirrorToString(value);
+          }
+
+          @Override
+          public String visitArray(List<? extends AnnotationValue> value, Void ignore) {
+            return value.stream()
+                .map(MoreAnnotationMirrors::stableAnnotationValueToString)
+                .collect(joining(", ", "{", "}"));
+          }
+        },
+        null);
+  }
+
+  private MoreAnnotationMirrors() {}
+}
diff --git a/java/dagger/internal/codegen/model/RequestKind.java b/java/dagger/internal/codegen/model/RequestKind.java
new file mode 100644
index 0000000..581a829
--- /dev/null
+++ b/java/dagger/internal/codegen/model/RequestKind.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static com.google.common.base.CaseFormat.UPPER_CAMEL;
+import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
+
+import dagger.Lazy;
+import dagger.producers.Produced;
+import dagger.producers.Producer;
+import javax.inject.Provider;
+
+/**
+ * Represents the different kinds of {@link javax.lang.model.type.TypeMirror types} that may be
+ * requested as dependencies for the same key. For example, {@code String}, {@code
+ * Provider<String>}, and {@code Lazy<String>} can all be requested if a key exists for {@code
+ * String}; they have the {@link #INSTANCE}, {@link #PROVIDER}, and {@link #LAZY} request kinds,
+ * respectively.
+ */
+public enum RequestKind {
+  /** A default request for an instance. E.g.: {@code FooType} */
+  INSTANCE,
+
+  /** A request for a {@link Provider}. E.g.: {@code Provider<FooType>} */
+  PROVIDER,
+
+  /** A request for a {@link Lazy}. E.g.: {@code Lazy<FooType>} */
+  LAZY,
+
+  /** A request for a {@link Provider} of a {@link Lazy}. E.g.: {@code Provider<Lazy<FooType>>} */
+  PROVIDER_OF_LAZY,
+
+  /**
+   * A request for a members injection. E.g. {@code void injectMembers(FooType);}. Can only be
+   * requested by component interfaces.
+   */
+  MEMBERS_INJECTION,
+
+  /** A request for a {@link Producer}. E.g.: {@code Producer<FooType>} */
+  PRODUCER,
+
+  /** A request for a {@link Produced}. E.g.: {@code Produced<FooType>} */
+  PRODUCED,
+
+  /**
+   * A request for a {@link com.google.common.util.concurrent.ListenableFuture}. E.g.: {@code
+   * ListenableFuture<FooType>}. These can only be requested by component interfaces.
+   */
+  FUTURE,
+  ;
+
+  /** Returns a string that represents requests of this kind for a key. */
+  public String format(Key key) {
+    switch (this) {
+      case INSTANCE:
+        return key.toString();
+
+      case PROVIDER_OF_LAZY:
+        return String.format("Provider<Lazy<%s>>", key);
+
+      case MEMBERS_INJECTION:
+        return String.format("injectMembers(%s)", key);
+
+      case FUTURE:
+        return String.format("ListenableFuture<%s>", key);
+
+      default:
+        return String.format("%s<%s>", UPPER_UNDERSCORE.to(UPPER_CAMEL, name()), key);
+    }
+  }
+}
diff --git a/java/dagger/internal/codegen/model/Scope.java b/java/dagger/internal/codegen/model/Scope.java
new file mode 100644
index 0000000..0ea13b3
--- /dev/null
+++ b/java/dagger/internal/codegen/model/Scope.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.model;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auto.value.AutoValue;
+import com.squareup.javapoet.ClassName;
+
+/** A representation of a {@link javax.inject.Scope}. */
+@AutoValue
+public abstract class Scope {
+  /**
+   * Creates a {@link Scope} object from the {@link javax.inject.Scope}-annotated annotation type.
+   */
+  public static Scope scope(DaggerAnnotation scopeAnnotation) {
+    checkArgument(isScope(scopeAnnotation));
+    return new AutoValue_Scope(scopeAnnotation);
+  }
+
+  /**
+   * Returns {@code true} if {@link #scopeAnnotation()} is a {@link javax.inject.Scope} annotation.
+   */
+  public static boolean isScope(DaggerAnnotation scopeAnnotation) {
+    return isScope(scopeAnnotation.annotationTypeElement());
+  }
+
+  /**
+   * Returns {@code true} if {@code scopeAnnotationType} is a {@link javax.inject.Scope} annotation.
+   */
+  public static boolean isScope(DaggerTypeElement scopeAnnotationType) {
+    return scopeAnnotationType.xprocessing().hasAnnotation(SCOPE)
+        || scopeAnnotationType.xprocessing().hasAnnotation(SCOPE_JAVAX);
+  }
+
+  private static final ClassName PRODUCTION_SCOPE =
+      ClassName.get("dagger.producers", "ProductionScope");
+  private static final ClassName SINGLETON = ClassName.get("jakarta.inject", "Singleton");
+  private static final ClassName SINGLETON_JAVAX = ClassName.get("javax.inject", "Singleton");
+  private static final ClassName REUSABLE = ClassName.get("dagger", "Reusable");
+  private static final ClassName SCOPE = ClassName.get("jakarta.inject", "Scope");
+  private static final ClassName SCOPE_JAVAX = ClassName.get("javax.inject", "Scope");
+
+
+  /** The {@link DaggerAnnotation} that represents the scope annotation. */
+  public abstract DaggerAnnotation scopeAnnotation();
+
+  public final ClassName className() {
+    return scopeAnnotation().className();
+  }
+
+  /** Returns {@code true} if this scope is the {@link javax.inject.Singleton @Singleton} scope. */
+  public final boolean isSingleton() {
+    return isScope(SINGLETON) || isScope(SINGLETON_JAVAX);
+  }
+
+  /** Returns {@code true} if this scope is the {@link dagger.Reusable @Reusable} scope. */
+  public final boolean isReusable() {
+    return isScope(REUSABLE);
+  }
+
+  /**
+   * Returns {@code true} if this scope is the {@link
+   * dagger.producers.ProductionScope @ProductionScope} scope.
+   */
+  public final boolean isProductionScope() {
+    return isScope(PRODUCTION_SCOPE);
+  }
+
+  private boolean isScope(ClassName annotation) {
+    return scopeAnnotation().className().equals(annotation);
+  }
+
+  /** Returns a debug representation of the scope. */
+  @Override
+  public final String toString() {
+    return scopeAnnotation().toString();
+  }
+}
diff --git a/java/dagger/internal/codegen/processingstep/BUILD b/java/dagger/internal/codegen/processingstep/BUILD
index 97d79af..bcd8c33 100644
--- a/java/dagger/internal/codegen/processingstep/BUILD
+++ b/java/dagger/internal/codegen/processingstep/BUILD
@@ -31,10 +31,10 @@
         "//java/dagger/internal/codegen/extension",
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/validation",
         "//java/dagger/internal/codegen/writing",
         "//java/dagger/internal/codegen/xprocessing",
-        "//java/dagger/spi",
         "//third_party/java/auto:common",
         "//third_party/java/error_prone:annotations",
         "//third_party/java/guava/base",
diff --git a/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java b/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java
index bac8d21..5b17c00 100644
--- a/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java
+++ b/java/dagger/internal/codegen/processingstep/ComponentProcessingStep.java
@@ -105,7 +105,7 @@
       return;
     }
 
-    Supplier<dagger.spi.model.BindingGraph> fullBindingGraphSupplier =
+    Supplier<dagger.internal.codegen.model.BindingGraph> fullBindingGraphSupplier =
         Suppliers.memoize(
             () -> bindingGraphFactory.create(componentDescriptor, true).topLevelBindingGraph());
     if (bindingGraphValidator.shouldDoFullBindingGraphValidation(component)) {
diff --git a/java/dagger/internal/codegen/processingstep/ModuleProcessingStep.java b/java/dagger/internal/codegen/processingstep/ModuleProcessingStep.java
index e3daf21..1df14ec 100644
--- a/java/dagger/internal/codegen/processingstep/ModuleProcessingStep.java
+++ b/java/dagger/internal/codegen/processingstep/ModuleProcessingStep.java
@@ -130,7 +130,11 @@
         inaccessibleMapKeyProxyGenerator.generate(bindsMethodBinding(module, method), messager);
       }
     }
-    moduleConstructorProxyGenerator.generate(module, messager);
+    // We should never need to generate a constructor proxy for a companion object since we never
+    // need to call a companion object's constructor.
+    if (!module.isCompanionObject()) {
+      moduleConstructorProxyGenerator.generate(module, messager);
+    }
   }
 
   private <B extends ContributionBinding> void generate(
diff --git a/java/dagger/internal/codegen/validation/BUILD b/java/dagger/internal/codegen/validation/BUILD
index fa2a6b8..cc36708 100644
--- a/java/dagger/internal/codegen/validation/BUILD
+++ b/java/dagger/internal/codegen/validation/BUILD
@@ -33,6 +33,7 @@
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/xprocessing",
         "//java/dagger/spi",
         "//third_party/java/auto:common",
@@ -46,5 +47,6 @@
         "//third_party/java/guava/util/concurrent",
         "//third_party/java/javapoet",
         "//third_party/java/jsr330_inject",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
diff --git a/java/dagger/internal/codegen/validation/BindingElementValidator.java b/java/dagger/internal/codegen/validation/BindingElementValidator.java
index 16812e6..c7c9f76 100644
--- a/java/dagger/internal/codegen/validation/BindingElementValidator.java
+++ b/java/dagger/internal/codegen/validation/BindingElementValidator.java
@@ -39,9 +39,9 @@
 import dagger.internal.codegen.base.SetType;
 import dagger.internal.codegen.binding.InjectionAnnotations;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.xprocessing.XElements;
-import dagger.spi.model.Key;
-import dagger.spi.model.Scope;
 import java.util.Formatter;
 import java.util.HashMap;
 import java.util.Map;
diff --git a/java/dagger/internal/codegen/validation/BindingGraphValidator.java b/java/dagger/internal/codegen/validation/BindingGraphValidator.java
index d20fe99..e478f4b 100644
--- a/java/dagger/internal/codegen/validation/BindingGraphValidator.java
+++ b/java/dagger/internal/codegen/validation/BindingGraphValidator.java
@@ -21,7 +21,7 @@
 import com.google.common.base.Supplier;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.compileroption.ValidationType;
-import dagger.spi.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
diff --git a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java
index d0a924f..10523ef 100644
--- a/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java
+++ b/java/dagger/internal/codegen/validation/ComponentDescriptorValidator.java
@@ -63,8 +63,8 @@
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.compileroption.ValidationType;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.xprocessing.XTypes;
-import dagger.spi.model.Scope;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Deque;
diff --git a/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java b/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java
index 6ea19d6..6a129cc 100644
--- a/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java
+++ b/java/dagger/internal/codegen/validation/ComponentHierarchyValidator.java
@@ -43,7 +43,7 @@
 import dagger.internal.codegen.binding.InjectionAnnotations;
 import dagger.internal.codegen.binding.ModuleDescriptor;
 import dagger.internal.codegen.compileroption.CompilerOptions;
-import dagger.spi.model.Scope;
+import dagger.internal.codegen.model.Scope;
 import java.util.Collection;
 import java.util.Formatter;
 import java.util.Map;
diff --git a/java/dagger/internal/codegen/validation/ComponentValidator.java b/java/dagger/internal/codegen/validation/ComponentValidator.java
index b3fc1a9..b503448 100644
--- a/java/dagger/internal/codegen/validation/ComponentValidator.java
+++ b/java/dagger/internal/codegen/validation/ComponentValidator.java
@@ -69,10 +69,10 @@
 import dagger.internal.codegen.binding.MethodSignatureFormatter;
 import dagger.internal.codegen.javapoet.TypeNames;
 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.xprocessing.XTypeElements;
 import dagger.internal.codegen.xprocessing.XTypes;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.HashMap;
diff --git a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java
index 8f49b0e..5510048 100644
--- a/java/dagger/internal/codegen/validation/DependencyRequestValidator.java
+++ b/java/dagger/internal/codegen/validation/DependencyRequestValidator.java
@@ -44,8 +44,8 @@
 import dagger.internal.codegen.binding.InjectionAnnotations;
 import dagger.internal.codegen.javapoet.TypeNames;
 import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.xprocessing.XTypes;
-import dagger.spi.model.RequestKind;
 import java.util.Optional;
 import javax.inject.Inject;
 
@@ -92,7 +92,11 @@
     new Validator(report, requestElement, requestType).validate();
   }
 
-  /** Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers. */
+  /**
+   * Returns {@code true} if a kotlin inject field is missing metadata about its qualifiers.
+   *
+   * <p>See https://youtrack.jetbrains.com/issue/KT-34684.
+   */
   private boolean missingQualifierMetadata(XElement requestElement) {
     if (isField(requestElement)) {
       XFieldElement fieldElement = asField(requestElement);
diff --git a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java
index 6767524..fc83e31 100644
--- a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java
+++ b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java
@@ -52,14 +52,14 @@
 import dagger.internal.codegen.base.ElementFormatter;
 import dagger.internal.codegen.base.Formatter;
 import dagger.internal.codegen.binding.DependencyRequestFormatter;
-import dagger.spi.model.Binding;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.Edge;
-import dagger.spi.model.BindingGraph.MaybeBinding;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.DaggerElement;
+import dagger.internal.codegen.model.Binding;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.Edge;
+import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.DaggerElement;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
@@ -262,7 +262,8 @@
    * Returns the dependency trace from one of the {@code entryPoints} to {@code binding} to {@code
    * message} as a list <i>ending with</i> the entry point.
    */
-  // TODO(ronshapiro): Adding a DependencyPath type to dagger.spi.model could be useful, i.e.
+  // TODO(ronshapiro): Adding a DependencyPath type to dagger.internal.codegen.model could be
+  // useful, i.e.
   // bindingGraph.shortestPathFromEntryPoint(DependencyEdge, MaybeBindingNode)
   public ImmutableList<DependencyEdge> dependencyTrace(
       MaybeBinding binding, ImmutableSet<DependencyEdge> entryPoints) {
diff --git a/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java b/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java
index 66ad74b..dad81d5 100644
--- a/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java
+++ b/java/dagger/internal/codegen/validation/DiagnosticReporterFactory.java
@@ -24,12 +24,12 @@
 import androidx.room.compiler.processing.XMessager;
 import androidx.room.compiler.processing.XTypeElement;
 import com.google.common.collect.ImmutableSet;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingGraph.DependencyEdge;
-import dagger.spi.model.BindingGraph.MaybeBinding;
-import dagger.spi.model.DiagnosticReporter;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
+import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import javax.inject.Inject;
 import javax.tools.Diagnostic;
 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
diff --git a/java/dagger/internal/codegen/validation/External.java b/java/dagger/internal/codegen/validation/External.java
index 73787f5..7fbbdbc 100644
--- a/java/dagger/internal/codegen/validation/External.java
+++ b/java/dagger/internal/codegen/validation/External.java
@@ -22,8 +22,8 @@
 import javax.inject.Qualifier;
 
 /**
- * Qualifier annotation for the {@link dagger.spi.model.BindingGraphPlugin}s that are registered by
- * users.
+ * Qualifier annotation for the {@link dagger.internal.codegen.model.BindingGraphPlugin}s that are
+ * registered by users.
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/java/dagger/internal/codegen/validation/ExternalBindingGraphConverter.java b/java/dagger/internal/codegen/validation/ExternalBindingGraphConverter.java
deleted file mode 100644
index af1836b..0000000
--- a/java/dagger/internal/codegen/validation/ExternalBindingGraphConverter.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2021 The Dagger Authors.
- *
- * 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 dagger.internal.codegen.validation;
-
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-
-import com.google.auto.value.AutoValue;
-import com.google.auto.value.extension.memoized.Memoized;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.graph.EndpointPair;
-import com.google.common.graph.ImmutableNetwork;
-import com.google.common.graph.MutableNetwork;
-import com.google.common.graph.Network;
-import com.google.common.graph.NetworkBuilder;
-import com.google.errorprone.annotations.FormatMethod;
-import dagger.model.Binding;
-import dagger.model.BindingGraph;
-import dagger.model.BindingGraph.ChildFactoryMethodEdge;
-import dagger.model.BindingGraph.ComponentNode;
-import dagger.model.BindingGraph.DependencyEdge;
-import dagger.model.BindingGraph.Edge;
-import dagger.model.BindingGraph.MaybeBinding;
-import dagger.model.BindingGraph.MissingBinding;
-import dagger.model.BindingGraph.Node;
-import dagger.model.BindingGraph.SubcomponentCreatorBindingEdge;
-import dagger.model.BindingKind;
-import dagger.model.ComponentPath;
-import dagger.model.DependencyRequest;
-import dagger.model.Key;
-import dagger.model.Key.MultibindingContributionIdentifier;
-import dagger.model.RequestKind;
-import dagger.model.Scope;
-import dagger.spi.DiagnosticReporter;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DaggerElement;
-import dagger.spi.model.DaggerTypeElement;
-import java.util.Optional;
-import javax.tools.Diagnostic;
-
-/** A Utility class for converting to the {@link BindingGraph} used by external plugins. */
-public final class ExternalBindingGraphConverter {
-  private ExternalBindingGraphConverter() {}
-
-  /** Returns a {@link DiagnosticReporter} from a {@link dagger.spi.DiagnosticReporter}. */
-  public static DiagnosticReporter fromSpiModel(dagger.spi.model.DiagnosticReporter reporter) {
-    return DiagnosticReporterImpl.create(reporter);
-  }
-
-  /** Returns a {@link BindingGraph} from a {@link dagger.spi.model.BindingGraph}. */
-  public static BindingGraph fromSpiModel(dagger.spi.model.BindingGraph graph) {
-    return BindingGraphImpl.create(graph);
-  }
-
-  private static ImmutableNetwork<Node, Edge> fromSpiModel(
-      Network<dagger.spi.model.BindingGraph.Node, dagger.spi.model.BindingGraph.Edge> spiNetwork) {
-    MutableNetwork<Node, Edge> network =
-        NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
-
-    ImmutableMap<dagger.spi.model.BindingGraph.Node, Node> fromSpiNodes =
-        spiNetwork.nodes().stream()
-            .collect(
-                toImmutableMap(
-                    spiNode -> spiNode,
-                    ExternalBindingGraphConverter::fromSpiModel));
-
-    for (Node node : fromSpiNodes.values()) {
-      network.addNode(node);
-    }
-    for (dagger.spi.model.BindingGraph.Edge edge : spiNetwork.edges()) {
-      EndpointPair<dagger.spi.model.BindingGraph.Node> edgePair = spiNetwork.incidentNodes(edge);
-      network.addEdge(
-          fromSpiNodes.get(edgePair.source()),
-          fromSpiNodes.get(edgePair.target()),
-          fromSpiModel(edge));
-    }
-    return ImmutableNetwork.copyOf(network);
-  }
-
-  private static Node fromSpiModel(dagger.spi.model.BindingGraph.Node node) {
-    if (node instanceof dagger.spi.model.Binding) {
-      return BindingNodeImpl.create((dagger.spi.model.Binding) node);
-    } else if (node instanceof dagger.spi.model.BindingGraph.ComponentNode) {
-      return ComponentNodeImpl.create((dagger.spi.model.BindingGraph.ComponentNode) node);
-    } else if (node instanceof dagger.spi.model.BindingGraph.MissingBinding) {
-      return MissingBindingImpl.create((dagger.spi.model.BindingGraph.MissingBinding) node);
-    } else {
-      throw new IllegalStateException("Unhandled node type: " + node.getClass());
-    }
-  }
-
-  private static Edge fromSpiModel(dagger.spi.model.BindingGraph.Edge edge) {
-    if (edge instanceof dagger.spi.model.BindingGraph.DependencyEdge) {
-      return DependencyEdgeImpl.create((dagger.spi.model.BindingGraph.DependencyEdge) edge);
-    } else if (edge instanceof dagger.spi.model.BindingGraph.ChildFactoryMethodEdge) {
-      return ChildFactoryMethodEdgeImpl.create(
-          (dagger.spi.model.BindingGraph.ChildFactoryMethodEdge) edge);
-    } else if (edge instanceof dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge) {
-      return SubcomponentCreatorBindingEdgeImpl.create(
-          (dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge) edge);
-    } else {
-      throw new IllegalStateException("Unhandled edge type: " + edge.getClass());
-    }
-  }
-
-  private static MultibindingContributionIdentifier fromSpiModel(
-      dagger.spi.model.Key.MultibindingContributionIdentifier identifier) {
-    return new MultibindingContributionIdentifier(
-        identifier.bindingMethod(), identifier.contributingModule());
-  }
-
-  private static Key fromSpiModel(dagger.spi.model.Key key) {
-    return Key.builder(key.type().java())
-        .qualifier(key.qualifier().map(DaggerAnnotation::java))
-        .multibindingContributionIdentifier(
-            key.multibindingContributionIdentifier().isPresent()
-                ? Optional.of(fromSpiModel(key.multibindingContributionIdentifier().get()))
-                : Optional.empty())
-        .build();
-  }
-
-  private static BindingKind fromSpiModel(dagger.spi.model.BindingKind bindingKind) {
-    return BindingKind.valueOf(bindingKind.name());
-  }
-
-  private static RequestKind fromSpiModel(dagger.spi.model.RequestKind requestKind) {
-    return RequestKind.valueOf(requestKind.name());
-  }
-
-  private static DependencyRequest fromSpiModel(dagger.spi.model.DependencyRequest request) {
-    DependencyRequest.Builder builder =
-        DependencyRequest.builder()
-            .kind(fromSpiModel(request.kind()))
-            .key(fromSpiModel(request.key()))
-            .isNullable(request.isNullable());
-
-    request.requestElement().ifPresent(e -> builder.requestElement(e.java()));
-    return builder.build();
-  }
-
-  private static Scope fromSpiModel(dagger.spi.model.Scope scope) {
-    return Scope.scope(scope.scopeAnnotation().java());
-  }
-
-  private static ComponentPath fromSpiModel(dagger.spi.model.ComponentPath path) {
-    return ComponentPath.create(
-        path.components().stream().map(DaggerTypeElement::java).collect(toImmutableList()));
-  }
-
-  private static dagger.spi.model.BindingGraph.ComponentNode toSpiModel(
-      ComponentNode componentNode) {
-    return ((ComponentNodeImpl) componentNode).spiDelegate();
-  }
-
-  private static dagger.spi.model.BindingGraph.MaybeBinding toSpiModel(MaybeBinding maybeBinding) {
-    if (maybeBinding instanceof MissingBindingImpl) {
-      return ((MissingBindingImpl) maybeBinding).spiDelegate();
-    } else if (maybeBinding instanceof BindingNodeImpl) {
-      return ((BindingNodeImpl) maybeBinding).spiDelegate();
-    } else {
-      throw new IllegalStateException("Unhandled binding type: " + maybeBinding.getClass());
-    }
-  }
-
-  private static dagger.spi.model.BindingGraph.DependencyEdge toSpiModel(
-      DependencyEdge dependencyEdge) {
-    return ((DependencyEdgeImpl) dependencyEdge).spiDelegate();
-  }
-
-  private static dagger.spi.model.BindingGraph.ChildFactoryMethodEdge toSpiModel(
-      ChildFactoryMethodEdge childFactoryMethodEdge) {
-    return ((ChildFactoryMethodEdgeImpl) childFactoryMethodEdge).spiDelegate();
-  }
-
-  @AutoValue
-  abstract static class ComponentNodeImpl implements ComponentNode {
-    static ComponentNode create(dagger.spi.model.BindingGraph.ComponentNode componentNode) {
-      return new AutoValue_ExternalBindingGraphConverter_ComponentNodeImpl(
-          fromSpiModel(componentNode.componentPath()),
-          componentNode.isSubcomponent(),
-          componentNode.isRealComponent(),
-          componentNode.entryPoints().stream()
-              .map(ExternalBindingGraphConverter::fromSpiModel)
-              .collect(toImmutableSet()),
-          componentNode.scopes().stream()
-              .map(ExternalBindingGraphConverter::fromSpiModel)
-              .collect(toImmutableSet()),
-          componentNode);
-    }
-
-    abstract dagger.spi.model.BindingGraph.ComponentNode spiDelegate();
-
-    @Override
-    public final String toString() {
-      return spiDelegate().toString();
-    }
-  }
-
-  @AutoValue
-  abstract static class BindingNodeImpl implements Binding {
-    static Binding create(dagger.spi.model.Binding binding) {
-      return new AutoValue_ExternalBindingGraphConverter_BindingNodeImpl(
-          fromSpiModel(binding.key()),
-          fromSpiModel(binding.componentPath()),
-          binding.dependencies().stream()
-              .map(ExternalBindingGraphConverter::fromSpiModel)
-              .collect(toImmutableSet()),
-          binding.bindingElement().map(DaggerElement::java),
-          binding.contributingModule().map(DaggerTypeElement::java),
-          binding.requiresModuleInstance(),
-          binding.scope().map(ExternalBindingGraphConverter::fromSpiModel),
-          binding.isNullable(),
-          binding.isProduction(),
-          fromSpiModel(binding.kind()),
-          binding);
-    }
-
-    abstract dagger.spi.model.Binding spiDelegate();
-
-    @Override
-    public final String toString() {
-      return spiDelegate().toString();
-    }
-  }
-
-  @AutoValue
-  abstract static class MissingBindingImpl extends MissingBinding {
-    static MissingBinding create(dagger.spi.model.BindingGraph.MissingBinding missingBinding) {
-      return new AutoValue_ExternalBindingGraphConverter_MissingBindingImpl(
-          fromSpiModel(missingBinding.componentPath()),
-          fromSpiModel(missingBinding.key()),
-          missingBinding);
-    }
-
-    abstract dagger.spi.model.BindingGraph.MissingBinding spiDelegate();
-
-    @Memoized
-    @Override
-    public abstract int hashCode();
-
-    @Override
-    public abstract boolean equals(Object o);
-  }
-
-  @AutoValue
-  abstract static class DependencyEdgeImpl implements DependencyEdge {
-    static DependencyEdge create(dagger.spi.model.BindingGraph.DependencyEdge dependencyEdge) {
-      return new AutoValue_ExternalBindingGraphConverter_DependencyEdgeImpl(
-          fromSpiModel(dependencyEdge.dependencyRequest()),
-          dependencyEdge.isEntryPoint(),
-          dependencyEdge);
-    }
-
-    abstract dagger.spi.model.BindingGraph.DependencyEdge spiDelegate();
-
-    @Override
-    public final String toString() {
-      return spiDelegate().toString();
-    }
-  }
-
-  @AutoValue
-  abstract static class ChildFactoryMethodEdgeImpl implements ChildFactoryMethodEdge {
-    static ChildFactoryMethodEdge create(
-        dagger.spi.model.BindingGraph.ChildFactoryMethodEdge childFactoryMethodEdge) {
-      return new AutoValue_ExternalBindingGraphConverter_ChildFactoryMethodEdgeImpl(
-          childFactoryMethodEdge.factoryMethod().java(), childFactoryMethodEdge);
-    }
-
-    abstract dagger.spi.model.BindingGraph.ChildFactoryMethodEdge spiDelegate();
-
-    @Override
-    public final String toString() {
-      return spiDelegate().toString();
-    }
-  }
-
-  @AutoValue
-  abstract static class SubcomponentCreatorBindingEdgeImpl
-      implements SubcomponentCreatorBindingEdge {
-    static SubcomponentCreatorBindingEdge create(
-        dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge
-            subcomponentCreatorBindingEdge) {
-      return new AutoValue_ExternalBindingGraphConverter_SubcomponentCreatorBindingEdgeImpl(
-          subcomponentCreatorBindingEdge.declaringModules().stream()
-              .map(DaggerTypeElement::java)
-              .collect(toImmutableSet()),
-          subcomponentCreatorBindingEdge);
-    }
-
-    abstract dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge spiDelegate();
-
-    @Override
-    public final String toString() {
-      return spiDelegate().toString();
-    }
-  }
-
-  @AutoValue
-  abstract static class BindingGraphImpl extends BindingGraph {
-    static BindingGraph create(dagger.spi.model.BindingGraph bindingGraph) {
-      BindingGraphImpl bindingGraphImpl =
-          new AutoValue_ExternalBindingGraphConverter_BindingGraphImpl(
-              fromSpiModel(bindingGraph.network()), bindingGraph.isFullBindingGraph());
-
-      bindingGraphImpl.componentNodesByPath =
-          bindingGraphImpl.componentNodes().stream()
-              .collect(toImmutableMap(ComponentNode::componentPath, node -> node));
-
-      return bindingGraphImpl;
-    }
-
-    private ImmutableMap<ComponentPath, ComponentNode> componentNodesByPath;
-
-    // This overrides dagger.model.BindingGraph with a more efficient implementation.
-    @Override
-    public Optional<ComponentNode> componentNode(ComponentPath componentPath) {
-      return componentNodesByPath.containsKey(componentPath)
-          ? Optional.of(componentNodesByPath.get(componentPath))
-          : Optional.empty();
-    }
-
-    // This overrides dagger.model.BindingGraph to memoize the output.
-    @Override
-    @Memoized
-    public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() {
-      return super.nodesByClass();
-    }
-  }
-
-  private static final class DiagnosticReporterImpl implements DiagnosticReporter {
-    static DiagnosticReporterImpl create(dagger.spi.model.DiagnosticReporter reporter) {
-      return new DiagnosticReporterImpl(reporter);
-    }
-
-    private final dagger.spi.model.DiagnosticReporter delegate;
-
-    DiagnosticReporterImpl(dagger.spi.model.DiagnosticReporter delegate) {
-      this.delegate = delegate;
-    }
-
-    @Override
-    public void reportComponent(
-        Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message) {
-      delegate.reportComponent(diagnosticKind, toSpiModel(componentNode), message);
-    }
-
-    @Override
-    @FormatMethod
-    public void reportComponent(
-        Diagnostic.Kind diagnosticKind,
-        ComponentNode componentNode,
-        String messageFormat,
-        Object firstArg,
-        Object... moreArgs) {
-      delegate.reportComponent(
-          diagnosticKind, toSpiModel(componentNode), messageFormat, firstArg, moreArgs);
-    }
-
-    @Override
-    public void reportBinding(
-        Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message) {
-      delegate.reportBinding(diagnosticKind, toSpiModel(binding), message);
-    }
-
-    @Override
-    @FormatMethod
-    public void reportBinding(
-        Diagnostic.Kind diagnosticKind,
-        MaybeBinding binding,
-        String messageFormat,
-        Object firstArg,
-        Object... moreArgs) {
-      delegate.reportBinding(
-          diagnosticKind, toSpiModel(binding), messageFormat, firstArg, moreArgs);
-    }
-
-    @Override
-    public void reportDependency(
-        Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) {
-      delegate.reportDependency(diagnosticKind, toSpiModel(dependencyEdge), message);
-    }
-
-    @Override
-    @FormatMethod
-    public void reportDependency(
-        Diagnostic.Kind diagnosticKind,
-        DependencyEdge dependencyEdge,
-        String messageFormat,
-        Object firstArg,
-        Object... moreArgs) {
-      delegate.reportDependency(
-          diagnosticKind, toSpiModel(dependencyEdge), messageFormat, firstArg, moreArgs);
-    }
-
-    @Override
-    public void reportSubcomponentFactoryMethod(
-        Diagnostic.Kind diagnosticKind,
-        ChildFactoryMethodEdge childFactoryMethodEdge,
-        String message) {
-      delegate.reportSubcomponentFactoryMethod(
-          diagnosticKind, toSpiModel(childFactoryMethodEdge), message);
-    }
-
-    @Override
-    @FormatMethod
-    public void reportSubcomponentFactoryMethod(
-        Diagnostic.Kind diagnosticKind,
-        ChildFactoryMethodEdge childFactoryMethodEdge,
-        String messageFormat,
-        Object firstArg,
-        Object... moreArgs) {
-      delegate.reportSubcomponentFactoryMethod(
-          diagnosticKind, toSpiModel(childFactoryMethodEdge), messageFormat, firstArg, moreArgs);
-    }
-  }
-}
diff --git a/java/dagger/internal/codegen/validation/ExternalBindingGraphPlugins.java b/java/dagger/internal/codegen/validation/ExternalBindingGraphPlugins.java
index d628246..ba8e6a0 100644
--- a/java/dagger/internal/codegen/validation/ExternalBindingGraphPlugins.java
+++ b/java/dagger/internal/codegen/validation/ExternalBindingGraphPlugins.java
@@ -30,7 +30,6 @@
 import dagger.spi.DiagnosticReporter;
 import dagger.spi.model.BindingGraph;
 import dagger.spi.model.BindingGraphPlugin;
-import dagger.spi.model.DaggerProcessingEnv;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Stream;
@@ -82,7 +81,7 @@
         supportedOptions.isEmpty()
             ? ImmutableMap.of()
             : Maps.filterKeys(processingOptions, supportedOptions::contains);
-    plugin.init(DaggerProcessingEnv.from(processingEnv), filteredOptions);
+    plugin.init(SpiModelBindingGraphConverter.toSpiModel(processingEnv), filteredOptions);
   }
 
   private void initializeLegacyPlugin(dagger.spi.BindingGraphPlugin plugin) {
@@ -96,22 +95,21 @@
   }
 
   /** Returns {@code false} if any of the plugins reported an error. */
-  boolean visit(BindingGraph graph) {
+  boolean visit(dagger.internal.codegen.model.BindingGraph graph) {
     return visitLegacyPlugins(graph) && visitPlugins(graph);
   }
 
-  private boolean visitLegacyPlugins(BindingGraph graph) {
+  private boolean visitLegacyPlugins(dagger.internal.codegen.model.BindingGraph graph) {
     // Return early to avoid converting the binding graph when there are no external plugins.
     if (legacyPlugins.isEmpty()) {
       return true;
     }
-
-    dagger.model.BindingGraph legacyGraph = ExternalBindingGraphConverter.fromSpiModel(graph);
+    dagger.model.BindingGraph legacyGraph = ModelBindingGraphConverter.toModel(graph);
     boolean isClean = true;
     for (dagger.spi.BindingGraphPlugin legacyPlugin : legacyPlugins) {
       DiagnosticReporterImpl reporter =
           diagnosticReporterFactory.reporter(graph, legacyPlugin.pluginName());
-      DiagnosticReporter legacyReporter = ExternalBindingGraphConverter.fromSpiModel(reporter);
+      DiagnosticReporter legacyReporter = ModelBindingGraphConverter.toModel(reporter);
       legacyPlugin.visitGraph(legacyGraph, legacyReporter);
       if (reporter.reportedDiagnosticKinds().contains(ERROR)) {
         isClean = false;
@@ -120,12 +118,13 @@
     return isClean;
   }
 
-  private boolean visitPlugins(BindingGraph graph) {
+  private boolean visitPlugins(dagger.internal.codegen.model.BindingGraph graph) {
+    BindingGraph spiGraph = SpiModelBindingGraphConverter.toSpiModel(graph, processingEnv);
     boolean isClean = true;
     for (BindingGraphPlugin plugin : plugins) {
       DiagnosticReporterImpl reporter =
           diagnosticReporterFactory.reporter(graph, plugin.pluginName());
-      plugin.visitGraph(graph, reporter);
+      plugin.visitGraph(spiGraph, SpiModelBindingGraphConverter.toSpiModel(reporter));
       if (reporter.reportedDiagnosticKinds().contains(ERROR)) {
         isClean = false;
       }
diff --git a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java
index 3c2ed69..cc2d6e9 100644
--- a/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java
+++ b/java/dagger/internal/codegen/validation/InjectBindingRegistryImpl.java
@@ -55,7 +55,7 @@
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.Key;
 import java.util.ArrayDeque;
 import java.util.Deque;
 import java.util.Map;
diff --git a/java/dagger/internal/codegen/validation/InjectValidator.java b/java/dagger/internal/codegen/validation/InjectValidator.java
index e359a6d..f223f6a 100644
--- a/java/dagger/internal/codegen/validation/InjectValidator.java
+++ b/java/dagger/internal/codegen/validation/InjectValidator.java
@@ -48,8 +48,8 @@
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.TypeNames;
 import dagger.internal.codegen.langmodel.Accessibility;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.xprocessing.XAnnotations;
-import dagger.spi.model.Scope;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
diff --git a/java/dagger/internal/codegen/validation/ModelBindingGraphConverter.java b/java/dagger/internal/codegen/validation/ModelBindingGraphConverter.java
new file mode 100644
index 0000000..314ed5f
--- /dev/null
+++ b/java/dagger/internal/codegen/validation/ModelBindingGraphConverter.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.validation;
+
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.graph.EndpointPair;
+import com.google.common.graph.ImmutableNetwork;
+import com.google.common.graph.MutableNetwork;
+import com.google.common.graph.Network;
+import com.google.common.graph.NetworkBuilder;
+import com.google.errorprone.annotations.FormatMethod;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DaggerElement;
+import dagger.internal.codegen.model.DaggerTypeElement;
+import dagger.internal.codegen.xprocessing.XElements;
+import dagger.model.Binding;
+import dagger.model.BindingGraph;
+import dagger.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.model.BindingGraph.ComponentNode;
+import dagger.model.BindingGraph.DependencyEdge;
+import dagger.model.BindingGraph.Edge;
+import dagger.model.BindingGraph.MaybeBinding;
+import dagger.model.BindingGraph.MissingBinding;
+import dagger.model.BindingGraph.Node;
+import dagger.model.BindingGraph.SubcomponentCreatorBindingEdge;
+import dagger.model.BindingKind;
+import dagger.model.ComponentPath;
+import dagger.model.DependencyRequest;
+import dagger.model.Key;
+import dagger.model.Key.MultibindingContributionIdentifier;
+import dagger.model.RequestKind;
+import dagger.model.Scope;
+import dagger.spi.DiagnosticReporter;
+import java.util.Optional;
+import javax.tools.Diagnostic;
+
+/** A Utility class for converting to the {@link BindingGraph} used by external plugins. */
+public final class ModelBindingGraphConverter {
+  private ModelBindingGraphConverter() {}
+
+  /** Returns a {@link DiagnosticReporter} from a {@link dagger.spi.DiagnosticReporter}. */
+  public static DiagnosticReporter toModel(
+      dagger.internal.codegen.model.DiagnosticReporter reporter) {
+    return DiagnosticReporterImpl.create(reporter);
+  }
+
+  /** Returns a {@link BindingGraph} from a {@link dagger.internal.codegen.model.BindingGraph}. */
+  public static BindingGraph toModel(dagger.internal.codegen.model.BindingGraph graph) {
+    return BindingGraphImpl.create(graph);
+  }
+
+  private static ImmutableNetwork<Node, Edge> toModel(
+      Network<
+              dagger.internal.codegen.model.BindingGraph.Node,
+              dagger.internal.codegen.model.BindingGraph.Edge>
+          internalNetwork) {
+    MutableNetwork<Node, Edge> network =
+        NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
+
+    ImmutableMap<dagger.internal.codegen.model.BindingGraph.Node, Node> fromInternalNodes =
+        internalNetwork.nodes().stream()
+            .collect(toImmutableMap(node -> node, ModelBindingGraphConverter::toModel));
+
+    for (Node node : fromInternalNodes.values()) {
+      network.addNode(node);
+    }
+    for (dagger.internal.codegen.model.BindingGraph.Edge edge : internalNetwork.edges()) {
+      EndpointPair<dagger.internal.codegen.model.BindingGraph.Node> edgePair =
+          internalNetwork.incidentNodes(edge);
+      network.addEdge(
+          fromInternalNodes.get(edgePair.source()),
+          fromInternalNodes.get(edgePair.target()),
+          toModel(edge));
+    }
+    return ImmutableNetwork.copyOf(network);
+  }
+
+  private static Node toModel(dagger.internal.codegen.model.BindingGraph.Node node) {
+    if (node instanceof dagger.internal.codegen.model.Binding) {
+      return BindingNodeImpl.create((dagger.internal.codegen.model.Binding) node);
+    } else if (node instanceof dagger.internal.codegen.model.BindingGraph.ComponentNode) {
+      return ComponentNodeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.ComponentNode) node);
+    } else if (node instanceof dagger.internal.codegen.model.BindingGraph.MissingBinding) {
+      return MissingBindingImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.MissingBinding) node);
+    } else {
+      throw new IllegalStateException("Unhandled node type: " + node.getClass());
+    }
+  }
+
+  private static Edge toModel(dagger.internal.codegen.model.BindingGraph.Edge edge) {
+    if (edge instanceof dagger.internal.codegen.model.BindingGraph.DependencyEdge) {
+      return DependencyEdgeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.DependencyEdge) edge);
+    } else if (edge instanceof dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge) {
+      return ChildFactoryMethodEdgeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge) edge);
+    } else if (edge
+        instanceof dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge) {
+      return SubcomponentCreatorBindingEdgeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge) edge);
+    } else {
+      throw new IllegalStateException("Unhandled edge type: " + edge.getClass());
+    }
+  }
+
+  private static MultibindingContributionIdentifier toModel(
+      dagger.internal.codegen.model.Key.MultibindingContributionIdentifier identifier) {
+    return new MultibindingContributionIdentifier(
+        XElements.getSimpleName(identifier.bindingMethod().xprocessing()),
+        identifier.contributingModule().xprocessing().getQualifiedName());
+  }
+
+  private static Key toModel(dagger.internal.codegen.model.Key key) {
+    return Key.builder(key.type().java())
+        .qualifier(key.qualifier().map(DaggerAnnotation::java))
+        .multibindingContributionIdentifier(
+            key.multibindingContributionIdentifier().isPresent()
+                ? Optional.of(toModel(key.multibindingContributionIdentifier().get()))
+                : Optional.empty())
+        .build();
+  }
+
+  private static BindingKind toModel(dagger.internal.codegen.model.BindingKind bindingKind) {
+    return BindingKind.valueOf(bindingKind.name());
+  }
+
+  private static RequestKind toModel(dagger.internal.codegen.model.RequestKind requestKind) {
+    return RequestKind.valueOf(requestKind.name());
+  }
+
+  private static DependencyRequest toModel(
+      dagger.internal.codegen.model.DependencyRequest request) {
+    DependencyRequest.Builder builder =
+        DependencyRequest.builder()
+            .kind(toModel(request.kind()))
+            .key(toModel(request.key()))
+            .isNullable(request.isNullable());
+
+    request.requestElement().ifPresent(e -> builder.requestElement(e.java()));
+    return builder.build();
+  }
+
+  private static Scope toModel(dagger.internal.codegen.model.Scope scope) {
+    return Scope.scope(scope.scopeAnnotation().java());
+  }
+
+  private static ComponentPath toModel(dagger.internal.codegen.model.ComponentPath path) {
+    return ComponentPath.create(
+        path.components().stream().map(DaggerTypeElement::java).collect(toImmutableList()));
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.ComponentNode toInternal(
+      ComponentNode componentNode) {
+    return ((ComponentNodeImpl) componentNode).delegate();
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.MaybeBinding toInternal(
+      MaybeBinding maybeBinding) {
+    if (maybeBinding instanceof MissingBindingImpl) {
+      return ((MissingBindingImpl) maybeBinding).delegate();
+    } else if (maybeBinding instanceof BindingNodeImpl) {
+      return ((BindingNodeImpl) maybeBinding).delegate();
+    } else {
+      throw new IllegalStateException("Unhandled binding type: " + maybeBinding.getClass());
+    }
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.DependencyEdge toInternal(
+      DependencyEdge dependencyEdge) {
+    return ((DependencyEdgeImpl) dependencyEdge).delegate();
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge toInternal(
+      ChildFactoryMethodEdge childFactoryMethodEdge) {
+    return ((ChildFactoryMethodEdgeImpl) childFactoryMethodEdge).delegate();
+  }
+
+  @AutoValue
+  abstract static class ComponentNodeImpl implements ComponentNode {
+    static ComponentNode create(
+        dagger.internal.codegen.model.BindingGraph.ComponentNode componentNode) {
+      return new AutoValue_ModelBindingGraphConverter_ComponentNodeImpl(
+          toModel(componentNode.componentPath()),
+          componentNode.isSubcomponent(),
+          componentNode.isRealComponent(),
+          componentNode.entryPoints().stream()
+              .map(ModelBindingGraphConverter::toModel)
+              .collect(toImmutableSet()),
+          componentNode.scopes().stream()
+              .map(ModelBindingGraphConverter::toModel)
+              .collect(toImmutableSet()),
+          componentNode);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.ComponentNode delegate();
+
+    @Override
+    public final String toString() {
+      return delegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class BindingNodeImpl implements Binding {
+    static Binding create(dagger.internal.codegen.model.Binding binding) {
+      return new AutoValue_ModelBindingGraphConverter_BindingNodeImpl(
+          toModel(binding.key()),
+          toModel(binding.componentPath()),
+          binding.dependencies().stream()
+              .map(ModelBindingGraphConverter::toModel)
+              .collect(toImmutableSet()),
+          binding.bindingElement().map(DaggerElement::java),
+          binding.contributingModule().map(DaggerTypeElement::java),
+          binding.requiresModuleInstance(),
+          binding.scope().map(ModelBindingGraphConverter::toModel),
+          binding.isNullable(),
+          binding.isProduction(),
+          toModel(binding.kind()),
+          binding);
+    }
+
+    abstract dagger.internal.codegen.model.Binding delegate();
+
+    @Override
+    public final String toString() {
+      return delegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class MissingBindingImpl extends MissingBinding {
+    static MissingBinding create(
+        dagger.internal.codegen.model.BindingGraph.MissingBinding missingBinding) {
+      return new AutoValue_ModelBindingGraphConverter_MissingBindingImpl(
+          toModel(missingBinding.componentPath()), toModel(missingBinding.key()), missingBinding);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.MissingBinding delegate();
+
+    @Memoized
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object o);
+  }
+
+  @AutoValue
+  abstract static class DependencyEdgeImpl implements DependencyEdge {
+    static DependencyEdge create(
+        dagger.internal.codegen.model.BindingGraph.DependencyEdge dependencyEdge) {
+      return new AutoValue_ModelBindingGraphConverter_DependencyEdgeImpl(
+          toModel(dependencyEdge.dependencyRequest()),
+          dependencyEdge.isEntryPoint(),
+          dependencyEdge);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.DependencyEdge delegate();
+
+    @Override
+    public final String toString() {
+      return delegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class ChildFactoryMethodEdgeImpl implements ChildFactoryMethodEdge {
+    static ChildFactoryMethodEdge create(
+        dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge childFactoryMethodEdge) {
+      return new AutoValue_ModelBindingGraphConverter_ChildFactoryMethodEdgeImpl(
+          childFactoryMethodEdge.factoryMethod().java(), childFactoryMethodEdge);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge delegate();
+
+    @Override
+    public final String toString() {
+      return delegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class SubcomponentCreatorBindingEdgeImpl
+      implements SubcomponentCreatorBindingEdge {
+    static SubcomponentCreatorBindingEdge create(
+        dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge
+            subcomponentCreatorBindingEdge) {
+      return new AutoValue_ModelBindingGraphConverter_SubcomponentCreatorBindingEdgeImpl(
+          subcomponentCreatorBindingEdge.declaringModules().stream()
+              .map(DaggerTypeElement::java)
+              .collect(toImmutableSet()),
+          subcomponentCreatorBindingEdge);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge delegate();
+
+    @Override
+    public final String toString() {
+      return delegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class BindingGraphImpl extends BindingGraph {
+    static BindingGraph create(dagger.internal.codegen.model.BindingGraph bindingGraph) {
+      BindingGraphImpl bindingGraphImpl =
+          new AutoValue_ModelBindingGraphConverter_BindingGraphImpl(
+              toModel(bindingGraph.network()), bindingGraph.isFullBindingGraph());
+
+      bindingGraphImpl.componentNodesByPath =
+          bindingGraphImpl.componentNodes().stream()
+              .collect(toImmutableMap(ComponentNode::componentPath, node -> node));
+
+      return bindingGraphImpl;
+    }
+
+    private ImmutableMap<ComponentPath, ComponentNode> componentNodesByPath;
+
+    // This overrides dagger.model.BindingGraph with a more efficient implementation.
+    @Override
+    public Optional<ComponentNode> componentNode(ComponentPath componentPath) {
+      return componentNodesByPath.containsKey(componentPath)
+          ? Optional.of(componentNodesByPath.get(componentPath))
+          : Optional.empty();
+    }
+
+    // This overrides dagger.model.BindingGraph to memoize the output.
+    @Override
+    @Memoized
+    public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() {
+      return super.nodesByClass();
+    }
+  }
+
+  private static final class DiagnosticReporterImpl implements DiagnosticReporter {
+    static DiagnosticReporterImpl create(
+        dagger.internal.codegen.model.DiagnosticReporter reporter) {
+      return new DiagnosticReporterImpl(reporter);
+    }
+
+    private final dagger.internal.codegen.model.DiagnosticReporter delegate;
+
+    DiagnosticReporterImpl(dagger.internal.codegen.model.DiagnosticReporter delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void reportComponent(
+        Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message) {
+      delegate.reportComponent(diagnosticKind, toInternal(componentNode), message);
+    }
+
+    @Override
+    @FormatMethod
+    public void reportComponent(
+        Diagnostic.Kind diagnosticKind,
+        ComponentNode componentNode,
+        String messageFormat,
+        Object firstArg,
+        Object... moreArgs) {
+      delegate.reportComponent(
+          diagnosticKind, toInternal(componentNode), messageFormat, firstArg, moreArgs);
+    }
+
+    @Override
+    public void reportBinding(
+        Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message) {
+      delegate.reportBinding(diagnosticKind, toInternal(binding), message);
+    }
+
+    @Override
+    @FormatMethod
+    public void reportBinding(
+        Diagnostic.Kind diagnosticKind,
+        MaybeBinding binding,
+        String messageFormat,
+        Object firstArg,
+        Object... moreArgs) {
+      delegate.reportBinding(
+          diagnosticKind, toInternal(binding), messageFormat, firstArg, moreArgs);
+    }
+
+    @Override
+    public void reportDependency(
+        Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) {
+      delegate.reportDependency(diagnosticKind, toInternal(dependencyEdge), message);
+    }
+
+    @Override
+    @FormatMethod
+    public void reportDependency(
+        Diagnostic.Kind diagnosticKind,
+        DependencyEdge dependencyEdge,
+        String messageFormat,
+        Object firstArg,
+        Object... moreArgs) {
+      delegate.reportDependency(
+          diagnosticKind, toInternal(dependencyEdge), messageFormat, firstArg, moreArgs);
+    }
+
+    @Override
+    public void reportSubcomponentFactoryMethod(
+        Diagnostic.Kind diagnosticKind,
+        ChildFactoryMethodEdge childFactoryMethodEdge,
+        String message) {
+      delegate.reportSubcomponentFactoryMethod(
+          diagnosticKind, toInternal(childFactoryMethodEdge), message);
+    }
+
+    @Override
+    @FormatMethod
+    public void reportSubcomponentFactoryMethod(
+        Diagnostic.Kind diagnosticKind,
+        ChildFactoryMethodEdge childFactoryMethodEdge,
+        String messageFormat,
+        Object firstArg,
+        Object... moreArgs) {
+      delegate.reportSubcomponentFactoryMethod(
+          diagnosticKind, toInternal(childFactoryMethodEdge), messageFormat, firstArg, moreArgs);
+    }
+  }
+}
diff --git a/java/dagger/internal/codegen/validation/ModuleValidator.java b/java/dagger/internal/codegen/validation/ModuleValidator.java
index 1e65187..a1d9b0d 100644
--- a/java/dagger/internal/codegen/validation/ModuleValidator.java
+++ b/java/dagger/internal/codegen/validation/ModuleValidator.java
@@ -62,10 +62,10 @@
 import dagger.internal.codegen.binding.InjectionAnnotations;
 import dagger.internal.codegen.binding.MethodSignatureFormatter;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.xprocessing.XElements;
 import dagger.internal.codegen.xprocessing.XTypeElements;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.Scope;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.EnumSet;
@@ -537,7 +537,7 @@
 
   private void validateModuleVisibility(
       XTypeElement moduleElement, ModuleKind moduleKind, ValidationReport.Builder reportBuilder) {
-    if (moduleElement.isPrivate()) {
+    if (moduleElement.isPrivate() || moduleElement.isKtPrivate()) {
       reportBuilder.addError("Modules cannot be private.", moduleElement);
     } else if (isEffectivelyPrivate(moduleElement)) {
       reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement);
diff --git a/java/dagger/internal/codegen/validation/ProducesMethodValidator.java b/java/dagger/internal/codegen/validation/ProducesMethodValidator.java
index 4e7ccda..cf78b63 100644
--- a/java/dagger/internal/codegen/validation/ProducesMethodValidator.java
+++ b/java/dagger/internal/codegen/validation/ProducesMethodValidator.java
@@ -17,7 +17,6 @@
 package dagger.internal.codegen.validation;
 
 import static com.google.common.collect.Iterables.getOnlyElement;
-import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableAnnotation;
 import static dagger.internal.codegen.validation.BindingElementValidator.AllowsMultibindings.ALLOWS_MULTIBINDINGS;
 import static dagger.internal.codegen.validation.BindingElementValidator.AllowsScoping.NO_SCOPING;
 import static dagger.internal.codegen.validation.BindingMethodValidator.Abstractness.MUST_BE_CONCRETE;
@@ -29,6 +28,7 @@
 import androidx.room.compiler.processing.XType;
 import com.google.common.util.concurrent.ListenableFuture;
 import dagger.internal.codegen.binding.InjectionAnnotations;
+import dagger.internal.codegen.binding.Nullability;
 import dagger.internal.codegen.javapoet.TypeNames;
 import dagger.internal.codegen.xprocessing.XTypes;
 import java.util.Optional;
@@ -89,7 +89,8 @@
      */
     // TODO(beder): Properly handle nullable with producer methods.
     private void checkNullable() {
-      if (getNullableAnnotation(method).isPresent()) {
+      Nullability nullability = Nullability.of(method);
+      if (nullability.nullableAnnotation().isPresent()) {
         report.addWarning("@Nullable on @Produces methods does not do anything");
       }
     }
diff --git a/java/dagger/internal/codegen/validation/SpiModelBindingGraphConverter.java b/java/dagger/internal/codegen/validation/SpiModelBindingGraphConverter.java
new file mode 100644
index 0000000..75d0f7f
--- /dev/null
+++ b/java/dagger/internal/codegen/validation/SpiModelBindingGraphConverter.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.validation;
+
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+import static androidx.room.compiler.processing.compat.XConverters.toKS;
+import static androidx.room.compiler.processing.compat.XConverters.toKSResolver;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
+
+import androidx.room.compiler.processing.XAnnotation;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XExecutableElement;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
+import com.google.auto.value.AutoValue;
+import com.google.auto.value.extension.memoized.Memoized;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.graph.EndpointPair;
+import com.google.common.graph.ImmutableNetwork;
+import com.google.common.graph.MutableNetwork;
+import com.google.common.graph.Network;
+import com.google.common.graph.NetworkBuilder;
+import dagger.internal.codegen.xprocessing.XElements;
+import dagger.spi.model.Binding;
+import dagger.spi.model.BindingGraph;
+import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge;
+import dagger.spi.model.BindingGraph.ComponentNode;
+import dagger.spi.model.BindingGraph.DependencyEdge;
+import dagger.spi.model.BindingGraph.Edge;
+import dagger.spi.model.BindingGraph.MaybeBinding;
+import dagger.spi.model.BindingGraph.MissingBinding;
+import dagger.spi.model.BindingGraph.Node;
+import dagger.spi.model.BindingGraph.SubcomponentCreatorBindingEdge;
+import dagger.spi.model.BindingKind;
+import dagger.spi.model.ComponentPath;
+import dagger.spi.model.DaggerAnnotation;
+import dagger.spi.model.DaggerElement;
+import dagger.spi.model.DaggerExecutableElement;
+import dagger.spi.model.DaggerProcessingEnv;
+import dagger.spi.model.DaggerProcessingEnv.Backend;
+import dagger.spi.model.DaggerType;
+import dagger.spi.model.DaggerTypeElement;
+import dagger.spi.model.DependencyRequest;
+import dagger.spi.model.DiagnosticReporter;
+import dagger.spi.model.Key;
+import dagger.spi.model.RequestKind;
+import dagger.spi.model.Scope;
+import java.util.Optional;
+import javax.tools.Diagnostic;
+
+/** A Utility class for converting to the {@link BindingGraph} used by external plugins. */
+public final class SpiModelBindingGraphConverter {
+  private SpiModelBindingGraphConverter() {}
+
+  public static DiagnosticReporter toSpiModel(
+      dagger.internal.codegen.model.DiagnosticReporter reporter) {
+    return DiagnosticReporterImpl.create(reporter);
+  }
+
+  public static BindingGraph toSpiModel(
+      dagger.internal.codegen.model.BindingGraph graph, XProcessingEnv env) {
+    return BindingGraphImpl.create(graph, env);
+  }
+
+  private static ImmutableNetwork<Node, Edge> toSpiModel(
+      Network<
+              dagger.internal.codegen.model.BindingGraph.Node,
+              dagger.internal.codegen.model.BindingGraph.Edge>
+          internalNetwork,
+      XProcessingEnv env) {
+    MutableNetwork<Node, Edge> network =
+        NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
+
+    ImmutableMap<dagger.internal.codegen.model.BindingGraph.Node, Node> fromInternalNodes =
+        internalNetwork.nodes().stream()
+            .collect(
+                toImmutableMap(
+                    node -> node, node -> SpiModelBindingGraphConverter.toSpiModel(node, env)));
+
+    for (Node node : fromInternalNodes.values()) {
+      network.addNode(node);
+    }
+    for (dagger.internal.codegen.model.BindingGraph.Edge edge : internalNetwork.edges()) {
+      EndpointPair<dagger.internal.codegen.model.BindingGraph.Node> edgePair =
+          internalNetwork.incidentNodes(edge);
+      network.addEdge(
+          fromInternalNodes.get(edgePair.source()),
+          fromInternalNodes.get(edgePair.target()),
+          toSpiModel(edge, env));
+    }
+    return ImmutableNetwork.copyOf(network);
+  }
+
+  private static Node toSpiModel(
+      dagger.internal.codegen.model.BindingGraph.Node node, XProcessingEnv env) {
+    if (node instanceof dagger.internal.codegen.model.Binding) {
+      return BindingNodeImpl.create((dagger.internal.codegen.model.Binding) node, env);
+    } else if (node instanceof dagger.internal.codegen.model.BindingGraph.ComponentNode) {
+      return ComponentNodeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.ComponentNode) node, env);
+    } else if (node instanceof dagger.internal.codegen.model.BindingGraph.MissingBinding) {
+      return MissingBindingImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.MissingBinding) node, env);
+    } else {
+      throw new IllegalStateException("Unhandled node type: " + node.getClass());
+    }
+  }
+
+  private static Edge toSpiModel(
+      dagger.internal.codegen.model.BindingGraph.Edge edge, XProcessingEnv env) {
+    if (edge instanceof dagger.internal.codegen.model.BindingGraph.DependencyEdge) {
+      return DependencyEdgeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.DependencyEdge) edge, env);
+    } else if (edge instanceof dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge) {
+      return ChildFactoryMethodEdgeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge) edge, env);
+    } else if (edge
+        instanceof dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge) {
+      return SubcomponentCreatorBindingEdgeImpl.create(
+          (dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge) edge, env);
+    } else {
+      throw new IllegalStateException("Unhandled edge type: " + edge.getClass());
+    }
+  }
+
+  private static Key toSpiModel(dagger.internal.codegen.model.Key key, XProcessingEnv env) {
+    Key.Builder builder =
+        Key.builder(toSpiModel(key.type().xprocessing(), env))
+            .qualifier(key.qualifier().map(qualifier -> toSpiModel(qualifier.xprocessing(), env)));
+    if (key.multibindingContributionIdentifier().isPresent()) {
+      return builder
+          .multibindingContributionIdentifier(
+              toSpiModel(
+                  key.multibindingContributionIdentifier().get().contributingModule().xprocessing(),
+                  env),
+              toSpiModel(
+                  key.multibindingContributionIdentifier().get().bindingMethod().xprocessing(),
+                  env))
+          .build();
+    }
+    return builder.build().withoutMultibindingContributionIdentifier();
+  }
+
+  private static BindingKind toSpiModel(dagger.internal.codegen.model.BindingKind bindingKind) {
+    return BindingKind.valueOf(bindingKind.name());
+  }
+
+  private static RequestKind toSpiModel(dagger.internal.codegen.model.RequestKind requestKind) {
+    return RequestKind.valueOf(requestKind.name());
+  }
+
+  @SuppressWarnings("CheckReturnValue")
+  private static DependencyRequest toSpiModel(
+      dagger.internal.codegen.model.DependencyRequest request, XProcessingEnv env) {
+    DependencyRequest.Builder builder =
+        DependencyRequest.builder()
+            .kind(toSpiModel(request.kind()))
+            .key(toSpiModel(request.key(), env))
+            .isNullable(request.isNullable());
+
+    request
+        .requestElement()
+        .ifPresent(e -> builder.requestElement(toSpiModel(e.xprocessing(), env)));
+    return builder.build();
+  }
+
+  private static Scope toSpiModel(dagger.internal.codegen.model.Scope scope, XProcessingEnv env) {
+    return Scope.scope(toSpiModel(scope.scopeAnnotation().xprocessing(), env));
+  }
+
+  private static ComponentPath toSpiModel(
+      dagger.internal.codegen.model.ComponentPath path, XProcessingEnv env) {
+    return ComponentPath.create(
+        path.components().stream()
+            .map(component -> toSpiModel(component.xprocessing(), env))
+            .collect(toImmutableList()));
+  }
+
+  private static DaggerTypeElement toSpiModel(XTypeElement typeElement, XProcessingEnv env) {
+    switch (env.getBackend()) {
+      case JAVAC:
+        return DaggerTypeElement.fromJavac(toJavac(typeElement));
+      case KSP:
+        return DaggerTypeElement.fromKsp(toKS(typeElement));
+    }
+    throw new IllegalStateException(
+        String.format("Backend %s is not supported yet.", env.getBackend()));
+  }
+
+  private static DaggerType toSpiModel(XType type, XProcessingEnv env) {
+    switch (env.getBackend()) {
+      case JAVAC:
+        return DaggerType.fromJavac(toJavac(type));
+      case KSP:
+        return DaggerType.fromKsp(toKS(type));
+    }
+    throw new IllegalStateException(
+        String.format("Backend %s is not supported yet.", env.getBackend()));
+  }
+
+  static DaggerAnnotation toSpiModel(XAnnotation annotation, XProcessingEnv env) {
+    DaggerTypeElement typeElement = toSpiModel(annotation.getTypeElement(), env);
+
+    switch (env.getBackend()) {
+      case JAVAC:
+        return DaggerAnnotation.fromJavac(typeElement, toJavac(annotation));
+      case KSP:
+        return DaggerAnnotation.fromKsp(typeElement, toKS(annotation));
+    }
+    throw new IllegalStateException(
+        String.format("Backend %s is not supported yet.", env.getBackend()));
+  }
+
+  private static DaggerElement toSpiModel(XElement element, XProcessingEnv env) {
+    switch (env.getBackend()) {
+      case JAVAC:
+        return DaggerElement.fromJavac(toJavac(element));
+      case KSP:
+        return DaggerElement.fromKsp(XElements.toKSAnnotated(element));
+    }
+    throw new IllegalStateException(
+        String.format("Backend %s is not supported yet.", env.getBackend()));
+  }
+
+  private static DaggerExecutableElement toSpiModel(
+      XExecutableElement executableElement, XProcessingEnv env) {
+    switch (env.getBackend()) {
+      case JAVAC:
+        return DaggerExecutableElement.fromJava(toJavac(executableElement));
+      case KSP:
+        return DaggerExecutableElement.fromKsp(toKS(executableElement));
+    }
+    throw new IllegalStateException(
+        String.format("Backend %s is not supported yet.", env.getBackend()));
+  }
+
+  static DaggerProcessingEnv toSpiModel(XProcessingEnv env) {
+    switch (env.getBackend()) {
+      case JAVAC:
+        return DaggerProcessingEnv.fromJavac(toJavac(env));
+      case KSP:
+        return DaggerProcessingEnv.fromKsp(toKS(env), toKSResolver(env));
+    }
+    throw new IllegalStateException(
+        String.format("Backend %s is not supported yet.", env.getBackend()));
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.ComponentNode toInternal(
+      ComponentNode componentNode) {
+    return ((ComponentNodeImpl) componentNode).internalDelegate();
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.MaybeBinding toInternal(
+      MaybeBinding maybeBinding) {
+    if (maybeBinding instanceof MissingBindingImpl) {
+      return ((MissingBindingImpl) maybeBinding).internalDelegate();
+    } else if (maybeBinding instanceof BindingNodeImpl) {
+      return ((BindingNodeImpl) maybeBinding).internalDelegate();
+    } else {
+      throw new IllegalStateException("Unhandled binding type: " + maybeBinding.getClass());
+    }
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.DependencyEdge toInternal(
+      DependencyEdge dependencyEdge) {
+    return ((DependencyEdgeImpl) dependencyEdge).internalDelegate();
+  }
+
+  private static dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge toInternal(
+      ChildFactoryMethodEdge childFactoryMethodEdge) {
+    return ((ChildFactoryMethodEdgeImpl) childFactoryMethodEdge).internalDelegate();
+  }
+
+  @AutoValue
+  abstract static class ComponentNodeImpl implements ComponentNode {
+    static ComponentNode create(
+        dagger.internal.codegen.model.BindingGraph.ComponentNode componentNode,
+        XProcessingEnv env) {
+      return new AutoValue_SpiModelBindingGraphConverter_ComponentNodeImpl(
+          toSpiModel(componentNode.componentPath(), env),
+          componentNode.isSubcomponent(),
+          componentNode.isRealComponent(),
+          componentNode.entryPoints().stream()
+              .map(request -> SpiModelBindingGraphConverter.toSpiModel(request, env))
+              .collect(toImmutableSet()),
+          componentNode.scopes().stream()
+              .map(request -> SpiModelBindingGraphConverter.toSpiModel(request, env))
+              .collect(toImmutableSet()),
+          componentNode);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.ComponentNode internalDelegate();
+
+    @Override
+    public final String toString() {
+      return internalDelegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class BindingNodeImpl implements Binding {
+    static Binding create(dagger.internal.codegen.model.Binding binding, XProcessingEnv env) {
+      return new AutoValue_SpiModelBindingGraphConverter_BindingNodeImpl(
+          toSpiModel(binding.key(), env),
+          toSpiModel(binding.componentPath(), env),
+          binding.dependencies().stream()
+              .map(request -> SpiModelBindingGraphConverter.toSpiModel(request, env))
+              .collect(toImmutableSet()),
+          binding.bindingElement().map(element -> toSpiModel(element.xprocessing(), env)),
+          binding.contributingModule().map(module -> toSpiModel(module.xprocessing(), env)),
+          binding.requiresModuleInstance(),
+          binding.scope().map(scope -> SpiModelBindingGraphConverter.toSpiModel(scope, env)),
+          binding.isNullable(),
+          binding.isProduction(),
+          toSpiModel(binding.kind()),
+          binding);
+    }
+
+    abstract dagger.internal.codegen.model.Binding internalDelegate();
+
+    @Override
+    public final String toString() {
+      return internalDelegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class MissingBindingImpl extends MissingBinding {
+    static MissingBinding create(
+        dagger.internal.codegen.model.BindingGraph.MissingBinding missingBinding,
+        XProcessingEnv env) {
+      return new AutoValue_SpiModelBindingGraphConverter_MissingBindingImpl(
+          toSpiModel(missingBinding.componentPath(), env),
+          toSpiModel(missingBinding.key(), env),
+          missingBinding);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.MissingBinding internalDelegate();
+
+    @Memoized
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object o);
+  }
+
+  @AutoValue
+  abstract static class DependencyEdgeImpl implements DependencyEdge {
+    static DependencyEdge create(
+        dagger.internal.codegen.model.BindingGraph.DependencyEdge dependencyEdge,
+        XProcessingEnv env) {
+      return new AutoValue_SpiModelBindingGraphConverter_DependencyEdgeImpl(
+          toSpiModel(dependencyEdge.dependencyRequest(), env),
+          dependencyEdge.isEntryPoint(),
+          dependencyEdge);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.DependencyEdge internalDelegate();
+
+    @Override
+    public final String toString() {
+      return internalDelegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class ChildFactoryMethodEdgeImpl implements ChildFactoryMethodEdge {
+    static ChildFactoryMethodEdge create(
+        dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge childFactoryMethodEdge,
+        XProcessingEnv env) {
+      return new AutoValue_SpiModelBindingGraphConverter_ChildFactoryMethodEdgeImpl(
+          toSpiModel(childFactoryMethodEdge.factoryMethod().xprocessing(), env),
+          childFactoryMethodEdge);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge internalDelegate();
+
+    @Override
+    public final String toString() {
+      return internalDelegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class SubcomponentCreatorBindingEdgeImpl
+      implements SubcomponentCreatorBindingEdge {
+    static SubcomponentCreatorBindingEdge create(
+        dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge
+            subcomponentCreatorBindingEdge,
+        XProcessingEnv env) {
+      return new AutoValue_SpiModelBindingGraphConverter_SubcomponentCreatorBindingEdgeImpl(
+          subcomponentCreatorBindingEdge.declaringModules().stream()
+              .map(module -> toSpiModel(module.xprocessing(), env))
+              .collect(toImmutableSet()),
+          subcomponentCreatorBindingEdge);
+    }
+
+    abstract dagger.internal.codegen.model.BindingGraph.SubcomponentCreatorBindingEdge
+        internalDelegate();
+
+    @Override
+    public final String toString() {
+      return internalDelegate().toString();
+    }
+  }
+
+  @AutoValue
+  abstract static class BindingGraphImpl extends BindingGraph {
+    static BindingGraph create(
+        dagger.internal.codegen.model.BindingGraph bindingGraph, XProcessingEnv env) {
+      BindingGraphImpl bindingGraphImpl =
+          new AutoValue_SpiModelBindingGraphConverter_BindingGraphImpl(
+              toSpiModel(bindingGraph.network(), env),
+              bindingGraph.isFullBindingGraph(),
+              Backend.valueOf(env.getBackend().name()));
+
+      bindingGraphImpl.componentNodesByPath =
+          bindingGraphImpl.componentNodes().stream()
+              .collect(toImmutableMap(ComponentNode::componentPath, node -> node));
+
+      return bindingGraphImpl;
+    }
+
+    private ImmutableMap<ComponentPath, ComponentNode> componentNodesByPath;
+
+    // This overrides dagger.model.BindingGraph with a more efficient implementation.
+    @Override
+    public Optional<ComponentNode> componentNode(ComponentPath componentPath) {
+      return componentNodesByPath.containsKey(componentPath)
+          ? Optional.of(componentNodesByPath.get(componentPath))
+          : Optional.empty();
+    }
+
+    // This overrides dagger.model.BindingGraph to memoize the output.
+    @Override
+    @Memoized
+    public ImmutableSetMultimap<Class<? extends Node>, ? extends Node> nodesByClass() {
+      return super.nodesByClass();
+    }
+  }
+
+  private static final class DiagnosticReporterImpl extends DiagnosticReporter {
+    static DiagnosticReporterImpl create(
+        dagger.internal.codegen.model.DiagnosticReporter reporter) {
+      return new DiagnosticReporterImpl(reporter);
+    }
+
+    private final dagger.internal.codegen.model.DiagnosticReporter delegate;
+
+    DiagnosticReporterImpl(dagger.internal.codegen.model.DiagnosticReporter delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void reportComponent(
+        Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message) {
+      delegate.reportComponent(diagnosticKind, toInternal(componentNode), message);
+    }
+
+    @Override
+    public void reportBinding(
+        Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message) {
+      delegate.reportBinding(diagnosticKind, toInternal(binding), message);
+    }
+
+    @Override
+    public void reportDependency(
+        Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) {
+      delegate.reportDependency(diagnosticKind, toInternal(dependencyEdge), message);
+    }
+
+    @Override
+    public void reportSubcomponentFactoryMethod(
+        Diagnostic.Kind diagnosticKind,
+        ChildFactoryMethodEdge childFactoryMethodEdge,
+        String message) {
+      delegate.reportSubcomponentFactoryMethod(
+          diagnosticKind, toInternal(childFactoryMethodEdge), message);
+    }
+  }
+}
diff --git a/java/dagger/internal/codegen/validation/Validation.java b/java/dagger/internal/codegen/validation/Validation.java
index 6d73151..3f9fcc7 100644
--- a/java/dagger/internal/codegen/validation/Validation.java
+++ b/java/dagger/internal/codegen/validation/Validation.java
@@ -22,8 +22,8 @@
 import javax.inject.Qualifier;
 
 /**
- * Qualifier annotation for the {@link dagger.spi.model.BindingGraphPlugin}s that are used to
- * implement core Dagger validation.
+ * Qualifier annotation for the {@link dagger.internal.codegen.model.BindingGraphPlugin}s that are
+ * used to implement core Dagger validation.
  */
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugin.java b/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugin.java
index e431069..b64d580 100644
--- a/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugin.java
+++ b/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugin.java
@@ -17,10 +17,10 @@
 package dagger.internal.codegen.validation;
 
 import com.google.common.base.Preconditions;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraph.ComponentNode;
-import dagger.spi.model.BindingGraphPlugin;
-import dagger.spi.model.DiagnosticReporter;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraph.ComponentNode;
+import dagger.internal.codegen.model.BindingGraphPlugin;
+import dagger.internal.codegen.model.DiagnosticReporter;
 import java.util.HashSet;
 import java.util.Set;
 
diff --git a/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugins.java b/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugins.java
index 9e2bbf4..dd22852 100644
--- a/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugins.java
+++ b/java/dagger/internal/codegen/validation/ValidationBindingGraphPlugins.java
@@ -28,10 +28,10 @@
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.compileroption.ProcessingOptions;
 import dagger.internal.codegen.compileroption.ValidationType;
+import dagger.internal.codegen.model.BindingGraph;
+import dagger.internal.codegen.model.BindingGraphPlugin;
+import dagger.internal.codegen.model.DaggerProcessingEnv;
 import dagger.internal.codegen.validation.DiagnosticReporterFactory.DiagnosticReporterImpl;
-import dagger.spi.model.BindingGraph;
-import dagger.spi.model.BindingGraphPlugin;
-import dagger.spi.model.DaggerProcessingEnv;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
diff --git a/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java b/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java
index 7256fd6..43811af 100644
--- a/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/AnonymousProviderCreationExpression.java
@@ -19,7 +19,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
 import static dagger.internal.codegen.javapoet.CodeBlocks.anonymousProvider;
-import static dagger.spi.model.RequestKind.INSTANCE;
+import static dagger.internal.codegen.model.RequestKind.INSTANCE;
 
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.CodeBlock;
diff --git a/java/dagger/internal/codegen/writing/AssistedFactoryRequestRepresentation.java b/java/dagger/internal/codegen/writing/AssistedFactoryRequestRepresentation.java
index cda0a0c..93a8ce5 100644
--- a/java/dagger/internal/codegen/writing/AssistedFactoryRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/AssistedFactoryRequestRepresentation.java
@@ -38,7 +38,7 @@
 import dagger.internal.codegen.binding.BindingGraph;
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.javapoet.Expression;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DependencyRequest;
 import java.util.Optional;
 
 /**
diff --git a/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java b/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java
index 308b4b1..42551a0 100644
--- a/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java
+++ b/java/dagger/internal/codegen/writing/AssistedInjectionParameters.java
@@ -32,8 +32,8 @@
 import dagger.internal.codegen.binding.AssistedInjectionAnnotations;
 import dagger.internal.codegen.binding.AssistedInjectionAnnotations.AssistedFactoryMetadata;
 import dagger.internal.codegen.binding.Binding;
+import dagger.internal.codegen.model.BindingKind;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
-import dagger.spi.model.BindingKind;
 import java.util.List;
 
 /** Utility class for generating unique assisted parameter names for a component shard. */
diff --git a/java/dagger/internal/codegen/writing/BUILD b/java/dagger/internal/codegen/writing/BUILD
index c99a253..c8f9b61 100644
--- a/java/dagger/internal/codegen/writing/BUILD
+++ b/java/dagger/internal/codegen/writing/BUILD
@@ -33,9 +33,9 @@
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/xprocessing",
         "//java/dagger/producers",
-        "//java/dagger/spi",
         "//third_party/java/auto:common",
         "//third_party/java/auto:value",
         "//third_party/java/error_prone:annotations",
diff --git a/java/dagger/internal/codegen/writing/ComponentImplementation.java b/java/dagger/internal/codegen/writing/ComponentImplementation.java
index e4028dd..204d6fa 100644
--- a/java/dagger/internal/codegen/writing/ComponentImplementation.java
+++ b/java/dagger/internal/codegen/writing/ComponentImplementation.java
@@ -84,10 +84,10 @@
 import dagger.internal.codegen.javapoet.TypeNames;
 import dagger.internal.codegen.javapoet.TypeSpecs;
 import dagger.internal.codegen.langmodel.Accessibility;
+import dagger.internal.codegen.model.BindingGraph.Node;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.xprocessing.XTypeElements;
-import dagger.spi.model.BindingGraph.Node;
-import dagger.spi.model.Key;
-import dagger.spi.model.RequestKind;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
diff --git a/java/dagger/internal/codegen/writing/ComponentNames.java b/java/dagger/internal/codegen/writing/ComponentNames.java
index 8101037..b5f2f2f 100644
--- a/java/dagger/internal/codegen/writing/ComponentNames.java
+++ b/java/dagger/internal/codegen/writing/ComponentNames.java
@@ -36,8 +36,8 @@
 import dagger.internal.codegen.binding.ComponentDescriptor;
 import dagger.internal.codegen.binding.KeyFactory;
 import dagger.internal.codegen.compileroption.CompilerOptions;
-import dagger.spi.model.ComponentPath;
-import dagger.spi.model.Key;
+import dagger.internal.codegen.model.ComponentPath;
+import dagger.internal.codegen.model.Key;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
diff --git a/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java b/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java
index cfe6deb..124eba1 100644
--- a/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java
+++ b/java/dagger/internal/codegen/writing/ComponentRequestRepresentations.java
@@ -47,8 +47,8 @@
 import dagger.internal.codegen.binding.ProductionBinding;
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.javapoet.Expression;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.RequestKind;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
diff --git a/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java b/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java
index cdfdf26..cb442d4 100644
--- a/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java
+++ b/java/dagger/internal/codegen/writing/ComponentRequirementExpression.java
@@ -23,8 +23,8 @@
 /**
  * A factory for expressions of {@link ComponentRequirement}s in the generated component. This is
  * <em>not</em> a {@link RequestRepresentation}, since {@link ComponentRequirement}s do not have a
- * {@link dagger.spi.model.Key}. See {@link ComponentRequirementRequestRepresentation} for binding
- * expressions that are themselves a component requirement.
+ * {@link dagger.internal.codegen.model.Key}. See {@link ComponentRequirementRequestRepresentation}
+ * for binding expressions that are themselves a component requirement.
  */
 interface ComponentRequirementExpression {
   /**
diff --git a/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java b/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java
index 777bdff..673a659 100644
--- a/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/DelegateRequestRepresentation.java
@@ -22,7 +22,7 @@
 import static dagger.internal.codegen.base.RequestKinds.requestType;
 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
-import static dagger.spi.model.BindingKind.DELEGATE;
+import static dagger.internal.codegen.model.BindingKind.DELEGATE;
 
 import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XType;
@@ -36,7 +36,7 @@
 import dagger.internal.codegen.binding.BindsTypeChecker;
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.javapoet.Expression;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.RequestKind;
 
 /** A {@link dagger.internal.codegen.writing.RequestRepresentation} for {@code @Binds} methods. */
 final class DelegateRequestRepresentation extends RequestRepresentation {
diff --git a/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java b/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java
index 096d109..1fbb2cb 100644
--- a/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/DelegatingFrameworkInstanceCreationExpression.java
@@ -27,8 +27,8 @@
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.CodeBlocks;
+import dagger.internal.codegen.model.DependencyRequest;
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
-import dagger.spi.model.DependencyRequest;
 
 /** A framework instance creation expression for a {@link dagger.Binds @Binds} binding. */
 final class DelegatingFrameworkInstanceCreationExpression
diff --git a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java
index 339aeca..49cc024 100644
--- a/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/DependencyMethodProviderCreationExpression.java
@@ -47,6 +47,7 @@
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 
 /**
  * A {@link javax.inject.Provider} creation expression for a provision method on a component's
@@ -102,9 +103,12 @@
             .addModifiers(PUBLIC)
             .returns(keyType)
             .addStatement("return $L", invocation);
-    if (binding.nullableType().isPresent()) {
-      getMethod.addAnnotation(binding.nullableType().get().getTypeElement().getClassName());
-    }
+
+    binding
+        .nullability()
+        .nullableAnnotation()
+        .map(XAnnotations::getClassName)
+        .ifPresent(getMethod::addAnnotation);
 
     // We need to use the componentShard here since the generated type is static and shards are
     // not static classes so it can't be nested inside the shard.
diff --git a/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java b/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java
index 4da3df1..4095a60 100644
--- a/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/DerivedFromFrameworkInstanceRequestRepresentation.java
@@ -29,8 +29,8 @@
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.binding.FrameworkType;
 import dagger.internal.codegen.javapoet.Expression;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.RequestKind;
 
 /** A binding expression that depends on a framework instance. */
 final class DerivedFromFrameworkInstanceRequestRepresentation extends RequestRepresentation {
diff --git a/java/dagger/internal/codegen/writing/DirectInstanceBindingRepresentation.java b/java/dagger/internal/codegen/writing/DirectInstanceBindingRepresentation.java
index 2ac3888..1eb4249 100644
--- a/java/dagger/internal/codegen/writing/DirectInstanceBindingRepresentation.java
+++ b/java/dagger/internal/codegen/writing/DirectInstanceBindingRepresentation.java
@@ -26,8 +26,8 @@
 import dagger.internal.codegen.binding.BindingRequest;
 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
 import dagger.internal.codegen.binding.ProvisionBinding;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
-import dagger.spi.model.RequestKind;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
diff --git a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java
index c66204a..1ebbb24 100644
--- a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java
+++ b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviderDependencyRepresentation.java
@@ -33,10 +33,10 @@
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.javapoet.Expression;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.RequestKind;
 
 /**
  * Returns type casted expressions to satisfy dependency requests from experimental switching
diff --git a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java
index 8bb0ec4..586b5f5 100644
--- a/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java
+++ b/java/dagger/internal/codegen/writing/ExperimentalSwitchingProviders.java
@@ -40,9 +40,9 @@
 import com.squareup.javapoet.TypeVariableName;
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.javapoet.CodeBlocks;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
-import dagger.spi.model.Key;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/java/dagger/internal/codegen/writing/FactoryGenerator.java b/java/dagger/internal/codegen/writing/FactoryGenerator.java
index 015ffd4..9ec1314 100644
--- a/java/dagger/internal/codegen/writing/FactoryGenerator.java
+++ b/java/dagger/internal/codegen/writing/FactoryGenerator.java
@@ -32,10 +32,10 @@
 import static dagger.internal.codegen.javapoet.AnnotationSpecs.suppressWarnings;
 import static dagger.internal.codegen.javapoet.CodeBlocks.makeParametersCodeBlock;
 import static dagger.internal.codegen.javapoet.TypeNames.factoryOf;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
+import static dagger.internal.codegen.model.BindingKind.PROVISION;
 import static dagger.internal.codegen.writing.GwtCompatibility.gwtIncompatibleAnnotation;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
-import static dagger.spi.model.BindingKind.INJECTION;
-import static dagger.spi.model.BindingKind.PROVISION;
 import static javax.lang.model.element.Modifier.FINAL;
 import static javax.lang.model.element.Modifier.PRIVATE;
 import static javax.lang.model.element.Modifier.PUBLIC;
@@ -44,8 +44,6 @@
 import androidx.room.compiler.processing.XElement;
 import androidx.room.compiler.processing.XFiler;
 import androidx.room.compiler.processing.XProcessingEnv;
-import androidx.room.compiler.processing.XType;
-import androidx.room.compiler.processing.XTypeElement;
 import androidx.room.compiler.processing.XVariableElement;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -66,13 +64,14 @@
 import dagger.internal.codegen.binding.SourceFiles;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.Scope;
 import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
 import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.Scope;
+import dagger.internal.codegen.xprocessing.XAnnotations;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Stream;
@@ -270,9 +269,9 @@
 
     if (binding.kind().equals(PROVISION)) {
       binding
-          .nullableType()
-          .map(XType::getTypeElement)
-          .map(XTypeElement::getClassName)
+          .nullability()
+          .nullableAnnotation()
+          .map(XAnnotations::getClassName)
           .ifPresent(getMethod::addAnnotation);
       getMethod.addStatement("return $L", invokeNewInstance);
     } else if (!binding.injectionSites().isEmpty()) {
diff --git a/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java b/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java
index c1ae77c..5a98177 100644
--- a/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java
+++ b/java/dagger/internal/codegen/writing/FrameworkFieldInitializer.java
@@ -34,8 +34,8 @@
 import dagger.internal.codegen.binding.FrameworkField;
 import dagger.internal.codegen.javapoet.AnnotationSpecs;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.BindingKind;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
-import dagger.spi.model.BindingKind;
 import java.util.Optional;
 
 /**
diff --git a/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java b/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java
index 1e1517c..bb62d52 100644
--- a/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java
+++ b/java/dagger/internal/codegen/writing/FrameworkInstanceBindingRepresentation.java
@@ -18,8 +18,8 @@
 
 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
 import static dagger.internal.codegen.binding.BindingRequest.bindingRequest;
+import static dagger.internal.codegen.model.BindingKind.DELEGATE;
 import static dagger.internal.codegen.writing.ProvisionBindingRepresentation.needsCaching;
-import static dagger.spi.model.BindingKind.DELEGATE;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
@@ -28,7 +28,7 @@
 import dagger.internal.codegen.binding.BindingRequest;
 import dagger.internal.codegen.binding.FrameworkType;
 import dagger.internal.codegen.binding.ProvisionBinding;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.RequestKind;
 import java.util.HashMap;
 import java.util.Map;
 
diff --git a/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java b/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java
index 8bceddc..1353e88 100644
--- a/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java
+++ b/java/dagger/internal/codegen/writing/FrameworkInstanceKind.java
@@ -16,11 +16,11 @@
 
 package dagger.internal.codegen.writing;
 
-import static dagger.spi.model.BindingKind.DELEGATE;
+import static dagger.internal.codegen.model.BindingKind.DELEGATE;
 
 import dagger.internal.codegen.binding.ContributionBinding;
+import dagger.internal.codegen.model.BindingKind;
 import dagger.internal.codegen.writing.ComponentImplementation.CompilerMode;
-import dagger.spi.model.BindingKind;
 
 /** Generation mode for satisfying framework request to Provision Binding. */
 enum FrameworkInstanceKind {
diff --git a/java/dagger/internal/codegen/writing/InjectionMethods.java b/java/dagger/internal/codegen/writing/InjectionMethods.java
index 13ca2c9..75b950a 100644
--- a/java/dagger/internal/codegen/writing/InjectionMethods.java
+++ b/java/dagger/internal/codegen/writing/InjectionMethods.java
@@ -24,7 +24,6 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.squareup.javapoet.MethodSpec.methodBuilder;
 import static dagger.internal.codegen.binding.AssistedInjectionAnnotations.isAssistedParameter;
-import static dagger.internal.codegen.binding.ConfigurationAnnotations.getNullableType;
 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
 import static dagger.internal.codegen.binding.SourceFiles.memberInjectedFieldSignatureForVariable;
 import static dagger.internal.codegen.binding.SourceFiles.membersInjectorNameForType;
@@ -71,13 +70,14 @@
 import dagger.internal.Preconditions;
 import dagger.internal.codegen.base.UniqueNameSet;
 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
+import dagger.internal.codegen.binding.Nullability;
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.extension.DaggerCollectors;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DependencyRequest;
 import dagger.internal.codegen.xprocessing.XAnnotations;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DependencyRequest;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Function;
@@ -406,9 +406,9 @@
     if (isVoid(method.getReturnType())) {
       return builder.addStatement("$L", invocation).build();
     } else {
-      getNullableType(method)
-          .map(XType::getTypeElement)
-          .map(XTypeElement::getClassName)
+      Nullability.of(method)
+          .nullableAnnotation()
+          .map(XAnnotations::getClassName)
           .ifPresent(builder::addAnnotation);
       return builder
           .returns(method.getReturnType().getTypeName())
diff --git a/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java b/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java
index b9bc70b..d32531b 100644
--- a/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/InjectionOrProvisionProviderCreationExpression.java
@@ -18,7 +18,7 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static dagger.internal.codegen.binding.SourceFiles.generatedClassNameForBinding;
-import static dagger.spi.model.BindingKind.INJECTION;
+import static dagger.internal.codegen.model.BindingKind.INJECTION;
 
 import com.squareup.javapoet.CodeBlock;
 import dagger.assisted.Assisted;
diff --git a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java
index a326e0a..1f857db 100644
--- a/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/MapFactoryCreationExpression.java
@@ -31,7 +31,7 @@
 import dagger.internal.codegen.binding.BindingGraph;
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DependencyRequest;
 import java.util.stream.Stream;
 
 /** A factory creation expression for a multibound map. */
diff --git a/java/dagger/internal/codegen/writing/MapRequestRepresentation.java b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java
index cf14aab..bb49ebe 100644
--- a/java/dagger/internal/codegen/writing/MapRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/MapRequestRepresentation.java
@@ -22,8 +22,8 @@
 import static dagger.internal.codegen.binding.MapKeys.getMapKeyExpression;
 import static dagger.internal.codegen.javapoet.CodeBlocks.toParametersCodeBlock;
 import static dagger.internal.codegen.langmodel.Accessibility.isTypeAccessibleFrom;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_MAP;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
-import static dagger.spi.model.BindingKind.MULTIBOUND_MAP;
 
 import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XType;
@@ -41,8 +41,8 @@
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.javapoet.Expression;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.DependencyRequest;
 import java.util.Collections;
 
 /** A {@link RequestRepresentation} for multibound maps. */
diff --git a/java/dagger/internal/codegen/writing/MembersInjectionBindingRepresentation.java b/java/dagger/internal/codegen/writing/MembersInjectionBindingRepresentation.java
index cfad745..a1cde64 100644
--- a/java/dagger/internal/codegen/writing/MembersInjectionBindingRepresentation.java
+++ b/java/dagger/internal/codegen/writing/MembersInjectionBindingRepresentation.java
@@ -23,7 +23,7 @@
 import dagger.assisted.AssistedInject;
 import dagger.internal.codegen.binding.BindingRequest;
 import dagger.internal.codegen.binding.MembersInjectionBinding;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.RequestKind;
 
 /**
  * A binding representation that wraps code generation methods that satisfy all kinds of request for
diff --git a/java/dagger/internal/codegen/writing/MembersInjectionMethods.java b/java/dagger/internal/codegen/writing/MembersInjectionMethods.java
index 38707dc..bac7490 100644
--- a/java/dagger/internal/codegen/writing/MembersInjectionMethods.java
+++ b/java/dagger/internal/codegen/writing/MembersInjectionMethods.java
@@ -41,9 +41,9 @@
 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.javapoet.Expression;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
-import dagger.spi.model.Key;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import javax.inject.Inject;
diff --git a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java
index 9c65a45..82d12dd 100644
--- a/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java
+++ b/java/dagger/internal/codegen/writing/MembersInjectorGenerator.java
@@ -61,10 +61,10 @@
 import dagger.internal.codegen.binding.MembersInjectionBinding.InjectionSite;
 import dagger.internal.codegen.binding.SourceFiles;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DaggerAnnotation;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.writing.InjectionMethods.InjectionSiteMethod;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
 import java.util.Map.Entry;
 import javax.inject.Inject;
 
diff --git a/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java
index 97fecae..5cb47bb 100644
--- a/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/MultibindingFactoryCreationExpression.java
@@ -22,9 +22,9 @@
 import dagger.internal.codegen.binding.BindingRequest;
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.javapoet.CodeBlocks;
+import dagger.internal.codegen.model.DependencyRequest;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
-import dagger.spi.model.DependencyRequest;
 
 /** An abstract factory creation expression for multibindings. */
 abstract class MultibindingFactoryCreationExpression
diff --git a/java/dagger/internal/codegen/writing/OptionalFactories.java b/java/dagger/internal/codegen/writing/OptionalFactories.java
index 43677b4..deb9dbf 100644
--- a/java/dagger/internal/codegen/writing/OptionalFactories.java
+++ b/java/dagger/internal/codegen/writing/OptionalFactories.java
@@ -61,9 +61,9 @@
 import dagger.internal.codegen.binding.FrameworkType;
 import dagger.internal.codegen.javapoet.AnnotationSpecs;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.producers.Producer;
 import dagger.producers.internal.Producers;
-import dagger.spi.model.RequestKind;
 import java.util.Comparator;
 import java.util.Map;
 import java.util.Optional;
diff --git a/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java b/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java
index df9d15e..2f2ae58 100644
--- a/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/OptionalFactoryInstanceCreationExpression.java
@@ -27,8 +27,8 @@
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
 
 /**
- * A {@link FrameworkInstanceCreationExpression} for {@link dagger.spi.model.BindingKind#OPTIONAL
- * optional bindings}.
+ * A {@link FrameworkInstanceCreationExpression} for {@link
+ * dagger.internal.codegen.model.BindingKind#OPTIONAL optional bindings}.
  */
 final class OptionalFactoryInstanceCreationExpression
     implements FrameworkInstanceCreationExpression {
diff --git a/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java b/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java
index 28d0495..b775314 100644
--- a/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/OptionalRequestRepresentation.java
@@ -32,7 +32,7 @@
 import dagger.internal.codegen.base.OptionalType.OptionalKind;
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.javapoet.Expression;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DependencyRequest;
 
 /** A binding expression for optional bindings. */
 final class OptionalRequestRepresentation extends RequestRepresentation {
diff --git a/java/dagger/internal/codegen/writing/PrivateMethodRequestRepresentation.java b/java/dagger/internal/codegen/writing/PrivateMethodRequestRepresentation.java
index a1de5fb..fe973dc 100644
--- a/java/dagger/internal/codegen/writing/PrivateMethodRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/PrivateMethodRequestRepresentation.java
@@ -35,8 +35,8 @@
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.ExpressionType;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
-import dagger.spi.model.RequestKind;
 
 /**
  * A binding expression that wraps the dependency expressions in a private, no-arg method.
diff --git a/java/dagger/internal/codegen/writing/ProducerEntryPointView.java b/java/dagger/internal/codegen/writing/ProducerEntryPointView.java
index 752bc19..648e253 100644
--- a/java/dagger/internal/codegen/writing/ProducerEntryPointView.java
+++ b/java/dagger/internal/codegen/writing/ProducerEntryPointView.java
@@ -29,11 +29,11 @@
 import dagger.internal.codegen.binding.ComponentDescriptor.ComponentMethodDescriptor;
 import dagger.internal.codegen.javapoet.Expression;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.producers.Producer;
 import dagger.producers.internal.CancellationListener;
 import dagger.producers.internal.Producers;
-import dagger.spi.model.RequestKind;
 import java.util.Optional;
 
 /**
diff --git a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java
index 05b2a21..e6c88a9 100644
--- a/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java
+++ b/java/dagger/internal/codegen/writing/ProducerFactoryGenerator.java
@@ -74,12 +74,12 @@
 import dagger.internal.codegen.javapoet.AnnotationSpecs;
 import dagger.internal.codegen.javapoet.AnnotationSpecs.Suppression;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.DependencyRequest;
+import dagger.internal.codegen.model.Key;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.producers.Producer;
 import dagger.producers.internal.AbstractProducesMethodProducer;
 import dagger.producers.internal.Producers;
-import dagger.spi.model.DependencyRequest;
-import dagger.spi.model.Key;
-import dagger.spi.model.RequestKind;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map.Entry;
diff --git a/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java b/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java
index d385613..05dd50b 100644
--- a/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/ProducerFromProviderCreationExpression.java
@@ -24,9 +24,9 @@
 import dagger.assisted.AssistedInject;
 import dagger.internal.codegen.binding.FrameworkType;
 import dagger.internal.codegen.javapoet.TypeNames;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
 import dagger.producers.Producer;
-import dagger.spi.model.RequestKind;
 import java.util.Optional;
 
 /** An {@link Producer} creation expression for provision bindings. */
diff --git a/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java b/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java
index 186b159..813010c 100644
--- a/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/ProducerNodeInstanceRequestRepresentation.java
@@ -26,9 +26,9 @@
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.binding.FrameworkType;
 import dagger.internal.codegen.javapoet.Expression;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.producers.internal.Producers;
-import dagger.spi.model.Key;
 
 /** Binding expression for producer node instances. */
 final class ProducerNodeInstanceRequestRepresentation
diff --git a/java/dagger/internal/codegen/writing/ProductionBindingRepresentation.java b/java/dagger/internal/codegen/writing/ProductionBindingRepresentation.java
index 1e5fa6c..0793d6e 100644
--- a/java/dagger/internal/codegen/writing/ProductionBindingRepresentation.java
+++ b/java/dagger/internal/codegen/writing/ProductionBindingRepresentation.java
@@ -17,8 +17,8 @@
 package dagger.internal.codegen.writing;
 
 import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
-import static dagger.spi.model.BindingKind.MULTIBOUND_MAP;
-import static dagger.spi.model.BindingKind.MULTIBOUND_SET;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_MAP;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_SET;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
diff --git a/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java b/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java
index faed99e..4ae7f37 100644
--- a/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java
+++ b/java/dagger/internal/codegen/writing/ProvisionBindingRepresentation.java
@@ -16,8 +16,8 @@
 
 package dagger.internal.codegen.writing;
 
+import static dagger.internal.codegen.model.BindingKind.DELEGATE;
 import static dagger.internal.codegen.writing.DelegateRequestRepresentation.isBindsScopeStrongerThanDependencyScope;
-import static dagger.spi.model.BindingKind.DELEGATE;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
@@ -26,8 +26,8 @@
 import dagger.internal.codegen.binding.BindingRequest;
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.compileroption.CompilerOptions;
+import dagger.internal.codegen.model.RequestKind;
 import dagger.internal.codegen.writing.ComponentImplementation.CompilerMode;
-import dagger.spi.model.RequestKind;
 
 /**
  * A binding representation that wraps code generation methods that satisfy all kinds of request for
diff --git a/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java b/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java
index 624a412..559bb7d 100644
--- a/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java
+++ b/java/dagger/internal/codegen/writing/SetFactoryCreationExpression.java
@@ -29,7 +29,7 @@
 import dagger.internal.codegen.binding.BindingType;
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DependencyRequest;
 
 /** A factory creation expression for a multibound set. */
 final class SetFactoryCreationExpression extends MultibindingFactoryCreationExpression {
diff --git a/java/dagger/internal/codegen/writing/SetRequestRepresentation.java b/java/dagger/internal/codegen/writing/SetRequestRepresentation.java
index 5c842a2..f5d77ad 100644
--- a/java/dagger/internal/codegen/writing/SetRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/SetRequestRepresentation.java
@@ -38,7 +38,7 @@
 import dagger.internal.codegen.javapoet.CodeBlocks;
 import dagger.internal.codegen.javapoet.Expression;
 import dagger.internal.codegen.javapoet.TypeNames;
-import dagger.spi.model.DependencyRequest;
+import dagger.internal.codegen.model.DependencyRequest;
 import java.util.Collections;
 
 /** A binding expression for multibound sets. */
diff --git a/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java b/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java
index 0ac9bfd..4026f16 100644
--- a/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java
+++ b/java/dagger/internal/codegen/writing/SimpleMethodRequestRepresentation.java
@@ -41,14 +41,14 @@
 import dagger.internal.codegen.binding.ProvisionBinding;
 import dagger.internal.codegen.compileroption.CompilerOptions;
 import dagger.internal.codegen.javapoet.Expression;
+import dagger.internal.codegen.model.DependencyRequest;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.internal.codegen.writing.InjectionMethods.ProvisionMethod;
-import dagger.spi.model.DependencyRequest;
 import java.util.Optional;
 
 /**
  * A binding expression that invokes methods or constructors directly (without attempting to scope)
- * {@link dagger.spi.model.RequestKind#INSTANCE} requests.
+ * {@link dagger.internal.codegen.model.RequestKind#INSTANCE} requests.
  */
 final class SimpleMethodRequestRepresentation extends RequestRepresentation {
   private final CompilerOptions compilerOptions;
diff --git a/java/dagger/internal/codegen/writing/StaticFactoryInstanceSupplier.java b/java/dagger/internal/codegen/writing/StaticFactoryInstanceSupplier.java
index 5537c8f..070cd63 100644
--- a/java/dagger/internal/codegen/writing/StaticFactoryInstanceSupplier.java
+++ b/java/dagger/internal/codegen/writing/StaticFactoryInstanceSupplier.java
@@ -16,8 +16,8 @@
 
 package dagger.internal.codegen.writing;
 
-import static dagger.spi.model.BindingKind.MULTIBOUND_MAP;
-import static dagger.spi.model.BindingKind.MULTIBOUND_SET;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_MAP;
+import static dagger.internal.codegen.model.BindingKind.MULTIBOUND_SET;
 
 import dagger.assisted.Assisted;
 import dagger.assisted.AssistedFactory;
diff --git a/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java b/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java
index 839d136..7bea206 100644
--- a/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java
+++ b/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java
@@ -26,9 +26,9 @@
 import dagger.internal.codegen.binding.Binding;
 import dagger.internal.codegen.binding.BindingGraph;
 import dagger.internal.codegen.binding.ProvisionBinding;
+import dagger.internal.codegen.model.BindingKind;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
-import dagger.spi.model.BindingKind;
 
 /**
  * An object that initializes a framework-type component field for a binding using instances created
diff --git a/java/dagger/internal/codegen/writing/SwitchingProviders.java b/java/dagger/internal/codegen/writing/SwitchingProviders.java
index dc6ec94..e6c3bbf 100644
--- a/java/dagger/internal/codegen/writing/SwitchingProviders.java
+++ b/java/dagger/internal/codegen/writing/SwitchingProviders.java
@@ -41,11 +41,11 @@
 import com.squareup.javapoet.TypeVariableName;
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.javapoet.CodeBlocks;
+import dagger.internal.codegen.model.BindingKind;
+import dagger.internal.codegen.model.Key;
 import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
 import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
 import dagger.internal.codegen.xprocessing.XProcessingEnvs;
-import dagger.spi.model.BindingKind;
-import dagger.spi.model.Key;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
diff --git a/java/dagger/internal/codegen/writing/UnscopedDirectInstanceRequestRepresentationFactory.java b/java/dagger/internal/codegen/writing/UnscopedDirectInstanceRequestRepresentationFactory.java
index 28c2370..cf4f260 100644
--- a/java/dagger/internal/codegen/writing/UnscopedDirectInstanceRequestRepresentationFactory.java
+++ b/java/dagger/internal/codegen/writing/UnscopedDirectInstanceRequestRepresentationFactory.java
@@ -19,7 +19,7 @@
 import dagger.internal.codegen.binding.ComponentRequirement;
 import dagger.internal.codegen.binding.ContributionBinding;
 import dagger.internal.codegen.binding.ProvisionBinding;
-import dagger.spi.model.RequestKind;
+import dagger.internal.codegen.model.RequestKind;
 import javax.inject.Inject;
 
 /**
diff --git a/java/dagger/internal/codegen/writing/UnscopedFrameworkInstanceCreationExpressionFactory.java b/java/dagger/internal/codegen/writing/UnscopedFrameworkInstanceCreationExpressionFactory.java
index 5f37625..36807c2 100644
--- a/java/dagger/internal/codegen/writing/UnscopedFrameworkInstanceCreationExpressionFactory.java
+++ b/java/dagger/internal/codegen/writing/UnscopedFrameworkInstanceCreationExpressionFactory.java
@@ -156,7 +156,7 @@
   private InstanceFactoryCreationExpression instanceFactoryCreationExpression(
       ContributionBinding binding, ComponentRequirement componentRequirement) {
     return new InstanceFactoryCreationExpression(
-        binding.nullableType().isPresent(),
+        binding.isNullable(),
         () ->
             componentRequirementExpressions.getExpressionDuringInitialization(
                 componentRequirement, componentImplementation.name()));
diff --git a/java/dagger/internal/codegen/xprocessing/BUILD b/java/dagger/internal/codegen/xprocessing/BUILD
index b798835..262be17 100644
--- a/java/dagger/internal/codegen/xprocessing/BUILD
+++ b/java/dagger/internal/codegen/xprocessing/BUILD
@@ -34,7 +34,9 @@
         "//third_party/java/guava/base",
         "//third_party/java/guava/collect",
         "//third_party/java/javapoet",
+        "//third_party/java/jsr305_annotations",
         "//third_party/kotlin/kotlinpoet",
+        "@maven//:com_google_devtools_ksp_symbol_processing_api",
         "@maven//:org_jetbrains_kotlin_kotlin_stdlib",
     ],
 )
diff --git a/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java b/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java
index 7a081fa..b28d9c0 100644
--- a/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java
+++ b/java/dagger/internal/codegen/xprocessing/JavaPoetExt.java
@@ -18,8 +18,10 @@
 
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 
+import androidx.room.compiler.processing.XExecutableParameterElement;
 import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
+import com.squareup.javapoet.ParameterSpec;
 import com.squareup.javapoet.TypeSpec;
 
 // TODO(bcorso): Consider moving these methods into XProcessing library.
@@ -47,5 +49,10 @@
     return builder;
   }
 
+  public static ParameterSpec toParameterSpec(XExecutableParameterElement param) {
+    return ParameterSpec.builder(param.getType().getTypeName(), XElements.getSimpleName(param))
+        .build();
+  }
+
   private JavaPoetExt() {}
 }
diff --git a/java/dagger/internal/codegen/xprocessing/XAnnotations.java b/java/dagger/internal/codegen/xprocessing/XAnnotations.java
index 98f4dfc..f75fab2 100644
--- a/java/dagger/internal/codegen/xprocessing/XAnnotations.java
+++ b/java/dagger/internal/codegen/xprocessing/XAnnotations.java
@@ -18,13 +18,17 @@
 
 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static java.util.stream.Collectors.joining;
 
 import androidx.room.compiler.processing.JavaPoetExtKt;
 import androidx.room.compiler.processing.XAnnotation;
 import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import androidx.room.compiler.processing.XTypeElement;
 import com.google.auto.common.AnnotationMirrors;
 import com.google.common.base.Equivalence;
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.AnnotationSpec;
 import com.squareup.javapoet.ClassName;
 import java.util.Arrays;
@@ -35,7 +39,7 @@
 
   /** Returns the {@link AnnotationSpec} for the given annotation */
   public static AnnotationSpec getAnnotationSpec(XAnnotation annotation) {
-    return JavaPoetExtKt.toAnnotationSpec(annotation);
+    return JavaPoetExtKt.toAnnotationSpec(annotation, false);
   }
 
   /** Returns the string representation of the given annotation. */
@@ -101,12 +105,6 @@
       if (annotation.getType().isError()) {
         return "@" + annotation.getName(); // SUPPRESS_GET_NAME_CHECK
       }
-      // TODO(b/264089557): Non-annotation elements can be incorrectly treated as annotation in KSP,
-      // therefore calling getAnnotationValues() can cause confusing error.
-      if (getProcessingEnv(annotation).getBackend() == XProcessingEnv.Backend.KSP
-          && annotation.getTypeElement().getConstructors().size() != 1) {
-        return String.format("@%s", getClassName(annotation).canonicalName());
-      }
       return annotation.getAnnotationValues().isEmpty()
           // If the annotation doesn't have values then skip the empty parenthesis.
           ? String.format("@%s", getClassName(annotation).canonicalName())
@@ -132,5 +130,18 @@
     }
   }
 
+  /** Returns the value of the given [key] as a type element. */
+  public static XTypeElement getAsTypeElement(XAnnotation annotation, String key) {
+    return annotation.getAsType(key).getTypeElement();
+  }
+
+  /** Returns the value of the given [key] as a list of type elements. */
+  public static ImmutableList<XTypeElement> getAsTypeElementList(
+      XAnnotation annotation, String key) {
+    return annotation.getAsTypeList(key).stream()
+        .map(XType::getTypeElement)
+        .collect(toImmutableList());
+  }
+
   private XAnnotations() {}
 }
diff --git a/java/dagger/internal/codegen/xprocessing/XElements.java b/java/dagger/internal/codegen/xprocessing/XElements.java
index 53404f8..552be65 100644
--- a/java/dagger/internal/codegen/xprocessing/XElements.java
+++ b/java/dagger/internal/codegen/xprocessing/XElements.java
@@ -22,70 +22,38 @@
 import static androidx.room.compiler.processing.XElementKt.isMethodParameter;
 import static androidx.room.compiler.processing.XElementKt.isTypeElement;
 import static androidx.room.compiler.processing.XElementKt.isVariableElement;
-import static androidx.room.compiler.processing.XTypeKt.isArray;
-import static androidx.room.compiler.processing.XTypeKt.isByte;
-import static androidx.room.compiler.processing.XTypeKt.isInt;
-import static androidx.room.compiler.processing.XTypeKt.isKotlinUnit;
-import static androidx.room.compiler.processing.XTypeKt.isLong;
-import static androidx.room.compiler.processing.XTypeKt.isVoid;
-import static androidx.room.compiler.processing.XTypeKt.isVoidObject;
 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static androidx.room.compiler.processing.compat.XConverters.toJavac;
-import static com.google.auto.common.MoreElements.asType;
-import static com.google.auto.common.MoreElements.isType;
+import static androidx.room.compiler.processing.compat.XConverters.toKS;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
-import static dagger.internal.codegen.xprocessing.XTypeElements.isNested;
-import static dagger.internal.codegen.xprocessing.XTypes.isBoolean;
-import static dagger.internal.codegen.xprocessing.XTypes.isChar;
-import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
-import static dagger.internal.codegen.xprocessing.XTypes.isDouble;
-import static dagger.internal.codegen.xprocessing.XTypes.isFloat;
-import static dagger.internal.codegen.xprocessing.XTypes.isShort;
-import static dagger.internal.codegen.xprocessing.XTypes.isTypeVariable;
-import static dagger.internal.codegen.xprocessing.XTypes.isWildcard;
 import static java.util.stream.Collectors.joining;
 
 import androidx.room.compiler.processing.XAnnotated;
 import androidx.room.compiler.processing.XAnnotation;
-import androidx.room.compiler.processing.XArrayType;
 import androidx.room.compiler.processing.XConstructorElement;
 import androidx.room.compiler.processing.XElement;
 import androidx.room.compiler.processing.XEnumEntry;
 import androidx.room.compiler.processing.XEnumTypeElement;
 import androidx.room.compiler.processing.XExecutableElement;
 import androidx.room.compiler.processing.XExecutableParameterElement;
-import androidx.room.compiler.processing.XExecutableType;
 import androidx.room.compiler.processing.XFieldElement;
 import androidx.room.compiler.processing.XHasModifiers;
 import androidx.room.compiler.processing.XMemberContainer;
 import androidx.room.compiler.processing.XMethodElement;
-import androidx.room.compiler.processing.XMethodType;
 import androidx.room.compiler.processing.XProcessingEnv;
-import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
 import androidx.room.compiler.processing.XTypeParameterElement;
 import androidx.room.compiler.processing.XVariableElement;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.ksp.symbol.KSAnnotated;
 import com.squareup.javapoet.ClassName;
 import java.util.Collection;
 import java.util.Optional;
-import javax.lang.model.element.Element;
+import javax.annotation.Nullable;
 import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.ArrayType;
-import javax.lang.model.type.DeclaredType;
-import javax.lang.model.type.ErrorType;
-import javax.lang.model.type.ExecutableType;
-import javax.lang.model.type.IntersectionType;
-import javax.lang.model.type.NoType;
-import javax.lang.model.type.PrimitiveType;
-import javax.lang.model.type.TypeMirror;
-import javax.lang.model.type.TypeVariable;
-import javax.lang.model.type.WildcardType;
-import javax.lang.model.util.SimpleTypeVisitor8;
 
 // TODO(bcorso): Consider moving these methods into XProcessing library.
 /** A utility class for {@link XElement} helper methods. */
@@ -126,9 +94,43 @@
     throw new AssertionError("No simple name for: " + element);
   }
 
+  private static boolean isSyntheticElement(XElement element) {
+    if (isMethodParameter(element)) {
+      XExecutableParameterElement executableParam = asMethodParameter(element);
+      return executableParam.isContinuationParam()
+          || executableParam.isReceiverParam()
+          || executableParam.isKotlinPropertyParam();
+    }
+    if (isMethod(element)) {
+      return asMethod(element).isKotlinPropertyMethod();
+    }
+    return false;
+  }
+
+  @Nullable
+  public static KSAnnotated toKSAnnotated(XElement element) {
+    if (isSyntheticElement(element)) {
+      return toKS(element);
+    }
+    if (isExecutable(element)) {
+      return toKS(asExecutable(element));
+    }
+    if (isTypeElement(element)) {
+      return toKS(asTypeElement(element));
+    }
+    if (isField(element)) {
+      return toKS(asField(element));
+    }
+    if (isMethodParameter(element)) {
+      return toKS(asMethodParameter(element));
+    }
+    throw new IllegalStateException(
+        "Returning KSAnnotated declaration for " + element + " is not supported.");
+  }
+
   /**
    * Returns the closest enclosing element that is a {@link XTypeElement} or throws an {@link
-   * IllegalStateException} if one doesn't exists.
+   * IllegalStateException} if one doesn't exist.
    */
   public static XTypeElement closestEnclosingTypeElement(XElement element) {
     return optionalClosestEnclosingTypeElement(element)
@@ -313,216 +315,13 @@
   }
 
   /**
-   * Returns the field descriptor of the given {@code element}.
-   *
-   * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST.
-   *
-   * <p>For reference, see the <a
-   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2">JVM
-   * specification, section 4.3.2</a>.
-   */
-  public static String getFieldDescriptor(XFieldElement element) {
-    return getSimpleName(element) + ":" + getDescriptor(element.getType());
-  }
-
-  /**
-   * Returns the method descriptor of the given {@code element}.
-   *
-   * <p>This is useful for matching Kotlin Metadata JVM Signatures with elements from the AST.
-   *
-   * <p>For reference, see the <a
-   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3">JVM
-   * specification, section 4.3.3</a>.
-   */
-  // TODO(bcorso): Expose getMethodDescriptor() method in XProcessing instead.
-  public static String getMethodDescriptor(XMethodElement element) {
-    return getSimpleName(element) + getDescriptor(element.getExecutableType());
-  }
-
-  private static String getDescriptor(XExecutableType type) {
-    String parameterDescriptors =
-        type.getParameterTypes().stream().map(XElements::getDescriptor).collect(joining());
-    String returnDescriptor =
-        XTypes.isMethod(type) ? getDescriptor(((XMethodType) type).getReturnType()) : "V";
-    return "(" + parameterDescriptors + ")" + returnDescriptor;
-  }
-
-  private static String getDescriptor(XType type) {
-    XProcessingEnv processingEnv = getProcessingEnv(type);
-    switch (processingEnv.getBackend()) {
-      case JAVAC:
-        return javacGetDescriptor(toJavac(type));
-      case KSP:
-        return kspGetDescriptor(type);
-    }
-    throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
-  }
-
-  private static String kspGetDescriptor(XType type) {
-    if (isKotlinUnit(type) || type.isNone() || isVoid(type) || isVoidObject(type)) {
-      return "V";
-    } else if (isArray(type)) {
-      XArrayType arrayType = (XArrayType) type;
-      return "[" + getDescriptor(arrayType.getComponentType());
-    } else if (isDeclared(type) || type.isError()) {
-      return "L" + getInternalName(type.getTypeElement()) + ";";
-    } else if (XTypes.isExecutable(type)) {
-      return getDescriptor((XExecutableType) type);
-    } else if (isTypeVariable(type)) {
-      // TODO(b/231169600) Expose bounds for type argument from XProcessing to obtain descriptor.
-      throw new AssertionError("Generic type is currently unsupported: " + type);
-    } else if (isInt(type)) {
-      return "I";
-    } else if (isLong(type)) {
-      return "J";
-    } else if (isByte(type)) {
-      return "B";
-    } else if (isShort(type)) {
-      return "S";
-    } else if (isDouble(type)) {
-      return "D";
-    } else if (isFloat(type)) {
-      return "F";
-    } else if (isBoolean(type)) {
-      return "Z";
-    } else if (isChar(type)) {
-      return "C";
-    } else if (isWildcard(type)) {
-      return "";
-    }
-    throw new AssertionError("Unexpected type: " + type);
-  }
-
-  /**
-   * Returns the name of this element in its "internal form".
-   *
-   * <p>For reference, see the <a
-   * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.2">JVM
-   * specification, section 4.2</a>.
-   */
-  private static String getInternalName(XTypeElement element) {
-    if (isNested(element)) {
-      return getInternalName(element.getEnclosingTypeElement()) + "$" + getSimpleName(element);
-    }
-    return element.getQualifiedName().replace('.', '/');
-  }
-
-  private static String javacGetDescriptor(TypeMirror type) {
-    return type.accept(JVM_DESCRIPTOR_TYPE_VISITOR, null);
-  }
-
-  private static final SimpleTypeVisitor8<String, Void> JVM_DESCRIPTOR_TYPE_VISITOR =
-      new SimpleTypeVisitor8<String, Void>() {
-
-        @Override
-        public String visitArray(ArrayType arrayType, Void v) {
-          return "[" + javacGetDescriptor(arrayType.getComponentType());
-        }
-
-        @Override
-        public String visitDeclared(DeclaredType declaredType, Void v) {
-          return "L" + getInternalName(declaredType.asElement()) + ";";
-        }
-
-        @Override
-        public String visitError(ErrorType errorType, Void v) {
-          // For descriptor generating purposes we don't need a fully modeled type since we are
-          // only interested in obtaining the class name in its "internal form".
-          return visitDeclared(errorType, v);
-        }
-
-        @Override
-        public String visitExecutable(ExecutableType executableType, Void v) {
-          String parameterDescriptors =
-              executableType.getParameterTypes().stream()
-                  .map(XElements::javacGetDescriptor)
-                  .collect(joining());
-          String returnDescriptor = javacGetDescriptor(executableType.getReturnType());
-          return "(" + parameterDescriptors + ")" + returnDescriptor;
-        }
-
-        @Override
-        public String visitIntersection(IntersectionType intersectionType, Void v) {
-          // For a type variable with multiple bounds: "the erasure of a type variable is determined
-          // by the first type in its bound" - JVM Spec Sec 4.4
-          return javacGetDescriptor(intersectionType.getBounds().get(0));
-        }
-
-        @Override
-        public String visitNoType(NoType noType, Void v) {
-          return "V";
-        }
-
-        @Override
-        public String visitPrimitive(PrimitiveType primitiveType, Void v) {
-          switch (primitiveType.getKind()) {
-            case BOOLEAN:
-              return "Z";
-            case BYTE:
-              return "B";
-            case SHORT:
-              return "S";
-            case INT:
-              return "I";
-            case LONG:
-              return "J";
-            case CHAR:
-              return "C";
-            case FLOAT:
-              return "F";
-            case DOUBLE:
-              return "D";
-            default:
-              throw new IllegalArgumentException("Unknown primitive type.");
-          }
-        }
-
-        @Override
-        public String visitTypeVariable(TypeVariable typeVariable, Void v) {
-          // The erasure of a type variable is the erasure of its leftmost bound. - JVM Spec Sec 4.6
-          return javacGetDescriptor(typeVariable.getUpperBound());
-        }
-
-        @Override
-        public String defaultAction(TypeMirror typeMirror, Void v) {
-          throw new IllegalArgumentException("Unsupported type: " + typeMirror);
-        }
-
-        @Override
-        public String visitWildcard(WildcardType wildcardType, Void v) {
-          return "";
-        }
-
-        /**
-         * Returns the name of this element in its "internal form".
-         *
-         * <p>For reference, see the <a
-         * href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.2">JVM
-         * specification, section 4.2</a>.
-         */
-        private String getInternalName(Element element) {
-          if (isType(element)) {
-            TypeElement typeElement = asType(element);
-            switch (typeElement.getNestingKind()) {
-              case TOP_LEVEL:
-                return typeElement.getQualifiedName().toString().replace('.', '/');
-              case MEMBER:
-                return getInternalName(typeElement.getEnclosingElement())
-                    + "$"
-                    + typeElement.getSimpleName();
-              default:
-                throw new IllegalArgumentException("Unsupported nesting kind.");
-            }
-          }
-          return element.getSimpleName().toString();
-        }
-      };
-
-  /**
    * Returns a string representation of {@link XElement} that is independent of the backend
    * (javac/ksp).
    */
   public static String toStableString(XElement element) {
+    if (element == null) {
+      return "<null>";
+    }
     try {
       if (isTypeElement(element)) {
         return asTypeElement(element).getQualifiedName();
@@ -580,5 +379,9 @@
     return element.kindName();
   }
 
+  public static String packageName(XElement element) {
+    return element.getClosestMemberContainer().asClassName().getPackageName();
+  }
+
   private XElements() {}
 }
diff --git a/java/dagger/internal/codegen/xprocessing/XExecutableTypes.java b/java/dagger/internal/codegen/xprocessing/XExecutableTypes.java
index 92c3936..dee7235 100644
--- a/java/dagger/internal/codegen/xprocessing/XExecutableTypes.java
+++ b/java/dagger/internal/codegen/xprocessing/XExecutableTypes.java
@@ -16,17 +16,77 @@
 
 package dagger.internal.codegen.xprocessing;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static java.util.stream.Collectors.joining;
 
+import androidx.room.compiler.codegen.XTypeNameKt;
 import androidx.room.compiler.processing.XConstructorType;
+import androidx.room.compiler.processing.XExecutableElement;
 import androidx.room.compiler.processing.XExecutableType;
 import androidx.room.compiler.processing.XMethodType;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XType;
+import com.google.common.collect.ImmutableList;
 import com.squareup.javapoet.TypeName;
 
 /** A utility class for {@link XExecutableType} helper methods. */
 // TODO(bcorso): Consider moving these methods into XProcessing library.
 public final class XExecutableTypes {
 
+  // TODO(b/271177465): Remove this method once XProcessing supports this feature.
+  public static boolean isSubsignature(XExecutableElement method1, XExecutableElement method2) {
+    XProcessingEnv processingEnv = getProcessingEnv(method1);
+    switch (processingEnv.getBackend()) {
+      case JAVAC:
+        return isSubsignatureJavac(method1, method2, processingEnv);
+      case KSP:
+        return isSubsignatureKsp(method1, method2);
+    }
+    throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
+  }
+
+  private static boolean isSubsignatureKsp(XExecutableElement method1, XExecutableElement method2) {
+    if (method1.getParameters().size() != method2.getParameters().size()) {
+      return false;
+    }
+    ImmutableList<TypeName> method1Parameters = getParameters(method1);
+    ImmutableList<TypeName> method1TypeParameters = getTypeParameters(method1);
+    ImmutableList<TypeName> method2TypeParameters = getTypeParameters(method2);
+    return (method1TypeParameters.equals(method2TypeParameters)
+            && method1Parameters.equals(getParameters(method2)))
+        || (method1TypeParameters
+                .isEmpty() // "The erasure of the signature of a generic method has no type
+            // parameters."
+            && method1Parameters.equals(
+                method2.getExecutableType().getParameterTypes().stream()
+                    .map(XTypes::erasedTypeName)
+                    .collect(toImmutableList())));
+  }
+
+  private static ImmutableList<TypeName> getParameters(XExecutableElement method) {
+    return method.getExecutableType().getParameterTypes().stream()
+        .map(XType::asTypeName)
+        .map(XTypeNameKt::toJavaPoet)
+        .collect(toImmutableList());
+  }
+
+  private static ImmutableList<TypeName> getTypeParameters(XExecutableElement method) {
+    return method.getTypeParameters().stream()
+        .map(it -> it.getBounds().get(0))
+        .map(XType::asTypeName)
+        .map(XTypeNameKt::toJavaPoet)
+        .collect(toImmutableList());
+  }
+
+  private static boolean isSubsignatureJavac(
+      XExecutableElement method1, XExecutableElement method2, XProcessingEnv env) {
+    return toJavac(env)
+        .getTypeUtils() // ALLOW_TYPES_ELEMENTS
+        .isSubsignature(toJavac(method1.getExecutableType()), toJavac(method2.getExecutableType()));
+  }
+
   public static boolean isConstructorType(XExecutableType executableType) {
     return executableType instanceof XConstructorType;
   }
diff --git a/java/dagger/internal/codegen/xprocessing/XMethodElements.java b/java/dagger/internal/codegen/xprocessing/XMethodElements.java
index 3cd8711..3066d79 100644
--- a/java/dagger/internal/codegen/xprocessing/XMethodElements.java
+++ b/java/dagger/internal/codegen/xprocessing/XMethodElements.java
@@ -16,7 +16,11 @@
 
 package dagger.internal.codegen.xprocessing;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
+import static androidx.room.compiler.processing.compat.XConverters.toJavac;
+
 import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XTypeElement;
 
 // TODO(bcorso): Consider moving these methods into XProcessing library.
@@ -36,5 +40,19 @@
     return !method.getExecutableType().getTypeVariableNames().isEmpty();
   }
 
+  // TODO(b/278628663): Replace with XMethodElement#isDefault.
+  public static boolean isDefault(XMethodElement method) {
+    XProcessingEnv processingEnv = getProcessingEnv(method);
+    switch (processingEnv.getBackend()) {
+      case JAVAC:
+        return toJavac(method).isDefault();
+      case KSP:
+        throw new AssertionError(
+            "XMethodElement#isDefault() is not supported on KSP yet: "
+                + XElements.toStableString(method));
+    }
+    throw new AssertionError(String.format("Unsupported backend %s", processingEnv.getBackend()));
+  }
+
   private XMethodElements() {}
 }
diff --git a/java/dagger/internal/codegen/xprocessing/XTypeElements.java b/java/dagger/internal/codegen/xprocessing/XTypeElements.java
index 8f5aa81..6d9c564 100644
--- a/java/dagger/internal/codegen/xprocessing/XTypeElements.java
+++ b/java/dagger/internal/codegen/xprocessing/XTypeElements.java
@@ -16,16 +16,21 @@
 
 package dagger.internal.codegen.xprocessing;
 
+import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
 import static kotlin.streams.jdk8.StreamsKt.asStream;
 
 import androidx.room.compiler.processing.XHasModifiers;
 import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XTypeElement;
 import androidx.room.compiler.processing.XTypeParameterElement;
+import androidx.room.compiler.processing.compat.XConverters;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.ksp.symbol.Origin;
+import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.TypeVariableName;
 
 // TODO(bcorso): Consider moving these methods into XProcessing library.
@@ -81,15 +86,20 @@
         .collect(toImmutableList());
   }
 
-  // TODO(b/229784604): This is needed until the XProcessing getAllMethods fix is upstreamed. Due
-  // to the existing bug, XTypeElement#getAllMethods() will currently contain some inaccessible
-  // package-private methods from base classes, so we filter them manually here.
+  // TODO(wanyingd): rename this to getAllMethodsWithoutPrivate, since the private method declared
+  // within this element is being filtered out. This doesn't mirror {@code
+  // MoreElements#getAllMethods}'s behavior but have the same name, and can cause confusion to
+  // developers.
   public static ImmutableList<XMethodElement> getAllMethods(XTypeElement type) {
     return asStream(type.getAllMethods())
         .filter(method -> isAccessibleFrom(method, type))
         .collect(toImmutableList());
   }
 
+  public static ImmutableList<XMethodElement> getAllMethodsIncludingPrivate(XTypeElement type) {
+    return asStream(type.getAllMethods()).collect(toImmutableList());
+  }
+
   private static boolean isAccessibleFrom(XMethodElement method, XTypeElement type) {
     if (method.isPublic() || method.isProtected()) {
       return true;
@@ -113,6 +123,10 @@
     return allVisibilities(element).contains(Visibility.PRIVATE);
   }
 
+  public static boolean isJvmClass(XTypeElement element) {
+    return element.isClass() || element.isKotlinObject() || element.isCompanionObject();
+  }
+
   /**
    * Returns a list of visibilities containing visibility of the given element and the visibility of
    * its enclosing elements.
@@ -128,5 +142,20 @@
     return visibilities.build();
   }
 
+  /** Returns true if the source of the given type element is Kotlin. */
+  public static boolean isKotlinSource(XTypeElement typeElement) {
+    XProcessingEnv processingEnv = getProcessingEnv(typeElement);
+    switch (processingEnv.getBackend()) {
+      case KSP:
+        // If this is KSP, then we should be able to check the origin of the declaration.
+        Origin origin = XConverters.toKS(typeElement).getOrigin();
+        return origin == Origin.KOTLIN || origin == Origin.KOTLIN_LIB;
+      case JAVAC:
+        // If this is KAPT, then the java stubs should have kotlin metadata.
+        return typeElement.hasAnnotation(ClassName.get("kotlin", "Metadata"));
+    }
+    throw new AssertionError("Unhandled backend kind: " + processingEnv.getBackend());
+  }
+
   private XTypeElements() {}
 }
diff --git a/java/dagger/internal/codegen/xprocessing/XTypes.java b/java/dagger/internal/codegen/xprocessing/XTypes.java
index 8261468..654ce69 100644
--- a/java/dagger/internal/codegen/xprocessing/XTypes.java
+++ b/java/dagger/internal/codegen/xprocessing/XTypes.java
@@ -40,6 +40,7 @@
 import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XType;
 import androidx.room.compiler.processing.XTypeElement;
+import androidx.room.compiler.processing.XTypeVariableType;
 import com.google.auto.common.MoreElements;
 import com.google.common.base.Equivalence;
 import com.squareup.javapoet.ArrayTypeName;
@@ -63,26 +64,79 @@
 // TODO(bcorso): Consider moving these methods into XProcessing library.
 /** A utility class for {@link XType} helper methods. */
 public final class XTypes {
-  private static final Equivalence<XType> XTYPE_EQUIVALENCE =
-      new Equivalence<XType>() {
-        @Override
-        protected boolean doEquivalent(XType left, XType right) {
-          return left.getTypeName().equals(right.getTypeName());
-        }
+  private static class XTypeEquivalence extends Equivalence<XType> {
+    private final boolean ignoreVariance;
 
-        @Override
-        protected int doHash(XType type) {
-          return type.getTypeName().hashCode();
-        }
+    XTypeEquivalence(boolean ignoreVariance) {
+      this.ignoreVariance = ignoreVariance;
+    }
 
-        @Override
-        public String toString() {
-          return "XTypes.equivalence()";
-        }
-      };
+    @Override
+    protected boolean doEquivalent(XType left, XType right) {
+      return getTypeName(left).equals(getTypeName(right));
+    }
+
+    @Override
+    protected int doHash(XType type) {
+      return getTypeName(type).hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return "XTypes.equivalence()";
+    }
+
+    private TypeName getTypeName(XType type) {
+      return ignoreVariance ? stripVariances(type.getTypeName()) : type.getTypeName();
+    }
+  }
+
+  public static TypeName stripVariances(TypeName typeName) {
+    if (typeName instanceof WildcardTypeName) {
+      WildcardTypeName wildcardTypeName = (WildcardTypeName) typeName;
+      if (!wildcardTypeName.lowerBounds.isEmpty()) {
+        return stripVariances(getOnlyElement(wildcardTypeName.lowerBounds));
+      } else if (!wildcardTypeName.upperBounds.isEmpty()) {
+        return stripVariances(getOnlyElement(wildcardTypeName.upperBounds));
+      }
+    } else if (typeName instanceof ArrayTypeName) {
+      ArrayTypeName arrayTypeName = (ArrayTypeName) typeName;
+      return ArrayTypeName.of(stripVariances(arrayTypeName.componentType));
+    } else if (typeName instanceof ParameterizedTypeName) {
+      ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
+      if (parameterizedTypeName.typeArguments.isEmpty()) {
+        return parameterizedTypeName;
+      } else {
+        return ParameterizedTypeName.get(
+            parameterizedTypeName.rawType,
+            parameterizedTypeName.typeArguments.stream()
+                .map(XTypes::stripVariances)
+                .toArray(TypeName[]::new));
+      }
+    }
+    return typeName;
+  }
+
+  private static final Equivalence<XType> XTYPE_EQUIVALENCE_IGNORING_VARIANCE =
+      new XTypeEquivalence(/* ignoreVariance= */ true);
 
   /**
-   * Returns an {@link Equivalence} for {@link XType}.
+   * Returns an {@link Equivalence} for {@link XType} based on the {@link TypeName} with variances
+   * ignored (e.g. {@code Foo<? extends Bar>} would be equivalent to {@code Foo<Bar>}).
+   *
+   * <p>Currently, this equivalence does not take into account nullability, as it just relies on
+   * JavaPoet's {@link TypeName}. Thus, two types with the same type name but different nullability
+   * are equal with this equivalence.
+   */
+  public static Equivalence<XType> equivalenceIgnoringVariance() {
+    return XTYPE_EQUIVALENCE_IGNORING_VARIANCE;
+  }
+
+  private static final Equivalence<XType> XTYPE_EQUIVALENCE =
+      new XTypeEquivalence(/* ignoreVariance= */ false);
+
+  /**
+   * Returns an {@link Equivalence} for {@link XType} based on the {@link TypeName}.
    *
    * <p>Currently, this equivalence does not take into account nullability, as it just relies on
    * JavaPoet's {@link TypeName}. Thus, two types with the same type name but different nullability
@@ -215,6 +269,11 @@
     return (XArrayType) type;
   }
 
+  /** Returns the given {@code type} as an {@link XTypeVariableType}. */
+  public static XTypeVariableType asTypeVariable(XType type) {
+    return (XTypeVariableType) type;
+  }
+
   /** Returns {@code true} if the raw type of {@code type} is equal to {@code className}. */
   public static boolean isTypeOf(XType type, ClassName className) {
     return isDeclared(type) && type.getTypeElement().getClassName().equals(className);
@@ -283,10 +342,6 @@
     return !type.getTypeArguments().isEmpty();
   }
 
-  public static boolean isExecutable(XType type) {
-    return type instanceof XExecutableType;
-  }
-
   public static boolean isMethod(XExecutableType type) {
     return type instanceof XMethodType;
   }
diff --git a/java/dagger/internal/codegen/xprocessing/xprocessing.jar b/java/dagger/internal/codegen/xprocessing/xprocessing.jar
index b034604..1c41e20 100644
--- a/java/dagger/internal/codegen/xprocessing/xprocessing.jar
+++ b/java/dagger/internal/codegen/xprocessing/xprocessing.jar
Binary files differ
diff --git a/java/dagger/model/BindingKind.java b/java/dagger/model/BindingKind.java
index 9bef8fc..1bfb080 100644
--- a/java/dagger/model/BindingKind.java
+++ b/java/dagger/model/BindingKind.java
@@ -98,7 +98,7 @@
   OPTIONAL,
 
   /**
-   * A binding for {@link dagger.Binds}-annotated method that that delegates from requests for one
+   * A binding for {@link dagger.Binds}-annotated method that delegates from requests for one
    * key to another.
    */
   // TODO(dpb,ronshapiro): This name is confusing and could use work. Not all usages of @Binds
diff --git a/java/dagger/spi/BUILD b/java/dagger/spi/BUILD
index 0e18ca6..13428c1 100644
--- a/java/dagger/spi/BUILD
+++ b/java/dagger/spi/BUILD
@@ -58,12 +58,10 @@
     artifact_target = ":spi",
     artifact_target_libs = [
         "//java/dagger/internal/codegen/extension",
-        "//java/dagger/internal/codegen/xprocessing",
         "//java/dagger/model",
         "//java/dagger/spi/model",
     ],
     artifact_target_maven_deps = [
-        "com.google.auto:auto-common",
         "com.google.code.findbugs:jsr305",
         "com.google.dagger:dagger-producers",
         "com.google.dagger:dagger",
@@ -71,9 +69,7 @@
         "com.google.guava:failureaccess",
         "com.google.guava:guava",
         "com.squareup:javapoet",
-        "com.squareup:kotlinpoet",
         "javax.inject:javax.inject",
-        "org.jetbrains.kotlin:kotlin-stdlib",
     ],
     javadoc_root_packages = [
         "dagger.model",
@@ -85,6 +81,7 @@
     # util/deploy-dagger.sh
     shaded_deps = [
         "//third_party/java/auto:common",
+        "@maven//:org_jetbrains_kotlinx_kotlinx_metadata_jvm",
         "//java/dagger/internal/codegen/xprocessing:xprocessing-jar",
     ],
 )
diff --git a/java/dagger/spi/model/BUILD b/java/dagger/spi/model/BUILD
index afe6e9a..e9346dd 100644
--- a/java/dagger/spi/model/BUILD
+++ b/java/dagger/spi/model/BUILD
@@ -15,12 +15,7 @@
 # Description:
 #   Dagger's core APIs exposed for plugins
 
-load("@rules_java//java:defs.bzl", "java_library")
-load(
-    "//:build_defs.bzl",
-    "DOCLINT_HTML_AND_SYNTAX",
-    "DOCLINT_REFERENCES",
-)
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library")
 
 package(default_visibility = [
     # The dagger/spi should be the only direct dependent on this target.
@@ -32,17 +27,19 @@
 
 filegroup(
     name = "model-srcs",
-    srcs = glob(["*.java"]),
+    srcs = glob([
+        "*.java",
+        "*.kt",
+    ]),
 )
 
-java_library(
+kt_jvm_library(
     name = "model",
     srcs = [":model-srcs"],
-    javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES,
+    # TODO(wanyingd): Add javacopts explicitly once kt_jvm_library supports them.
     deps = [
         "//java/dagger:core",
         "//java/dagger/internal/codegen/extension",
-        "//java/dagger/internal/codegen/xprocessing",
         "//java/dagger/producers",
         "//third_party/java/auto:common",
         "//third_party/java/auto:value",
@@ -56,3 +53,9 @@
         "@maven//:com_google_devtools_ksp_symbol_processing_api",
     ],
 )
+
+# See: https://github.com/bazelbuild/rules_kotlin/issues/324
+alias(
+    name = "libmodel-src.jar",
+    actual = ":model-sources.jar",
+)
diff --git a/java/dagger/spi/model/BindingGraph.java b/java/dagger/spi/model/BindingGraph.java
index f10ffe2..aab7c2a 100644
--- a/java/dagger/spi/model/BindingGraph.java
+++ b/java/dagger/spi/model/BindingGraph.java
@@ -117,6 +117,8 @@
    */
   public abstract boolean isFullBindingGraph();
 
+  public abstract DaggerProcessingEnv.Backend backend();
+
   /**
    * Returns {@code true} if the {@link #rootComponentNode()} is a subcomponent. This occurs in
    * when {@code -Adagger.fullBindingGraphValidation} is used in a compilation with a subcomponent.
diff --git a/java/dagger/spi/model/ComponentPath.java b/java/dagger/spi/model/ComponentPath.java
index 74195e4..63a5f6b 100644
--- a/java/dagger/spi/model/ComponentPath.java
+++ b/java/dagger/spi/model/ComponentPath.java
@@ -23,7 +23,6 @@
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.collect.ImmutableList;
-import com.squareup.javapoet.ClassName;
 
 /** A path containing a component and all of its ancestor components. */
 @AutoValue
@@ -90,10 +89,7 @@
 
   @Override
   public final String toString() {
-    return components().stream()
-        .map(DaggerTypeElement::className)
-        .map(ClassName::canonicalName)
-        .collect(joining(" → "));
+    return components().stream().map(DaggerTypeElement::qualifiedName).collect(joining(" → "));
   }
 
   @Memoized
diff --git a/java/dagger/spi/model/DaggerAnnotation.java b/java/dagger/spi/model/DaggerAnnotation.java
index b7c5ab0..2ec66c0 100644
--- a/java/dagger/spi/model/DaggerAnnotation.java
+++ b/java/dagger/spi/model/DaggerAnnotation.java
@@ -16,45 +16,55 @@
 
 package dagger.spi.model;
 
-import static androidx.room.compiler.processing.compat.XConverters.toJavac;
-
-import androidx.room.compiler.processing.XAnnotation;
+import com.google.auto.common.AnnotationMirrors;
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Preconditions;
-import com.squareup.javapoet.ClassName;
-import dagger.internal.codegen.xprocessing.XAnnotations;
+import com.google.devtools.ksp.symbol.KSAnnotation;
+import javax.annotation.Nullable;
 import javax.lang.model.element.AnnotationMirror;
 
 /** Wrapper type for an annotation. */
 @AutoValue
 public abstract class DaggerAnnotation {
-
-  public static DaggerAnnotation from(XAnnotation annotation) {
-    Preconditions.checkNotNull(annotation);
-    return new AutoValue_DaggerAnnotation(XAnnotations.equivalence().wrap(annotation));
+  public static DaggerAnnotation fromJavac(
+      DaggerTypeElement annotationTypeElement, AnnotationMirror annotation) {
+    return new AutoValue_DaggerAnnotation(annotationTypeElement, annotation, null);
   }
 
-  abstract Equivalence.Wrapper<XAnnotation> equivalenceWrapper();
-
-  public DaggerTypeElement annotationTypeElement() {
-    return DaggerTypeElement.from(xprocessing().getType().getTypeElement());
+  public static DaggerAnnotation fromKsp(
+      DaggerTypeElement annotationTypeElement, KSAnnotation ksp) {
+    return new AutoValue_DaggerAnnotation(annotationTypeElement, null, ksp);
   }
 
-  public ClassName className() {
-    return annotationTypeElement().className();
-  }
+  public abstract DaggerTypeElement annotationTypeElement();
 
-  public XAnnotation xprocessing() {
-    return equivalenceWrapper().get();
-  }
+  /**
+   * java representation for the annotation, returns {@code null} if the annotation isn't a java
+   * element.
+   */
+  @Nullable
+  public abstract AnnotationMirror java();
 
-  public AnnotationMirror java() {
-    return toJavac(xprocessing());
+  /** KSP declaration for the annotation, returns {@code null} not using KSP. */
+  @Nullable
+  public abstract KSAnnotation ksp();
+
+  public DaggerProcessingEnv.Backend backend() {
+    if (java() != null) {
+      return DaggerProcessingEnv.Backend.JAVAC;
+    } else if (ksp() != null) {
+      return DaggerProcessingEnv.Backend.KSP;
+    }
+    throw new AssertionError("Unexpected backend");
   }
 
   @Override
   public final String toString() {
-    return XAnnotations.toString(xprocessing());
+    switch (backend()) {
+      case JAVAC:
+        return AnnotationMirrors.toString(java());
+      case KSP:
+        return ksp().toString();
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
   }
 }
diff --git a/java/dagger/spi/model/DaggerElement.java b/java/dagger/spi/model/DaggerElement.java
index aa1b4a5..0f6a2cf 100644
--- a/java/dagger/spi/model/DaggerElement.java
+++ b/java/dagger/spi/model/DaggerElement.java
@@ -16,27 +16,49 @@
 
 package dagger.spi.model;
 
-import static androidx.room.compiler.processing.compat.XConverters.toJavac;
-
-import androidx.room.compiler.processing.XElement;
 import com.google.auto.value.AutoValue;
+import com.google.devtools.ksp.symbol.KSAnnotated;
+import javax.annotation.Nullable;
 import javax.lang.model.element.Element;
 
 /** Wrapper type for an element. */
 @AutoValue
 public abstract class DaggerElement {
-  public static DaggerElement from(XElement element) {
-    return new AutoValue_DaggerElement(element);
+  public static DaggerElement fromJavac(Element element) {
+    return new AutoValue_DaggerElement(element, null);
   }
 
-  public abstract XElement xprocessing();
+  public static DaggerElement fromKsp(KSAnnotated ksp) {
+    return new AutoValue_DaggerElement(null, ksp);
+  }
 
-  public Element java() {
-    return toJavac(xprocessing());
+  /**
+   * Java representation for the element, returns {@code null} not using java annotation processor.
+   */
+  @Nullable
+  public abstract Element java();
+
+  /** KSP declaration for the element, returns {@code null} not using KSP. */
+  @Nullable
+  public abstract KSAnnotated ksp();
+
+  public DaggerProcessingEnv.Backend backend() {
+    if (java() != null) {
+      return DaggerProcessingEnv.Backend.JAVAC;
+    } else if (ksp() != null) {
+      return DaggerProcessingEnv.Backend.KSP;
+    }
+    throw new AssertionError("Unexpected backend");
   }
 
   @Override
   public final String toString() {
-    return xprocessing().toString();
+    switch (backend()) {
+      case JAVAC:
+        return java().toString();
+      case KSP:
+        return ksp().toString();
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
   }
 }
diff --git a/java/dagger/spi/model/DaggerExecutableElement.java b/java/dagger/spi/model/DaggerExecutableElement.java
index e15ca37..7df6a1e 100644
--- a/java/dagger/spi/model/DaggerExecutableElement.java
+++ b/java/dagger/spi/model/DaggerExecutableElement.java
@@ -16,28 +16,59 @@
 
 package dagger.spi.model;
 
-import static androidx.room.compiler.processing.compat.XConverters.toJavac;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import androidx.room.compiler.processing.XExecutableElement;
 import com.google.auto.value.AutoValue;
+import com.google.devtools.ksp.symbol.KSFunctionDeclaration;
+import javax.annotation.Nullable;
 import javax.lang.model.element.ExecutableElement;
 
 /** Wrapper type for an executable element. */
 @AutoValue
 public abstract class DaggerExecutableElement {
-  public static DaggerExecutableElement from(XExecutableElement executableElement) {
-    return new AutoValue_DaggerExecutableElement(checkNotNull(executableElement));
+  public static DaggerExecutableElement fromJava(ExecutableElement executableElement) {
+    return new AutoValue_DaggerExecutableElement(executableElement, null);
   }
 
-  public abstract XExecutableElement xprocessing();
+  public static DaggerExecutableElement fromKsp(KSFunctionDeclaration declaration) {
+    return new AutoValue_DaggerExecutableElement(null, declaration);
+  }
 
-  public ExecutableElement java() {
-    return toJavac(xprocessing());
+  /**
+   * Java representation for the element, returns {@code null} not using java annotation processor.
+   */
+  @Nullable
+  public abstract ExecutableElement java();
+
+  /** KSP declaration for the element, returns {@code null} not using KSP. */
+  @Nullable
+  public abstract KSFunctionDeclaration ksp();
+
+  public DaggerProcessingEnv.Backend backend() {
+    if (java() != null) {
+      return DaggerProcessingEnv.Backend.JAVAC;
+    } else if (ksp() != null) {
+      return DaggerProcessingEnv.Backend.KSP;
+    }
+    throw new AssertionError("Unexpected backend");
   }
 
   @Override
   public final String toString() {
-    return xprocessing().toString();
+    switch (backend()) {
+      case JAVAC:
+        return java().toString();
+      case KSP:
+        return ksp().toString();
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
+  }
+
+  String simpleName() {
+    switch (backend()) {
+      case JAVAC:
+        return java().getSimpleName().toString();
+      case KSP:
+        return ksp().getSimpleName().toString();
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
   }
 }
diff --git a/java/dagger/spi/model/DaggerProcessingEnv.java b/java/dagger/spi/model/DaggerProcessingEnv.java
index 27c0493..8c652c8 100644
--- a/java/dagger/spi/model/DaggerProcessingEnv.java
+++ b/java/dagger/spi/model/DaggerProcessingEnv.java
@@ -16,10 +16,10 @@
 
 package dagger.spi.model;
 
-import static androidx.room.compiler.processing.compat.XConverters.toJavac;
-
-import androidx.room.compiler.processing.XProcessingEnv;
 import com.google.auto.value.AutoValue;
+import com.google.devtools.ksp.processing.Resolver;
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment;
+import javax.annotation.Nullable;
 import javax.annotation.processing.ProcessingEnvironment;
 
 /** Wrapper type for an element. */
@@ -28,18 +28,38 @@
   /** Represents a type of backend used for compilation. */
   public enum Backend { JAVAC, KSP }
 
-  public static DaggerProcessingEnv from(XProcessingEnv processingEnv) {
-    return new AutoValue_DaggerProcessingEnv(processingEnv);
+  public static boolean isJavac(Backend backend) {
+    return backend.equals(Backend.JAVAC);
   }
 
-  public abstract XProcessingEnv xprocessing();
+  public static DaggerProcessingEnv fromJavac(ProcessingEnvironment env) {
+    return new AutoValue_DaggerProcessingEnv(env, null, null);
+  }
+
+  public static DaggerProcessingEnv fromKsp(SymbolProcessorEnvironment env, Resolver resolver) {
+    return new AutoValue_DaggerProcessingEnv(null, env, resolver);
+  }
+
+  /**
+   * Java representation for the processing environment, returns {@code null} not using java
+   * annotation processor.
+   */
+  @Nullable
+  public abstract ProcessingEnvironment java();
+
+  @Nullable
+  public abstract SymbolProcessorEnvironment ksp();
+
+  @Nullable
+  public abstract Resolver resolver();
 
   /** Returns the backend used in this compilation. */
-  public Backend getBackend() {
-    return Backend.valueOf(xprocessing().getBackend().name());
-  }
-
-  public ProcessingEnvironment java() {
-    return toJavac(xprocessing());
+  public DaggerProcessingEnv.Backend backend() {
+    if (java() != null) {
+      return DaggerProcessingEnv.Backend.JAVAC;
+    } else if (ksp() != null) {
+      return DaggerProcessingEnv.Backend.KSP;
+    }
+    throw new AssertionError("Unexpected backend");
   }
 }
diff --git a/java/dagger/spi/model/DaggerType.java b/java/dagger/spi/model/DaggerType.java
index bb2f77c..249d494 100644
--- a/java/dagger/spi/model/DaggerType.java
+++ b/java/dagger/spi/model/DaggerType.java
@@ -16,39 +16,47 @@
 
 package dagger.spi.model;
 
-import static androidx.room.compiler.processing.compat.XConverters.toJavac;
-
-import androidx.room.compiler.processing.XType;
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Equivalence;
-import com.google.common.base.Preconditions;
-import dagger.internal.codegen.xprocessing.XTypes;
+import com.google.devtools.ksp.symbol.KSType;
+import javax.annotation.Nullable;
 import javax.lang.model.type.TypeMirror;
 
 /** Wrapper type for a type. */
 @AutoValue
 public abstract class DaggerType {
-  public static DaggerType from(XType type) {
-    Preconditions.checkNotNull(type);
-    return new AutoValue_DaggerType(XTypes.equivalence().wrap(type));
+  public static DaggerType fromJavac(TypeMirror type) {
+    return new AutoValue_DaggerType(type, null);
   }
 
-  abstract Equivalence.Wrapper<XType> equivalenceWrapper();
-
-  public XType xprocessing() {
-    return equivalenceWrapper().get();
+  public static DaggerType fromKsp(KSType type) {
+    return new AutoValue_DaggerType(null, type);
   }
 
-  public TypeMirror java() {
-    return toJavac(xprocessing());
+  /** Java representation for the type, returns {@code null} not using java annotation processor. */
+  @Nullable
+  public abstract TypeMirror java();
+
+  /** KSP declaration for the type, returns {@code null} not using KSP. */
+  @Nullable
+  public abstract KSType ksp();
+
+  public DaggerProcessingEnv.Backend backend() {
+    if (java() != null) {
+      return DaggerProcessingEnv.Backend.JAVAC;
+    } else if (ksp() != null) {
+      return DaggerProcessingEnv.Backend.KSP;
+    }
+    throw new AssertionError("Unexpected backend");
   }
 
   @Override
   public final String toString() {
-    // We define our own stable string rather than use XType#toString() here because
-    // XType#toString() is currently not stable across backends. In particular, in javac it returns
-    // the qualified type but in ksp it returns the simple name.
-    // TODO(bcorso): Consider changing XProcessing so that #toString() is stable across backends.
-    return XTypes.toStableString(xprocessing());
+    switch (backend()) {
+      case JAVAC:
+        return java().toString();
+      case KSP:
+        return ksp().toString();
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
   }
 }
diff --git a/java/dagger/spi/model/DaggerTypeElement.java b/java/dagger/spi/model/DaggerTypeElement.java
index aa47507..2f70a54 100644
--- a/java/dagger/spi/model/DaggerTypeElement.java
+++ b/java/dagger/spi/model/DaggerTypeElement.java
@@ -16,32 +16,78 @@
 
 package dagger.spi.model;
 
-import static androidx.room.compiler.processing.compat.XConverters.toJavac;
-
-import androidx.room.compiler.processing.XTypeElement;
+import com.google.auto.common.MoreElements;
 import com.google.auto.value.AutoValue;
-import com.squareup.javapoet.ClassName;
+import com.google.devtools.ksp.symbol.KSClassDeclaration;
+import javax.annotation.Nullable;
 import javax.lang.model.element.TypeElement;
 
 /** Wrapper type for a type element. */
 @AutoValue
 public abstract class DaggerTypeElement {
-  public static DaggerTypeElement from(XTypeElement typeElement) {
-    return new AutoValue_DaggerTypeElement(typeElement);
+  public static DaggerTypeElement fromJavac(@Nullable TypeElement element) {
+    return new AutoValue_DaggerTypeElement(element, null);
   }
 
-  public abstract XTypeElement xprocessing();
-
-  public TypeElement java() {
-    return toJavac(xprocessing());
+  public static DaggerTypeElement fromKsp(@Nullable KSClassDeclaration declaration) {
+    return new AutoValue_DaggerTypeElement(null, declaration);
   }
 
-  public ClassName className() {
-    return xprocessing().getClassName();
+  /** Java representation for the type, returns {@code null} not using java annotation processor. */
+  @Nullable
+  public abstract TypeElement java();
+
+  /** KSP declaration for the element, returns {@code null} not using KSP. */
+  @Nullable
+  public abstract KSClassDeclaration ksp();
+
+  public final boolean hasAnnotation(String annotationName) {
+    switch (backend()) {
+      case JAVAC:
+        return MoreElements.isAnnotationPresent(java(), annotationName);
+      case KSP:
+        return KspUtilsKt.hasAnnotation(ksp(), annotationName);
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
+  }
+
+  public String packageName() {
+    switch (backend()) {
+      case JAVAC:
+        return MoreElements.getPackage(java()).getQualifiedName().toString();
+      case KSP:
+        return KspUtilsKt.getNormalizedPackageName(ksp());
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
+  }
+
+  public String qualifiedName() {
+    switch (backend()) {
+      case JAVAC:
+        return java().getQualifiedName().toString();
+      case KSP:
+        return ksp().getQualifiedName().asString();
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
+  }
+
+  public DaggerProcessingEnv.Backend backend() {
+    if (java() != null) {
+      return DaggerProcessingEnv.Backend.JAVAC;
+    } else if (ksp() != null) {
+      return DaggerProcessingEnv.Backend.KSP;
+    }
+    throw new AssertionError("Unexpected backend");
   }
 
   @Override
   public final String toString() {
-    return xprocessing().toString();
+    switch (backend()) {
+      case JAVAC:
+        return java().toString();
+      case KSP:
+        return ksp().toString();
+    }
+    throw new IllegalStateException(String.format("Backend %s not supported yet.", backend()));
   }
 }
diff --git a/java/dagger/spi/model/Key.java b/java/dagger/spi/model/Key.java
index a50e5ac..d871e6d 100644
--- a/java/dagger/spi/model/Key.java
+++ b/java/dagger/spi/model/Key.java
@@ -16,12 +16,10 @@
 
 package dagger.spi.model;
 
-import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
 
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.base.Joiner;
-import dagger.internal.codegen.xprocessing.XAnnotations;
 import java.util.Optional;
 
 /**
@@ -89,10 +87,7 @@
     return Joiner.on(' ')
         .skipNulls()
         .join(
-            qualifier()
-                .map(DaggerAnnotation::xprocessing)
-                .map(XAnnotations::toStableString)
-                .orElse(null),
+            qualifier().map(DaggerAnnotation::toString).orElse(null),
             type(),
             multibindingContributionIdentifier().orElse(null));
   }
@@ -135,8 +130,7 @@
     private static MultibindingContributionIdentifier create(
         DaggerTypeElement contributingModule, DaggerExecutableElement bindingMethod) {
       return new AutoValue_Key_MultibindingContributionIdentifier(
-          contributingModule.xprocessing().getQualifiedName(),
-          getSimpleName(bindingMethod.xprocessing()));
+          contributingModule.qualifiedName(), bindingMethod.simpleName());
     }
 
     /** Returns the module containing the multibinding method. */
@@ -152,7 +146,7 @@
      * whole object.
      */
     @Override
-    public String toString() {
+    public final String toString() {
       return String.format("%s#%s", contributingModule(), bindingMethod());
     }
   }
diff --git a/java/dagger/spi/model/KspUtils.kt b/java/dagger/spi/model/KspUtils.kt
new file mode 100644
index 0000000..09d8aa2
--- /dev/null
+++ b/java/dagger/spi/model/KspUtils.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.spi.model
+
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSDeclaration
+
+fun KSClassDeclaration.hasAnnotation(annotationName: String): Boolean =
+  annotations.any {
+    it.annotationType.resolve().declaration.qualifiedName?.asString().equals(annotationName)
+  }
+
+/** Returns root package name as empty string instead of <root>. */
+fun KSDeclaration.getNormalizedPackageName(): String {
+  return packageName.asString().let {
+    if (it == "<root>") {
+      ""
+    } else {
+      it
+    }
+  }
+}
diff --git a/java/dagger/spi/model/Scope.java b/java/dagger/spi/model/Scope.java
index 8e2aaf3..d1505b9 100644
--- a/java/dagger/spi/model/Scope.java
+++ b/java/dagger/spi/model/Scope.java
@@ -19,7 +19,6 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.auto.value.AutoValue;
-import com.squareup.javapoet.ClassName;
 
 /** A representation of a {@link javax.inject.Scope}. */
 @AutoValue
@@ -43,25 +42,23 @@
    * Returns {@code true} if {@code scopeAnnotationType} is a {@link javax.inject.Scope} annotation.
    */
   public static boolean isScope(DaggerTypeElement scopeAnnotationType) {
-    return scopeAnnotationType.xprocessing().hasAnnotation(SCOPE)
-        || scopeAnnotationType.xprocessing().hasAnnotation(SCOPE_JAVAX);
+    return scopeAnnotationType.hasAnnotation(SCOPE)
+        || scopeAnnotationType.hasAnnotation(SCOPE_JAVAX);
   }
 
-  private static final ClassName PRODUCTION_SCOPE =
-      ClassName.get("dagger.producers", "ProductionScope");
-  private static final ClassName SINGLETON = ClassName.get("jakarta.inject", "Singleton");
-  private static final ClassName SINGLETON_JAVAX = ClassName.get("javax.inject", "Singleton");
-  private static final ClassName REUSABLE = ClassName.get("dagger", "Reusable");
-  private static final ClassName SCOPE = ClassName.get("jakarta.inject", "Scope");
-  private static final ClassName SCOPE_JAVAX = ClassName.get("javax.inject", "Scope");
-
+  private boolean isScope(String annotationName) {
+    return scopeAnnotation().toString().equals(annotationName);
+  }
 
   /** The {@link DaggerAnnotation} that represents the scope annotation. */
   public abstract DaggerAnnotation scopeAnnotation();
 
-  public final ClassName className() {
-    return scopeAnnotation().className();
-  }
+  private static final String PRODUCTION_SCOPE = "dagger.producers.ProductionScope";
+  private static final String SINGLETON = "jakarta.inject.Singleton";
+  private static final String SINGLETON_JAVAX = "javax.inject.Singleton";
+  private static final String REUSABLE = "dagger.Reusable";
+  private static final String SCOPE = "jakarta.inject.Scope";
+  private static final String SCOPE_JAVAX = "javax.inject.Scope";
 
   /** Returns {@code true} if this scope is the {@link javax.inject.Singleton @Singleton} scope. */
   public final boolean isSingleton() {
@@ -81,10 +78,6 @@
     return isScope(PRODUCTION_SCOPE);
   }
 
-  private boolean isScope(ClassName annotation) {
-    return scopeAnnotation().className().equals(annotation);
-  }
-
   /** Returns a debug representation of the scope. */
   @Override
   public final String toString() {
diff --git a/java/dagger/spi/model/testing/BindingGraphSubject.java b/java/dagger/spi/model/testing/BindingGraphSubject.java
index 32bf152..e206731 100644
--- a/java/dagger/spi/model/testing/BindingGraphSubject.java
+++ b/java/dagger/spi/model/testing/BindingGraphSubject.java
@@ -25,6 +25,7 @@
 import com.google.common.truth.Subject;
 import dagger.spi.model.Binding;
 import dagger.spi.model.BindingGraph;
+import dagger.spi.model.DaggerType;
 import javax.lang.model.type.TypeMirror;
 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
 
@@ -69,7 +70,7 @@
    * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()}
    */
   public BindingSubject bindingWithKey(String type) {
-    return bindingWithKeyString(keyString(type));
+    return bindingWithKeyString(type);
   }
 
   /**
@@ -94,18 +95,30 @@
 
   private ImmutableSet<Binding> getBindingNodes(String keyString) {
     return actual.bindings().stream()
-        .filter(binding -> binding.key().toString().equals(keyString))
+        .filter(binding -> keyString(binding).equals(keyString))
         .collect(toImmutableSet());
   }
 
-  private static String keyString(String type) {
-    return type;
+  public static String keyString(Binding binding) {
+    return binding.key().qualifier().isPresent()
+        ? keyString(binding.key().qualifier().get().toString(), formattedType(binding.key().type()))
+        : formattedType(binding.key().type());
   }
 
   private static String keyString(String qualifier, String type) {
     return String.format("%s %s", qualifier, type);
   }
 
+  private static String formattedType(DaggerType type) {
+    switch (type.backend()) {
+      case JAVAC:
+        return type.java().toString();
+      case KSP:
+        return type.ksp().getDeclaration().getQualifiedName().asString();
+    }
+    throw new AssertionError("Unsupported backend");
+  }
+
   /** A Truth subject for a {@link Binding}. */
   public final class BindingSubject extends Subject {
 
@@ -122,7 +135,7 @@
      * @param type the canonical name of the type, as returned by {@link TypeMirror#toString()}
      */
     public void dependsOnBindingWithKey(String type) {
-      dependsOnBindingWithKeyString(keyString(type));
+      dependsOnBindingWithKeyString(type);
     }
 
     /**
@@ -138,7 +151,7 @@
 
     private void dependsOnBindingWithKeyString(String keyString) {
       if (actualBindingGraph().requestedBindings(actual).stream()
-          .noneMatch(binding -> binding.key().toString().equals(keyString))) {
+          .noneMatch(binding -> keyString(binding).equals(keyString))) {
         failWithActual("expected to depend on binding with key", keyString);
       }
     }
diff --git a/java/dagger/testing/compile/CompilerTests.java b/java/dagger/testing/compile/CompilerTests.java
index b544e5e..5557420 100644
--- a/java/dagger/testing/compile/CompilerTests.java
+++ b/java/dagger/testing/compile/CompilerTests.java
@@ -78,23 +78,25 @@
   }
 
   /** Returns a {@link Source.KotlinSource} with the given file name and content. */
-  public static Source kotlinSource(String fileName, ImmutableCollection<String> srcLines) {
-    return Source.Companion.kotlin(fileName, String.join("\n", srcLines));
+  public static Source.KotlinSource kotlinSource(
+      String fileName, ImmutableCollection<String> srcLines) {
+    return (Source.KotlinSource) Source.Companion.kotlin(fileName, String.join("\n", srcLines));
   }
 
   /** Returns a {@link Source.KotlinSource} with the given file name and content. */
-  public static Source kotlinSource(String fileName, String... srcLines) {
-    return Source.Companion.kotlin(fileName, String.join("\n", srcLines));
+  public static Source.KotlinSource kotlinSource(String fileName, String... srcLines) {
+    return (Source.KotlinSource) Source.Companion.kotlin(fileName, String.join("\n", srcLines));
   }
 
   /** Returns a {@link Source.JavaSource} with the given file name and content. */
-  public static Source javaSource(String fileName, ImmutableCollection<String> srcLines) {
-    return Source.Companion.java(fileName, String.join("\n", srcLines));
+  public static Source.JavaSource javaSource(
+      String fileName, ImmutableCollection<String> srcLines) {
+    return (Source.JavaSource) Source.Companion.java(fileName, String.join("\n", srcLines));
   }
 
   /** Returns a {@link Source.JavaSource} with the given file name and content. */
-  public static Source javaSource(String fileName, String... srcLines) {
-    return Source.Companion.java(fileName, String.join("\n", srcLines));
+  public static Source.JavaSource javaSource(String fileName, String... srcLines) {
+    return (Source.JavaSource) Source.Companion.java(fileName, String.join("\n", srcLines));
   }
 
   /** Returns a {@link Compiler} instance with the given sources. */
@@ -205,14 +207,13 @@
           /* classpath= */ ImmutableList.of(),
           processorOptions(),
           /* javacArguments= */ ImmutableList.of(),
-          /* kotlincArguments= */ ImmutableList.of(),
+          /* kotlincArguments= */ ImmutableList.of(
+              "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true"),
           /* config= */ PROCESSING_ENV_CONFIG,
-          /* javacProcessors= */
-          ImmutableList.of(
+          /* javacProcessors= */ ImmutableList.of(
               ComponentProcessor.withTestPlugins(bindingGraphPlugins()),
               new CompilerProcessors.JavacProcessor(processingSteps())),
-          /* symbolProcessorProviders= */
-          ImmutableList.of(
+          /* symbolProcessorProviders= */ ImmutableList.of(
               KspComponentProcessor.Provider.withTestPlugins(bindingGraphPlugins()),
               new CompilerProcessors.KspProcessor.Provider(processingSteps())),
           result -> {
diff --git a/javatests/artifacts/dagger-ksp/build.gradle b/javatests/artifacts/dagger-ksp/build.gradle
new file mode 100644
index 0000000..9e92640
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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.
+ */
+
+buildscript {
+  ext {
+    dagger_version = "LOCAL-SNAPSHOT"
+    kotlin_version = "1.8.0"
+    ksp_version = "1.8.0-1.0.9"
+    junit_version = "4.13"
+    truth_version = "1.0.1"
+  }
+  repositories {
+    mavenCentral()
+    mavenLocal()
+  }
+  dependencies {
+    classpath("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:$ksp_version")
+  }
+}
+
+allprojects {
+  repositories {
+    mavenCentral()
+    mavenLocal()
+  }
+
+  configurations.all {
+    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+      if (details.requested.group == 'com.google.dagger'
+            && "$dagger_version" == 'LOCAL-SNAPSHOT') {
+        details.useVersion 'LOCAL-SNAPSHOT'
+        details.because 'LOCAL-SNAPSHOT should act as latest version.'
+      }
+    }
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/gradle.properties b/javatests/artifacts/dagger-ksp/gradle.properties
new file mode 100644
index 0000000..e68633c
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/gradle.properties
@@ -0,0 +1,2 @@
+org.gradle.caching=true
+org.gradle.parallel=true
\ No newline at end of file
diff --git a/javatests/artifacts/dagger-ksp/gradle/wrapper/gradle-wrapper.jar b/javatests/artifacts/dagger-ksp/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..5c2d1cf
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/javatests/artifacts/dagger-ksp/gradle/wrapper/gradle-wrapper.properties b/javatests/artifacts/dagger-ksp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0f80bbf
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/javatests/artifacts/dagger-ksp/gradlew b/javatests/artifacts/dagger-ksp/gradlew
new file mode 100755
index 0000000..b0d6d0a
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/gradlew
@@ -0,0 +1,188 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/javatests/artifacts/dagger-ksp/java-app/build.gradle b/javatests/artifacts/dagger-ksp/java-app/build.gradle
new file mode 100644
index 0000000..19383c3
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/java-app/build.gradle
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+  id 'application'
+  id 'org.jetbrains.kotlin.jvm'
+  id 'com.google.devtools.ksp'
+}
+
+dependencies {
+  implementation "com.google.dagger:dagger:$dagger_version"
+  ksp "com.google.dagger:dagger-compiler:$dagger_version"
+
+  testImplementation 'com.google.truth:truth:1.0.1'
+  testImplementation 'junit:junit:4.13'
+}
+
+mainClassName = 'app.SimpleApplication'
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java b/javatests/artifacts/dagger-ksp/java-app/src/main/java/app/AssistedInjectClasses.java
similarity index 78%
rename from javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java
rename to javatests/artifacts/dagger-ksp/java-app/src/main/java/app/AssistedInjectClasses.java
index 246c541..06e1c45 100644
--- a/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java
+++ b/javatests/artifacts/dagger-ksp/java-app/src/main/java/app/AssistedInjectClasses.java
@@ -24,7 +24,7 @@
 
 // This is a regression test for https://github.com/google/dagger/issues/2309
 /** A simple, skeletal application that defines an assisted inject binding. */
-public class AssistedInjects {
+public class AssistedInjectClasses {
   @Component
   interface MyComponent {
     FooFactory fooFactory();
@@ -38,8 +38,14 @@
   }
 
   static class Foo {
+    String assistedStr;
+    Bar bar;
+
     @AssistedInject
-    Foo(Bar bar, @Assisted String str) {}
+    Foo(Bar bar, @Assisted String assistedStr) {
+      this.assistedStr = assistedStr;
+      this.bar = bar;
+    }
   }
 
   @AssistedFactory
@@ -48,19 +54,18 @@
   }
 
   static class ParameterizedFoo<T1, T2> {
+    T1 t1;
+    T2 assistedT2;
+
     @AssistedInject
-    ParameterizedFoo(T1 t1, @Assisted T2 t2) {}
+    ParameterizedFoo(T1 t1, @Assisted T2 assistedT2) {
+      this.t1 = t1;
+      this.assistedT2 = assistedT2;
+    }
   }
 
   @AssistedFactory
   interface ParameterizedFooFactory<T1, T2> {
     ParameterizedFoo<T1, T2> create(T2 t2);
   }
-
-  public static void main(String[] args) {
-    Foo foo = DaggerAssistedInjects_MyComponent.create().fooFactory().create("");
-
-    ParameterizedFoo<Bar, String> parameterizedFoo =
-        DaggerAssistedInjects_MyComponent.create().parameterizedFooFactory().create("");
-  }
 }
diff --git a/javatests/artifacts/dagger-ksp/java-app/src/main/java/app/SimpleComponentClasses.java b/javatests/artifacts/dagger-ksp/java-app/src/main/java/app/SimpleComponentClasses.java
new file mode 100644
index 0000000..4e02386
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/java-app/src/main/java/app/SimpleComponentClasses.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 app;
+
+import dagger.Component;
+import dagger.Module;
+import dagger.Provides;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+/** A simple, skeletal application that defines a simple component. */
+public class SimpleComponentClasses {
+  static final class Foo {
+    @Inject
+    Foo() {}
+  }
+
+  @Singleton
+  static final class ScopedFoo {
+    @Inject
+    ScopedFoo() {}
+  }
+
+  static final class ProvidedFoo {
+    ProvidedFoo() {}
+  }
+
+  static final class ScopedProvidedFoo {
+    ScopedProvidedFoo() {}
+  }
+
+  @Module
+  static final class SimpleModule {
+    @Provides
+    static ProvidedFoo provideFoo() {
+      return new ProvidedFoo();
+    }
+
+    @Provides
+    @Singleton
+    static ScopedProvidedFoo provideScopedFoo() {
+      return new ScopedProvidedFoo();
+    }
+  }
+
+  @Singleton
+  @Component(modules = SimpleModule.class)
+  interface SimpleComponent {
+    Foo foo();
+
+    ScopedFoo scopedFoo();
+
+    ProvidedFoo providedFoo();
+
+    ScopedProvidedFoo scopedProvidedFoo();
+
+    Provider<ScopedFoo> scopedFooProvider();
+
+    Provider<ScopedProvidedFoo> scopedProvidedFooProvider();
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/java-app/src/test/java/app/AssistedInjectTest.java b/javatests/artifacts/dagger-ksp/java-app/src/test/java/app/AssistedInjectTest.java
new file mode 100644
index 0000000..33c73a3
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/java-app/src/test/java/app/AssistedInjectTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import app.AssistedInjectClasses.Bar;
+import app.AssistedInjectClasses.Foo;
+import app.AssistedInjectClasses.MyComponent;
+import app.AssistedInjectClasses.ParameterizedFoo;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AssistedInjectTest {
+  private MyComponent component;
+
+  @Before
+  public void setUp() {
+    component = DaggerAssistedInjectClasses_MyComponent.create();
+  }
+
+  @Test
+  public void testFoo() {
+    Foo foo = component.fooFactory().create("str1");
+    assertThat(foo).isNotNull();
+    assertThat(foo.bar).isNotNull();
+    assertThat(foo.assistedStr).isEqualTo("str1");
+  }
+
+  @Test
+  public void testParameterizedFoo() {
+    ParameterizedFoo<Bar, String> parameterizedFoo =
+        component.parameterizedFooFactory().create("str2");
+    assertThat(parameterizedFoo).isNotNull();
+    assertThat(parameterizedFoo.t1).isNotNull();
+    assertThat(parameterizedFoo.assistedT2).isEqualTo("str2");
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/java-app/src/test/java/app/SimpleComponentTest.java b/javatests/artifacts/dagger-ksp/java-app/src/test/java/app/SimpleComponentTest.java
new file mode 100644
index 0000000..6af89bb
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/java-app/src/test/java/app/SimpleComponentTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import app.SimpleComponentClasses.SimpleComponent;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SimpleComponentTest {
+  private SimpleComponent component;
+
+  @Before
+  public void setUp() {
+    component = DaggerSimpleComponentClasses_SimpleComponent.create();
+  }
+
+  @Test
+  public void fooTest() {
+    assertThat(component.foo()).isNotNull();
+    assertThat(component.foo()).isNotEqualTo(component.foo());
+  }
+
+  @Test
+  public void scopedFooTest() {
+    assertThat(component.scopedFoo()).isNotNull();
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFoo());
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFooProvider().get());
+  }
+
+  @Test
+  public void providedFooTest() {
+    assertThat(component.providedFoo()).isNotNull();
+    assertThat(component.providedFoo()).isNotEqualTo(component.providedFoo());
+  }
+
+  @Test
+  public void scopedProvidedFooTest() {
+    assertThat(component.scopedProvidedFoo()).isNotNull();
+    assertThat(component.scopedProvidedFoo()).isEqualTo(component.scopedProvidedFoo());
+    assertThat(component.scopedProvidedFoo())
+        .isEqualTo(component.scopedProvidedFooProvider().get());
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/kotlin-app/build.gradle b/javatests/artifacts/dagger-ksp/kotlin-app/build.gradle
new file mode 100644
index 0000000..5dc8897
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/build.gradle
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+    id 'application'
+    id 'org.jetbrains.kotlin.jvm'
+    id 'com.google.devtools.ksp'
+}
+
+java {
+    // Make sure the generated source is compatible with Java 8. This can be
+    // Java 8 because this is non-Android and so isn't required to be Java 11
+    // by AGP.
+    sourceCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+  implementation project(path: ':kotlin-app:kotlin-library')
+
+  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+  implementation "com.google.dagger:dagger:$dagger_version"
+  ksp "com.google.dagger:dagger-compiler:$dagger_version"
+
+  // This is testImplementation rather than kaptTest because we're actually
+  // testing the reference to ComponentProcessor.
+  // See https://github.com/google/dagger/issues/2765
+  testImplementation "com.google.dagger:dagger-compiler:$dagger_version"
+  testImplementation "com.google.truth:truth:$truth_version"
+  testImplementation "junit:junit:$junit_version"
+}
+
+application {
+    mainClass = 'app.SimpleApplicationKt'
+}
\ No newline at end of file
diff --git a/javatests/artifacts/dagger-ksp/kotlin-app/kotlin-library/build.gradle b/javatests/artifacts/dagger-ksp/kotlin-app/kotlin-library/build.gradle
new file mode 100644
index 0000000..b650b5f
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/kotlin-library/build.gradle
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+    id 'org.jetbrains.kotlin.jvm'
+    id 'com.google.devtools.ksp'
+}
+
+java {
+    // Make sure the generated source is compatible with Java 8. Test
+    sourceCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+  implementation "com.google.dagger:dagger:$dagger_version"
+  ksp "com.google.dagger:dagger-compiler:$dagger_version"
+
+  // This is testImplementation rather than kaptTest because we're actually
+  // testing the reference to ComponentProcessor.
+  // See https://github.com/google/dagger/issues/2765
+  testImplementation "com.google.dagger:dagger-compiler:$dagger_version"
+  testImplementation "com.google.truth:truth:$truth_version"
+  testImplementation "junit:junit:$junit_version"
+}
diff --git a/javatests/artifacts/dagger-ksp/kotlin-app/kotlin-library/src/main/kotlin/library/MySubcomponent.kt b/javatests/artifacts/dagger-ksp/kotlin-app/kotlin-library/src/main/kotlin/library/MySubcomponent.kt
new file mode 100644
index 0000000..70e9d3b
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/kotlin-library/src/main/kotlin/library/MySubcomponent.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 library
+
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+/**
+ * This subcomponent reproduces a regression in https://github.com/google/dagger/issues/2997.
+ */
+@Subcomponent
+abstract class MySubcomponent {
+    abstract fun instance(): InstanceType
+
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(@BindsInstance instance: InstanceType): MySubcomponent
+    }
+}
+
+class InstanceType
diff --git a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt b/javatests/artifacts/dagger-ksp/kotlin-app/src/main/kotlin/app/AssistedInjectClasses.kt
similarity index 74%
rename from javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt
rename to javatests/artifacts/dagger-ksp/kotlin-app/src/main/kotlin/app/AssistedInjectClasses.kt
index 0fe65fd..1a612e9 100644
--- a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/src/main/kotlin/app/AssistedInjectClasses.kt
@@ -24,7 +24,7 @@
 
 // This is a regression test for https://github.com/google/dagger/issues/2309
 /** A simple, skeletal application that defines an assisted inject binding. */
-class AssistedInjects {
+class AssistedInjectClasses {
   @Component
   interface MyComponent {
     fun fooFactory(): FooFactory
@@ -34,27 +34,19 @@
 
   class Bar @Inject constructor()
 
-  class Foo @AssistedInject constructor(val bar: Bar, @Assisted val str: String)
+  class Foo @AssistedInject constructor(val bar: Bar, @Assisted val assistedStr: String)
 
   @AssistedFactory
   interface FooFactory {
     fun create(str: String): Foo
   }
 
-  class ParameterizedFoo<T1, T2> @AssistedInject constructor(val t1: T1, @Assisted val t2: T2)
+  class ParameterizedFoo<T1, T2>
+  @AssistedInject
+  constructor(val t1: T1, @Assisted val assistedT2: T2)
 
   @AssistedFactory
   interface ParameterizedFooFactory<T1, T2> {
     fun create(t2: T2): ParameterizedFoo<T1, T2>
   }
-
-  companion object {
-    // Called from SimpleApplication.main()
-    fun main() {
-        val foo: Foo = DaggerAssistedInjects_MyComponent.create().fooFactory().create("")
-
-        val parameterizedFoo: ParameterizedFoo<Bar, String> =
-            DaggerAssistedInjects_MyComponent.create().parameterizedFooFactory().create("")
-    }
-  }
 }
diff --git a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt b/javatests/artifacts/dagger-ksp/kotlin-app/src/main/kotlin/app/SimpleComponentClasses.kt
similarity index 68%
rename from javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt
rename to javatests/artifacts/dagger-ksp/kotlin-app/src/main/kotlin/app/SimpleComponentClasses.kt
index 599d703..bcdbcce 100644
--- a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/src/main/kotlin/app/SimpleComponentClasses.kt
@@ -20,38 +20,35 @@
 import dagger.Module
 import dagger.Provides
 import javax.inject.Inject
+import javax.inject.Provider
 import javax.inject.Singleton
 import library.MySubcomponent
 
 /** A simple, skeletal application that defines a simple component. */
-class SimpleApplication {
+class SimpleComponentClasses {
   class Foo @Inject constructor()
+  @Singleton class ScopedFoo @Inject constructor()
+  class ProvidedFoo
+  class ScopedProvidedFoo
 
   @Module
   object SimpleModule {
-    @Provides
-    fun provideFoo(): Foo {
-      return Foo()
-    }
+    @Provides fun provideFoo(): ProvidedFoo = ProvidedFoo()
+
+    @Provides @Singleton fun provideScopedFoo(): ScopedProvidedFoo = ScopedProvidedFoo()
   }
 
   @Singleton
   @Component(modules = [SimpleModule::class])
   interface SimpleComponent {
     fun foo(): Foo
+    fun scopedFoo(): ScopedFoo
+    fun providedFoo(): ProvidedFoo
+    fun scopedProvidedFoo(): ScopedProvidedFoo
+    fun scopedFooProvider(): Provider<ScopedFoo>
+    fun scopedProvidedFooProvider(): Provider<ScopedProvidedFoo>
 
     // Reproduces a regression in https://github.com/google/dagger/issues/2997.
     fun mySubcomponentFactory(): MySubcomponent.Factory
   }
-
-  companion object {
-    fun main() {
-      val foo: Foo = DaggerSimpleApplication_SimpleComponent.create().foo()
-    }
-  }
-}
-
-fun main() {
-  SimpleApplication.main()
-  AssistedInjects.main()
 }
diff --git a/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/AssistedInjectTest.kt b/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/AssistedInjectTest.kt
new file mode 100644
index 0000000..10a14ef
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/AssistedInjectTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import app.AssistedInjectClasses.MyComponent
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class AssistedInjectTest {
+  private lateinit var component: MyComponent
+
+  @Before
+  fun setUp() {
+    component = DaggerAssistedInjectClasses_MyComponent.create()
+  }
+
+  @Test
+  fun testFoo() {
+    val foo = component.fooFactory().create("str1")
+    assertThat(foo).isNotNull()
+    assertThat(foo.bar).isNotNull()
+    assertThat(foo.assistedStr).isEqualTo("str1")
+  }
+
+  @Test
+  fun testParameterizedFoo() {
+    val parameterizedFoo = component.parameterizedFooFactory().create("str2")
+    assertThat(parameterizedFoo).isNotNull()
+    assertThat(parameterizedFoo.t1).isNotNull()
+    assertThat(parameterizedFoo.assistedT2).isEqualTo("str2")
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/ComponentProcessorBuildTest.kt b/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/ComponentProcessorBuildTest.kt
new file mode 100644
index 0000000..1b050f8
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/ComponentProcessorBuildTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 app
+
+import com.google.common.truth.Truth.assertThat
+import dagger.internal.codegen.ComponentProcessor
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ComponentProcessorBuildTest {
+
+  // This is a regression test for https://github.com/google/dagger/issues/2765
+  // to make sure ComponentProcessor builds in kotlin.
+  @Test
+  fun testComponentProcessor() {
+    val processor = ComponentProcessor.forTesting()
+
+    assertThat(processor).isNotNull()
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/SimpleComponentTest.kt b/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/SimpleComponentTest.kt
new file mode 100644
index 0000000..7bd94fd
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/kotlin-app/src/test/kotlin/app/SimpleComponentTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import app.SimpleComponentClasses.SimpleComponent
+import com.google.common.truth.Truth.assertThat
+import library.InstanceType
+import library.MySubcomponent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SimpleComponentTest {
+  private lateinit var component: SimpleComponent
+
+  @Before
+  fun setUp() {
+    component = DaggerSimpleComponentClasses_SimpleComponent.create()
+  }
+
+  @Test
+  fun fooTest() {
+    assertThat(component.foo()).isNotNull()
+    assertThat(component.foo()).isNotEqualTo(component.foo())
+  }
+
+  @Test
+  fun scopedFooTest() {
+    assertThat(component.scopedFoo()).isNotNull()
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFoo())
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFooProvider().get())
+  }
+
+  @Test
+  fun providedFooTest() {
+    assertThat(component.providedFoo()).isNotNull()
+    assertThat(component.providedFoo()).isNotEqualTo(component.providedFoo())
+  }
+
+  @Test
+  fun scopedProvidedFooTest() {
+    assertThat(component.scopedProvidedFoo()).isNotNull()
+    assertThat(component.scopedProvidedFoo()).isEqualTo(component.scopedProvidedFoo())
+    assertThat(component.scopedProvidedFoo()).isEqualTo(component.scopedProvidedFooProvider().get())
+  }
+
+  @Test
+  fun subcomponentTest() {
+    val instanceType = InstanceType()
+    val subcomponent = component.mySubcomponentFactory().create(instanceType)
+    assertThat(subcomponent).isNotNull()
+    assertThat(subcomponent.instance()).isEqualTo(instanceType)
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/settings.gradle b/javatests/artifacts/dagger-ksp/settings.gradle
new file mode 100644
index 0000000..e04b31a
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/settings.gradle
@@ -0,0 +1,7 @@
+rootProject.name = 'Dagger KSP Apps'
+include ':java-app'
+include ':kotlin-app'
+include ':kotlin-app:kotlin-library'
+include ':transitive-annotation-app'
+include ':transitive-annotation-app:library1'
+include ':transitive-annotation-app:library2'
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/build.gradle b/javatests/artifacts/dagger-ksp/transitive-annotation-app/build.gradle
new file mode 100644
index 0000000..d9e8723
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/build.gradle
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+  id 'application'
+  id 'org.jetbrains.kotlin.jvm'
+  id 'com.google.devtools.ksp'
+}
+
+dependencies {
+  implementation project(":transitive-annotation-app:library1")
+  implementation "com.google.dagger:dagger:$dagger_version"
+  ksp "com.google.dagger:dagger-compiler:$dagger_version"
+
+  testImplementation "junit:junit:$junit_version"
+  testImplementation "com.google.truth:truth:$truth_version"
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/build.gradle b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/build.gradle
new file mode 100644
index 0000000..7cfd83f
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+  id 'java-library'
+  id 'org.jetbrains.kotlin.jvm'
+  id 'com.google.devtools.ksp'
+}
+
+dependencies {
+  implementation project(":transitive-annotation-app:library2")
+  implementation "com.google.dagger:dagger:$dagger_version"
+  ksp "com.google.dagger:dagger-compiler:$dagger_version"
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/AssistedFoo.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/AssistedFoo.java
new file mode 100644
index 0000000..8377d1c
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/AssistedFoo.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+import javax.inject.Inject;
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+public final class AssistedFoo extends FooBase {
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  MyTransitiveType nonDaggerField;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  @MyQualifier
+  Dep daggerField;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  AssistedFoo(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    super(nonDaggerParameter);
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @AssistedInject
+  AssistedFoo(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          @Assisted
+          int i,
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          @MyQualifier
+          Dep dep) {
+    super(dep);
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  MyTransitiveType nonDaggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  void daggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          @MyQualifier
+          Dep dep) {}
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @AssistedFactory
+  public interface Factory {
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    AssistedFoo create(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            int i);
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/Dep.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/Dep.java
new file mode 100644
index 0000000..fa64e34
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/Dep.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+public final class Dep {}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/Foo.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/Foo.java
new file mode 100644
index 0000000..062acbe
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/Foo.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 library1;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+@Singleton
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+public final class Foo extends FooBase {
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  MyTransitiveType nonDaggerField;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  @MyQualifier
+  Dep daggerField;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  Foo(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    super(nonDaggerParameter);
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  Foo(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          @MyQualifier
+          Dep dep) {
+    super(dep);
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  MyTransitiveType nonDaggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  void daggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          @MyQualifier
+          Dep dep) {}
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/FooBase.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/FooBase.java
new file mode 100644
index 0000000..ad113dd
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/FooBase.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 library1;
+
+import javax.inject.Inject;
+import library2.MyTransitiveBaseAnnotation;
+import library2.MyTransitiveType;
+
+/** A baseclass for {@link Foo}. */
+@MyTransitiveBaseAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+public class FooBase {
+  @MyTransitiveBaseAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  int baseNonDaggerField;
+
+  @MyTransitiveBaseAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  @MyQualifier
+  Dep baseDaggerField;
+
+  @MyTransitiveBaseAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  FooBase(
+      @MyTransitiveBaseAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {}
+
+  @MyTransitiveBaseAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  FooBase(
+      @MyTransitiveBaseAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          @MyQualifier
+          Dep dep) {}
+
+  @MyTransitiveBaseAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  void baseNonDaggerMethod(
+      @MyTransitiveBaseAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          int i) {}
+
+  @MyTransitiveBaseAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Inject
+  void baseDaggerMethod(
+      @MyTransitiveBaseAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          @MyQualifier
+          Dep dep) {}
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyAnnotation.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyAnnotation.java
new file mode 100644
index 0000000..58ba4e7
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyAnnotation.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+/** An annotation that is a direct dependency of the app. */
+public @interface MyAnnotation {
+  int value();
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyBaseComponent.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyBaseComponent.java
new file mode 100644
index 0000000..78221ae
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyBaseComponent.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/**
+ * A class used to test that Dagger won't fail on unresolvable transitive types used in non-dagger
+ * related elements and annotations.
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+public abstract class MyBaseComponent {
+  // @MyTransitiveAnnotation cannot be used here.
+  @MyQualifier
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract MyComponentModule.UnscopedQualifiedBindsType unscopedQualifiedBindsTypeBase();
+
+  // @MyTransitiveAnnotation cannot be used here.
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract MyComponentModule.UnscopedUnqualifiedBindsType unscopedUnqualifiedBindsTypeBase();
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract void injectFooBase(
+      // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here)
+      @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) Foo binding);
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract static class Factory {
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public abstract MyBaseComponent create(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MyComponentModule myComponentModule,
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MyComponentDependency myComponentDependency);
+
+    // Non-dagger factory code
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public MyTransitiveType nonDaggerField = null;
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public static MyTransitiveType nonDaggerStaticField = null;
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public MyTransitiveType nonDaggerMethod(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MyTransitiveType nonDaggerParameter) {
+      return nonDaggerParameter;
+    }
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public static MyTransitiveType nonDaggerStaticMethod(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MyTransitiveType nonDaggerParameter) {
+      return nonDaggerParameter;
+    }
+  }
+
+  // Non-dagger code
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependency.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependency.java
new file mode 100644
index 0000000..fbaf588
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependency.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/**
+ * A class used to test that Dagger won't fail on unresolvable transitive types used in non-dagger
+ * related elements and annotations.
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+public final class MyComponentDependency {
+  private final MyComponentDependencyBinding qualifiedMyComponentDependencyBinding =
+      new MyComponentDependencyBinding();
+  private final MyComponentDependencyBinding unqualifiedMyComponentDependencyBinding =
+      new MyComponentDependencyBinding();
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyComponentDependency() {}
+
+  // @MyTransitiveAnnotation cannot be used here.
+  @MyQualifier
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyComponentDependencyBinding qualifiedMyComponentDependencyBinding() {
+    return qualifiedMyComponentDependencyBinding;
+  }
+
+  // @MyTransitiveAnnotation cannot be used here.
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyComponentDependencyBinding unqualifiedMyComponentDependencyBinding() {
+    return unqualifiedMyComponentDependencyBinding;
+  }
+
+  // Non-dagger code
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependencyBinding.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependencyBinding.java
new file mode 100644
index 0000000..c5d6bc9
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentDependencyBinding.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+/** Used as a binding in {@link MyComponentDependency}. */
+public final class MyComponentDependencyBinding {}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentModule.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentModule.java
new file mode 100644
index 0000000..048ed1a
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyComponentModule.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 library1;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import javax.inject.Singleton;
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+@Module(includes = {MyComponentModule.MyAbstractModule.class})
+public final class MyComponentModule {
+  // Define bindings for each configuration: Scoped/Unscoped, Qualified/UnQualified, Provides/Binds
+  public static class ScopedQualifiedBindsType {}
+  public static final class ScopedQualifiedProvidesType extends ScopedQualifiedBindsType {}
+  public static class ScopedUnqualifiedBindsType {}
+  public static final class ScopedUnqualifiedProvidesType extends ScopedUnqualifiedBindsType {}
+  public static class UnscopedQualifiedBindsType {}
+  public static final class UnscopedQualifiedProvidesType extends UnscopedQualifiedBindsType {}
+  public static class UnscopedUnqualifiedBindsType {}
+  public static final class UnscopedUnqualifiedProvidesType extends UnscopedUnqualifiedBindsType {}
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Provides
+  @Singleton
+  @MyQualifier
+  ScopedQualifiedProvidesType scopedQualifiedProvidesType(
+      @MyQualifier
+          @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          Dep dep) {
+    return new ScopedQualifiedProvidesType();
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Provides
+  @Singleton
+  ScopedUnqualifiedProvidesType scopedUnqualifiedProvidesType(
+      @MyQualifier
+          @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          Dep dep) {
+    return new ScopedUnqualifiedProvidesType();
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Provides
+  @MyQualifier
+  UnscopedQualifiedProvidesType unscopedQualifiedProvidesType(
+      @MyQualifier
+          @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          Dep dep) {
+    return new UnscopedQualifiedProvidesType();
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Provides
+  UnscopedUnqualifiedProvidesType unscopedUnqualifiedProvidesType(
+      @MyQualifier
+          @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          Dep dep) {
+    return new UnscopedUnqualifiedProvidesType();
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Module
+  interface MyAbstractModule {
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    @Binds
+    @Singleton
+    @MyQualifier
+    ScopedQualifiedBindsType scopedQualifiedBindsType(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyQualifier
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            ScopedQualifiedProvidesType scopedQualifiedProvidesType);
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    @Binds
+    @Singleton
+    ScopedUnqualifiedBindsType scopedUnqualifiedBindsType(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class)
+            ScopedUnqualifiedProvidesType scopedUnqualifiedProvidesType);
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    @Binds
+    @MyQualifier
+    UnscopedQualifiedBindsType unscopedQualifiedBindsType(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyQualifier
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            UnscopedQualifiedProvidesType unscopedQualifiedProvidesType);
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    @Binds
+    UnscopedUnqualifiedBindsType unscopedUnqualifiedBindsType(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class)
+            UnscopedUnqualifiedProvidesType unscopedUnqualifiedProvidesType);
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Provides
+  @MyQualifier
+  Dep provideQualifiedDep() {
+    return new Dep();
+  }
+
+  // Provide an unqualified Dep to ensure that if we accidentally drop the qualifier
+  // we'll get a runtime exception.
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Provides
+  Dep provideDep() {
+    throw new UnsupportedOperationException();
+  }
+
+  // Non-Dagger elements
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  private Dep dep;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  private MyTransitiveType nonDaggerField;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyComponentModule(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          Dep dep) {
+    this.dep = dep;
+    this.nonDaggerField = new MyTransitiveType();
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  MyTransitiveType nonDaggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  static MyTransitiveType nonDaggerStaticMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyOtherAnnotation.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyOtherAnnotation.java
new file mode 100644
index 0000000..c1cb6ae
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyOtherAnnotation.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+/** An annotation that is a direct dependency of the app. */
+public @interface MyOtherAnnotation {
+  Class<?> value();
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyQualifier.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyQualifier.java
new file mode 100644
index 0000000..c08ac98
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MyQualifier.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+public @interface MyQualifier {}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentBinding.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentBinding.java
new file mode 100644
index 0000000..61bb3f7
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentBinding.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+/** A simple binding that needs to be passed in when creating this component. */
+public final class MySubcomponentBinding {}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentModule.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentModule.java
new file mode 100644
index 0000000..da8de7b
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentModule.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import dagger.Module;
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/** A simple module that needs to be passed in when creating this component. */
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+@Module
+public final class MySubcomponentModule {
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MySubcomponentModule(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          int i) {}
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentScope.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentScope.java
new file mode 100644
index 0000000..e76b0b6
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentScope.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import javax.inject.Scope;
+
+/** A scope for {@link MySubcomponent}. */
+@Scope
+public @interface MySubcomponentScope {}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithBuilder.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithBuilder.java
new file mode 100644
index 0000000..27045ea
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithBuilder.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+@MySubcomponentScope // TODO(b/269172737): Fix issue that requires reordering to build successfully.
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+@Subcomponent(modules = MySubcomponentModule.class)
+public abstract class MySubcomponentWithBuilder {
+  @MyQualifier
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract MySubcomponentBinding qualifiedMySubcomponentBinding();
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract MySubcomponentBinding unqualifiedMySubcomponentBinding();
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract void injectFoo(
+      // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here)
+      @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) Foo foo);
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Subcomponent.Builder
+  public abstract static class Builder {
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public abstract Builder mySubcomponentModule(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MySubcomponentModule mySubcomponentModule);
+
+    @BindsInstance
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public abstract Builder qualifiedMySubcomponentBinding(
+        @MyQualifier
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MySubcomponentBinding subcomponentBinding);
+
+    @BindsInstance
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public abstract Builder unqualifiedMySubcomponentBinding(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class)
+            MySubcomponentBinding subcomponentBinding);
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public abstract MySubcomponentWithBuilder build();
+
+    // Non-dagger code
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public String nonDaggerField = "";
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public static String nonDaggerStaticField = "";
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public void nonDaggerMethod(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            String str) {}
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public static void nonDaggerStaticMethod(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            String str) {}
+  }
+
+  // Non-dagger code
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithFactory.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithFactory.java
new file mode 100644
index 0000000..9005193
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library1/src/main/java/library1/MySubcomponentWithFactory.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library1;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+import library2.MyTransitiveAnnotation;
+import library2.MyTransitiveType;
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ * <p>During the compilation of {@code :app}, {@link MyTransitiveAnnotation} will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+@MySubcomponentScope // TODO(b/269172737): Fix issue that requires reordering to build successfully.
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType.class)
+@Subcomponent(modules = MySubcomponentModule.class)
+public abstract class MySubcomponentWithFactory {
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+  @MyQualifier
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract MySubcomponentBinding qualifiedMySubcomponentBinding();
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract MySubcomponentBinding unqualifiedMySubcomponentBinding();
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public abstract void injectFoo(
+      // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here)
+      @MyAnnotation(MyTransitiveType.VALUE) @MyOtherAnnotation(MyTransitiveType.class) Foo foo);
+
+  // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  @Subcomponent.Factory
+  public abstract static class Factory {
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public abstract MySubcomponentWithFactory create(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MySubcomponentModule mySubcomponentModule,
+        @BindsInstance
+            @MyQualifier
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MySubcomponentBinding qualifiedSubcomponentBinding,
+        @BindsInstance
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MySubcomponentBinding unqualifiedSubcomponentBinding);
+
+    // Non-dagger code
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public MyTransitiveType nonDaggerField = null;
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public static MyTransitiveType nonDaggerStaticField = null;
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public MyTransitiveType nonDaggerMethod(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MyTransitiveType nonDaggerParameter) {
+      return nonDaggerParameter;
+    }
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType.class)
+    public static MyTransitiveType nonDaggerStaticMethod(
+        @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType.class)
+            MyTransitiveType nonDaggerParameter) {
+      return nonDaggerParameter;
+    }
+  }
+
+  // Non-dagger code
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticField = null;
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public MyTransitiveType nonDaggerMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+
+  @MyTransitiveAnnotation
+  @MyAnnotation(MyTransitiveType.VALUE)
+  @MyOtherAnnotation(MyTransitiveType.class)
+  public static MyTransitiveType nonDaggerStaticMethod(
+      @MyTransitiveAnnotation
+          @MyAnnotation(MyTransitiveType.VALUE)
+          @MyOtherAnnotation(MyTransitiveType.class)
+          MyTransitiveType nonDaggerParameter) {
+    return nonDaggerParameter;
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/build.gradle b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/build.gradle
new file mode 100644
index 0000000..edb5753
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/build.gradle
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+  id 'java-library'
+  id 'org.jetbrains.kotlin.jvm'
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveAnnotation.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveAnnotation.java
new file mode 100644
index 0000000..5f1f4bc
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveAnnotation.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library2;
+
+/** A simple annotation that is a transitive dependency of the app. */
+public @interface MyTransitiveAnnotation {}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveBaseAnnotation.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveBaseAnnotation.java
new file mode 100644
index 0000000..dcc6739
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveBaseAnnotation.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library2;
+
+/** A simple annotation that is a transitive dependency of the app. */
+public @interface MyTransitiveBaseAnnotation {}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveType.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveType.java
new file mode 100644
index 0000000..c6ecd5d
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/library2/src/main/java/library2/MyTransitiveType.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 library2;
+
+
+/** A class that is a transitive dependency of the app. */
+public final class MyTransitiveType {
+  public static final int VALUE = 3;
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/main/java/app/MyComponent.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/main/java/app/MyComponent.java
new file mode 100644
index 0000000..ee55d3a
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/main/java/app/MyComponent.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 app;
+
+import dagger.Component;
+import javax.inject.Singleton;
+import library1.AssistedFoo;
+import library1.Foo;
+import library1.MyBaseComponent;
+import library1.MyComponentDependency;
+import library1.MyComponentDependencyBinding;
+import library1.MyComponentModule;
+import library1.MyQualifier;
+import library1.MySubcomponentWithBuilder;
+import library1.MySubcomponentWithFactory;
+
+@Singleton
+@Component(dependencies = MyComponentDependency.class, modules = MyComponentModule.class)
+abstract class MyComponent extends MyBaseComponent {
+  abstract Foo foo();
+
+  abstract AssistedFoo.Factory assistedFooFactory();
+
+  @MyQualifier
+  abstract MyComponentModule.ScopedQualifiedBindsType scopedQualifiedBindsType();
+
+  abstract MyComponentModule.ScopedUnqualifiedBindsType scopedUnqualifiedBindsType();
+
+  @MyQualifier
+  abstract MyComponentModule.UnscopedQualifiedBindsType unscopedQualifiedBindsType();
+
+  abstract MyComponentModule.UnscopedUnqualifiedBindsType unscopedUnqualifiedBindsType();
+
+  @MyQualifier
+  abstract MyComponentModule.ScopedQualifiedProvidesType scopedQualifiedProvidesType();
+
+  abstract MyComponentModule.ScopedUnqualifiedProvidesType scopedUnqualifiedProvidesType();
+
+  @MyQualifier
+  abstract MyComponentModule.UnscopedQualifiedProvidesType unscopedQualifiedProvidesType();
+
+  abstract MyComponentModule.UnscopedUnqualifiedProvidesType unscopedUnqualifiedProvidesType();
+
+  abstract MySubcomponentWithFactory.Factory mySubcomponentWithFactory();
+
+  abstract MySubcomponentWithBuilder.Builder mySubcomponentWithBuilder();
+
+  @MyQualifier
+  abstract MyComponentDependencyBinding qualifiedMyComponentDependencyBinding();
+
+  abstract MyComponentDependencyBinding unqualifiedMyComponentDependencyBinding();
+
+  @Component.Factory
+  abstract static class Factory extends MyBaseComponent.Factory {
+    public abstract MyComponent create(
+        MyComponentModule myComponentModule,
+        MyComponentDependency myComponentDependency);
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MyComponentTest.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MyComponentTest.java
new file mode 100644
index 0000000..438d5db
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MyComponentTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Dagger Authors.
+ *
+ * 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 app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import library1.Dep;
+import library1.MyComponentDependency;
+import library1.MyComponentModule;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class MyComponentTest {
+  private MyComponent component;
+
+  @Before
+  public void setup() {
+    component = DaggerMyComponent.factory()
+        .create(new MyComponentModule(new Dep()), new MyComponentDependency());
+  }
+
+  @Test
+  public void testFooIsScoped() {
+    assertThat(component.foo()).isEqualTo(component.foo());
+  }
+
+  @Test
+  public void testAssistedFoo() {
+    assertThat(component.assistedFooFactory().create(5)).isNotNull();
+  }
+
+  @Test
+  public void testScopedQualifiedBindsTypeIsScoped() {
+    assertThat(component.scopedQualifiedBindsType())
+        .isEqualTo(component.scopedQualifiedBindsType());
+  }
+
+  @Test
+  public void testScopedUnqualifiedBindsTypeIsScoped() {
+    assertThat(component.scopedUnqualifiedBindsType())
+        .isEqualTo(component.scopedUnqualifiedBindsType());
+  }
+
+  @Test
+  public void testUnscopedQualifiedBindsTypeIsNotScoped() {
+    assertThat(component.unscopedQualifiedBindsType())
+        .isNotEqualTo(component.unscopedQualifiedBindsType());
+  }
+
+  @Test
+  public void testUnscopedUnqualifiedBindsTypeIsNotScoped() {
+    assertThat(component.unscopedUnqualifiedBindsType())
+        .isNotEqualTo(component.unscopedUnqualifiedBindsType());
+  }
+
+  @Test
+  public void testScopedQualifiedProvidesTypeIsScoped() {
+    assertThat(component.scopedQualifiedProvidesType())
+        .isEqualTo(component.scopedQualifiedProvidesType());
+  }
+
+  @Test
+  public void testScopedUnqualifiedProvidesTypeIsScoped() {
+    assertThat(component.scopedUnqualifiedProvidesType())
+        .isEqualTo(component.scopedUnqualifiedProvidesType());
+  }
+
+  @Test
+  public void testUnscopedQualifiedProvidesTypeIsNotScoped() {
+    assertThat(component.unscopedQualifiedProvidesType())
+        .isNotEqualTo(component.unscopedQualifiedProvidesType());
+  }
+
+  @Test
+  public void testUnscopedUnqualifiedProvidesTypeIsNotScoped() {
+    assertThat(component.unscopedUnqualifiedProvidesType())
+        .isNotEqualTo(component.unscopedUnqualifiedProvidesType());
+  }
+
+  @Test
+  public void testMyComponentDependencyBinding() {
+    assertThat(component.qualifiedMyComponentDependencyBinding())
+        .isNotEqualTo(component.unqualifiedMyComponentDependencyBinding());
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MySubcomponentWithBuilderTest.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MySubcomponentWithBuilderTest.java
new file mode 100644
index 0000000..3db241b
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MySubcomponentWithBuilderTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import library1.Dep;
+import library1.MyComponentDependency;
+import library1.MyComponentModule;
+import library1.MySubcomponentBinding;
+import library1.MySubcomponentModule;
+import library1.MySubcomponentWithBuilder;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class MySubcomponentWithBuilderTest {
+  private MySubcomponentWithBuilder subcomponentWithBuilder;
+
+  @Before
+  public void setup() {
+    subcomponentWithBuilder =
+        DaggerMyComponent.factory()
+            .create(new MyComponentModule(new Dep()), new MyComponentDependency())
+            .mySubcomponentWithBuilder()
+            .mySubcomponentModule(new MySubcomponentModule(3))
+            .qualifiedMySubcomponentBinding(new MySubcomponentBinding())
+            .unqualifiedMySubcomponentBinding(new MySubcomponentBinding())
+            .build();
+  }
+
+  // Test that the qualified and unqualified bindings are two separate objects
+  @Test
+  public void testMySubcomponentBinding() {
+    assertThat(subcomponentWithBuilder.qualifiedMySubcomponentBinding())
+        .isNotEqualTo(subcomponentWithBuilder.unqualifiedMySubcomponentBinding());
+  }
+}
diff --git a/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MySubcomponentWithFactoryTest.java b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MySubcomponentWithFactoryTest.java
new file mode 100644
index 0000000..a45dcc7
--- /dev/null
+++ b/javatests/artifacts/dagger-ksp/transitive-annotation-app/src/test/java/app/MySubcomponentWithFactoryTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import library1.Dep;
+import library1.MyComponentDependency;
+import library1.MyComponentModule;
+import library1.MySubcomponentBinding;
+import library1.MySubcomponentModule;
+import library1.MySubcomponentWithFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class MySubcomponentWithFactoryTest {
+  private MySubcomponentWithFactory subcomponentWithFactory;
+
+  @Before
+  public void setup() {
+    subcomponentWithFactory =
+        DaggerMyComponent.factory()
+            .create(new MyComponentModule(new Dep()), new MyComponentDependency())
+            .mySubcomponentWithFactory()
+            .create(
+                new MySubcomponentModule(1),
+                new MySubcomponentBinding(),
+                new MySubcomponentBinding());
+  }
+
+  // Test that the qualified and unqualified bindings are two separate objects
+  @Test
+  public void testMySubcomponentBinding() {
+    assertThat(subcomponentWithFactory.qualifiedMySubcomponentBinding())
+        .isNotEqualTo(subcomponentWithFactory.unqualifiedMySubcomponentBinding());
+  }
+}
diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveComponentDependenciesTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveComponentDependenciesTest.java
index efadf67..d3c4683 100644
--- a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveComponentDependenciesTest.java
+++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveComponentDependenciesTest.java
@@ -61,6 +61,7 @@
                 + "\n  "
                 + "\n  Dependency trace:"
                 + "\n      => element (CLASS): libraryB.ComponentB"
+                + "\n      => annotation type: dagger.Component"
                 + "\n      => annotation: @dagger.Component("
                 + "modules={}, dependencies={libraryA.ComponentA})"
                 + "\n      => annotation value (TYPE_ARRAY): dependencies={libraryA.ComponentA}"
diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentModulesTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentModulesTest.java
index 4673c86..d249c68 100644
--- a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentModulesTest.java
+++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveSubcomponentModulesTest.java
@@ -74,6 +74,7 @@
                 + "\n  "
                 + "\n  Dependency trace:"
                 + "\n      => element (CLASS): library1.MySubcomponent"
+                + "\n      => annotation type: dagger.Subcomponent"
                 + "\n      => annotation: @dagger.Subcomponent(modules={library2.TransitiveModule})"
                 + "\n      => annotation value (TYPE_ARRAY): modules={library2.TransitiveModule}"
                 + "\n      => annotation value (TYPE): modules=library2.TransitiveModule";
@@ -112,6 +113,7 @@
                 + "\n  "
                 + "\n  Dependency trace:"
                 + "\n      => element (INTERFACE): library1.IncludesTransitiveModule"
+                + "\n      => annotation type: dagger.Module"
                 + "\n      => annotation: "
                 + "@dagger.Module(includes={library2.TransitiveModule}, subcomponents={})"
                 + "\n      => annotation value (TYPE_ARRAY): includes={library2.TransitiveModule}"
diff --git a/javatests/artifacts/dagger/java-app/build.gradle b/javatests/artifacts/dagger/java-app/build.gradle
index e709d7d..16c6e66 100644
--- a/javatests/artifacts/dagger/java-app/build.gradle
+++ b/javatests/artifacts/dagger/java-app/build.gradle
@@ -27,6 +27,9 @@
 dependencies {
   implementation "com.google.dagger:dagger:$dagger_version"
   annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
+
+  testImplementation 'com.google.truth:truth:1.0.1'
+  testImplementation 'junit:junit:4.13'
 }
 
 mainClassName = 'app.SimpleApplication'
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java b/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjectClasses.java
similarity index 78%
copy from javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java
copy to javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjectClasses.java
index 246c541..06e1c45 100644
--- a/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjects.java
+++ b/javatests/artifacts/dagger/java-app/src/main/java/app/AssistedInjectClasses.java
@@ -24,7 +24,7 @@
 
 // This is a regression test for https://github.com/google/dagger/issues/2309
 /** A simple, skeletal application that defines an assisted inject binding. */
-public class AssistedInjects {
+public class AssistedInjectClasses {
   @Component
   interface MyComponent {
     FooFactory fooFactory();
@@ -38,8 +38,14 @@
   }
 
   static class Foo {
+    String assistedStr;
+    Bar bar;
+
     @AssistedInject
-    Foo(Bar bar, @Assisted String str) {}
+    Foo(Bar bar, @Assisted String assistedStr) {
+      this.assistedStr = assistedStr;
+      this.bar = bar;
+    }
   }
 
   @AssistedFactory
@@ -48,19 +54,18 @@
   }
 
   static class ParameterizedFoo<T1, T2> {
+    T1 t1;
+    T2 assistedT2;
+
     @AssistedInject
-    ParameterizedFoo(T1 t1, @Assisted T2 t2) {}
+    ParameterizedFoo(T1 t1, @Assisted T2 assistedT2) {
+      this.t1 = t1;
+      this.assistedT2 = assistedT2;
+    }
   }
 
   @AssistedFactory
   interface ParameterizedFooFactory<T1, T2> {
     ParameterizedFoo<T1, T2> create(T2 t2);
   }
-
-  public static void main(String[] args) {
-    Foo foo = DaggerAssistedInjects_MyComponent.create().fooFactory().create("");
-
-    ParameterizedFoo<Bar, String> parameterizedFoo =
-        DaggerAssistedInjects_MyComponent.create().parameterizedFooFactory().create("");
-  }
 }
diff --git a/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleApplication.java b/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleApplication.java
deleted file mode 100644
index dfe95ca..0000000
--- a/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleApplication.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2020 The Dagger Authors.
- *
- * 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 app;
-
-import dagger.Component;
-import dagger.Module;
-import dagger.Provides;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/** A simple, skeletal application that defines a simple component. */
-public class SimpleApplication {
-  static final class Foo {
-    @Inject Foo() {}
-  }
-
-  @Module
-  static final class SimpleModule {
-    @Provides
-    static Foo provideFoo() {
-      return new Foo();
-    }
-  }
-
-  @Singleton
-  @Component(modules = { SimpleModule.class })
-  interface SimpleComponent {
-    Foo foo();
-  }
-
-  public static void main(String[] args) {
-    Foo foo = DaggerSimpleApplication_SimpleComponent.create().foo();
-
-    // Execute other classes
-    AssistedInjects.main(args);
-  }
-}
diff --git a/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleComponentClasses.java b/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleComponentClasses.java
new file mode 100644
index 0000000..4e02386
--- /dev/null
+++ b/javatests/artifacts/dagger/java-app/src/main/java/app/SimpleComponentClasses.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 The Dagger Authors.
+ *
+ * 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 app;
+
+import dagger.Component;
+import dagger.Module;
+import dagger.Provides;
+import javax.inject.Inject;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+/** A simple, skeletal application that defines a simple component. */
+public class SimpleComponentClasses {
+  static final class Foo {
+    @Inject
+    Foo() {}
+  }
+
+  @Singleton
+  static final class ScopedFoo {
+    @Inject
+    ScopedFoo() {}
+  }
+
+  static final class ProvidedFoo {
+    ProvidedFoo() {}
+  }
+
+  static final class ScopedProvidedFoo {
+    ScopedProvidedFoo() {}
+  }
+
+  @Module
+  static final class SimpleModule {
+    @Provides
+    static ProvidedFoo provideFoo() {
+      return new ProvidedFoo();
+    }
+
+    @Provides
+    @Singleton
+    static ScopedProvidedFoo provideScopedFoo() {
+      return new ScopedProvidedFoo();
+    }
+  }
+
+  @Singleton
+  @Component(modules = SimpleModule.class)
+  interface SimpleComponent {
+    Foo foo();
+
+    ScopedFoo scopedFoo();
+
+    ProvidedFoo providedFoo();
+
+    ScopedProvidedFoo scopedProvidedFoo();
+
+    Provider<ScopedFoo> scopedFooProvider();
+
+    Provider<ScopedProvidedFoo> scopedProvidedFooProvider();
+  }
+}
diff --git a/javatests/artifacts/dagger/java-app/src/test/java/app/AssistedInjectTest.java b/javatests/artifacts/dagger/java-app/src/test/java/app/AssistedInjectTest.java
new file mode 100644
index 0000000..33c73a3
--- /dev/null
+++ b/javatests/artifacts/dagger/java-app/src/test/java/app/AssistedInjectTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import app.AssistedInjectClasses.Bar;
+import app.AssistedInjectClasses.Foo;
+import app.AssistedInjectClasses.MyComponent;
+import app.AssistedInjectClasses.ParameterizedFoo;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AssistedInjectTest {
+  private MyComponent component;
+
+  @Before
+  public void setUp() {
+    component = DaggerAssistedInjectClasses_MyComponent.create();
+  }
+
+  @Test
+  public void testFoo() {
+    Foo foo = component.fooFactory().create("str1");
+    assertThat(foo).isNotNull();
+    assertThat(foo.bar).isNotNull();
+    assertThat(foo.assistedStr).isEqualTo("str1");
+  }
+
+  @Test
+  public void testParameterizedFoo() {
+    ParameterizedFoo<Bar, String> parameterizedFoo =
+        component.parameterizedFooFactory().create("str2");
+    assertThat(parameterizedFoo).isNotNull();
+    assertThat(parameterizedFoo.t1).isNotNull();
+    assertThat(parameterizedFoo.assistedT2).isEqualTo("str2");
+  }
+}
diff --git a/javatests/artifacts/dagger/java-app/src/test/java/app/SimpleComponentTest.java b/javatests/artifacts/dagger/java-app/src/test/java/app/SimpleComponentTest.java
new file mode 100644
index 0000000..6af89bb
--- /dev/null
+++ b/javatests/artifacts/dagger/java-app/src/test/java/app/SimpleComponentTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import app.SimpleComponentClasses.SimpleComponent;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SimpleComponentTest {
+  private SimpleComponent component;
+
+  @Before
+  public void setUp() {
+    component = DaggerSimpleComponentClasses_SimpleComponent.create();
+  }
+
+  @Test
+  public void fooTest() {
+    assertThat(component.foo()).isNotNull();
+    assertThat(component.foo()).isNotEqualTo(component.foo());
+  }
+
+  @Test
+  public void scopedFooTest() {
+    assertThat(component.scopedFoo()).isNotNull();
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFoo());
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFooProvider().get());
+  }
+
+  @Test
+  public void providedFooTest() {
+    assertThat(component.providedFoo()).isNotNull();
+    assertThat(component.providedFoo()).isNotEqualTo(component.providedFoo());
+  }
+
+  @Test
+  public void scopedProvidedFooTest() {
+    assertThat(component.scopedProvidedFoo()).isNotNull();
+    assertThat(component.scopedProvidedFoo()).isEqualTo(component.scopedProvidedFoo());
+    assertThat(component.scopedProvidedFoo())
+        .isEqualTo(component.scopedProvidedFooProvider().get());
+  }
+}
diff --git a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjectClasses.kt
similarity index 74%
copy from javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt
copy to javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjectClasses.kt
index 0fe65fd..1a612e9 100644
--- a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjects.kt
+++ b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/AssistedInjectClasses.kt
@@ -24,7 +24,7 @@
 
 // This is a regression test for https://github.com/google/dagger/issues/2309
 /** A simple, skeletal application that defines an assisted inject binding. */
-class AssistedInjects {
+class AssistedInjectClasses {
   @Component
   interface MyComponent {
     fun fooFactory(): FooFactory
@@ -34,27 +34,19 @@
 
   class Bar @Inject constructor()
 
-  class Foo @AssistedInject constructor(val bar: Bar, @Assisted val str: String)
+  class Foo @AssistedInject constructor(val bar: Bar, @Assisted val assistedStr: String)
 
   @AssistedFactory
   interface FooFactory {
     fun create(str: String): Foo
   }
 
-  class ParameterizedFoo<T1, T2> @AssistedInject constructor(val t1: T1, @Assisted val t2: T2)
+  class ParameterizedFoo<T1, T2>
+  @AssistedInject
+  constructor(val t1: T1, @Assisted val assistedT2: T2)
 
   @AssistedFactory
   interface ParameterizedFooFactory<T1, T2> {
     fun create(t2: T2): ParameterizedFoo<T1, T2>
   }
-
-  companion object {
-    // Called from SimpleApplication.main()
-    fun main() {
-        val foo: Foo = DaggerAssistedInjects_MyComponent.create().fooFactory().create("")
-
-        val parameterizedFoo: ParameterizedFoo<Bar, String> =
-            DaggerAssistedInjects_MyComponent.create().parameterizedFooFactory().create("")
-    }
-  }
 }
diff --git a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleComponentClasses.kt
similarity index 68%
copy from javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt
copy to javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleComponentClasses.kt
index 599d703..bcdbcce 100644
--- a/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleApplication.kt
+++ b/javatests/artifacts/dagger/kotlin-app/src/main/kotlin/app/SimpleComponentClasses.kt
@@ -20,38 +20,35 @@
 import dagger.Module
 import dagger.Provides
 import javax.inject.Inject
+import javax.inject.Provider
 import javax.inject.Singleton
 import library.MySubcomponent
 
 /** A simple, skeletal application that defines a simple component. */
-class SimpleApplication {
+class SimpleComponentClasses {
   class Foo @Inject constructor()
+  @Singleton class ScopedFoo @Inject constructor()
+  class ProvidedFoo
+  class ScopedProvidedFoo
 
   @Module
   object SimpleModule {
-    @Provides
-    fun provideFoo(): Foo {
-      return Foo()
-    }
+    @Provides fun provideFoo(): ProvidedFoo = ProvidedFoo()
+
+    @Provides @Singleton fun provideScopedFoo(): ScopedProvidedFoo = ScopedProvidedFoo()
   }
 
   @Singleton
   @Component(modules = [SimpleModule::class])
   interface SimpleComponent {
     fun foo(): Foo
+    fun scopedFoo(): ScopedFoo
+    fun providedFoo(): ProvidedFoo
+    fun scopedProvidedFoo(): ScopedProvidedFoo
+    fun scopedFooProvider(): Provider<ScopedFoo>
+    fun scopedProvidedFooProvider(): Provider<ScopedProvidedFoo>
 
     // Reproduces a regression in https://github.com/google/dagger/issues/2997.
     fun mySubcomponentFactory(): MySubcomponent.Factory
   }
-
-  companion object {
-    fun main() {
-      val foo: Foo = DaggerSimpleApplication_SimpleComponent.create().foo()
-    }
-  }
-}
-
-fun main() {
-  SimpleApplication.main()
-  AssistedInjects.main()
 }
diff --git a/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/AssistedInjectTest.kt b/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/AssistedInjectTest.kt
new file mode 100644
index 0000000..10a14ef
--- /dev/null
+++ b/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/AssistedInjectTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import app.AssistedInjectClasses.MyComponent
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class AssistedInjectTest {
+  private lateinit var component: MyComponent
+
+  @Before
+  fun setUp() {
+    component = DaggerAssistedInjectClasses_MyComponent.create()
+  }
+
+  @Test
+  fun testFoo() {
+    val foo = component.fooFactory().create("str1")
+    assertThat(foo).isNotNull()
+    assertThat(foo.bar).isNotNull()
+    assertThat(foo.assistedStr).isEqualTo("str1")
+  }
+
+  @Test
+  fun testParameterizedFoo() {
+    val parameterizedFoo = component.parameterizedFooFactory().create("str2")
+    assertThat(parameterizedFoo).isNotNull()
+    assertThat(parameterizedFoo.t1).isNotNull()
+    assertThat(parameterizedFoo.assistedT2).isEqualTo("str2")
+  }
+}
diff --git a/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/SimpleComponentTest.kt b/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/SimpleComponentTest.kt
new file mode 100644
index 0000000..f111dfe
--- /dev/null
+++ b/javatests/artifacts/dagger/kotlin-app/src/test/kotlin/app/SimpleComponentTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import app.SimpleComponentClasses.SimpleComponent
+import com.google.common.truth.Truth.assertThat
+import library.InstanceType
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class SimpleComponentTest {
+  private lateinit var component: SimpleComponent
+
+  @Before
+  fun setUp() {
+    component = DaggerSimpleComponentClasses_SimpleComponent.create()
+  }
+
+  @Test
+  fun fooTest() {
+    assertThat(component.foo()).isNotNull()
+    assertThat(component.foo()).isNotEqualTo(component.foo())
+  }
+
+  @Test
+  fun scopedFooTest() {
+    assertThat(component.scopedFoo()).isNotNull()
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFoo())
+    assertThat(component.scopedFoo()).isEqualTo(component.scopedFooProvider().get())
+  }
+
+  @Test
+  fun providedFooTest() {
+    assertThat(component.providedFoo()).isNotNull()
+    assertThat(component.providedFoo()).isNotEqualTo(component.providedFoo())
+  }
+
+  @Test
+  fun scopedProvidedFooTest() {
+    assertThat(component.scopedProvidedFoo()).isNotNull()
+    assertThat(component.scopedProvidedFoo()).isEqualTo(component.scopedProvidedFoo())
+    assertThat(component.scopedProvidedFoo()).isEqualTo(component.scopedProvidedFooProvider().get())
+  }
+
+  @Test
+  fun subcomponentTest() {
+    val instanceType = InstanceType()
+    val subcomponent = component.mySubcomponentFactory().create(instanceType)
+    assertThat(subcomponent).isNotNull()
+    assertThat(subcomponent.instance()).isEqualTo(instanceType)
+  }
+}
diff --git a/javatests/artifacts/dagger/settings.gradle b/javatests/artifacts/dagger/settings.gradle
index 6ba1bae..89d5acc 100644
--- a/javatests/artifacts/dagger/settings.gradle
+++ b/javatests/artifacts/dagger/settings.gradle
@@ -6,3 +6,6 @@
 include ':transitive-annotation-app'
 include ':transitive-annotation-app:library1'
 include ':transitive-annotation-app:library2'
+include ':transitive-annotation-kotlin-app'
+include ':transitive-annotation-kotlin-app:library1'
+include ':transitive-annotation-kotlin-app:library2'
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/build.gradle b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/build.gradle
new file mode 100644
index 0000000..1b0c04d
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+  id 'application'
+  id 'org.jetbrains.kotlin.jvm' version "$kotlin_version"
+  id 'org.jetbrains.kotlin.kapt' version "$kotlin_version"
+}
+
+java {
+    // Make sure the generated source is compatible with Java 7.
+    sourceCompatibility = JavaVersion.VERSION_1_7
+}
+
+kapt {
+  correctErrorTypes = true
+}
+
+dependencies {
+  implementation project(":transitive-annotation-kotlin-app:library1")
+  implementation "com.google.dagger:dagger:$dagger_version"
+  kapt "com.google.dagger:dagger-compiler:$dagger_version"
+
+  testImplementation "junit:junit:$junit_version"
+  testImplementation "com.google.truth:truth:$truth_version"
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/build.gradle b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/build.gradle
new file mode 100644
index 0000000..3875913
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/build.gradle
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+  id 'java-library'
+  id 'org.jetbrains.kotlin.jvm'
+  id 'org.jetbrains.kotlin.kapt'
+}
+
+java {
+  // Make sure the generated source is compatible with Java 7.
+  sourceCompatibility = JavaVersion.VERSION_1_7
+}
+
+kapt {
+  correctErrorTypes = true
+}
+
+dependencies {
+  implementation project(":transitive-annotation-kotlin-app:library2")
+  implementation "com.google.dagger:dagger:$dagger_version"
+  kapt "com.google.dagger:dagger-compiler:$dagger_version"
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/AssistedFoo.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/AssistedFoo.kt
new file mode 100644
index 0000000..69c3941
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/AssistedFoo.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import javax.inject.Inject
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ *
+ * During the compilation of `:app`, [MyTransitiveAnnotation] will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+class AssistedFoo : FooBase {
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    lateinit var nonDaggerField: MyTransitiveType
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    @MyQualifier
+    lateinit var daggerField: Dep
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    internal constructor(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ) : super(nonDaggerParameter)
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @AssistedInject
+    internal constructor(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @Assisted
+        i: Int,
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @MyQualifier
+        dep: Dep
+    ) : super(dep)
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun nonDaggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ): MyTransitiveType = nonDaggerParameter
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    fun daggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @MyQualifier
+        dep: Dep
+    ) {}
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @AssistedFactory
+    interface Factory {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun create(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            i: Int
+        ): AssistedFoo
+    }
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/Dep.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/Dep.kt
new file mode 100644
index 0000000..09b9795
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/Dep.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+class Dep
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/Foo.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/Foo.kt
new file mode 100644
index 0000000..3406d56
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/Foo.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import javax.inject.Inject
+import javax.inject.Singleton
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ *
+ * During the compilation of `:app`, [MyTransitiveAnnotation] will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+@Singleton
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+class Foo : FooBase {
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    lateinit var nonDaggerField: MyTransitiveType
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    @MyQualifier
+    lateinit var daggerField: Dep
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    internal constructor(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ) : super(nonDaggerParameter)
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    internal constructor(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @MyQualifier
+        dep: Dep
+    ) : super(dep)
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun nonDaggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ): MyTransitiveType = nonDaggerParameter
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    fun daggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @MyQualifier
+        dep: Dep
+    ) {}
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/FooBase.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/FooBase.kt
new file mode 100644
index 0000000..0d5807e
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/FooBase.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import javax.inject.Inject
+import library2.MyTransitiveBaseAnnotation
+import library2.MyTransitiveType
+
+/** A baseclass for [Foo].  */
+@MyTransitiveBaseAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+open class FooBase {
+    @MyTransitiveBaseAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    var baseNonDaggerField = 0
+
+    @MyTransitiveBaseAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    @MyQualifier
+    lateinit var baseDaggerField: Dep
+
+    @MyTransitiveBaseAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    internal constructor(
+        @MyTransitiveBaseAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ) {}
+
+    @MyTransitiveBaseAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    internal constructor(
+        @MyTransitiveBaseAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @MyQualifier
+        dep: Dep
+    ) {}
+
+    @MyTransitiveBaseAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun baseNonDaggerMethod(
+        @MyTransitiveBaseAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        i: Int
+    ) {}
+
+    @MyTransitiveBaseAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Inject
+    fun baseDaggerMethod(
+        @MyTransitiveBaseAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @MyQualifier
+        dep: Dep
+    ) {}
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyAnnotation.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyAnnotation.kt
new file mode 100644
index 0000000..2022813
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyAnnotation.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+/** An annotation that is a direct dependency of the app.  */
+annotation class MyAnnotation(val value: Int)
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyBaseComponent.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyBaseComponent.kt
new file mode 100644
index 0000000..7ee914c
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyBaseComponent.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/**
+ * A class used to test that Dagger won't fail on unresolvable transitive types used in non-dagger
+ * related elements and annotations.
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+abstract class MyBaseComponent {
+    // @MyTransitiveAnnotation cannot be used here.
+    @MyQualifier
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun unscopedQualifiedBindsTypeBase(): MyComponentModule.UnscopedQualifiedBindsType
+
+    // @MyTransitiveAnnotation cannot be used here.
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun unscopedUnqualifiedBindsTypeBase(): MyComponentModule.UnscopedUnqualifiedBindsType
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun injectFooBase(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here)
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        binding: Foo
+    )
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract class Factory {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        abstract fun create(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            myComponentModule: MyComponentModule,
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            myComponentDependency: MyComponentDependency
+        ): MyBaseComponent
+
+        // Non-dagger factory code
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        lateinit var nonDaggerField: MyTransitiveType
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            nonDaggerParameter: MyTransitiveType
+        ): MyTransitiveType = nonDaggerParameter
+
+        companion object {
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            lateinit var nonDaggerStaticField: MyTransitiveType
+
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            fun nonDaggerStaticMethod(
+                @MyTransitiveAnnotation
+                @MyAnnotation(MyTransitiveType.VALUE)
+                @MyOtherAnnotation(MyTransitiveType::class)
+                nonDaggerParameter: MyTransitiveType
+            ): MyTransitiveType = nonDaggerParameter
+        }
+    }
+
+    // Non-dagger code
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    lateinit var nonDaggerField: MyTransitiveType
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun nonDaggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ): MyTransitiveType = nonDaggerParameter
+
+    companion object {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        lateinit var nonDaggerStaticField: MyTransitiveType
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerStaticMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            nonDaggerParameter: MyTransitiveType
+        ): MyTransitiveType = nonDaggerParameter
+    }
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentDependency.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentDependency.kt
new file mode 100644
index 0000000..31c3296
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentDependency.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/**
+ * A class used to test that Dagger won't fail on unresolvable transitive types used in non-dagger
+ * related elements and annotations.
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+class MyComponentDependency
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+constructor() {
+    private val qualifiedMyComponentDependencyBinding = MyComponentDependencyBinding()
+    private val unqualifiedMyComponentDependencyBinding = MyComponentDependencyBinding()
+
+    // @MyTransitiveAnnotation cannot be used here.
+    @MyQualifier
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun qualifiedMyComponentDependencyBinding(): MyComponentDependencyBinding {
+        return qualifiedMyComponentDependencyBinding
+    }
+
+    // @MyTransitiveAnnotation cannot be used here.
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun unqualifiedMyComponentDependencyBinding(): MyComponentDependencyBinding {
+        return unqualifiedMyComponentDependencyBinding
+    }
+
+    // Non-dagger code
+    // Note: As this is supposed to be non-dagger code we use @JvmField to avoid the getter method,
+    // otherwise, Dagger will interpret the method as contributing a binding.
+    @JvmField
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    var nonDaggerField: MyTransitiveType? = null
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun nonDaggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ): MyTransitiveType = nonDaggerParameter
+
+    companion object {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        lateinit var nonDaggerStaticField: MyTransitiveType
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerStaticMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            nonDaggerParameter: MyTransitiveType
+        ): MyTransitiveType = nonDaggerParameter
+    }
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentDependencyBinding.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentDependencyBinding.kt
new file mode 100644
index 0000000..4015f48
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentDependencyBinding.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+/** Used as a binding in [MyComponentDependency].  */
+class MyComponentDependencyBinding
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentModule.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentModule.kt
new file mode 100644
index 0000000..8ee1fd0
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyComponentModule.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import javax.inject.Singleton
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ *
+ * During the compilation of `:app`, [MyTransitiveAnnotation] will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+@Module(includes = [MyComponentModule.MyAbstractModule::class])
+class MyComponentModule
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+constructor(
+    // Non-Dagger elements
+    @field:MyOtherAnnotation(MyTransitiveType::class)
+    @field:MyAnnotation(MyTransitiveType.VALUE)
+    @field:MyTransitiveAnnotation
+    @param:MyTransitiveAnnotation
+    @param:MyAnnotation(MyTransitiveType.VALUE)
+    @param:MyOtherAnnotation(MyTransitiveType::class)
+    private val dep: Dep
+) {
+    // Define bindings for each configuration: Scoped/Unscoped, Qualified/UnQualified, Provides/Binds
+    open class ScopedQualifiedBindsType
+    class ScopedQualifiedProvidesType : ScopedQualifiedBindsType()
+    open class ScopedUnqualifiedBindsType
+    class ScopedUnqualifiedProvidesType : ScopedUnqualifiedBindsType()
+    open class UnscopedQualifiedBindsType
+    class UnscopedQualifiedProvidesType : UnscopedQualifiedBindsType()
+    open class UnscopedUnqualifiedBindsType
+    class UnscopedUnqualifiedProvidesType : UnscopedUnqualifiedBindsType()
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Provides
+    @Singleton
+    @MyQualifier
+    fun scopedQualifiedProvidesType(
+        @MyQualifier
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        dep: Dep
+    ): ScopedQualifiedProvidesType = ScopedQualifiedProvidesType()
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Provides
+    @Singleton
+    fun scopedUnqualifiedProvidesType(
+        @MyQualifier
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        dep: Dep
+    ): ScopedUnqualifiedProvidesType = ScopedUnqualifiedProvidesType()
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Provides
+    @MyQualifier
+    fun unscopedQualifiedProvidesType(
+        @MyQualifier
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        dep: Dep
+    ): UnscopedQualifiedProvidesType = UnscopedQualifiedProvidesType()
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Provides
+    fun unscopedUnqualifiedProvidesType(
+        @MyQualifier
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        dep: Dep
+    ): UnscopedUnqualifiedProvidesType = UnscopedUnqualifiedProvidesType()
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Module
+    internal interface MyAbstractModule {
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @Binds
+        @Singleton
+        @MyQualifier
+        fun scopedQualifiedBindsType(
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyQualifier
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            scopedQualifiedProvidesType: ScopedQualifiedProvidesType
+        ): ScopedQualifiedBindsType
+
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @Binds
+        @Singleton
+        fun scopedUnqualifiedBindsType(
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            scopedUnqualifiedProvidesType: ScopedUnqualifiedProvidesType
+        ): ScopedUnqualifiedBindsType
+
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @Binds
+        @MyQualifier
+        fun unscopedQualifiedBindsType(
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyQualifier
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            unscopedQualifiedProvidesType: UnscopedQualifiedProvidesType
+        ): UnscopedQualifiedBindsType
+
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        @Binds
+        fun unscopedUnqualifiedBindsType(
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            unscopedUnqualifiedProvidesType: UnscopedUnqualifiedProvidesType
+        ): UnscopedUnqualifiedBindsType
+    }
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Provides
+    @MyQualifier
+    fun provideQualifiedDep(): Dep = Dep()
+
+    // Provide an unqualified Dep to ensure that if we accidentally drop the qualifier
+    // we'll get a runtime exception.
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Provides
+    fun provideDep(): Dep = TODO()
+
+    // Non-Dagger elements
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    private val nonDaggerField: MyTransitiveType = MyTransitiveType()
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun nonDaggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ): MyTransitiveType = nonDaggerParameter
+
+    companion object {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerStaticMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            nonDaggerParameter: MyTransitiveType
+        ): MyTransitiveType = nonDaggerParameter
+    }
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyOtherAnnotation.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyOtherAnnotation.kt
new file mode 100644
index 0000000..8f5a8a5
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyOtherAnnotation.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import kotlin.reflect.KClass
+
+/** An annotation that is a direct dependency of the app.  */
+annotation class MyOtherAnnotation(val value: KClass<*>)
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyQualifier.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyQualifier.kt
new file mode 100644
index 0000000..0ca36f7
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MyQualifier.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import javax.inject.Qualifier
+
+@Qualifier
+annotation class MyQualifier
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentBinding.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentBinding.kt
new file mode 100644
index 0000000..620940b
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentBinding.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+/** A simple binding that needs to be passed in when creating this component.  */
+class MySubcomponentBinding
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentModule.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentModule.kt
new file mode 100644
index 0000000..98aa474
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import dagger.Module
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/** A simple module that needs to be passed in when creating this component.  */
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+@Module
+class MySubcomponentModule
+@MyTransitiveAnnotation
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+constructor(
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    i: Int
+)
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentScope.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentScope.kt
new file mode 100644
index 0000000..5c8a168
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentScope.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import javax.inject.Scope
+
+/** A scope for [MySubcomponentWithBuilder] and [MySubcomponentWithFactory].  */
+@Scope
+annotation class MySubcomponentScope
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentWithBuilder.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentWithBuilder.kt
new file mode 100644
index 0000000..f7c5878
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentWithBuilder.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import dagger.BindsInstance
+import dagger.Subcomponent
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ * During the compilation of `:app`, [MyTransitiveAnnotation] will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+@MySubcomponentScope
+@Subcomponent(modules = [MySubcomponentModule::class])
+abstract class MySubcomponentWithBuilder {
+    @MyQualifier // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun qualifiedMySubcomponentBinding(): MySubcomponentBinding
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun unqualifiedMySubcomponentBinding(): MySubcomponentBinding
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun injectFoo(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here)
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        foo: Foo
+    )
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Subcomponent.Builder
+    abstract class Builder {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        abstract fun mySubcomponentModule(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            mySubcomponentModule: MySubcomponentModule
+        ): Builder
+
+        @BindsInstance
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        abstract fun qualifiedMySubcomponentBinding(
+            @MyQualifier
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            subcomponentBinding: MySubcomponentBinding
+        ): Builder
+
+        @BindsInstance
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        abstract fun unqualifiedMySubcomponentBinding(
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            subcomponentBinding: MySubcomponentBinding
+        ): Builder
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        abstract fun build(): MySubcomponentWithBuilder
+
+        // Non-dagger code
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        var nonDaggerField = ""
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            str: String
+        ) {}
+
+        companion object {
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            var nonDaggerStaticField = ""
+
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            fun nonDaggerStaticMethod(
+                @MyTransitiveAnnotation
+                @MyAnnotation(MyTransitiveType.VALUE)
+                @MyOtherAnnotation(MyTransitiveType::class)
+                str: String
+            ) {}
+        }
+    }
+
+    // Non-dagger code
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    lateinit var nonDaggerField: MyTransitiveType
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun nonDaggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ): MyTransitiveType = nonDaggerParameter
+
+    companion object {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        lateinit var nonDaggerStaticField: MyTransitiveType
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerStaticMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            nonDaggerParameter: MyTransitiveType
+        ): MyTransitiveType = nonDaggerParameter
+    }
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentWithFactory.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentWithFactory.kt
new file mode 100644
index 0000000..1ce8028
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library1/src/main/kotlin/library1/MySubcomponentWithFactory.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library1
+
+import dagger.BindsInstance
+import dagger.Subcomponent
+import library2.MyTransitiveAnnotation
+import library2.MyTransitiveType
+
+/**
+ * A class used to test that Dagger won't fail when non-dagger related annotations cannot be
+ * resolved.
+ *
+ * During the compilation of `:app`, [MyTransitiveAnnotation] will no longer be on
+ * the classpath. In most cases, Dagger shouldn't care that the annotation isn't on the classpath
+ */
+// TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+@MyAnnotation(MyTransitiveType.VALUE)
+@MyOtherAnnotation(MyTransitiveType::class)
+@MySubcomponentScope
+@Subcomponent(modules = [MySubcomponentModule::class])
+abstract class MySubcomponentWithFactory {
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyQualifier
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun qualifiedMySubcomponentBinding(): MySubcomponentBinding
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun unqualifiedMySubcomponentBinding(): MySubcomponentBinding
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    abstract fun injectFoo(
+        // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here)
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        foo: Foo
+    )
+
+    // TODO(b/219587431): Support @MyTransitiveAnnotation (We shouldn't need scope/qualifier here).
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    @Subcomponent.Factory
+    abstract class Factory {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        abstract fun create(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            mySubcomponentModule: MySubcomponentModule,
+            @BindsInstance
+            @MyQualifier
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            qualifiedSubcomponentBinding: MySubcomponentBinding,
+            @BindsInstance
+            // TODO(b/219587431): Support @MyTransitiveAnnotation (Requires generating metadata).
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            unqualifiedSubcomponentBinding: MySubcomponentBinding
+        ): MySubcomponentWithFactory
+
+        // Non-dagger code
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        lateinit var nonDaggerField: MyTransitiveType
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            nonDaggerParameter: MyTransitiveType
+        ): MyTransitiveType = nonDaggerParameter
+
+        companion object {
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            lateinit var nonDaggerStaticField: MyTransitiveType
+
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            fun nonDaggerStaticMethod(
+                @MyTransitiveAnnotation
+                @MyAnnotation(MyTransitiveType.VALUE)
+                @MyOtherAnnotation(MyTransitiveType::class)
+                nonDaggerParameter: MyTransitiveType
+            ): MyTransitiveType = nonDaggerParameter
+        }
+    }
+
+    // Non-dagger code
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    lateinit var nonDaggerField: MyTransitiveType
+
+    @MyTransitiveAnnotation
+    @MyAnnotation(MyTransitiveType.VALUE)
+    @MyOtherAnnotation(MyTransitiveType::class)
+    fun nonDaggerMethod(
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        nonDaggerParameter: MyTransitiveType
+    ): MyTransitiveType = nonDaggerParameter
+
+    companion object {
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        lateinit var nonDaggerStaticField: MyTransitiveType
+
+        @MyTransitiveAnnotation
+        @MyAnnotation(MyTransitiveType.VALUE)
+        @MyOtherAnnotation(MyTransitiveType::class)
+        fun nonDaggerStaticMethod(
+            @MyTransitiveAnnotation
+            @MyAnnotation(MyTransitiveType.VALUE)
+            @MyOtherAnnotation(MyTransitiveType::class)
+            nonDaggerParameter: MyTransitiveType
+        ): MyTransitiveType = nonDaggerParameter
+    }
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/build.gradle b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/build.gradle
new file mode 100644
index 0000000..9b4c087
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/build.gradle
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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.
+ */
+
+plugins {
+  id 'java-library'
+  id 'org.jetbrains.kotlin.jvm'
+  id 'org.jetbrains.kotlin.kapt'
+}
+
+kapt {
+  correctErrorTypes = true
+}
+
+java {
+    // Make sure the generated source is compatible with Java 7.
+    sourceCompatibility = JavaVersion.VERSION_1_7
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveAnnotation.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveAnnotation.kt
new file mode 100644
index 0000000..531624e
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveAnnotation.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library2
+
+/** A simple annotation that is a transitive dependency of the app.  */
+annotation class MyTransitiveAnnotation
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveBaseAnnotation.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveBaseAnnotation.kt
new file mode 100644
index 0000000..2791ce2
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveBaseAnnotation.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library2
+
+/** A simple annotation that is a transitive dependency of the app.  */
+annotation class MyTransitiveBaseAnnotation
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveType.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveType.kt
new file mode 100644
index 0000000..213fb09
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/library2/src/main/kotlin/library2/MyTransitiveType.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 library2
+
+/** A class that is a transitive dependency of the app.  */
+class MyTransitiveType {
+    companion object {
+        const val VALUE = 3
+    }
+}
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/main/kotlin/app/MyComponent.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/main/kotlin/app/MyComponent.kt
new file mode 100644
index 0000000..47b1342
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/main/kotlin/app/MyComponent.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import dagger.Component
+import library1.AssistedFoo
+import library1.Foo
+import library1.MyBaseComponent
+import library1.MyComponentDependency
+import library1.MyComponentDependencyBinding
+import library1.MyComponentModule
+import library1.MySubcomponentWithBuilder
+import library1.MySubcomponentWithFactory
+import library1.MyQualifier
+import javax.inject.Singleton
+import library1.MyComponentModule.ScopedQualifiedBindsType
+import library1.MyComponentModule.ScopedUnqualifiedBindsType
+import library1.MyComponentModule.UnscopedQualifiedBindsType
+import library1.MyComponentModule.UnscopedUnqualifiedBindsType
+import library1.MyComponentModule.ScopedQualifiedProvidesType
+import library1.MyComponentModule.ScopedUnqualifiedProvidesType
+import library1.MyComponentModule.UnscopedQualifiedProvidesType
+import library1.MyComponentModule.UnscopedUnqualifiedProvidesType
+
+@Singleton
+@Component(dependencies = [MyComponentDependency::class], modules = [MyComponentModule::class])
+internal abstract class MyComponent : MyBaseComponent() {
+    abstract fun foo(): Foo
+    @MyQualifier
+    abstract fun scopedQualifiedBindsType(): ScopedQualifiedBindsType
+    abstract fun assistedFooFactory(): AssistedFoo.Factory
+    @MyQualifier
+    abstract fun unscopedQualifiedBindsType(): UnscopedQualifiedBindsType
+    abstract fun scopedUnqualifiedBindsType(): ScopedUnqualifiedBindsType
+    @MyQualifier
+    abstract fun scopedQualifiedProvidesType(): ScopedQualifiedProvidesType
+    abstract fun unscopedUnqualifiedBindsType(): UnscopedUnqualifiedBindsType
+    @MyQualifier
+    abstract fun unscopedQualifiedProvidesType(): UnscopedQualifiedProvidesType
+    abstract fun scopedUnqualifiedProvidesType(): ScopedUnqualifiedProvidesType
+    abstract fun unscopedUnqualifiedProvidesType(): UnscopedUnqualifiedProvidesType
+    abstract fun mySubcomponentWithFactory(): MySubcomponentWithFactory.Factory
+    abstract fun mySubcomponentWithBuilder(): MySubcomponentWithBuilder.Builder
+    @MyQualifier
+    abstract fun qualifiedMyComponentDependencyBinding(): MyComponentDependencyBinding
+    abstract fun unqualifiedMyComponentDependencyBinding(): MyComponentDependencyBinding
+
+
+    @Component.Factory
+    internal abstract class Factory : MyBaseComponent.Factory() {
+        abstract override fun create(
+            myComponentModule: MyComponentModule,
+            myComponentDependency: MyComponentDependency
+        ): MyComponent
+    }
+}
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MyComponentTest.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MyComponentTest.kt
new file mode 100644
index 0000000..ae57fd1
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MyComponentTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.Before
+import com.google.common.truth.Truth.assertThat
+import library1.*
+import org.junit.Test
+
+@RunWith(JUnit4::class)
+class MyComponentTest {
+    private lateinit var component: MyComponent
+
+    @Before
+    fun setup() {
+        component = DaggerMyComponent.factory()
+            .create(MyComponentModule(Dep()), MyComponentDependency())
+    }
+
+    @Test
+    fun testFooIsScoped() {
+        assertThat(component.foo()).isEqualTo(component.foo())
+    }
+
+    @Test
+    fun testAssistedFoo() {
+        assertThat(component.assistedFooFactory().create(5)).isNotNull()
+    }
+
+    @Test
+    fun testScopedQualifiedBindsTypeIsScoped() {
+        assertThat(component.scopedQualifiedBindsType())
+            .isEqualTo(component.scopedQualifiedBindsType())
+    }
+
+    @Test
+    fun testScopedUnqualifiedBindsTypeIsScoped() {
+        assertThat(component.scopedUnqualifiedBindsType())
+            .isEqualTo(component.scopedUnqualifiedBindsType())
+    }
+
+    @Test
+    fun testUnscopedQualifiedBindsTypeIsNotScoped() {
+        assertThat(component.unscopedQualifiedBindsType())
+            .isNotEqualTo(component.unscopedQualifiedBindsType())
+    }
+
+    @Test
+    fun testUnscopedUnqualifiedBindsTypeIsNotScoped() {
+        assertThat(component.unscopedUnqualifiedBindsType())
+            .isNotEqualTo(component.unscopedUnqualifiedBindsType())
+    }
+
+    @Test
+    fun testScopedQualifiedProvidesTypeIsScoped() {
+        assertThat(component.scopedQualifiedProvidesType())
+            .isEqualTo(component.scopedQualifiedProvidesType())
+    }
+
+    @Test
+    fun testScopedUnqualifiedProvidesTypeIsScoped() {
+        assertThat(component.scopedUnqualifiedProvidesType())
+            .isEqualTo(component.scopedUnqualifiedProvidesType())
+    }
+
+    @Test
+    fun testUnscopedQualifiedProvidesTypeIsNotScoped() {
+        assertThat(component.unscopedQualifiedProvidesType())
+            .isNotEqualTo(component.unscopedQualifiedProvidesType())
+    }
+
+    @Test
+    fun testUnscopedUnqualifiedProvidesTypeIsNotScoped() {
+        assertThat(component.unscopedUnqualifiedProvidesType())
+            .isNotEqualTo(component.unscopedUnqualifiedProvidesType())
+    }
+
+    @Test
+    fun testMyComponentDependencyBinding() {
+        assertThat(component.qualifiedMyComponentDependencyBinding())
+            .isNotEqualTo(component.unqualifiedMyComponentDependencyBinding())
+    }
+}
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MySubcomponentWithBuilderTest.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MySubcomponentWithBuilderTest.kt
new file mode 100644
index 0000000..72dc692
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MySubcomponentWithBuilderTest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import com.google.common.truth.Truth.assertThat
+import library1.Dep
+import library1.MyComponentDependency
+import library1.MyComponentModule
+import library1.MySubcomponentBinding
+import library1.MySubcomponentModule
+import library1.MySubcomponentWithBuilder
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MySubcomponentWithBuilderTest {
+    private lateinit var subcomponentWithBuilder: MySubcomponentWithBuilder
+
+    @Before
+    fun setup() {
+        subcomponentWithBuilder = DaggerMyComponent.factory()
+            .create(MyComponentModule(Dep()), MyComponentDependency())
+            .mySubcomponentWithBuilder()
+            .mySubcomponentModule(MySubcomponentModule(3))
+            .qualifiedMySubcomponentBinding(MySubcomponentBinding())
+            .unqualifiedMySubcomponentBinding(MySubcomponentBinding())
+            .build()
+    }
+
+    // Test that the qualified and unqualified bindings are two separate objects
+    @Test
+    fun testMySubcomponentBinding() {
+        assertThat(subcomponentWithBuilder.qualifiedMySubcomponentBinding())
+            .isNotEqualTo(subcomponentWithBuilder.unqualifiedMySubcomponentBinding())
+    }
+}
\ No newline at end of file
diff --git a/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MySubcomponentWithFactoryTest.kt b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MySubcomponentWithFactoryTest.kt
new file mode 100644
index 0000000..724254e
--- /dev/null
+++ b/javatests/artifacts/dagger/transitive-annotation-kotlin-app/src/test/kotlin/app/MySubcomponentWithFactoryTest.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 app
+
+import com.google.common.truth.Truth.assertThat
+import library1.Dep
+import library1.MyComponentDependency
+import library1.MyComponentModule
+import library1.MySubcomponentBinding
+import library1.MySubcomponentModule
+import library1.MySubcomponentWithFactory
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MySubcomponentWithFactoryTest {
+    private lateinit var subcomponentWithFactory: MySubcomponentWithFactory
+
+    @Before
+    fun setup() {
+        subcomponentWithFactory = DaggerMyComponent.factory()
+            .create(MyComponentModule(Dep()), MyComponentDependency())
+            .mySubcomponentWithFactory()
+            .create(
+                MySubcomponentModule(1),
+                MySubcomponentBinding(),
+                MySubcomponentBinding()
+            )
+    }
+
+    // Test that the qualified and unqualified bindings are two separate objects
+    @Test
+    fun testMySubcomponentBinding() {
+        assertThat(subcomponentWithFactory.qualifiedMySubcomponentBinding())
+            .isNotEqualTo(subcomponentWithFactory.unqualifiedMySubcomponentBinding())
+    }
+}
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle
index 14364e1..3c97d6f 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/android-library/build.gradle
@@ -2,6 +2,7 @@
     id 'com.android.library'
     id 'kotlin-android'
     id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
 }
 
 android {
@@ -21,7 +22,7 @@
     kotlinOptions {
         jvmTarget = '11'
     }
-    flavorDimensions "tier"
+    flavorDimensions "tier", "processorConfig"
     productFlavors {
         free {
             dimension "tier"
@@ -29,13 +30,24 @@
         premium {
             dimension "tier"
         }
+        withKapt {
+            dimension "processorConfig"
+        }
+        withKsp {
+            dimension "processorConfig"
+        }
     }
 }
 
+kotlin {
+    jvmToolchain(11)
+}
+
 dependencies {
     implementation project(':deep-android-lib')
     implementation project(':deep-kotlin-lib')
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
-    kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kaptWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kspWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
 }
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle
index 646e78d..0b860af 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/app/build.gradle
@@ -18,6 +18,7 @@
 apply plugin: 'kotlin-android'
 apply plugin: 'com.google.dagger.hilt.android'
 apply plugin: 'kotlin-kapt'
+apply plugin: 'com.google.devtools.ksp'
 
 android {
     compileSdkVersion 32
@@ -37,7 +38,7 @@
             shrinkResources true
         }
     }
-    flavorDimensions "tier"
+    flavorDimensions "tier", "processorConfig"
     productFlavors {
         free {
             dimension "tier"
@@ -46,14 +47,17 @@
             dimension "tier"
             matchingFallbacks = ["free"]
         }
+        withKapt {
+            dimension "processorConfig"
+        }
+        withKsp {
+            dimension "processorConfig"
+        }
     }
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_11
         targetCompatibility JavaVersion.VERSION_11
     }
-    kotlinOptions {
-        jvmTarget = '11'
-    }
     testOptions {
         unitTests.includeAndroidResources = true
     }
@@ -71,6 +75,10 @@
     }
 }
 
+kotlin {
+    jvmToolchain(11)
+}
+
 hilt {
     enableTransformForLocalTests = true
     enableAggregatingTask = true
@@ -84,7 +92,8 @@
     implementation 'androidx.activity:activity-ktx:1.5.0'
 
     implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
-    kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kaptWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kspWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
 
     testImplementation 'androidx.test.ext:junit:1.1.3'
     testImplementation 'androidx.test:runner:1.4.0'
@@ -95,12 +104,14 @@
     // TODO(bcorso): This multidex dep shouldn't be required -- it's a dep for the generated code.
     testImplementation 'androidx.multidex:multidex:2.0.0'
     testImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT'
-    kaptTest 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kaptTestWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kspTestWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
 
     androidTestImplementation 'androidx.fragment:fragment-ktx:1.5.0'
     androidTestImplementation 'androidx.test.ext:junit:1.1.3'
     androidTestImplementation 'androidx.test:runner:1.4.0'
     androidTestImplementation 'com.google.truth:truth:1.0.1'
     androidTestImplementation 'com.google.dagger:hilt-android-testing:LOCAL-SNAPSHOT'
-    kaptAndroidTest 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kaptAndroidTestWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kspAndroidTestWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
 }
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle
index 8d5cdcd..eee4806 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/build.gradle
@@ -16,7 +16,8 @@
 
 buildscript {
     ext {
-        kotlin_version = '1.8.0'
+        kotlin_version = '1.8.20'
+        ksp_version = '1.0.11'
         agp_version = System.getenv('AGP_VERSION') ?: "7.1.2"
     }
     repositories {
@@ -27,6 +28,7 @@
     dependencies {
         classpath "com.android.tools.build:gradle:$agp_version"
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$kotlin_version-$ksp_version"
         classpath 'com.google.dagger:hilt-android-gradle-plugin:LOCAL-SNAPSHOT'
     }
 }
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle
index 01662ee..b248a0b 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-android-lib/build.gradle
@@ -2,6 +2,7 @@
     id 'com.android.library'
     id 'kotlin-android'
     id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
     id 'com.google.dagger.hilt.android'
 }
 
@@ -27,13 +28,27 @@
     lintOptions {
         checkReleaseBuilds = false
     }
+    flavorDimensions "processorConfig"
+    productFlavors {
+        withKapt {
+            dimension "processorConfig"
+        }
+        withKsp {
+            dimension "processorConfig"
+        }
+    }
+}
+
+kotlin {
+    jvmToolchain(11)
 }
 
 dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
 
     implementation 'com.google.dagger:hilt-android:LOCAL-SNAPSHOT'
-    kapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kaptWithKapt 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
+    kspWithKsp 'com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT'
 
     testImplementation 'androidx.test.ext:junit:1.1.3'
     testImplementation 'com.google.truth:truth:1.0.1'
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/build.gradle
index 38fd64b..24e7246 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/deep-kotlin-lib/build.gradle
@@ -1,16 +1,14 @@
 plugins {
-    id 'java-library'
     id 'kotlin'
-    id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
 }
 
-java {
-    sourceCompatibility = JavaVersion.VERSION_11
-    targetCompatibility = JavaVersion.VERSION_11
+kotlin {
+    jvmToolchain(11)
 }
 
 dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     implementation "com.google.dagger:hilt-core:LOCAL-SNAPSHOT"
-    kapt "com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT"
+    ksp "com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT"
 }
\ No newline at end of file
diff --git a/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/build.gradle b/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/build.gradle
index a7c4398..ed01615 100644
--- a/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/build.gradle
+++ b/javatests/artifacts/hilt-android/simpleKotlin/kotlin-library/build.gradle
@@ -1,17 +1,15 @@
 plugins {
-    id 'java-library'
     id 'kotlin'
-    id 'kotlin-kapt'
+    id 'com.google.devtools.ksp'
 }
 
-java {
-    sourceCompatibility = JavaVersion.VERSION_11
-    targetCompatibility = JavaVersion.VERSION_11
+kotlin {
+    jvmToolchain(11)
 }
 
 dependencies {
     implementation project(':deep-kotlin-lib')
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     implementation "com.google.dagger:hilt-core:LOCAL-SNAPSHOT"
-    kapt "com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT"
+    ksp "com.google.dagger:hilt-compiler:LOCAL-SNAPSHOT"
 }
\ No newline at end of file
diff --git a/javatests/dagger/functional/kotlin/ObjectModuleClasses.kt b/javatests/dagger/functional/kotlin/ObjectModuleClasses.kt
index 68acafe..5b3d15c 100644
--- a/javatests/dagger/functional/kotlin/ObjectModuleClasses.kt
+++ b/javatests/dagger/functional/kotlin/ObjectModuleClasses.kt
@@ -64,7 +64,7 @@
 }
 
 @Module
-private object NonPublicObjectModule {
+internal object NonPublicObjectModule {
   @Provides
   fun provideInt() = 42
 }
diff --git a/javatests/dagger/functional/kotlinsrc/basic/BasicComponent.kt b/javatests/dagger/functional/kotlinsrc/basic/BasicComponent.kt
index 2ada338..fd3ef09 100644
--- a/javatests/dagger/functional/kotlinsrc/basic/BasicComponent.kt
+++ b/javatests/dagger/functional/kotlinsrc/basic/BasicComponent.kt
@@ -74,12 +74,12 @@
   fun lazyInjectedThingProviderFun(): Provider<Lazy<InjectedThing>>
   fun injectedThingMembersInjectorFun(): MembersInjector<InjectedThing>
 
-  @NullableModule.Nullable fun nullObjectFun(): Any
+  fun nullObjectFun(): Any?
   fun nullObjectProviderFun(): Provider<Any>
   fun lazyNullObjectFun(): Lazy<Any>
   fun typeWithInheritedMembersInjectionFun(): TypeWithInheritedMembersInjection
   fun typeWithInheritedMembersInjectionMembersInjectorFun():
-      MembersInjector<TypeWithInheritedMembersInjection>
+    MembersInjector<TypeWithInheritedMembersInjection>
 
   val byteVal: Byte
   val charVal: Char
@@ -124,11 +124,10 @@
   val lazyInjectedThingProviderVal: Provider<Lazy<InjectedThing>>
   val injectedThingMembersInjectorVal: MembersInjector<InjectedThing>
 
-  // TODO(b/261506732): Support nullable and qualifiers on properties without using @get:
-  @get:NullableModule.Nullable val nullObjectVal: Any
+  val nullObjectVal: Any?
   val nullObjectProviderVal: Provider<Any>
   val lazyNullObjectVal: Lazy<Any>
   val typeWithInheritedMembersInjectionVal: TypeWithInheritedMembersInjection
   val typeWithInheritedMembersInjectionMembersInjectorVal:
-      MembersInjector<TypeWithInheritedMembersInjection>
+    MembersInjector<TypeWithInheritedMembersInjection>
 }
diff --git a/javatests/dagger/functional/kotlinsrc/basic/NullableModule.kt b/javatests/dagger/functional/kotlinsrc/basic/NullableModule.kt
index f2381af..e8f8963 100644
--- a/javatests/dagger/functional/kotlinsrc/basic/NullableModule.kt
+++ b/javatests/dagger/functional/kotlinsrc/basic/NullableModule.kt
@@ -21,11 +21,5 @@
 
 @Module
 internal object NullableModule {
-  @Provides @Nullable fun nullObject(): Any? = null
-
-  /**
-   * A `Nullable` that isn't [javax.annotation.Nullable], to ensure that Dagger can be built without
-   * depending on JSR-305.
-   */
-  internal annotation class Nullable
+  @Provides fun nullObject(): Any? = null
 }
diff --git a/javatests/dagger/functional/kotlinsrc/builderbinds/TestComponent.kt b/javatests/dagger/functional/kotlinsrc/builderbinds/TestComponent.kt
index 81769d5..100d823 100644
--- a/javatests/dagger/functional/kotlinsrc/builderbinds/TestComponent.kt
+++ b/javatests/dagger/functional/kotlinsrc/builderbinds/TestComponent.kt
@@ -27,7 +27,7 @@
 
   @Named("input") fun input(): String
 
-  @Nullable @Named("nullable input") fun nullableInput(): String
+  @Named("nullable input") fun nullableInput(): String?
 
   fun listOfString(): List<String>
 
@@ -41,8 +41,7 @@
 
     @BindsInstance fun input(@Named("input") input: String): Builder
 
-    @BindsInstance
-    fun nullableInput(@Nullable @Named("nullable input") nullableInput: String?): Builder
+    @BindsInstance fun nullableInput(@Named("nullable input") nullableInput: String?): Builder
 
     @BindsInstance fun listOfString(listOfString: List<String>): Builder
 
diff --git a/javatests/dagger/functional/kotlinsrc/factory/FactoryBindsInstanceTest.kt b/javatests/dagger/functional/kotlinsrc/factory/FactoryBindsInstanceTest.kt
index 9e82292..cfbfeac 100644
--- a/javatests/dagger/functional/kotlinsrc/factory/FactoryBindsInstanceTest.kt
+++ b/javatests/dagger/functional/kotlinsrc/factory/FactoryBindsInstanceTest.kt
@@ -42,17 +42,13 @@
     assertThat(component.string()).isEqualTo("baz")
   }
 
-  @Target(AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
-  @Retention(AnnotationRetention.BINARY)
-  internal annotation class Nullable
-
   @Component
   internal interface NullableBindsInstanceComponent {
-    @Nullable fun string(): String?
+    fun string(): String?
 
     @Component.Factory
     interface Factory {
-      fun create(@BindsInstance @Nullable string: String?): NullableBindsInstanceComponent
+      fun create(@BindsInstance string: String?): NullableBindsInstanceComponent
     }
   }
 
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/BUILD b/javatests/dagger/functional/kotlinsrc/membersinject/BUILD
new file mode 100644
index 0000000..8124e3e
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/BUILD
@@ -0,0 +1,48 @@
+# Copyright (C) 2022 The Dagger Authors.
+#
+# 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.
+
+# Description:
+#   Functional tests for Dagger members inject usages.
+
+load(
+    "//:build_defs.bzl",
+    "DOCLINT_HTML_AND_SYNTAX",
+    "DOCLINT_REFERENCES",
+)
+load("//:test_defs.bzl", "GenKtTests")
+
+package(default_visibility = ["//:src"])
+
+GenKtTests(
+    name = "membersinject",
+    srcs = glob(["*.kt"]),
+    gen_library_deps = [
+        "//javatests/dagger/functional/kotlinsrc/membersinject/subpackage",
+        "//javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a:grandchild",
+        "//javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a:parent",
+        "//javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b:child",
+    ],
+    javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES,
+    deps = [
+        "//:dagger_with_compiler",
+        "//third_party/java/auto:factory",
+        "//third_party/java/guava/base",
+        "//third_party/java/guava/collect",
+        "//third_party/java/guava/util/concurrent",
+        "//third_party/java/jsr305_annotations",
+        "//third_party/java/jsr330_inject",
+        "//third_party/java/junit",
+        "//third_party/java/truth",
+    ],
+)
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfArrayOfParentOfStringArray.kt b/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfArrayOfParentOfStringArray.kt
new file mode 100644
index 0000000..34bde49
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfArrayOfParentOfStringArray.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+class ChildOfArrayOfParentOfStringArray :
+  MembersInjectGenericParent<Array<MembersInjectGenericParent<Array<String>>>>()
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfPrimitiveIntArray.kt b/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfPrimitiveIntArray.kt
new file mode 100644
index 0000000..174d0ce
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfPrimitiveIntArray.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+class ChildOfPrimitiveIntArray : MembersInjectGenericParent<IntArray>()
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfStringArray.kt b/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfStringArray.kt
new file mode 100644
index 0000000..0f6ab4b
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/ChildOfStringArray.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+class ChildOfStringArray : MembersInjectGenericParent<Array<String>>()
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectComponent.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectComponent.kt
new file mode 100644
index 0000000..08a3fbf
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectComponent.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import dagger.Component
+
+@Component(modules = [MembersInjectModule::class])
+interface MembersInjectComponent {
+  fun inject(subfoo: ChildOfStringArray)
+  fun inject(subfoo: ChildOfArrayOfParentOfStringArray)
+  fun inject(subfoo: ChildOfPrimitiveIntArray)
+  fun inject(rawFrameworkTypes: RawFrameworkTypes)
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectGenericParent.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectGenericParent.kt
new file mode 100644
index 0000000..7110f61
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectGenericParent.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import javax.inject.Inject
+
+open class MembersInjectGenericParent<T : Any> {
+  @Inject lateinit var t: T
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectModule.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectModule.kt
new file mode 100644
index 0000000..db3a007
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import dagger.Module
+import dagger.Provides
+
+@Module
+internal class MembersInjectModule {
+  @Provides fun provideStringArray(): Array<String> = arrayOf()
+
+  @Provides fun provideIntArray(): IntArray = intArrayOf()
+
+  @Provides
+  fun provideFooArrayOfStringArray(): Array<MembersInjectGenericParent<Array<String>>> = arrayOf()
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectTest.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectTest.kt
new file mode 100644
index 0000000..2fc4425
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import dagger.MembersInjector
+import dagger.functional.kotlinsrc.membersinject.subpackage.a.AGrandchild
+import dagger.functional.kotlinsrc.membersinject.subpackage.a.AParent
+import dagger.functional.kotlinsrc.membersinject.subpackage.b.BChild
+import javax.inject.Inject
+import javax.inject.Provider
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MembersInjectTest {
+  @Test
+  fun testMembersInject_arrays() {
+    val component = DaggerMembersInjectComponent.builder().build()
+    val childOfStringArray = ChildOfStringArray()
+    component.inject(childOfStringArray)
+  }
+
+  @Test
+  fun testMembersInject_nestedArrays() {
+    val component = DaggerMembersInjectComponent.builder().build()
+    val childOfArrayOfParentOfStringArray = ChildOfArrayOfParentOfStringArray()
+    component.inject(childOfArrayOfParentOfStringArray)
+  }
+
+  @Test
+  fun testMembersInject_primitives() {
+    val component = DaggerMembersInjectComponent.builder().build()
+    val childOfPrimitiveIntArray = ChildOfPrimitiveIntArray()
+    component.inject(childOfPrimitiveIntArray)
+  }
+
+  @Test
+  fun testMembersInject_overrides() {
+    val component = DaggerMembersInjectionVisibilityComponent.create()
+    val aParent = AParent()
+    component.inject(aParent)
+    assertThat(aParent.aParentField()).isNotNull()
+    assertThat(aParent.aParentMethod()).isNotNull()
+    val aChild = BChild()
+    component.inject(aChild)
+    assertThat(aChild.aParentField()).isNotNull()
+    assertThat(aChild.aParentMethod()).isNull()
+    assertThat(aChild.aChildField()).isNotNull()
+    assertThat(aChild.aChildMethod()).isNotNull()
+    val aGrandchild = AGrandchild()
+    component.inject(aGrandchild)
+    assertThat(aGrandchild.aParentField()).isNotNull()
+    assertThat(aGrandchild.aParentMethod()).isNotNull()
+    assertThat(aGrandchild.aChildField()).isNotNull()
+    assertThat(aGrandchild.aChildMethod()).isNull()
+    assertThat(aGrandchild.aGrandchildField()).isNotNull()
+    assertThat(aGrandchild.aGrandchildMethod()).isNotNull()
+  }
+
+  @Test
+  fun testNonRequestedMembersInjector() {
+    val child = NonRequestedChild()
+    val provider = Provider { "field!" }
+    val injector = NonRequestedChild_MembersInjector(provider)
+    injector.injectMembers(child)
+    assertThat(child.t).isEqualTo("field!")
+  }
+
+  class A : B() // No injected members
+  open class B : C() // No injected members
+  open class C {
+    @Inject lateinit var value: String
+  }
+
+  @Component
+  internal interface NonLocalMembersComponent {
+    fun aMembersInjector(): MembersInjector<A>
+
+    @Component.Factory
+    interface Factory {
+      fun create(@BindsInstance value: String): NonLocalMembersComponent
+    }
+  }
+
+  @Test
+  fun testNonLocalMembersInjection() {
+    val membersInjector =
+      DaggerMembersInjectTest_NonLocalMembersComponent.factory().create("test").aMembersInjector()
+    val testA = A()
+    membersInjector.injectMembers(testA)
+    assertThat(testA.value).isEqualTo("test")
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionOrdering.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionOrdering.kt
new file mode 100644
index 0000000..203935d
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionOrdering.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import dagger.Component
+import dagger.Module
+import dagger.Provides
+import javax.inject.Inject
+
+/**
+ * This exhibits a regression case, that albeit weird, is valid according to the JSR 330 spec. JSR
+ * 330 specifies a rough ordering by which members should be injected, and it is possible to rely on
+ * such ordering. When members injecting [Subtype], field injection is guaranteed to be performed on
+ * [Base] first. The binding for `@FirstToString` in [ ][OrderingModule.provideToString] relies on
+ * this ordering, and thus uses the value in [ ][Base.first] to satisfy the binding.
+ */
+class MembersInjectionOrdering {
+  open class Base {
+    @Inject lateinit var first: First
+  }
+
+  class Subtype : Base() {
+    @Inject lateinit var firstToString: String
+  }
+
+  @Module
+  class OrderingModule(private val subtype: Subtype) {
+    @Provides fun provideToString(): String = subtype.first.toString()
+  }
+
+  @Component(modules = [OrderingModule::class])
+  interface TestComponent {
+    fun inject(subtype: Subtype)
+  }
+
+  class First @Inject constructor()
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionOrderingTest.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionOrderingTest.kt
new file mode 100644
index 0000000..58644b5
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionOrderingTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import dagger.functional.kotlinsrc.membersinject.MembersInjectionOrdering.OrderingModule
+import dagger.functional.kotlinsrc.membersinject.MembersInjectionOrdering.Subtype
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MembersInjectionOrderingTest {
+  @Test
+  fun indirection() {
+    val toInject = Subtype()
+    DaggerMembersInjectionOrdering_TestComponent.builder()
+      .orderingModule(OrderingModule(toInject))
+      .build()
+      .inject(toInject)
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionVisibilityComponent.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionVisibilityComponent.kt
new file mode 100644
index 0000000..57210c7
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersInjectionVisibilityComponent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import dagger.Component
+import dagger.functional.kotlinsrc.membersinject.subpackage.a.AGrandchild
+import dagger.functional.kotlinsrc.membersinject.subpackage.a.AParent
+import dagger.functional.kotlinsrc.membersinject.subpackage.b.BChild
+
+/** A component that tests members injection across packages and subclasses. */
+@Component
+interface MembersInjectionVisibilityComponent {
+  fun inject(aParent: AParent)
+  fun inject(aChild: BChild)
+  fun inject(aGrandchild: AGrandchild)
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithInstanceNameTest.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithInstanceNameTest.kt
new file mode 100644
index 0000000..4c2085b
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithInstanceNameTest.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import dagger.Module
+import dagger.Provides
+import javax.inject.Inject
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MembersWithInstanceNameTest {
+  @Suppress("BadInject") // This is intentional for testing purposes.
+  internal class Foo @Inject constructor() {
+    // Checks that member injection fields can use "instance" as a name (b/175818837).
+    @Inject lateinit var instance: String
+  }
+
+  @Module
+  internal interface TestModule {
+    companion object {
+      @Provides fun provideString(): String = "test"
+    }
+  }
+
+  @Component(modules = [TestModule::class])
+  internal interface TestComponent {
+    fun foo(): Foo
+  }
+
+  @Test
+  fun testMemberWithInstanceName() {
+    val component = DaggerMembersWithInstanceNameTest_TestComponent.create()
+    val foo = component.foo()
+    assertThat(foo).isNotNull()
+    assertThat(foo.instance).isEqualTo("test")
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithSameNameChild.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithSameNameChild.kt
new file mode 100644
index 0000000..680acc6
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithSameNameChild.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import dagger.functional.kotlinsrc.membersinject.subpackage.MembersWithSameNameParent
+import javax.inject.Inject
+
+// https://github.com/google/dagger/issues/755
+class MembersWithSameNameChild : MembersWithSameNameParent() {
+  @Inject internal lateinit var sameName: String
+  internal var sameNameStringWasInvoked = false
+  internal var sameNameObjectWasInvoked = false
+
+  @Inject
+  internal fun sameName(@Suppress("UNUSED_PARAMETER") sameName: String) {
+    sameNameStringWasInvoked = true
+  }
+
+  @Inject
+  internal fun sameName(@Suppress("UNUSED_PARAMETER") sameName: Any) {
+    sameNameObjectWasInvoked = true
+  }
+
+  fun childSameName(): String {
+    return sameName
+  }
+
+  fun childSameNameStringWasInvoked(): Boolean {
+    return sameNameStringWasInvoked
+  }
+
+  fun childSameNameObjectWasInvoked(): Boolean {
+    return sameNameObjectWasInvoked
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithSameNameTest.kt b/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithSameNameTest.kt
new file mode 100644
index 0000000..4ea92b1
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/MembersWithSameNameTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import com.google.common.truth.Truth.assertThat
+import dagger.Binds
+import dagger.Component
+import dagger.Module
+import dagger.Provides
+import dagger.functional.kotlinsrc.membersinject.subpackage.MembersWithSameNameParent
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+// https://github.com/google/dagger/issues/755
+@RunWith(JUnit4::class)
+class MembersWithSameNameTest {
+  @Test
+  fun parentInjectsMaskedMembers() {
+    val parent = MembersWithSameNameParent()
+    val component = DaggerMembersWithSameNameTest_TestComponent.create()
+    component.injectParent(parent)
+    assertThat(parent.parentSameName()).isNotNull()
+    assertThat(parent.parentSameNameStringWasInvoked()).isTrue()
+    assertThat(parent.parentSameNameObjectWasInvoked()).isTrue()
+  }
+
+  @Test
+  fun childInjectsMaskedMembers() {
+    val child = MembersWithSameNameChild()
+    val component = DaggerMembersWithSameNameTest_TestComponent.create()
+    component.injectChild(child)
+    assertThat(child.parentSameName()).isNotNull()
+    assertThat(child.parentSameNameStringWasInvoked()).isTrue()
+    assertThat(child.parentSameNameObjectWasInvoked()).isTrue()
+    assertThat(child.childSameName()).isNotNull()
+    assertThat(child.childSameNameStringWasInvoked()).isTrue()
+    assertThat(child.childSameNameObjectWasInvoked()).isTrue()
+  }
+
+  @Module
+  internal abstract class TestModule {
+    @Binds abstract fun bindObject(string: String): Any
+
+    companion object {
+      @Provides fun provideString(): String = ""
+    }
+  }
+
+  @Component(modules = [TestModule::class])
+  internal interface TestComponent {
+    fun injectParent(parent: MembersWithSameNameParent)
+    fun injectChild(child: MembersWithSameNameChild)
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/NonRequestedChild.kt b/javatests/dagger/functional/kotlinsrc/membersinject/NonRequestedChild.kt
new file mode 100644
index 0000000..f8a8644
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/NonRequestedChild.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import javax.inject.Inject
+
+/**
+ * A class that should not be requested by any component, to ensure that we still generate a members
+ * injector for it.
+ */
+class NonRequestedChild @Inject constructor() : MembersInjectGenericParent<String>()
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/RawFrameworkTypes.kt b/javatests/dagger/functional/kotlinsrc/membersinject/RawFrameworkTypes.kt
new file mode 100644
index 0000000..32a7bcd
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/RawFrameworkTypes.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject
+
+import dagger.Lazy
+import dagger.MembersInjector
+import javax.inject.Provider
+
+// https://github.com/google/dagger/issues/419
+// Note: This is converted from its associate java test in dagger/function/membersinject, but given
+// that there isn't actually raw types in kotlin I'm not sure how useful it is to keep it. For the
+// time being, I've decided to leave this as it tests the next closest thing, which is star types.
+class RawFrameworkTypes {
+  fun nonInjectMethodWithARawProvider(@Suppress("UNUSED_PARAMETER") rawProvider: Provider<*>) {}
+  fun nonInjectMethodWithARawLazy(@Suppress("UNUSED_PARAMETER") rawLazy: Lazy<*>) {}
+  fun nonInjectMethodWithARawMembersInjector(
+    @Suppress("UNUSED_PARAMETER") rawMembersInjector: MembersInjector<*>
+  ) {}
+}
diff --git a/javatests/dagger/functional/kotlinsrc/variance/BUILD b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/BUILD
similarity index 76%
rename from javatests/dagger/functional/kotlinsrc/variance/BUILD
rename to javatests/dagger/functional/kotlinsrc/membersinject/subpackage/BUILD
index 0d4b884..5fc9da9 100644
--- a/javatests/dagger/functional/kotlinsrc/variance/BUILD
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/BUILD
@@ -1,4 +1,4 @@
-# Copyright (C) 2023 The Dagger Authors.
+# Copyright (C) 2022 The Dagger Authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,24 +13,22 @@
 # limitations under the License.
 
 # Description:
-#   Functional tests for Dagger with static @Provides usages.
+#   Functional tests for Dagger members inject usages.
 
 load(
     "//:build_defs.bzl",
     "DOCLINT_HTML_AND_SYNTAX",
     "DOCLINT_REFERENCES",
 )
-load("//:test_defs.bzl", "GenKtTests")
+load("//:test_defs.bzl", "GenKtLibrary")
 
 package(default_visibility = ["//:src"])
 
-GenKtTests(
-    name = "variance",
+GenKtLibrary(
+    name = "subpackage",
     srcs = glob(["*.kt"]),
     javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES,
     deps = [
         "//:dagger_with_compiler",
-        "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/MembersWithSameNameParent.kt b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/MembersWithSameNameParent.kt
new file mode 100644
index 0000000..a5e56f3
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/MembersWithSameNameParent.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject.subpackage
+
+import javax.inject.Inject
+
+// https://github.com/google/dagger/issues/755
+open class MembersWithSameNameParent {
+  // Note: This field is purposely non-public so that MembersWithSameNameChild#sameName hides
+  // this field rather than overrides it.
+  @Inject internal lateinit var sameName: String
+  internal var sameNameStringWasInvoked = false
+  internal var sameNameObjectWasInvoked = false
+
+  // Note: This method is purposely non-public so that MembersWithSameNameChild#sameName(String)
+  // hides this method rather than overrides it.
+  @Inject
+  internal fun sameName(@Suppress("UNUSED_PARAMETER") sameName: String) {
+    sameNameStringWasInvoked = true
+  }
+
+  // Note: This method is purposely non-public so that MembersWithSameNameChild#sameName(Object)
+  // hides this method rather than overrides it.
+  @Inject
+  internal fun sameName(@Suppress("UNUSED_PARAMETER") sameName: Any) {
+    sameNameObjectWasInvoked = true
+  }
+
+  fun parentSameName(): String {
+    return sameName
+  }
+
+  fun parentSameNameStringWasInvoked(): Boolean {
+    return sameNameStringWasInvoked
+  }
+
+  fun parentSameNameObjectWasInvoked(): Boolean {
+    return sameNameObjectWasInvoked
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AGrandchild.kt b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AGrandchild.kt
new file mode 100644
index 0000000..044d34c
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AGrandchild.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject.subpackage.a
+
+import dagger.functional.kotlinsrc.membersinject.subpackage.b.BChild
+import javax.inject.Inject
+
+class AGrandchild : BChild() {
+  @Inject internal lateinit var aGrandchildField: APublicObject
+  private lateinit var aGrandchildMethod: APublicObject
+
+  @Inject
+  fun aGrandchildMethod(aGrandchildMethod: APublicObject) {
+    this.aGrandchildMethod = aGrandchildMethod
+  }
+
+  @Inject
+  protected override fun aParentMethod(aParentMethod: APublicObject) {
+    super.aParentMethod(aParentMethod)
+  }
+
+  protected override fun aChildMethod(aChildMethod: APublicObject) {
+    super.aChildMethod(aChildMethod)
+  }
+
+  fun aGrandchildField(): APublicObject = aGrandchildField
+
+  fun aGrandchildMethod(): APublicObject = aGrandchildMethod
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AInternalObject.kt b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AInternalObject.kt
new file mode 100644
index 0000000..366c60c
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AInternalObject.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject.subpackage.a
+
+import javax.inject.Inject
+
+internal class AInternalObject @Inject constructor() : APublicObject()
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AParent.kt b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AParent.kt
new file mode 100644
index 0000000..d11137d
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/AParent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject.subpackage.a
+
+import javax.inject.Inject
+
+open class AParent {
+  @Inject internal lateinit var aParentField: AInternalObject
+  private var aParentMethod: APublicObject? = null
+
+  @Inject
+  protected open fun aParentMethod(aParentMethod: APublicObject) {
+    this.aParentMethod = aParentMethod
+  }
+
+  fun aParentField(): APublicObject {
+    return aParentField
+  }
+
+  fun aParentMethod(): APublicObject? {
+    return aParentMethod
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/APublicObject.kt b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/APublicObject.kt
new file mode 100644
index 0000000..87b06b4
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/APublicObject.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject.subpackage.a
+
+import javax.inject.Inject
+
+open class APublicObject @Inject constructor()
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/BUILD b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/BUILD
new file mode 100644
index 0000000..cd2b55b
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a/BUILD
@@ -0,0 +1,51 @@
+# Copyright (C) 2022 The Dagger Authors.
+#
+# 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.
+
+# Description:
+#   Functional tests for Dagger members inject usages.
+
+load(
+    "//:build_defs.bzl",
+    "DOCLINT_HTML_AND_SYNTAX",
+    "DOCLINT_REFERENCES",
+)
+load("//:test_defs.bzl", "GenKtLibrary")
+
+package(default_visibility = ["//:src"])
+
+GenKtLibrary(
+    name = "grandchild",
+    srcs = ["AGrandchild.kt"],
+    gen_library_deps = [
+        ":parent",
+        "//javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b:child",
+    ],
+    javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES,
+    deps = [
+        "//:dagger_with_compiler",
+    ],
+)
+
+GenKtLibrary(
+    name = "parent",
+    srcs = [
+        "AInternalObject.kt",
+        "AParent.kt",
+        "APublicObject.kt",
+    ],
+    javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES,
+    deps = [
+        "//:dagger_with_compiler",
+    ],
+)
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BChild.kt b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BChild.kt
new file mode 100644
index 0000000..b16705e
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BChild.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject.subpackage.b
+
+import dagger.functional.kotlinsrc.membersinject.subpackage.a.AParent
+import dagger.functional.kotlinsrc.membersinject.subpackage.a.APublicObject
+import javax.inject.Inject
+
+open class BChild : AParent() {
+  @Inject internal lateinit var aChildField: BInternalObject
+  private var aChildMethod: APublicObject? = null
+
+  @Inject
+  protected open fun aChildMethod(aChildMethod: APublicObject) {
+    this.aChildMethod = aChildMethod
+  }
+
+  protected override fun aParentMethod(aParentMethod: APublicObject) {
+    super.aParentMethod(aParentMethod)
+  }
+
+  fun aChildField(): APublicObject {
+    return aChildField
+  }
+
+  fun aChildMethod(): APublicObject? {
+    return aChildMethod
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BInternalObject.kt b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BInternalObject.kt
new file mode 100644
index 0000000..cc96eec
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BInternalObject.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.membersinject.subpackage.b
+
+import dagger.functional.kotlinsrc.membersinject.subpackage.a.APublicObject
+import javax.inject.Inject
+
+internal class BInternalObject @Inject constructor() : APublicObject()
diff --git a/javatests/dagger/functional/kotlinsrc/variance/BUILD b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BUILD
similarity index 69%
copy from javatests/dagger/functional/kotlinsrc/variance/BUILD
copy to javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BUILD
index 0d4b884..4afa314 100644
--- a/javatests/dagger/functional/kotlinsrc/variance/BUILD
+++ b/javatests/dagger/functional/kotlinsrc/membersinject/subpackage/b/BUILD
@@ -1,4 +1,4 @@
-# Copyright (C) 2023 The Dagger Authors.
+# Copyright (C) 2022 The Dagger Authors.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -13,24 +13,28 @@
 # limitations under the License.
 
 # Description:
-#   Functional tests for Dagger with static @Provides usages.
+#   Functional tests for Dagger members inject usages.
 
 load(
     "//:build_defs.bzl",
     "DOCLINT_HTML_AND_SYNTAX",
     "DOCLINT_REFERENCES",
 )
-load("//:test_defs.bzl", "GenKtTests")
+load("//:test_defs.bzl", "GenKtLibrary")
 
 package(default_visibility = ["//:src"])
 
-GenKtTests(
-    name = "variance",
-    srcs = glob(["*.kt"]),
+GenKtLibrary(
+    name = "child",
+    srcs = [
+        "BChild.kt",
+        "BInternalObject.kt",
+    ],
+    gen_library_deps = [
+        "//javatests/dagger/functional/kotlinsrc/membersinject/subpackage/a:parent",
+    ],
     javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES,
     deps = [
         "//:dagger_with_compiler",
-        "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
diff --git a/javatests/dagger/functional/kotlinsrc/multipackage/moduleconstructor/ModuleWithInaccessibleConstructor.kt b/javatests/dagger/functional/kotlinsrc/multipackage/moduleconstructor/ModuleWithInaccessibleConstructor.kt
index 45aa1ef..b33b730 100644
--- a/javatests/dagger/functional/kotlinsrc/multipackage/moduleconstructor/ModuleWithInaccessibleConstructor.kt
+++ b/javatests/dagger/functional/kotlinsrc/multipackage/moduleconstructor/ModuleWithInaccessibleConstructor.kt
@@ -27,4 +27,9 @@
   private val i: Int = Random.nextInt()
 
   @Provides fun i(): Int = i
+
+  // This is a regression test for b/283164293
+  private companion object {
+    fun someMethod(): String = "someString"
+  }
 }
diff --git a/javatests/dagger/functional/kotlinsrc/nullables/BUILD b/javatests/dagger/functional/kotlinsrc/nullables/BUILD
index 062f46b..e9db7a7 100644
--- a/javatests/dagger/functional/kotlinsrc/nullables/BUILD
+++ b/javatests/dagger/functional/kotlinsrc/nullables/BUILD
@@ -34,3 +34,14 @@
         "//third_party/java/truth",
     ],
 )
+
+GenKtTests(
+    name = "NullabilityInteroptTest",
+    srcs = ["NullabilityInteroptTest.kt"],
+    javacopts = DOCLINT_HTML_AND_SYNTAX + DOCLINT_REFERENCES,
+    deps = [
+        "//:dagger_with_compiler",
+        "//third_party/java/junit",
+        "//third_party/java/truth",
+    ],
+)
diff --git a/javatests/dagger/functional/kotlinsrc/nullables/NullabilityInteroptTest.kt b/javatests/dagger/functional/kotlinsrc/nullables/NullabilityInteroptTest.kt
new file mode 100644
index 0000000..27dd43f
--- /dev/null
+++ b/javatests/dagger/functional/kotlinsrc/nullables/NullabilityInteroptTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.functional.kotlinsrc.nullables
+
+import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import dagger.Module
+import dagger.Provides
+import javax.inject.Inject
+import javax.inject.Qualifier
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * A test that ensures nullable bindings created using an {@code @Nullable} annotation are
+ * interoperable with nullable bindings created using {@code T?} types in kotlin source.
+ */
+@RunWith(JUnit4::class)
+class NullabilityInteroptTest {
+  annotation class Nullable
+
+  @Qualifier annotation class ProvidedWithNullable
+
+  @Qualifier annotation class ProvidedWithNullType
+
+  @Component(modules = [TestModule::class])
+  interface TestComponent {
+    fun providesUsage(): ProvidesUsage
+
+    fun injectUsage(): InjectUsage
+
+    @ProvidedWithNullable @Nullable fun nullableWithNullable(): String
+
+    @ProvidedWithNullable fun nullableWithNullType(): String?
+
+    @ProvidedWithNullType @Nullable fun nullTypeWithNullable(): String
+
+    @ProvidedWithNullType fun nullTypeWithNullType(): String?
+  }
+
+  @Module
+  object TestModule {
+    @Provides
+    @ProvidedWithNullable
+    @Nullable
+    fun providedWithNullable(): String = PROVIDED_WITH_NULLABLE
+
+    @Provides @ProvidedWithNullType fun providedWithNullType(): String? = PROVIDED_WITH_NULL_TYPE
+
+    @Provides
+    fun providesUsage(
+      @ProvidedWithNullable nullableWithNullType: String?,
+      @ProvidedWithNullable @Nullable nullableWithNullable: String,
+      @ProvidedWithNullType nullTypeWithNullType: String?,
+      @ProvidedWithNullType @Nullable nullTypeWithNullable: String,
+    ): ProvidesUsage {
+      return ProvidesUsage(
+        nullableWithNullType,
+        nullableWithNullable,
+        nullTypeWithNullType,
+        nullTypeWithNullable,
+      )
+    }
+  }
+
+  class ProvidesUsage
+  constructor(
+    val nullableWithNullType: String?,
+    val nullableWithNullable: String,
+    val nullTypeWithNullType: String?,
+    val nullTypeWithNullable: String,
+  )
+
+  class InjectUsage
+  @Inject
+  constructor(
+    @ProvidedWithNullable val nullableWithNullType: String?,
+    @ProvidedWithNullable @Nullable val nullableWithNullable: String,
+    @ProvidedWithNullType val nullTypeWithNullType: String?,
+    @ProvidedWithNullType @Nullable val nullTypeWithNullable: String,
+  )
+
+  @Test
+  fun testEntryPoints() {
+    val component = DaggerNullabilityInteroptTest_TestComponent.create()
+    assertThat(component.nullableWithNullable()).isEqualTo(PROVIDED_WITH_NULLABLE)
+    assertThat(component.nullableWithNullType()).isEqualTo(PROVIDED_WITH_NULLABLE)
+    assertThat(component.nullTypeWithNullable()).isEqualTo(PROVIDED_WITH_NULL_TYPE)
+    assertThat(component.nullTypeWithNullType()).isEqualTo(PROVIDED_WITH_NULL_TYPE)
+  }
+
+  @Test
+  fun testInjectUsage() {
+    val injectUsage = DaggerNullabilityInteroptTest_TestComponent.create().injectUsage()
+    assertThat(injectUsage.nullableWithNullable).isEqualTo(PROVIDED_WITH_NULLABLE)
+    assertThat(injectUsage.nullableWithNullType).isEqualTo(PROVIDED_WITH_NULLABLE)
+    assertThat(injectUsage.nullTypeWithNullable).isEqualTo(PROVIDED_WITH_NULL_TYPE)
+    assertThat(injectUsage.nullTypeWithNullType).isEqualTo(PROVIDED_WITH_NULL_TYPE)
+  }
+
+  @Test
+  fun testProvidesUsage() {
+    val providesUsage = DaggerNullabilityInteroptTest_TestComponent.create().providesUsage()
+    assertThat(providesUsage.nullableWithNullable).isEqualTo(PROVIDED_WITH_NULLABLE)
+    assertThat(providesUsage.nullableWithNullType).isEqualTo(PROVIDED_WITH_NULLABLE)
+    assertThat(providesUsage.nullTypeWithNullable).isEqualTo(PROVIDED_WITH_NULL_TYPE)
+    assertThat(providesUsage.nullTypeWithNullType).isEqualTo(PROVIDED_WITH_NULL_TYPE)
+  }
+
+  companion object {
+    const val PROVIDED_WITH_NULLABLE: String = "ProvidedWithNullable"
+    const val PROVIDED_WITH_NULL_TYPE: String = "ProvidedWithNullType"
+  }
+}
diff --git a/javatests/dagger/functional/kotlinsrc/nullables/NullabilityTest.kt b/javatests/dagger/functional/kotlinsrc/nullables/NullabilityTest.kt
index 998b337..c790a66 100644
--- a/javatests/dagger/functional/kotlinsrc/nullables/NullabilityTest.kt
+++ b/javatests/dagger/functional/kotlinsrc/nullables/NullabilityTest.kt
@@ -31,11 +31,9 @@
 
 @RunWith(JUnit4::class)
 class NullabilityTest {
-  internal annotation class Nullable
-
   @Component(dependencies = [NullComponent::class])
   internal interface NullComponentWithDependency {
-    @Nullable fun string(): String?
+    fun string(): String?
     fun number(): Number
     fun stringProvider(): Provider<String>
     fun numberProvider(): Provider<Number>
@@ -43,8 +41,8 @@
 
   @Component(modules = [NullModule::class])
   internal interface NullComponent {
-    @Nullable fun string(): String?
-    @Nullable fun integer(): Int?
+    fun string(): String?
+    fun integer(): Int?
     fun nullFoo(): NullFoo
     fun number(): Number
     fun stringProvider(): Provider<String>
@@ -56,11 +54,10 @@
     var numberValue: Number? = null
     var integerCallCount = 0
 
-    @Nullable @Provides fun provideNullableString(): String? = null
+    @Provides fun provideNullableString(): String? = null
 
     @Provides fun provideNumber(): Number = numberValue!!
 
-    @Nullable
     @Provides
     @Reusable
     fun provideNullReusableInteger(): Int? {
@@ -73,7 +70,7 @@
   internal class NullFoo
   @Inject
   constructor(
-    @param:Nullable val string: String?,
+    val string: String?,
     val number: Number,
     val stringProvider: Provider<String>,
     val numberProvider: Provider<Number>
@@ -85,7 +82,7 @@
 
     @Inject
     fun inject(
-      @Nullable string: String?,
+      string: String?,
       number: Number,
       stringProvider: Provider<String>,
       numberProvider: Provider<Number>
@@ -96,7 +93,7 @@
       methodInjectedNumberProvider = numberProvider
     }
 
-    @JvmField @Nullable @Inject var fieldInjectedString: String? = null
+    @JvmField @Inject var fieldInjectedString: String? = null
     @Inject lateinit var fieldInjectedNumber: Number
     @Inject lateinit var fieldInjectedStringProvider: Provider<String>
     @Inject lateinit var fieldInjectedNumberProvider: Provider<Number>
diff --git a/javatests/dagger/functional/kotlinsrc/variance/DeclarationVarianceTest.kt b/javatests/dagger/functional/kotlinsrc/variance/DeclarationVarianceTest.kt
deleted file mode 100644
index 61615af..0000000
--- a/javatests/dagger/functional/kotlinsrc/variance/DeclarationVarianceTest.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright (C) 2023 The Dagger Authors.
- *
- * 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 dagger.functional.kotlinsrc.variance
-
-import com.google.common.truth.Truth.assertThat
-import dagger.Component
-import dagger.Module
-import dagger.Provides
-import javax.inject.Inject
-import javax.inject.Singleton
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-// This class tests some usages of the declaration-site variance defined in the declaration of the
-// List class, i.e. List<out T>. In these tests, I'm only using @JvmWildcard and
-// @JvmSuppressWildcards when it's required for correctness, which depends on:
-//
-//   1) Where the type is used
-//   2) If the type argument is an open or final class
-//
-// This isn't an exhaustive list, but covers some of the common cases and we can add more as we find
-// them.
-@RunWith(JUnit4::class)
-class DeclarationVarianceTest {
-
-  @Singleton
-  @Component(modules = [BarModule::class])
-  interface BarComponent {
-    fun listBar(): List<Bar>
-    fun listOutBar(): List<@JvmWildcard Bar>
-    fun barUsage(): BarUsage
-  }
-
-  @Module
-  object BarModule {
-    @Singleton
-    @Provides
-    fun provideListBar(): List<Bar> = listOf(Bar("provideListBar"))
-
-    @Singleton
-    @Provides
-    fun provideListOutBar(): List<@JvmWildcard Bar> = listOf(Bar("provideListOutBar"))
-  }
-
-  @Suppress("BadInject") // We're using constructor and members injection on purpose for this test.
-  class BarUsage @Inject constructor(
-    val listBar1: List<Bar>,
-    val listOutBar1: List<@JvmWildcard Bar>,
-  ) {
-    @Inject lateinit var listBar2: List<Bar>
-    @Inject lateinit var listOutBar2: List<@JvmWildcard Bar>
-    lateinit var listBar3: List<Bar>
-    lateinit var listOutBar3: List<Bar>
-
-    @Inject
-    fun injectMethod(
-      listBar3: List<Bar>,
-      listOutBar3: List<@JvmWildcard Bar>,
-    ) {
-      this.listBar3 = listBar3
-      this.listOutBar3 = listOutBar3
-    }
-  }
-
-  class Bar(val providesMethod: String) {
-    override fun toString(): String = providesMethod
-  }
-
-  @Test
-  fun testFinalClassBarUsedAsTypeArgument() {
-    val component = DaggerDeclarationVarianceTest_BarComponent.create()
-    val listBar = component.listBar()
-    val listOutBar = component.listOutBar()
-    val barUsage = component.barUsage()
-
-    assertThat(listBar).isNotNull()
-    assertThat(listOutBar).isNotNull()
-    assertThat(listBar).isNotEqualTo(listOutBar)
-
-    assertThat(barUsage.listBar1).isEqualTo(listBar)
-    assertThat(barUsage.listBar2).isEqualTo(listBar)
-    assertThat(barUsage.listBar3).isEqualTo(listBar)
-
-    assertThat(barUsage.listOutBar1).isEqualTo(listOutBar)
-    assertThat(barUsage.listOutBar2).isEqualTo(listOutBar)
-    assertThat(barUsage.listOutBar3).isEqualTo(listOutBar)
-  }
-
-  @Singleton
-  @Component(modules = [FooModule::class])
-  interface FooComponent {
-    fun listFoo(): List<Foo>
-    fun listOutFoo(): List<@JvmWildcard Foo>
-    fun fooUsage(): FooUsage
-  }
-
-  @Module
-  object FooModule {
-    @Singleton
-    @Provides
-    fun provideListFoo(): List<Foo> = listOf(Foo("provideListFoo"))
-
-    @Singleton
-    @Provides
-    fun provideListOutFoo(): List<@JvmWildcard Foo> = listOf(Foo("provideListOutFoo"))
-  }
-
-  @Suppress("BadInject") // We're using constructor and members injection on purpose for this test.
-  class FooUsage @Inject constructor(
-    val listFoo1: List<@JvmSuppressWildcards Foo>,
-    val listOutFoo1: List<Foo>,
-  ) {
-    @Inject lateinit var listFoo2: List<@JvmSuppressWildcards Foo>
-    @Inject lateinit var listOutFoo2: List<@JvmWildcard Foo>
-    lateinit var listFoo3: List<Foo>
-    lateinit var listOutFoo3: List<Foo>
-
-    @Inject
-    fun injectMethod(
-      listFoo3: List<@JvmSuppressWildcards Foo>,
-      listOutFoo3: List<Foo>,
-    ) {
-      this.listFoo3 = listFoo3
-      this.listOutFoo3 = listOutFoo3
-    }
-  }
-
-  open class Foo(val providesMethod: String) {
-    override fun toString(): String = providesMethod
-  }
-
-  @Test
-  fun testOpenClassFooUsedAsTypeArgument() {
-    val component = DaggerDeclarationVarianceTest_FooComponent.create()
-    val listFoo = component.listFoo()
-    val listOutFoo = component.listOutFoo()
-    val fooUsage = component.fooUsage()
-
-    assertThat(listFoo).isNotNull()
-    assertThat(listOutFoo).isNotNull()
-    assertThat(listFoo).isNotEqualTo(listOutFoo)
-
-    assertThat(fooUsage.listFoo1).isEqualTo(listFoo)
-    assertThat(fooUsage.listFoo2).isEqualTo(listFoo)
-    assertThat(fooUsage.listFoo3).isEqualTo(listFoo)
-
-    assertThat(fooUsage.listOutFoo1).isEqualTo(listOutFoo)
-    assertThat(fooUsage.listOutFoo2).isEqualTo(listOutFoo)
-    assertThat(fooUsage.listOutFoo3).isEqualTo(listOutFoo)
-  }
-}
diff --git a/javatests/dagger/hilt/android/BUILD b/javatests/dagger/hilt/android/BUILD
index 97798ea..8f927fb 100644
--- a/javatests/dagger/hilt/android/BUILD
+++ b/javatests/dagger/hilt/android/BUILD
@@ -14,7 +14,7 @@
 # Description:
 #   Tests for internal code for implementing Hilt processors.
 
-load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library")
+load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library")
 
 package(default_visibility = ["//:src"])
 
diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD
index 1a4d0c5..146f537 100644
--- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD
+++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/BUILD
@@ -34,9 +34,9 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/compile_testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
@@ -53,9 +53,8 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
-        "//third_party/java/compile_testing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java
index b39cd6b..1a36c71 100644
--- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java
+++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/EarlyEntryPointProcessorTest.java
@@ -16,12 +16,8 @@
 
 package dagger.hilt.android.processor.internal.aggregateddeps;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
-
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
-import javax.tools.JavaFileObject;
+import androidx.room.compiler.processing.util.Source;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -31,8 +27,8 @@
 
   @Test
   public void testUsedWithEntryPoint_fails() {
-    JavaFileObject entryPoint =
-        JavaFileObjects.forSourceLines(
+    Source entryPoint =
+        HiltCompilerTests.javaSource(
             "test.UsedWithEntryPoint",
             "package test;",
             "",
@@ -45,21 +41,24 @@
             "@EntryPoint",
             "@InstallIn(SingletonComponent.class)",
             "public interface UsedWithEntryPoint {}");
-    Compilation compilation = compiler().compile(entryPoint);
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "Only one of the following annotations can be used on test.UsedWithEntryPoint: "
-                + "[dagger.hilt.EntryPoint, dagger.hilt.android.EarlyEntryPoint]")
-        .inFile(entryPoint)
-        .onLine(11);
+    HiltCompilerTests.hiltCompiler(entryPoint)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "Only one of the following annotations can be used on"
+                          + " test.UsedWithEntryPoint: [dagger.hilt.EntryPoint,"
+                          + " dagger.hilt.android.EarlyEntryPoint]")
+                  .onSource(entryPoint)
+                  .onLine(11);
+            });
   }
 
   @Test
   public void testNotSingletonComponent_fails() {
-    JavaFileObject entryPoint =
-        JavaFileObjects.forSourceLines(
+    Source entryPoint =
+        HiltCompilerTests.javaSource(
             "test.NotSingletonComponent",
             "package test;",
             "",
@@ -72,21 +71,23 @@
             "@InstallIn(ActivityComponent.class)",
             "public interface NotSingletonComponent {}");
 
-    Compilation compilation = compiler().compile(entryPoint);
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@EarlyEntryPoint can only be installed into the SingletonComponent. "
-                + "Found: [dagger.hilt.android.components.ActivityComponent]")
-        .inFile(entryPoint)
-        .onLine(10);
+    HiltCompilerTests.hiltCompiler(entryPoint)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "@EarlyEntryPoint can only be installed into the SingletonComponent. "
+                          + "Found: [dagger.hilt.android.components.ActivityComponent]")
+                  .onSource(entryPoint)
+                  .onLine(10);
+            });
   }
 
   @Test
   public void testThatTestInstallInCannotOriginateFromTest() {
-    JavaFileObject test =
-        JavaFileObjects.forSourceLines(
+    Source test =
+        HiltCompilerTests.javaSource(
             "test.MyTest",
             "package test;",
             "",
@@ -102,15 +103,17 @@
             "  @InstallIn(SingletonComponent.class)",
             "  interface NestedEarlyEntryPoint {}",
             "}");
-    Compilation compilation = compiler().compile(test);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@EarlyEntryPoint-annotated entry point, test.MyTest.NestedEarlyEntryPoint, cannot "
-                + "be nested in (or originate from) a @HiltAndroidTest-annotated class, "
-                + "test.MyTest.")
-        .inFile(test)
-        .onLine(13);
+    HiltCompilerTests.hiltCompiler(test)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "@EarlyEntryPoint-annotated entry point, test.MyTest.NestedEarlyEntryPoint,"
+                          + " cannot be nested in (or originate from) a @HiltAndroidTest-annotated"
+                          + " class, test.MyTest.")
+                  .onSource(test)
+                  .onLine(13);
+            });
   }
 }
diff --git a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java
index e4e9f67..390af9a 100644
--- a/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java
+++ b/javatests/dagger/hilt/android/processor/internal/aggregateddeps/TestInstallInTest.java
@@ -19,8 +19,10 @@
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
 
+import androidx.room.compiler.processing.util.Source;
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,6 +31,7 @@
 @RunWith(JUnit4.class)
 public class TestInstallInTest {
 
+  // TODO(danysantiago): Migrate to hiltCompiler() after b/288893275 is fixed.
   @Test
   public void testMissingValues() {
     JavaFileObject testInstallInModule =
@@ -55,8 +58,8 @@
 
   @Test
   public void testEmptyComponentValues() {
-    JavaFileObject installInModule =
-        JavaFileObjects.forSourceLines(
+    Source installInModule =
+        HiltCompilerTests.javaSource(
             "test.InstallInModule",
             "package test;",
             "",
@@ -67,8 +70,8 @@
             "@Module",
             "@InstallIn(SingletonComponent.class)",
             "interface InstallInModule {}");
-    JavaFileObject testInstallInModule =
-        JavaFileObjects.forSourceLines(
+    Source testInstallInModule =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInModule",
             "package test;",
             "",
@@ -78,21 +81,23 @@
             "@Module",
             "@TestInstallIn(components = {}, replaces = InstallInModule.class)",
             "interface TestInstallInModule {}");
-    Compilation compilation = compiler().compile(installInModule, testInstallInModule);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    // TODO(bcorso): Add inFile().onLine() whenever we've fixed Processors.getAnnotationClassValues
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@TestInstallIn, 'components' class is invalid or missing: "
-                + "@dagger.hilt.testing.TestInstallIn("
-                + "components={}, replaces={test.InstallInModule.class})");
+    HiltCompilerTests.hiltCompiler(installInModule, testInstallInModule)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              // TODO(bcorso): Add inFile().onLine() whenever we've fixed
+              // Processors.getAnnotationClassValues
+              subject.hasErrorContaining(
+                  "@TestInstallIn, 'components' class is invalid or missing: "
+                      + "@dagger.hilt.testing.TestInstallIn("
+                      + "components={}, replaces={test.InstallInModule})");
+            });
   }
 
   @Test
   public void testEmptyReplacesValues() {
-    JavaFileObject testInstallInModule =
-        JavaFileObjects.forSourceLines(
+    Source testInstallInModule =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInModule",
             "package test;",
             "",
@@ -103,21 +108,23 @@
             "@Module",
             "@TestInstallIn(components = SingletonComponent.class, replaces = {})",
             "interface TestInstallInModule {}");
-    Compilation compilation = compiler().compile(testInstallInModule);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    // TODO(bcorso): Add inFile().onLine() whenever we've fixed Processors.getAnnotationClassValues
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@TestInstallIn, 'replaces' class is invalid or missing: "
-                + "@dagger.hilt.testing.TestInstallIn("
-                + "components={dagger.hilt.components.SingletonComponent.class}, replaces={})");
+    HiltCompilerTests.hiltCompiler(testInstallInModule)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              // TODO(bcorso): Add inFile().onLine() whenever we've fixed
+              // Processors.getAnnotationClassValues
+              subject.hasErrorContaining(
+                  "@TestInstallIn, 'replaces' class is invalid or missing: "
+                      + "@dagger.hilt.testing.TestInstallIn("
+                      + "components={dagger.hilt.components.SingletonComponent}, replaces={})");
+            });
   }
 
   @Test
   public void testMissingModuleAnnotation() {
-    JavaFileObject installInModule =
-        JavaFileObjects.forSourceLines(
+    Source installInModule =
+        HiltCompilerTests.javaSource(
             "test.InstallInModule",
             "package test;",
             "",
@@ -128,8 +135,8 @@
             "@Module",
             "@InstallIn(SingletonComponent.class)",
             "interface InstallInModule {}");
-    JavaFileObject testInstallInModule =
-        JavaFileObjects.forSourceLines(
+    Source testInstallInModule =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInModule",
             "package test;",
             "",
@@ -141,21 +148,23 @@
             "    components = SingletonComponent.class,",
             "    replaces = InstallInModule.class)",
             "interface TestInstallInModule {}");
-    Compilation compilation = compiler().compile(installInModule, testInstallInModule);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@TestInstallIn-annotated classes must also be annotated with @Module or @EntryPoint: "
-                + "test.TestInstallInModule")
-        .inFile(testInstallInModule)
-        .onLine(10);
+    HiltCompilerTests.hiltCompiler(installInModule, testInstallInModule)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "@TestInstallIn-annotated classes must also be annotated with @Module or"
+                          + " @EntryPoint: test.TestInstallInModule")
+                  .onSource(testInstallInModule)
+                  .onLine(10);
+            });
   }
 
   @Test
   public void testInvalidUsageOnEntryPoint() {
-    JavaFileObject installInModule =
-        JavaFileObjects.forSourceLines(
+    Source installInModule =
+        HiltCompilerTests.javaSource(
             "test.InstallInModule",
             "package test;",
             "",
@@ -166,8 +175,8 @@
             "@Module",
             "@InstallIn(SingletonComponent.class)",
             "interface InstallInModule {}");
-    JavaFileObject testInstallInEntryPoint =
-        JavaFileObjects.forSourceLines(
+    Source testInstallInEntryPoint =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInEntryPoint",
             "package test;",
             "",
@@ -180,21 +189,22 @@
             "    components = SingletonComponent.class,",
             "    replaces = InstallInModule.class)",
             "interface TestInstallInEntryPoint {}");
-    Compilation compilation = compiler().compile(installInModule, testInstallInEntryPoint);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining("@TestInstallIn can only be used with modules")
-        .inFile(testInstallInEntryPoint)
-        .onLine(11);
+    HiltCompilerTests.hiltCompiler(installInModule, testInstallInEntryPoint)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("@TestInstallIn can only be used with modules")
+                  .onSource(testInstallInEntryPoint)
+                  .onLine(11);
+            });
   }
 
   @Test
   public void testInvalidReplaceModules() {
-    JavaFileObject foo =
-        JavaFileObjects.forSourceLines("test.Foo", "package test;", "", "class Foo {}");
-    JavaFileObject testInstallInModule =
-        JavaFileObjects.forSourceLines(
+    Source foo = HiltCompilerTests.javaSource("test.Foo", "package test;", "", "class Foo {}");
+    Source testInstallInModule =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInModule",
             "package test;",
             "",
@@ -207,20 +217,23 @@
             "    components = SingletonComponent.class,",
             "    replaces = Foo.class)",
             "interface TestInstallInModule {}");
-    Compilation compilation = compiler().compile(foo, testInstallInModule);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@TestInstallIn#replaces() can only contain @InstallIn modules, but found: [test.Foo]")
-        .inFile(testInstallInModule)
-        .onLine(11);
+    HiltCompilerTests.hiltCompiler(foo, testInstallInModule)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "@TestInstallIn#replaces() can only contain @InstallIn modules, but found:"
+                          + " [test.Foo]")
+                  .onSource(testInstallInModule)
+                  .onLine(11);
+            });
   }
 
   @Test
   public void testInternalDaggerReplaceModules() {
-    JavaFileObject testInstallInModule =
-        JavaFileObjects.forSourceLines(
+    Source testInstallInModule =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInModule",
             "package test;",
             "",
@@ -233,21 +246,23 @@
             "    components = SingletonComponent.class,",
             "    replaces = dagger.hilt.android.internal.modules.ApplicationContextModule.class)",
             "interface TestInstallInModule {}");
-    Compilation compilation = compiler().compile(testInstallInModule);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: "
-                + "[dagger.hilt.android.internal.modules.ApplicationContextModule]")
-        .inFile(testInstallInModule)
-        .onLine(11);
+    HiltCompilerTests.hiltCompiler(testInstallInModule)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "@TestInstallIn#replaces() cannot contain internal Hilt modules, but found: "
+                          + "[dagger.hilt.android.internal.modules.ApplicationContextModule]")
+                  .onSource(testInstallInModule)
+                  .onLine(11);
+            });
   }
 
   @Test
   public void testHiltWrapperDaggerReplaceModules() {
-    JavaFileObject testInstallInModule =
-        JavaFileObjects.forSourceLines(
+    Source testInstallInModule =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInModule",
             "package test;",
             "",
@@ -264,22 +279,25 @@
             // handle modules generated in the same round.
             "    replaces = HiltWrapper_InstallInModule.class)",
             "interface TestInstallInModule {}");
-    Compilation compilation = compiler().compile(testInstallInModule);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper modules, "
-                + "but found: [dagger.hilt.android.processor.internal.aggregateddeps."
-                + "HiltWrapper_InstallInModule]")
-        .inFile(testInstallInModule)
-        .onLine(12);
+    HiltCompilerTests.hiltCompiler(testInstallInModule)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "@TestInstallIn#replaces() cannot contain Hilt generated public wrapper"
+                          + " modules, but found:"
+                          + " [dagger.hilt.android.processor.internal.aggregateddeps."
+                          + "HiltWrapper_InstallInModule]")
+                  .onSource(testInstallInModule)
+                  .onLine(12);
+            });
   }
 
   @Test
   public void testCannotReplaceLocalInstallInModule() {
-    JavaFileObject test =
-        JavaFileObjects.forSourceLines(
+    Source test =
+        HiltCompilerTests.javaSource(
             "test.MyTest",
             "package test;",
             "",
@@ -295,8 +313,8 @@
             "  @InstallIn(SingletonComponent.class)",
             "  interface LocalInstallInModule {}",
             "}");
-    JavaFileObject testInstallIn =
-        JavaFileObjects.forSourceLines(
+    Source testInstallIn =
+        HiltCompilerTests.javaSource(
             "test.TestInstallInModule",
             "package test;",
             "",
@@ -309,21 +327,23 @@
             "    components = SingletonComponent.class,",
             "    replaces = MyTest.LocalInstallInModule.class)",
             "interface TestInstallInModule {}");
-    Compilation compilation = compiler().compile(test, testInstallIn);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "TestInstallIn#replaces() cannot replace test specific @InstallIn modules, but found: "
-                + "[test.MyTest.LocalInstallInModule].")
-        .inFile(testInstallIn)
-        .onLine(11);
+    HiltCompilerTests.hiltCompiler(test, testInstallIn)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "TestInstallIn#replaces() cannot replace test specific @InstallIn modules,"
+                          + " but found: [test.MyTest.LocalInstallInModule].")
+                  .onSource(testInstallIn)
+                  .onLine(11);
+            });
   }
 
   @Test
   public void testThatTestInstallInCannotOriginateFromTest() {
-    JavaFileObject installInModule =
-        JavaFileObjects.forSourceLines(
+    Source installInModule =
+        HiltCompilerTests.javaSource(
             "test.InstallInModule",
             "package test;",
             "",
@@ -334,8 +354,8 @@
             "@Module",
             "@InstallIn(SingletonComponent.class)",
             "interface InstallInModule {}");
-    JavaFileObject test =
-        JavaFileObjects.forSourceLines(
+    Source test =
+        HiltCompilerTests.javaSource(
             "test.MyTest",
             "package test;",
             "",
@@ -352,14 +372,16 @@
             "      replaces = InstallInModule.class)",
             "  interface TestInstallInModule {}",
             "}");
-    Compilation compilation = compiler().compile(test, installInModule);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@TestInstallIn modules cannot be nested in (or originate from) a "
-                + "@HiltAndroidTest-annotated class:  test.MyTest")
-        .inFile(test)
-        .onLine(14);
+    HiltCompilerTests.hiltCompiler(test, installInModule)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining(
+                      "@TestInstallIn modules cannot be nested in (or originate from) a "
+                          + "@HiltAndroidTest-annotated class:  test.MyTest")
+                  .onSource(test)
+                  .onLine(14);
+            });
   }
 }
diff --git a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java
index b6674d4..b58c355 100644
--- a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java
+++ b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/ActivityGeneratorTest.java
@@ -16,23 +16,22 @@
 
 package dagger.hilt.android.processor.internal.androidentrypoint;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
-
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
-import javax.tools.JavaFileObject;
+import androidx.room.compiler.processing.util.Source;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
+import dagger.testing.golden.GoldenFileRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
 public class ActivityGeneratorTest {
+  @Rule public GoldenFileRule goldenFileRule = new GoldenFileRule();
 
   @Test
   public void generate_componentActivity() {
-    JavaFileObject myActivity =
-        JavaFileObjects.forSourceLines(
+    Source myActivity =
+        HiltCompilerTests.javaSource(
             "test.MyActivity",
             "package test;",
             "",
@@ -42,14 +41,13 @@
             "@AndroidEntryPoint(ComponentActivity.class)",
             "public class MyActivity extends Hilt_MyActivity {",
             "}");
-    Compilation compilation = compiler().compile(myActivity);
-    assertThat(compilation).succeeded();
+    HiltCompilerTests.hiltCompiler(myActivity).compile(subject -> subject.hasErrorCount(0));
   }
 
   @Test
   public void generate_baseHiltComponentActivity() {
-    JavaFileObject baseActivity =
-        JavaFileObjects.forSourceLines(
+    Source baseActivity =
+        HiltCompilerTests.javaSource(
             "test.BaseActivity",
             "package test;",
             "",
@@ -59,8 +57,8 @@
             "@AndroidEntryPoint(ComponentActivity.class)",
             "public class BaseActivity extends Hilt_BaseActivity {",
             "}");
-    JavaFileObject myActivity =
-        JavaFileObjects.forSourceLines(
+    Source myActivity =
+        HiltCompilerTests.javaSource(
             "test.MyActivity",
             "package test;",
             "",
@@ -70,7 +68,7 @@
             "@AndroidEntryPoint(BaseActivity.class)",
             "public class MyActivity extends Hilt_MyActivity {",
             "}");
-    Compilation compilation = compiler().compile(baseActivity, myActivity);
-    assertThat(compilation).succeeded();
+    HiltCompilerTests.hiltCompiler(baseActivity, myActivity)
+        .compile(subject -> subject.hasErrorCount(0));
   }
 }
diff --git a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java
index a5f85a2..f39d897 100644
--- a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java
+++ b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/AndroidEntryPointProcessorTest.java
@@ -19,8 +19,12 @@
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
 
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.util.CompilationResultSubject;
+import androidx.room.compiler.processing.util.Source;
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -28,11 +32,31 @@
 
 @RunWith(JUnit4.class)
 public class AndroidEntryPointProcessorTest {
+  @Test
+  public void testAndroidEntryPoint() {
+    Source testActivity =
+        HiltCompilerTests.javaSource(
+            "test.MyActivity",
+            "package test;",
+            "",
+            "import androidx.activity.ComponentActivity;",
+            "import dagger.hilt.android.AndroidEntryPoint;",
+            "",
+            "@AndroidEntryPoint(ComponentActivity.class)",
+            "public class MyActivity extends Hilt_MyActivity {}");
+
+    HiltCompilerTests.hiltCompiler(testActivity)
+        .compile(
+            (CompilationResultSubject subject) -> {
+              subject.hasErrorCount(0);
+              subject.generatedSourceFileWithPath("test/Hilt_MyActivity.java");
+            });
+  }
 
   @Test
   public void missingBaseClass() {
-    JavaFileObject testActivity =
-        JavaFileObjects.forSourceLines(
+    Source testActivity =
+        HiltCompilerTests.javaSource(
             "test.MyActivity",
             "package test;",
             "",
@@ -41,17 +65,20 @@
             "",
             "@AndroidEntryPoint",
             "public class MyActivity extends ComponentActivity { }");
-    Compilation compilation = compiler().compile(testActivity);
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Expected @AndroidEntryPoint to have a value.")
-        ;
+    HiltCompilerTests.hiltCompiler(testActivity)
+        .compile(
+            (CompilationResultSubject subject) -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("Expected @AndroidEntryPoint to have a value.")
+                  ;
+            });
   }
 
   @Test
   public void incorrectSuperclass() {
-    JavaFileObject testActivity =
-        JavaFileObjects.forSourceLines(
+    Source testActivity =
+        HiltCompilerTests.javaSource(
             "test.MyActivity",
             "package test;",
             "",
@@ -60,13 +87,21 @@
             "",
             "@AndroidEntryPoint(ComponentActivity.class)",
             "public class MyActivity extends ComponentActivity { }");
-    Compilation compilation = compiler().compile(testActivity);
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@AndroidEntryPoint class expected to extend Hilt_MyActivity. "
-                + "Found: ComponentActivity")
-        ;
+    HiltCompilerTests.hiltCompiler(testActivity)
+        .compile(
+            (CompilationResultSubject subject) -> {
+              // TODO(b/288210593): Add this check back to KSP once this bug is fixed.
+              if (HiltCompilerTests.backend(subject) == XProcessingEnv.Backend.KSP) {
+                subject.hasErrorCount(0);
+              } else {
+                subject.hasErrorCount(1);
+                subject
+                    .hasErrorContaining(
+                        "@AndroidEntryPoint class expected to extend Hilt_MyActivity. "
+                            + "Found: ComponentActivity")
+                    ;
+              }
+            });
   }
 
   @Test
@@ -150,8 +185,8 @@
 
   @Test
   public void checkAndroidEntryPointOnApplicationRecommendsHiltAndroidApp() {
-    JavaFileObject testActivity =
-        JavaFileObjects.forSourceLines(
+    Source testActivity =
+        HiltCompilerTests.javaSource(
             "test.MyApplication",
             "package test;",
             "",
@@ -160,10 +195,20 @@
             "",
             "@AndroidEntryPoint(Application.class)",
             "public class MyApplication extends Hilt_MyApplication { }");
-    Compilation compilation = compiler().compile(testActivity);
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("@AndroidEntryPoint cannot be used on an Application. "
-            + "Use @HiltAndroidApp instead.");
+    HiltCompilerTests.hiltCompiler(testActivity)
+        .compile(
+            (CompilationResultSubject subject) -> {
+              if (HiltCompilerTests.backend(subject) == XProcessingEnv.Backend.KSP) {
+                subject.hasErrorCount(1);
+              } else {
+                // Javac has an extra error due to the missing symbol.
+                subject.hasErrorCount(2);
+                subject.hasErrorContaining(
+                    "cannot find symbol\n      symbol: class Hilt_MyApplication");
+              }
+              subject.hasErrorContaining(
+                  "@AndroidEntryPoint cannot be used on an Application. "
+                      + "Use @HiltAndroidApp instead.");
+            });
   }
 }
diff --git a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
index c823542..10be147 100644
--- a/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
+++ b/javatests/dagger/hilt/android/processor/internal/androidentrypoint/BUILD
@@ -25,11 +25,14 @@
         "//java/dagger/hilt/android:android_entry_point",
         "@androidsdk//:platforms/android-32/android.jar",
     ],
+    resources = glob([
+        "goldens/ActivityGeneratorTest_*",
+    ]),
     deps = [
         "//java/dagger/hilt/android/testing/compile",
-        "//third_party/java/compile_testing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
+        "//java/dagger/testing/golden",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
@@ -42,10 +45,12 @@
         "@androidsdk//:platforms/android-32/android.jar",
     ],
     deps = [
+        "//java/dagger/hilt/android:android_entry_point",
         "//java/dagger/hilt/android/testing/compile",
+        "//java/dagger/internal/codegen/xprocessing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/compile_testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD
index 7525833..a8d2cb9 100644
--- a/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD
+++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/BUILD
@@ -29,7 +29,7 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
-        "//third_party/java/compile_testing",
+        "//third_party/java/guava/collect",
         "//third_party/java/junit",
         "//third_party/java/truth",
     ],
diff --git a/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java
index beadecf..215296f 100644
--- a/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java
+++ b/javatests/dagger/hilt/android/processor/internal/customtestapplication/CustomTestApplicationProcessorTest.java
@@ -16,23 +16,27 @@
 
 package dagger.hilt.android.processor.internal.customtestapplication;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
+import static com.google.common.truth.Truth.assertThat;
 
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
+import com.google.common.collect.ImmutableList;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
+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 CustomTestApplicationProcessorTest {
 
+  @Rule public TemporaryFolder tempFolderRule = new TemporaryFolder();
+
   @Test
   public void validBaseClass_succeeds() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    // TODO(danysantiago): Add KSP test once b/288966076 is resolved.
+    HiltCompilerTests.compileWithKapt(
+        ImmutableList.of(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
@@ -42,41 +46,35 @@
                 "",
                 "@CustomTestApplication(Application.class)",
                 "@HiltAndroidTest",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).succeeded();
+                "public class HiltTest {}")),
+        tempFolderRule,
+        result -> assertThat(result.getSuccess()).isTrue());
   }
 
   @Test
   public void incorrectBaseType_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
-                "test.Foo",
-                "package test;",
-                "",
-                "public class Foo {}"),
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource("test.Foo", "package test;", "", "public class Foo {}"),
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(Foo.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication value should be an instance of android.app.Application. "
-                + "Found: test.Foo");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication value should be an instance of android.app.Application. "
+                      + "Found: test.Foo");
+            });
   }
 
   @Test
   public void baseWithHiltAndroidApp_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -85,27 +83,26 @@
                 "",
                 "@HiltAndroidApp(Application.class)",
                 "public class BaseApplication extends Hilt_BaseApplication {}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(BaseApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. "
-                + "Found: test.BaseApplication");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. "
+                      + "Found: test.BaseApplication");
+            });
   }
 
   @Test
   public void superclassWithHiltAndroidApp_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -114,32 +111,31 @@
                 "",
                 "@HiltAndroidApp(Application.class)",
                 "public class BaseApplication extends Hilt_BaseApplication {}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.ParentApplication",
                 "package test;",
                 "",
                 "public class ParentApplication extends BaseApplication {}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(ParentApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. "
-                + "Found: test.BaseApplication");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication value cannot be annotated with @HiltAndroidApp. "
+                      + "Found: test.BaseApplication");
+            });
   }
 
   @Test
   public void withInjectField_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -149,27 +145,27 @@
                 "public class BaseApplication extends Application {",
                 "  @Inject String str;",
                 "}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(BaseApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication does not support application classes (or super classes) with "
-                + "@Inject fields. Found test.BaseApplication with @Inject fields [str]");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication does not support application classes (or super classes)"
+                      + " with @Inject fields. Found test.BaseApplication with @Inject fields"
+                      + " [str]");
+            });
   }
 
   @Test
   public void withSuperclassInjectField_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -179,32 +175,32 @@
                 "public class BaseApplication extends Application {",
                 "  @Inject String str;",
                 "}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.ParentApplication",
                 "package test;",
                 "",
                 "public class ParentApplication extends BaseApplication {}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(ParentApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication does not support application classes (or super classes) with "
-                + "@Inject fields. Found test.BaseApplication with @Inject fields [str]");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication does not support application classes (or super classes)"
+                      + " with @Inject fields. Found test.BaseApplication with @Inject fields"
+                      + " [str]");
+            });
   }
 
   @Test
   public void withInjectMethod_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -214,27 +210,27 @@
                 "public class BaseApplication extends Application {",
                 "  @Inject String str() { return null; }",
                 "}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(BaseApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication does not support application classes (or super classes) with "
-                + "@Inject methods. Found test.BaseApplication with @Inject methods [str()]");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication does not support application classes (or super classes)"
+                      + " with @Inject methods. Found test.BaseApplication with @Inject methods"
+                      + " [str()]");
+            });
   }
 
   @Test
   public void withSuperclassInjectMethod_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -244,32 +240,32 @@
                 "public class BaseApplication extends Application {",
                 "  @Inject String str() { return null; }",
                 "}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.ParentApplication",
                 "package test;",
                 "",
                 "public class ParentApplication extends BaseApplication {}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(ParentApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication does not support application classes (or super classes) with "
-                + "@Inject methods. Found test.BaseApplication with @Inject methods [str()]");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication does not support application classes (or super classes)"
+                      + " with @Inject methods. Found test.BaseApplication with @Inject methods"
+                      + " [str()]");
+            });
   }
 
   @Test
   public void withInjectConstructor_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -279,28 +275,27 @@
                 "public class BaseApplication extends Application {",
                 "  @Inject BaseApplication() {}",
                 "}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(BaseApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication does not support application classes (or super classes) with "
-                + "@Inject constructors. Found test.BaseApplication with @Inject constructors "
-                + "[BaseApplication()]");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication does not support application classes (or super classes)"
+                      + " with @Inject constructors. Found test.BaseApplication with @Inject"
+                      + " constructors [BaseApplication()]");
+            });
   }
 
   @Test
   public void withSuperclassInjectConstructor_fails() {
-    Compilation compilation =
-        compiler().compile(
-            JavaFileObjects.forSourceLines(
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
                 "test.BaseApplication",
                 "package test;",
                 "",
@@ -310,25 +305,25 @@
                 "public class BaseApplication extends Application {",
                 "  @Inject BaseApplication() {}",
                 "}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.ParentApplication",
                 "package test;",
                 "",
                 "public class ParentApplication extends BaseApplication {}"),
-            JavaFileObjects.forSourceLines(
+            HiltCompilerTests.javaSource(
                 "test.HiltTest",
                 "package test;",
                 "",
                 "import dagger.hilt.android.testing.CustomTestApplication;",
                 "",
                 "@CustomTestApplication(ParentApplication.class)",
-                "public class HiltTest {}"));
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@CustomTestApplication does not support application classes (or super classes) with "
-                + "@Inject constructors. Found test.BaseApplication with @Inject constructors "
-                + "[BaseApplication()]");
+                "public class HiltTest {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorContaining(
+                  "@CustomTestApplication does not support application classes (or super classes)"
+                      + " with @Inject constructors. Found test.BaseApplication with @Inject"
+                      + " constructors [BaseApplication()]");
+            });
   }
 }
diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD b/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD
index 99d00dc..79d7cbe 100644
--- a/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD
+++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/BUILD
@@ -20,30 +20,20 @@
 
 package(default_visibility = ["//:src"])
 
-java_test(
+kt_compiler_test(
     name = "ViewModelProcessorTest",
-    runtime_deps = [
-        ":ViewModelProcessorTestLib",
-        "//java/dagger/hilt/android/lifecycle:hilt_view_model",
-        "//third_party/java/compile_testing",
-        "//third_party/java/truth",
+    srcs = ["ViewModelProcessorTest.kt"],
+    compiler_deps = [
+        "//java/dagger/hilt/android:hilt_android_app",
         "@androidsdk//:platforms/android-32/android.jar",
-        "@maven//:androidx_lifecycle_lifecycle_viewmodel",
-        "@maven//:androidx_lifecycle_lifecycle_viewmodel_savedstate",
-    ],
-)
-
-kt_jvm_library(
-    name = "ViewModelProcessorTestLib",
-    srcs = [
-        "ViewModelProcessorTest.kt",
+        "//java/dagger/hilt/android/lifecycle:hilt_view_model",
     ],
     deps = [
-        ":test_utils",
         "//java/dagger/hilt/android/processor/internal/viewmodel:processor_lib",
-        "//third_party/java/compile_testing",
+        "//java/dagger/hilt/android/testing/compile",
+        "//java/dagger/internal/codegen/xprocessing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessorTest.kt b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessorTest.kt
index b3eaeab..b35aed2 100644
--- a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessorTest.kt
+++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelProcessorTest.kt
@@ -16,20 +16,22 @@
 
 package dagger.hilt.android.processor.internal.viewmodel
 
-import com.google.testing.compile.CompilationSubject.assertThat
-import com.google.testing.compile.Compiler
+import androidx.room.compiler.processing.ExperimentalProcessingApi
+import androidx.room.compiler.processing.util.Source
+import dagger.hilt.android.testing.compile.HiltCompilerTests
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
+@OptIn(ExperimentalProcessingApi::class)
 @RunWith(JUnit4::class)
 class ViewModelProcessorTest {
-
-  private fun compiler(): Compiler = Compiler.javac().withProcessors(ViewModelProcessor())
-
   @Test
   fun validViewModel() {
-    val myViewModel = """
+    val myViewModel =
+      Source.java(
+        "dagger.hilt.android.test.MyViewModel",
+        """
         package dagger.hilt.android.test;
 
         import androidx.lifecycle.ViewModel;
@@ -40,15 +42,21 @@
         class MyViewModel extends ViewModel {
             @Inject MyViewModel() { }
         }
-        """.toJFO("dagger.hilt.android.test.MyViewModel")
-
-    val compilation = compiler().compile(myViewModel)
-    assertThat(compilation).succeeded()
+        """
+          .trimIndent()
+      )
+    HiltCompilerTests.hiltCompiler(myViewModel)
+      .withAdditionalJavacProcessors(ViewModelProcessor())
+      .withAdditionalKspProcessors(KspViewModelProcessor.Provider())
+      .compile { subject -> subject.hasErrorCount(0) }
   }
 
   @Test
   fun verifyEnclosingElementExtendsViewModel() {
-    val myViewModel = """
+    val myViewModel =
+      Source.java(
+        "dagger.hilt.android.test.MyViewModel",
+        """
         package dagger.hilt.android.test;
 
         import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -59,21 +67,28 @@
             @Inject
             MyViewModel() { }
         }
-        """.toJFO("dagger.hilt.android.test.MyViewModel")
-
-    val compilation = compiler().compile(myViewModel)
-    assertThat(compilation).apply {
-      failed()
-      hadErrorCount(1)
-      hadErrorContainingMatch(
-        "@HiltViewModel is only supported on types that subclass androidx.lifecycle.ViewModel."
+        """
+          .trimIndent()
       )
-    }
+
+    HiltCompilerTests.hiltCompiler(myViewModel)
+      .withAdditionalJavacProcessors(ViewModelProcessor())
+      .withAdditionalKspProcessors(KspViewModelProcessor.Provider())
+      .compile { subject ->
+        subject.compilationDidFail()
+        subject.hasErrorCount(1)
+        subject.hasErrorContainingMatch(
+          "@HiltViewModel is only supported on types that subclass androidx.lifecycle.ViewModel."
+        )
+      }
   }
 
   @Test
   fun verifySingleAnnotatedConstructor() {
-    val myViewModel = """
+    val myViewModel =
+      Source.java(
+        "dagger.hilt.android.test.MyViewModel",
+        """
         package dagger.hilt.android.test;
 
         import androidx.lifecycle.ViewModel;
@@ -88,21 +103,31 @@
             @Inject
             MyViewModel(String s) { }
         }
-        """.toJFO("dagger.hilt.android.test.MyViewModel")
-
-    val compilation = compiler().compile(myViewModel)
-    assertThat(compilation).apply {
-      failed()
-      hadErrorCount(1)
-      hadErrorContainingMatch(
-        "@HiltViewModel annotated class should contain exactly one @Inject annotated constructor."
+        """
+          .trimIndent()
       )
-    }
+
+    HiltCompilerTests.hiltCompiler(myViewModel)
+      .withAdditionalJavacProcessors(ViewModelProcessor())
+      .withAdditionalKspProcessors(KspViewModelProcessor.Provider())
+      .compile { subject ->
+        subject.compilationDidFail()
+        subject.hasErrorCount(2)
+        subject.hasErrorContaining(
+          "Type dagger.hilt.android.test.MyViewModel may only contain one injected constructor. Found: [@Inject dagger.hilt.android.test.MyViewModel(), @Inject dagger.hilt.android.test.MyViewModel(String)]"
+        )
+        subject.hasErrorContaining(
+          "@HiltViewModel annotated class should contain exactly one @Inject annotated constructor."
+        )
+      }
   }
 
   @Test
   fun verifyNonPrivateConstructor() {
-    val myViewModel = """
+    val myViewModel =
+      Source.java(
+        "dagger.hilt.android.test.MyViewModel",
+        """
         package dagger.hilt.android.test;
 
         import androidx.lifecycle.ViewModel;
@@ -114,22 +139,27 @@
             @Inject
             private MyViewModel() { }
         }
-        """.toJFO("dagger.hilt.android.test.MyViewModel")
-
-    val compilation = compiler().compile(myViewModel)
-    assertThat(compilation).apply {
-      failed()
-      hadErrorCount(1)
-      hadErrorContainingMatch(
-        "@Inject annotated constructors must not be " +
-          "private."
+        """
+          .trimIndent()
       )
-    }
+
+    HiltCompilerTests.hiltCompiler(myViewModel)
+      .withAdditionalJavacProcessors(ViewModelProcessor())
+      .withAdditionalKspProcessors(KspViewModelProcessor.Provider())
+      .compile { subject ->
+        subject.compilationDidFail()
+        subject.hasErrorCount(2)
+        subject.hasErrorContaining("Dagger does not support injection into private constructors")
+        subject.hasErrorContaining("@Inject annotated constructors must not be private.")
+      }
   }
 
   @Test
   fun verifyInnerClassIsStatic() {
-    val myViewModel = """
+    val myViewModel =
+      Source.java(
+        "dagger.hilt.android.test.Outer",
+        """
         package dagger.hilt.android.test;
 
         import androidx.lifecycle.ViewModel;
@@ -143,21 +173,31 @@
                 MyViewModel() { }
             }
         }
-        """.toJFO("dagger.hilt.android.test.Outer")
-
-    val compilation = compiler().compile(myViewModel)
-    assertThat(compilation).apply {
-      failed()
-      hadErrorCount(1)
-      hadErrorContainingMatch(
-        "@HiltViewModel may only be used on inner classes if they are static."
+        """
+          .trimIndent()
       )
-    }
+
+    HiltCompilerTests.hiltCompiler(myViewModel)
+      .withAdditionalJavacProcessors(ViewModelProcessor())
+      .withAdditionalKspProcessors(KspViewModelProcessor.Provider())
+      .compile { subject ->
+        subject.compilationDidFail()
+        subject.hasErrorCount(2)
+        subject.hasErrorContaining(
+          "@Inject constructors are invalid on inner classes. Did you mean to make the class static?"
+        )
+        subject.hasErrorContaining(
+          "@HiltViewModel may only be used on inner classes if they are static."
+        )
+      }
   }
 
   @Test
   fun verifyNoScopeAnnotation() {
-    val myViewModel = """
+    val myViewModel =
+      Source.java(
+        "dagger.hilt.android.test.MyViewModel",
+        """
         package dagger.hilt.android.test;
 
         import androidx.lifecycle.ViewModel;
@@ -170,15 +210,19 @@
         class MyViewModel extends ViewModel {
             @Inject MyViewModel() { }
         }
-        """.toJFO("dagger.hilt.android.test.MyViewModel")
-
-    val compilation = compiler().compile(myViewModel)
-    assertThat(compilation).apply {
-      failed()
-      hadErrorCount(1)
-      hadErrorContainingMatch(
-        "@HiltViewModel classes should not be scoped. Found: @javax.inject.Singleton"
+        """
+          .trimIndent()
       )
-    }
+
+    HiltCompilerTests.hiltCompiler(myViewModel)
+      .withAdditionalJavacProcessors(ViewModelProcessor())
+      .withAdditionalKspProcessors(KspViewModelProcessor.Provider())
+      .compile { subject ->
+        subject.compilationDidFail()
+        subject.hasErrorCount(1)
+        subject.hasErrorContainingMatch(
+          "@HiltViewModel classes should not be scoped. Found: @javax.inject.Singleton"
+        )
+      }
   }
 }
diff --git a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt
index 5fe40dd..89a79bb 100644
--- a/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt
+++ b/javatests/dagger/hilt/android/processor/internal/viewmodel/ViewModelValidationPluginTest.kt
@@ -28,8 +28,7 @@
 class ViewModelValidationPluginTest {
 
   private fun testCompiler(): Compiler = compiler(
-    ComponentProcessor.forTesting(ViewModelValidationPlugin()),
-    ViewModelProcessor()
+    ComponentProcessor.withTestPlugins(ViewModelValidationPlugin()), ViewModelProcessor()
   )
 
   private val hiltAndroidApp = """
diff --git a/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt b/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt
index 6338ecf..15ecda0 100644
--- a/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt
+++ b/javatests/dagger/hilt/android/testing/BindValueInKotlinValTest.kt
@@ -30,6 +30,9 @@
 
     @Named(TEST_QUALIFIER)
     fun bindValueString2(): String
+
+    @Named(TEST_QUALIFIER_INTERNAL)
+    fun bindValueString3(): String
   }
 
   @get:Rule
@@ -42,6 +45,10 @@
   @Named(TEST_QUALIFIER)
   val bindValueString2 = BIND_VALUE_STRING2
 
+  @BindValue
+  @Named(TEST_QUALIFIER_INTERNAL)
+  internal val bindValueString3 = BIND_VALUE_STRING3
+
   @BindValueIntoMap
   @MyMapKey(BIND_VALUE_MAP_KEY_STRING)
   val mapContribution = BIND_VALUE_MAP_VALUE_STRING
@@ -54,6 +61,10 @@
   lateinit var string2: String
 
   @Inject
+  @Named(TEST_QUALIFIER_INTERNAL)
+  lateinit var string3: String
+
+  @Inject
   lateinit var map: Map<String, String>
 
   @Test
@@ -61,6 +72,7 @@
     rule.inject()
     assertThat(string1).isEqualTo(BIND_VALUE_STRING1)
     assertThat(string2).isEqualTo(BIND_VALUE_STRING2)
+    assertThat(string3).isEqualTo(BIND_VALUE_STRING3)
     assertThat(map).containsExactlyEntriesIn(
         mapOf(BIND_VALUE_MAP_KEY_STRING to BIND_VALUE_MAP_VALUE_STRING))
   }
@@ -68,8 +80,10 @@
   companion object {
     private const val BIND_VALUE_STRING1 = "BIND_VALUE_STRING1"
     private const val BIND_VALUE_STRING2 = "BIND_VALUE_STRING2"
+    private const val BIND_VALUE_STRING3 = "BIND_VALUE_STRING3"
     private const val BIND_VALUE_MAP_KEY_STRING = "BIND_VALUE_MAP_KEY_STRING"
     private const val BIND_VALUE_MAP_VALUE_STRING = "BIND_VALUE_MAP_VALUE_STRING"
     private const val TEST_QUALIFIER = "TEST_QUALIFIER"
+    private const val TEST_QUALIFIER_INTERNAL = "TEST_QUALIFIER_INTERNAL"
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/BUILD b/javatests/dagger/hilt/processor/internal/BUILD
index d3fe2ac..3dfe1ea 100644
--- a/javatests/dagger/hilt/processor/internal/BUILD
+++ b/javatests/dagger/hilt/processor/internal/BUILD
@@ -18,18 +18,6 @@
 package(default_visibility = ["//:src"])
 
 java_test(
-    name = "ElementDescriptorsTest",
-    size = "small",
-    srcs = ["ElementDescriptorsTest.java"],
-    deps = [
-        "//java/dagger/hilt/processor/internal:element_descriptors",
-        "//third_party/java/compile_testing",
-        "//third_party/java/junit",
-        "//third_party/java/truth",
-    ],
-)
-
-java_test(
     name = "ProcessorsTest",
     size = "small",
     srcs = ["ProcessorsTest.java"],
diff --git a/javatests/dagger/hilt/processor/internal/ElementDescriptorsTest.java b/javatests/dagger/hilt/processor/internal/ElementDescriptorsTest.java
deleted file mode 100644
index a84bad9..0000000
--- a/javatests/dagger/hilt/processor/internal/ElementDescriptorsTest.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2019 The Dagger Authors.
- *
- * 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 dagger.hilt.processor.internal;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.testing.compile.CompilationRule;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.lang.model.element.TypeElement;
-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 ElementDescriptorsTest {
-
-  @Rule public CompilationRule compilation = new CompilationRule();
-
-  static class TestClassA<T> {
-    int field1;
-    String field2;
-    T field3;
-    List<String> field4;
-  }
-
-  @Test
-  public void fieldDescriptor() {
-    assertThat(getFieldDescriptors(TestClassA.class.getCanonicalName()))
-        .containsExactly(
-            "field1:I",
-            "field2:Ljava/lang/String;",
-            "field3:Ljava/lang/Object;",
-            "field4:Ljava/util/List;");
-  }
-
-  static class TestClassB<T> {
-    void method1(boolean yesOrNo, int number) {}
-
-    byte method2(char letter) {
-      return 0;
-    }
-
-    void method3(double realNumber1, float realNummber2) {}
-
-    void method4(long bigNumber, short littlerNumber) {}
-  }
-
-  @Test
-  public void methodDescriptor_primitives() {
-    assertThat(getMethodDescriptors(TestClassB.class.getCanonicalName()))
-        .containsExactly("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V");
-  }
-
-  static class TestClassC<T> {
-    void method1(Object something) {}
-
-    Object method2() {
-      return null;
-    }
-
-    List<String> method3(ArrayList<Integer> list) {
-      return null;
-    }
-
-    Map<String, Object> method4() {
-      return null;
-    }
-  }
-
-  @Test
-  public void methodDescriptor_javaTypes() {
-    assertThat(getMethodDescriptors(TestClassC.class.getCanonicalName()))
-        .containsExactly(
-            "method1(Ljava/lang/Object;)V",
-            "method2()Ljava/lang/Object;",
-            "method3(Ljava/util/ArrayList;)Ljava/util/List;",
-            "method4()Ljava/util/Map;");
-  }
-
-  static class TestClassD<T> {
-    void method1(TestDataClass data) {}
-
-    TestDataClass method2() {
-      return null;
-    }
-  }
-
-  @Test
-  public void methodDescriptor_testTypes() {
-    assertThat(getMethodDescriptors(TestClassD.class.getCanonicalName()))
-        .containsExactly(
-            "method1(Ldagger/hilt/processor/internal/TestDataClass;)V",
-            "method2()Ldagger/hilt/processor/internal/TestDataClass;");
-  }
-
-  static class TestClassE<T> {
-    void method1(TestDataClass[] data) {}
-
-    TestDataClass[] method2() {
-      return null;
-    }
-
-    void method3(int[] array) {}
-
-    void method4(int... array) {}
-  }
-
-  @Test
-  public void methodDescriptor_arrays() {
-    assertThat(getMethodDescriptors(TestClassE.class.getCanonicalName()))
-        .containsExactly(
-            "method1([Ldagger/hilt/processor/internal/TestDataClass;)V",
-            "method2()[Ldagger/hilt/processor/internal/TestDataClass;",
-            "method3([I)V",
-            "method4([I)V");
-  }
-
-  static class TestClassF<T> {
-    void method1(TestDataClass.MemberInnerData data) {}
-
-    void method2(TestDataClass.StaticInnerData data) {}
-
-    void method3(TestDataClass.EnumData enumData) {}
-
-    TestDataClass.StaticInnerData method4() {
-      return null;
-    }
-  }
-
-  @Test
-  public void methodDescriptor_innerTestType() {
-    assertThat(getMethodDescriptors(TestClassF.class.getCanonicalName()))
-        .containsExactly(
-            "method1(Ldagger/hilt/processor/internal/TestDataClass$MemberInnerData;)V",
-            "method2(Ldagger/hilt/processor/internal/TestDataClass$StaticInnerData;)V",
-            "method3(Ldagger/hilt/processor/internal/TestDataClass$EnumData;)V",
-            "method4()Ldagger/hilt/processor/internal/TestDataClass$StaticInnerData;");
-  }
-
-  @SuppressWarnings("TypeParameterUnusedInFormals")
-  static class TestClassG<T> {
-    void method1(T something) {}
-
-    T method2() {
-      return null;
-    }
-
-    List<? extends String> method3() {
-      return null;
-    }
-
-    Map<T, String> method4() {
-      return null;
-    }
-
-    ArrayList<Map<T, String>> method5() {
-      return null;
-    }
-
-    static <I, O extends I> O method6(I input) {
-      return null;
-    }
-
-    static <I, O extends String> O method7(I input) {
-      return null;
-    }
-
-    static <P extends Collection<String> & Comparable<String>> P method8() {
-      return null;
-    }
-
-    static <P extends String & List<Character>> P method9() {
-      return null;
-    }
-  }
-
-  @Test
-  public void methodDescriptor_erasure() {
-    assertThat(getMethodDescriptors(TestClassG.class.getCanonicalName()))
-        .containsExactly(
-            "method1(Ljava/lang/Object;)V",
-            "method2()Ljava/lang/Object;",
-            "method3()Ljava/util/List;",
-            "method4()Ljava/util/Map;",
-            "method5()Ljava/util/ArrayList;",
-            "method6(Ljava/lang/Object;)Ljava/lang/Object;",
-            "method7(Ljava/lang/Object;)Ljava/lang/String;",
-            "method8()Ljava/util/Collection;",
-            "method9()Ljava/lang/String;");
-  }
-
-  private Set<String> getFieldDescriptors(String className) {
-    TypeElement testElement = compilation.getElements().getTypeElement(className);
-    return ElementFilter.fieldsIn(testElement.getEnclosedElements()).stream()
-        .map(ElementDescriptors::getFieldDescriptor)
-        .collect(Collectors.toSet());
-  }
-
-  private Set<String> getMethodDescriptors(String className) {
-    TypeElement testElement = compilation.getElements().getTypeElement(className);
-    return ElementFilter.methodsIn(testElement.getEnclosedElements()).stream()
-        .map(ElementDescriptors::getMethodDescriptor)
-        .collect(Collectors.toSet());
-  }
-}
-
-@SuppressWarnings("ClassCanBeStatic")
-class TestDataClass {
-  class MemberInnerData {}
-
-  static class StaticInnerData {}
-
-  enum EnumData {
-    VALUE1,
-    VALUE2
-  }
-}
diff --git a/javatests/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessorErrorsTest.java b/javatests/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessorErrorsTest.java
index b36a71d..71bdfdc 100644
--- a/javatests/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessorErrorsTest.java
+++ b/javatests/dagger/hilt/processor/internal/aggregateddeps/AggregatedDepsProcessorErrorsTest.java
@@ -16,271 +16,286 @@
 
 package dagger.hilt.processor.internal.aggregateddeps;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.common.truth.Truth.assertThat;
 
-import com.google.common.base.Joiner;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
+import androidx.room.compiler.processing.util.Source;
+import com.google.common.collect.ImmutableList;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import dagger.hilt.processor.internal.GeneratedImport;
-import dagger.testing.compile.CompilerTests;
-import javax.tools.JavaFileObject;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 /** Tests for errors generated by {@link AggregatedDepsProcessor} */
 @RunWith(JUnit4.class)
 public class AggregatedDepsProcessorErrorsTest {
-  private static final Joiner LINES = Joiner.on("\n");
+
+  @Rule public TemporaryFolder tempFolderRule = new TemporaryFolder();
 
   @Test
   public void reportMultipleAnnotationTypeKindErrors() {
-    JavaFileObject source =
-        JavaFileObjects.forSourceString(
+    Source source =
+        HiltCompilerTests.javaSource(
             "foo.bar.AnnotationsOnWrongTypeKind",
-            LINES.join(
-                "package foo.bar;",
-                "",
-                "import dagger.hilt.EntryPoint;",
-                "import dagger.hilt.InstallIn;",
-                "import dagger.Module;",
-                "import dagger.hilt.components.SingletonComponent;",
-                "import dagger.hilt.internal.ComponentEntryPoint;",
-                "import dagger.hilt.internal.GeneratedEntryPoint;",
-                "",
-                "@InstallIn(SingletonComponent.class)",
-                "@Module",
-                "enum FooModule { VALUE }",
-                "",
-                "@InstallIn(SingletonComponent.class)",
-                "@EntryPoint",
-                "final class BarEntryPoint {}",
-                "",
-                "@InstallIn(SingletonComponent.class)",
-                "@ComponentEntryPoint",
-                "final class BazComponentEntryPoint {}",
-                "",
-                "@EntryPoint",
-                "interface QuxEntryPoint {}",
-                "",
-                "@EntryPoint",
-                "@Module",
-                "interface DontMix{}",
-                ""));
+            "package foo.bar;",
+            "",
+            "import dagger.hilt.EntryPoint;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.Module;",
+            "import dagger.hilt.components.SingletonComponent;",
+            "import dagger.hilt.internal.ComponentEntryPoint;",
+            "import dagger.hilt.internal.GeneratedEntryPoint;",
+            "",
+            "@InstallIn(SingletonComponent.class)",
+            "@Module",
+            "enum FooModule { VALUE }",
+            "",
+            "@InstallIn(SingletonComponent.class)",
+            "@EntryPoint",
+            "final class BarEntryPoint {}",
+            "",
+            "@InstallIn(SingletonComponent.class)",
+            "@ComponentEntryPoint",
+            "final class BazComponentEntryPoint {}",
+            "",
+            "@EntryPoint",
+            "interface QuxEntryPoint {}",
+            "",
+            "@EntryPoint",
+            "@Module",
+            "interface DontMix{}",
+            "");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Only classes and interfaces can be annotated with @Module")
-        .inFile(source)
-        .onLine(12);
-    assertThat(compilation)
-        .hadErrorContaining("Only interfaces can be annotated with @EntryPoint")
-        .inFile(source)
-        .onLine(16);
-    assertThat(compilation)
-        .hadErrorContaining("Only interfaces can be annotated with @ComponentEntryPoint")
-        .inFile(source)
-        .onLine(20);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@EntryPoint foo.bar.QuxEntryPoint must also be annotated with @InstallIn")
-        .inFile(source)
-        .onLine(23);
-    assertThat(compilation)
-        .hadErrorContaining("@Module and @EntryPoint cannot be used on the same interface")
-        .inFile(source)
-        .onLine(27);
+    HiltCompilerTests.hiltCompiler(source)
+        .compile(
+            subject -> {
+              subject.compilationDidFail();
+              subject
+                  .hasErrorContaining("Only classes and interfaces can be annotated with @Module")
+                  .onSource(source)
+                  .onLine(12);
+              subject
+                  .hasErrorContaining("Only interfaces can be annotated with @EntryPoint")
+                  .onSource(source)
+                  .onLine(16);
+              subject
+                  .hasErrorContaining("Only interfaces can be annotated with @ComponentEntryPoint")
+                  .onSource(source)
+                  .onLine(20);
+              subject
+                  .hasErrorContaining(
+                      "@EntryPoint foo.bar.QuxEntryPoint must also be annotated with @InstallIn")
+                  .onSource(source)
+                  .onLine(23);
+              subject
+                  .hasErrorContaining(
+                      "@Module and @EntryPoint cannot be used on the same interface")
+                  .onSource(source)
+                  .onLine(27);
+            });
   }
 
   @Test
   public void testInvalidComponentInInstallInAnnotation() {
-    JavaFileObject module = JavaFileObjects.forSourceLines(
-        "test.FooModule",
-        "package test;",
-        "",
-        "import dagger.Module;",
-        "import dagger.hilt.InstallIn;",
-        "import dagger.hilt.android.qualifiers.ApplicationContext;",
-        "",
-        "@InstallIn(ApplicationContext.class)", // Error: Not a Hilt component
-        "@Module",
-        "final class FooModule {}");
+    Source module =
+        HiltCompilerTests.javaSource(
+            "test.FooModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.hilt.android.qualifiers.ApplicationContext;",
+            "",
+            "@InstallIn(ApplicationContext.class)", // Error: Not a Hilt component
+            "@Module",
+            "final class FooModule {}");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(module);
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@InstallIn, can only be used with @DefineComponent-annotated classes, but found: "
-                + "[dagger.hilt.android.qualifiers.ApplicationContext]")
-        .inFile(module)
-        .onLine(9);
+    HiltCompilerTests.hiltCompiler(module)
+        .compile(
+            subject -> {
+              subject.compilationDidFail();
+              subject
+                  .hasErrorContaining(
+                      "@InstallIn, can only be used with @DefineComponent-annotated classes, but"
+                          + " found: [dagger.hilt.android.qualifiers.ApplicationContext]")
+                  .onSource(module)
+                  .onLine(9);
+            });
   }
 
   @Test
   public void testMissingInstallInAnnotation() {
-    JavaFileObject source = JavaFileObjects.forSourceString(
-        "foo.bar.AnnotationsOnWrongTypeKind",
-        LINES.join(
+    Source source =
+        HiltCompilerTests.javaSource(
+            "foo.bar.AnnotationsOnWrongTypeKind",
             "package foo.bar;",
             "",
             "import dagger.Module;",
             "",
-            "@Module",     // Error: Doesn't have InstallIn annotation
-            "final class FooModule {}"));
+            "@Module", // Error: Doesn't have InstallIn annotation
+            "final class FooModule {}");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
-        .inFile(source)
-        .onLine(6);
+    HiltCompilerTests.hiltCompiler(source)
+        .compile(
+            subject -> {
+              subject.compilationDidFail();
+              subject
+                  .hasErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
+                  .onSource(source)
+                  .onLine(6);
+            });
   }
 
   @Test
   public void testNoErrorOnDaggerGeneratedModules() {
-    JavaFileObject source =
-        JavaFileObjects.forSourceString(
-            "foo.bar",
-            LINES.join(
-                "package foo.bar;",
-                "",
-                GeneratedImport.IMPORT_GENERATED_ANNOTATION,
-                "import dagger.Module;",
-                "",
-                "@Module",
-                "@Generated(value = \"something\")", // Error: Isn't Dagger-generated but missing
-                                                     // InstallIn
-                "final class FooModule {}",
-                "",
-                "@Module",
-                "@Generated(value = \"dagger\")", // No error because the module is dagger generated
-                "final class BarModule {}"));
+    Source source =
+        HiltCompilerTests.javaSource(
+            "foo.bar.BarModule",
+            "package foo.bar;",
+            "",
+            GeneratedImport.IMPORT_GENERATED_ANNOTATION,
+            "import dagger.Module;",
+            "",
+            "@Module",
+            "@Generated(value = \"something\")", // Error: Isn't Dagger-generated but missing
+            // InstallIn
+            "final class FooModule {}",
+            "",
+            "@Module",
+            "@Generated(value = \"dagger\")", // No error because the module is dagger generated
+            "final class BarModule {}");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
-
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
-        .inFile(source)
-        .onLine(8);
+    HiltCompilerTests.hiltCompiler(source)
+        .compile(
+            subject -> {
+              subject.compilationDidFail();
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
+                  .onSource(source)
+                  .onLine(8);
+            });
   }
 
   @Test
   public void testModuleWithOnlyParamConstructor_fails() {
-    JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
-        "package foo.bar;",
-        "",
-        "import dagger.Module;",
-        "import dagger.Provides;",
-        "import dagger.hilt.InstallIn;",
-        "import dagger.hilt.components.SingletonComponent;",
-        "",
-        "@Module",
-        "@InstallIn(SingletonComponent.class)",
-        "final class FooModule {",
-        "  FooModule(String arg) {}",
-        "",
-        "  @Provides",
-        "  String provideString() {",
-        "    return \"\";",
-        "  }",
-        "}"));
+    Source source =
+        HiltCompilerTests.javaSource(
+            "foo.bar.FooModule",
+            "package foo.bar;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.hilt.components.SingletonComponent;",
+            "",
+            "@Module",
+            "@InstallIn(SingletonComponent.class)",
+            "final class FooModule {",
+            "  FooModule(String arg) {}",
+            "",
+            "  @Provides",
+            "  String provideString() {",
+            "    return \"\";",
+            "  }",
+            "}");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
-
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "Modules that need to be instantiated by Hilt must have a visible, empty constructor.");
+    HiltCompilerTests.hiltCompiler(source)
+        .compile(
+            subject -> {
+              subject.compilationDidFail();
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "Modules that need to be instantiated by Hilt must have a visible, empty"
+                      + " constructor.");
+            });
   }
 
   @Test
   public void testInnerModule() {
-    JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
-        "package foo.bar;",
-        "",
-        "import dagger.Module;",
-        "import dagger.hilt.InstallIn;",
-        "import dagger.hilt.components.SingletonComponent;",
-        "",
-        "final class Outer {",
-        "  @Module",
-        "  @InstallIn(SingletonComponent.class)",
-        "  final class InnerModule {}",
-        "}"));
+    Source source =
+        HiltCompilerTests.javaSource(
+            "foo.bar.Outer",
+            "package foo.bar;",
+            "",
+            "import dagger.Module;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.hilt.components.SingletonComponent;",
+            "",
+            "final class Outer {",
+            "  @Module",
+            "  @InstallIn(SingletonComponent.class)",
+            "  final class InnerModule {}",
+            "}");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
-
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "Nested @InstallIn modules must be static unless they are directly nested within a "
-                + "test. Found: foo.bar.Outer.InnerModule");
+    HiltCompilerTests.hiltCompiler(source)
+        .compile(
+            subject -> {
+              subject.compilationDidFail();
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "Nested @InstallIn modules must be static unless they are directly nested within"
+                      + " a test. Found: foo.bar.Outer.InnerModule");
+            });
   }
 
   @Test
   public void testInnerModuleInTest() {
-    JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
-        "package foo.bar;",
-        "",
-        "import dagger.Module;",
-        "import dagger.hilt.InstallIn;",
-        "import dagger.hilt.components.SingletonComponent;",
-        "import dagger.hilt.android.testing.HiltAndroidTest;",
-        "",
-        "@HiltAndroidTest",
-        "final class Outer {",
-        "  static class Nested {",
-        "    @Module",
-        "    @InstallIn(SingletonComponent.class)",
-        "    final class InnerModule {}",
-        "  }",
-        "}"));
+    Source source =
+        HiltCompilerTests.javaSource(
+            "foo.bar.Outer",
+            "package foo.bar;",
+            "",
+            "import dagger.Module;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.hilt.components.SingletonComponent;",
+            "import dagger.hilt.android.testing.HiltAndroidTest;",
+            "",
+            "@HiltAndroidTest",
+            "final class Outer {",
+            "  static class Nested {",
+            "    @Module",
+            "    @InstallIn(SingletonComponent.class)",
+            "    final class InnerModule {}",
+            "  }",
+            "}");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
-
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "Nested @InstallIn modules must be static unless they are directly nested within a "
-                + "test. Found: foo.bar.Outer.Nested.InnerModule");
+    HiltCompilerTests.hiltCompiler(source)
+        .compile(
+            subject -> {
+              subject.compilationDidFail();
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "Nested @InstallIn modules must be static unless they are directly nested within"
+                      + " a test. Found: foo.bar.Outer.Nested.InnerModule");
+            });
   }
 
   @Test
   public void testInnerModuleInTest_succeeds() {
-    JavaFileObject source = JavaFileObjects.forSourceString("foo.bar", LINES.join(
-        "package foo.bar;",
-        "",
-        "import dagger.Module;",
-        "import dagger.hilt.InstallIn;",
-        "import dagger.hilt.components.SingletonComponent;",
-        "import dagger.hilt.android.testing.HiltAndroidTest;",
-        "",
-        "@HiltAndroidTest",
-        "final class Outer {",
-        "  @Module",
-        "  @InstallIn(SingletonComponent.class)",
-        "  final class InnerModule {}",
-        "}"));
+    Source source =
+        HiltCompilerTests.javaSource(
+            "foo.bar.Outer",
+            "package foo.bar;",
+            "",
+            "import dagger.Module;",
+            "import dagger.hilt.InstallIn;",
+            "import dagger.hilt.components.SingletonComponent;",
+            "import dagger.hilt.android.testing.HiltAndroidTest;",
+            "",
+            "@HiltAndroidTest",
+            "public final class Outer {",
+            "  @Module",
+            "  @InstallIn(SingletonComponent.class)",
+            "  static final class InnerModule {}",
+            "}");
 
-    Compilation compilation =
-        CompilerTests.compiler().withProcessors(new AggregatedDepsProcessor()).compile(source);
-
-    assertThat(compilation).succeeded();
+    // TODO(danysantiago): Add KSP test once b/288966076 is resolved.
+    HiltCompilerTests.compileWithKapt(
+        ImmutableList.of(source),
+        tempFolderRule,
+        result -> assertThat(result.getSuccess()).isTrue());
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD b/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD
index 180bba1..9ddf52a 100644
--- a/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD
+++ b/javatests/dagger/hilt/processor/internal/aggregateddeps/BUILD
@@ -32,12 +32,13 @@
         "//java/dagger/hilt:install_in",
         "//java/dagger/hilt/android/testing:hilt_android_test",
         "//java/dagger/hilt/android/components",
+        "@androidsdk//:platforms/android-32/android.jar",
     ],
     deps = [
-        "//java/dagger/hilt/processor/internal/aggregateddeps:processor_lib",
+        "//java/dagger/hilt/android/testing/compile",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//javatests/dagger/hilt/processor/internal:generated_import",
-        "//third_party/java/compile_testing",
-        "//third_party/java/guava/base",
+        "//third_party/java/guava/collect",
         "//third_party/java/junit",
         "//third_party/java/truth",
     ],
diff --git a/javatests/dagger/hilt/processor/internal/aliasof/AliasOfProcessorTest.java b/javatests/dagger/hilt/processor/internal/aliasof/AliasOfProcessorTest.java
index f04dc76..0654e34 100644
--- a/javatests/dagger/hilt/processor/internal/aliasof/AliasOfProcessorTest.java
+++ b/javatests/dagger/hilt/processor/internal/aliasof/AliasOfProcessorTest.java
@@ -19,8 +19,10 @@
 import static com.google.testing.compile.CompilationSubject.assertThat;
 import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
 
+import androidx.room.compiler.processing.util.Source;
 import com.google.testing.compile.Compilation;
 import com.google.testing.compile.JavaFileObjects;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,8 +75,6 @@
             .compile(root, defineComponent, scope);
 
     assertThat(compilation).failed();
-    // One extra error for the missing Hilt_MyApp reference
-    assertThat(compilation).hadErrorCount(2);
     assertThat(compilation)
         .hadErrorContaining(
             "@DefineComponent test.ChildComponent, references invalid scope(s) annotated with"
@@ -83,6 +83,30 @@
   }
 
   @Test
+  public void fails_alisOfOnNonScope() {
+    Source scope =
+        HiltCompilerTests.javaSource(
+            "test.AliasScope",
+            "package test;",
+            "",
+            "import javax.inject.Scope;",
+            "import javax.inject.Singleton;",
+            "import dagger.hilt.migration.AliasOf;",
+            "",
+            "@AliasOf(Singleton.class)",
+            "public @interface AliasScope{}");
+
+    HiltCompilerTests.hiltCompiler(scope)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "AliasOf should only be used on scopes. However, it was found "
+                      + "annotating test.AliasScope");
+            });
+  }
+
+  @Test
   public void fails_conflictingAliasScope() {
     JavaFileObject scope =
         JavaFileObjects.forSourceLines(
diff --git a/javatests/dagger/hilt/processor/internal/aliasof/BUILD b/javatests/dagger/hilt/processor/internal/aliasof/BUILD
index 4dcd003..bc7874f 100644
--- a/javatests/dagger/hilt/processor/internal/aliasof/BUILD
+++ b/javatests/dagger/hilt/processor/internal/aliasof/BUILD
@@ -33,8 +33,8 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/compile_testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
diff --git a/javatests/dagger/hilt/processor/internal/definecomponent/BUILD b/javatests/dagger/hilt/processor/internal/definecomponent/BUILD
index 9de94ba..ee8e066 100644
--- a/javatests/dagger/hilt/processor/internal/definecomponent/BUILD
+++ b/javatests/dagger/hilt/processor/internal/definecomponent/BUILD
@@ -15,28 +15,23 @@
 # Description:
 #   Tests for Hilt's DefineComponentProcessor
 
-load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX")
-load("//:test_defs.bzl", "GenJavaTests")
+load("//java/dagger/testing/compile:macros.bzl", "compiler_test")
 
 package(default_visibility = ["//:src"])
 
-GenJavaTests(
-    name = "hilt_processor_tests",
-    srcs = glob(["*.java"]),
-    functional = False,
-    javacopts = DOCLINT_HTML_AND_SYNTAX,
-    deps = [
-        "//:dagger_with_compiler",
+compiler_test(
+    name = "DefineComponentProcessorTest",
+    srcs = ["DefineComponentProcessorTest.java"],
+    compiler_deps = [
         "//java/dagger/hilt:entry_point",
         "//java/dagger/hilt:install_in",
         "//java/dagger/hilt/android/components",
         "//java/dagger/hilt/android/qualifiers",
-        "//java/dagger/hilt/processor/internal/definecomponent:define_components",
-        "//java/dagger/hilt/processor/internal/definecomponent:processor_lib",
-        "//javatests/dagger/hilt/processor/internal:generated_import",
-        "//third_party/java/compile_testing",
+    ],
+    deps = [
+        "//java/dagger/hilt/android/testing/compile",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
diff --git a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java
index 373061c..33510ab 100644
--- a/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java
+++ b/javatests/dagger/hilt/processor/internal/definecomponent/DefineComponentProcessorTest.java
@@ -16,13 +16,8 @@
 
 package dagger.hilt.processor.internal.definecomponent;
 
-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.Compiler;
-import com.google.testing.compile.JavaFileObjects;
-import javax.tools.JavaFileObject;
+import androidx.room.compiler.processing.util.Source;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -32,8 +27,8 @@
 
   @Test
   public void testDefineComponentOutput() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -46,8 +41,8 @@
             "  static int staticMethod() { return staticField; }",
             "}");
 
-    JavaFileObject builder =
-        JavaFileObjects.forSourceLines(
+    Source builder =
+        HiltCompilerTests.javaSource(
             "test.FooComponentBuilder",
             "package test;",
             "",
@@ -61,42 +56,57 @@
             "  FooComponent create();",
             "}");
 
-    JavaFileObject componentOutput =
-        JavaFileObjects.forSourceLines(
+    Source componentOutput =
+        HiltCompilerTests.javaSource(
             "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponent",
             "package dagger.hilt.processor.internal.definecomponent.codegen;",
             "",
-            "@DefineComponentClasses(component = \"test.FooComponent\")",
-            "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")",
-            "public class _test_FooComponent {}");
+            "import dagger.hilt.internal.definecomponent.DefineComponentClasses;",
+            "import javax.annotation.processing.Generated;",
+            "",
+            "/**",
+            " * This class should only be referenced by generated code! This class aggregates "
+                + "information across multiple compilations.",
+            " */",
+            "@DefineComponentClasses(",
+            "    component = \"test.FooComponent\"",
+            ")",
+            "@Generated(\"dagger.hilt.processor.internal.definecomponent.DefineComponentProcessingStep\")",
+            "public class _test_FooComponent {",
+            "}");
 
-    JavaFileObject builderOutput =
-        JavaFileObjects.forSourceLines(
+    Source builderOutput =
+        HiltCompilerTests.javaSource(
             "dagger.hilt.processor.internal.definecomponent.codegen._test_FooComponentBuilder",
             "package dagger.hilt.processor.internal.definecomponent.codegen;",
             "",
-            "@DefineComponentClasses(builder = \"test.FooComponentBuilder\")",
-            "@Generated(\"" + DefineComponentProcessor.class.getName() + "\")",
-            "public class _test_FooComponentBuilder {}");
+            "import dagger.hilt.internal.definecomponent.DefineComponentClasses;",
+            "import javax.annotation.processing.Generated;",
+            "",
+            "/**",
+            " * This class should only be referenced by generated code! This class aggregates "
+                + "information across multiple compilations.",
+            " */",
+            "@DefineComponentClasses(",
+            "    builder = \"test.FooComponentBuilder\"",
+            ")",
+            "@Generated(\"dagger.hilt.processor.internal.definecomponent.DefineComponentProcessingStep\")",
+            "public class _test_FooComponentBuilder {",
+            "}");
 
-    Compilation compilation = compiler().compile(component, builder);
-    assertThat(compilation).succeeded();
-    assertThat(compilation)
-        .generatedSourceFile(sourceName(componentOutput))
-        .containsElementsIn(componentOutput);
-    assertThat(compilation)
-        .generatedSourceFile(sourceName(builderOutput))
-        .containsElementsIn(builderOutput);
-  }
-
-  private static String sourceName(JavaFileObject fileObject) {
-    return fileObject.getName().replace(".java", "").replace('.', '/');
+    HiltCompilerTests.hiltCompiler(component, builder)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(0);
+              subject.generatedSource(componentOutput);
+              subject.generatedSource(builderOutput);
+            });
   }
 
   @Test
   public void testDefineComponentClass_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -106,18 +116,19 @@
             "@DefineComponent( parent = SingletonComponent.class )",
             "abstract class FooComponent {}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent is only allowed on interfaces. Found: test.FooComponent");
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent is only allowed on interfaces. Found: test.FooComponent");
+            });
   }
 
   @Test
   public void testDefineComponentWithTypeParameters_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -127,17 +138,19 @@
             "@DefineComponent( parent = SingletonComponent.class )",
             "interface FooComponent<T> {}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining("@DefineComponent test.FooComponent<T>, cannot have type parameters.");
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent test.FooComponent<T>, cannot have type parameters.");
+            });
   }
 
   @Test
   public void testDefineComponentWithInvalidComponent_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -147,20 +160,21 @@
             "@DefineComponent( parent = ApplicationContext.class )",
             "interface FooComponent {}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent test.FooComponent, references a type not annotated with "
-                + "@DefineComponent: dagger.hilt.android.qualifiers.ApplicationContext")
-        .inFile(component);
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                      "@DefineComponent test.FooComponent, references a type not annotated with "
+                          + "@DefineComponent: dagger.hilt.android.qualifiers.ApplicationContext")
+                  .onSource(component);
+            });
   }
 
   @Test
   public void testDefineComponentExtendsInterface_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -172,19 +186,20 @@
             "@DefineComponent( parent = SingletonComponent.class )",
             "interface FooComponent extends Foo {}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent test.FooComponent, cannot extend a super class or interface."
-                + " Found: test.Foo");
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent test.FooComponent, cannot extend a super class or interface."
+                      + " Found: [test.Foo]");
+            });
   }
 
   @Test
   public void testDefineComponentNonStaticMethod_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -196,19 +211,20 @@
             "  int nonStaticMethod();",
             "}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent test.FooComponent, cannot have non-static methods. "
-                + "Found: [nonStaticMethod()]");
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent test.FooComponent, cannot have non-static methods. "
+                      + "Found: [nonStaticMethod()]");
+            });
   }
 
   @Test
   public void testDefineComponentDependencyCycle_fails() {
-    JavaFileObject component1 =
-        JavaFileObjects.forSourceLines(
+    Source component1 =
+        HiltCompilerTests.javaSource(
             "test.Component1",
             "package test;",
             "",
@@ -217,8 +233,8 @@
             "@DefineComponent(parent = Component2.class)",
             "interface Component1 {}");
 
-    JavaFileObject component2 =
-        JavaFileObjects.forSourceLines(
+    Source component2 =
+        HiltCompilerTests.javaSource(
             "test.Component2",
             "package test;",
             "",
@@ -227,21 +243,21 @@
             "@DefineComponent(parent = Component1.class)",
             "interface Component2 {}");
 
-    Compilation compilation = compiler().compile(component1, component2);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(2);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent cycle: test.Component1 -> test.Component2 -> test.Component1");
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent cycle: test.Component2 -> test.Component1 -> test.Component2");
+    HiltCompilerTests.hiltCompiler(component1, component2)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(2);
+              subject.hasErrorContaining(
+                  "@DefineComponent cycle: test.Component1 -> test.Component2 -> test.Component1");
+              subject.hasErrorContaining(
+                  "@DefineComponent cycle: test.Component2 -> test.Component1 -> test.Component2");
+            });
   }
 
   @Test
   public void testDefineComponentNoParent_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -249,15 +265,20 @@
             "",
             "@DefineComponent",
             "interface FooComponent {}");
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation)
-        .hadErrorContaining("@DefineComponent test.FooComponent is missing a parent declaration.");
+
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent test.FooComponent is missing a parent declaration.");
+            });
   }
 
   @Test
   public void testDefineComponentBuilderClass_fails() {
-    JavaFileObject builder =
-        JavaFileObjects.forSourceLines(
+    Source builder =
+        HiltCompilerTests.javaSource(
             "test.FooComponentBuilder",
             "package test;",
             "",
@@ -266,19 +287,20 @@
             "@DefineComponent.Builder",
             "abstract class FooComponentBuilder {}");
 
-    Compilation compilation = compiler().compile(builder);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent.Builder is only allowed on interfaces. "
-                + "Found: test.FooComponentBuilder");
+    HiltCompilerTests.hiltCompiler(builder)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent.Builder is only allowed on interfaces. "
+                      + "Found: test.FooComponentBuilder");
+            });
   }
 
   @Test
   public void testDefineComponentBuilderWithTypeParameters_fails() {
-    JavaFileObject builder =
-        JavaFileObjects.forSourceLines(
+    Source builder =
+        HiltCompilerTests.javaSource(
             "test.FooComponentBuilder",
             "package test;",
             "",
@@ -287,19 +309,20 @@
             "@DefineComponent.Builder",
             "interface FooComponentBuilder<T> {}");
 
-    Compilation compilation = compiler().compile(builder);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent.Builder test.FooComponentBuilder<T>, cannot have type "
-                + "parameters.");
+    HiltCompilerTests.hiltCompiler(builder)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent.Builder test.FooComponentBuilder<T>, cannot have type "
+                      + "parameters.");
+            });
   }
 
   @Test
   public void testDefineComponentBuilderExtendsInterface_fails() {
-    JavaFileObject builder =
-        JavaFileObjects.forSourceLines(
+    Source builder =
+        HiltCompilerTests.javaSource(
             "test.FooComponentBuilder",
             "package test;",
             "",
@@ -310,19 +333,20 @@
             "@DefineComponent.Builder",
             "interface FooComponentBuilder extends Foo {}");
 
-    Compilation compilation = compiler().compile(builder);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent.Builder test.FooComponentBuilder, cannot extend a super class "
-                + "or interface. Found: test.Foo");
+    HiltCompilerTests.hiltCompiler(builder)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent.Builder test.FooComponentBuilder, cannot extend a super class "
+                      + "or interface. Found: [test.Foo]");
+            });
   }
 
   @Test
   public void testDefineComponentBuilderNoBuilderMethod_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -331,19 +355,20 @@
             "@DefineComponent.Builder",
             "interface FooComponentBuilder {}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent.Builder test.FooComponentBuilder, must have exactly 1 build "
-                + "method that takes no parameters. Found: []");
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent.Builder test.FooComponentBuilder, must have exactly 1 build "
+                      + "method that takes no parameters. Found: []");
+            });
   }
 
   @Test
   public void testDefineComponentBuilderPrimitiveReturnType_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -354,19 +379,20 @@
             "  int nonStaticMethod();",
             "}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent.Builder method, test.FooComponentBuilder#nonStaticMethod(), "
-                + "must return a @DefineComponent type. Found: int");
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent.Builder method, test.FooComponentBuilder#nonStaticMethod(), "
+                      + "must return a @DefineComponent type. Found: int");
+            });
   }
 
   @Test
   public void testDefineComponentBuilderWrongReturnType_fails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        HiltCompilerTests.javaSource(
             "test.FooComponent",
             "package test;",
             "",
@@ -379,16 +405,13 @@
             "  Foo build();",
             "}");
 
-    Compilation compilation = compiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DefineComponent.Builder method, test.FooComponentBuilder#build(), must return "
-                + "a @DefineComponent type. Found: test.Foo");
-  }
-
-  private static Compiler compiler() {
-    return javac().withProcessors(new DefineComponentProcessor());
+    HiltCompilerTests.hiltCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@DefineComponent.Builder method, test.FooComponentBuilder#build(), must return "
+                      + "a @DefineComponent type. Found: test.Foo");
+            });
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD b/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD
index 11252a6..2e88642 100644
--- a/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD
+++ b/javatests/dagger/hilt/processor/internal/disableinstallincheck/BUILD
@@ -28,12 +28,15 @@
         "//:dagger_with_compiler",
         "//third_party/java/jsr250_annotations",
         "//java/dagger/hilt:entry_point",
+        "//java/dagger/hilt:install_in",
+        "//java/dagger/hilt/android/components",
     ],
     deps = [
+        "//java/dagger/hilt/android/testing/compile",
         "//java/dagger/hilt/processor/internal/disableinstallincheck:processor_lib",
-        "//third_party/java/compile_testing",
+        "//java/dagger/internal/codegen/xprocessing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
diff --git a/javatests/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessorErrorsTest.java b/javatests/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessorErrorsTest.java
index 8ea715d..0031736 100644
--- a/javatests/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessorErrorsTest.java
+++ b/javatests/dagger/hilt/processor/internal/disableinstallincheck/DisableInstallInCheckProcessorErrorsTest.java
@@ -16,12 +16,9 @@
 
 package dagger.hilt.processor.internal.disableinstallincheck;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
+import androidx.room.compiler.processing.util.Source;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import dagger.testing.compile.CompilerTests;
-import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -32,38 +29,49 @@
 
   @Test
   public void testIllegalCombinationInstallIn() {
-    JavaFileObject source =
-        JavaFileObjects.forSourceLines(
-            "foo.bar",
+    Source module =
+        CompilerTests.javaSource(
+            "foo.bar.NotModule",
             "package foo.bar;",
             "",
             "import dagger.hilt.migration.DisableInstallInCheck;",
-            "import dagger.hilt.EntryPoint;",
             "",
             "@DisableInstallInCheck",
-            "final class NotModule {}",
+            "final class NotModule {}");
+
+    Source entryPoint =
+        CompilerTests.javaSource(
+            "foo.bar.FooEntryPoint",
+            "package foo.bar;",
+            "",
+            "import dagger.hilt.components.SingletonComponent;",
+            "import dagger.hilt.migration.DisableInstallInCheck;",
+            "import dagger.hilt.EntryPoint;",
+            "import dagger.hilt.InstallIn;",
             "",
             "@DisableInstallInCheck",
             "@EntryPoint",
+            "@InstallIn(SingletonComponent.class)",
             "interface FooEntryPoint {}");
 
-    Compilation compilation =
-        CompilerTests.compiler()
-            .withProcessors(new DisableInstallInCheckProcessor())
-            .compile(source);
-
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DisableInstallInCheck should only be used on modules. However, it was found"
-                + " annotating foo.bar.NotModule")
-        .inFile(source)
-        .onLine(7);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@DisableInstallInCheck should only be used on modules. However, it was found"
-                + " annotating foo.bar.FooEntryPoint")
-        .inFile(source)
-        .onLine(11);
+    HiltCompilerTests.hiltCompiler(module, entryPoint)
+        .withAdditionalJavacProcessors(new DisableInstallInCheckProcessor())
+        .withAdditionalKspProcessors(new KspDisableInstallInCheckProcessor.Provider())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(2);
+              subject
+                  .hasErrorContaining(
+                      "@DisableInstallInCheck should only be used on modules. However, it was found"
+                          + " annotating foo.bar.NotModule")
+                  .onSource(module)
+                  .onLine(6);
+              subject
+                  .hasErrorContaining(
+                      "@DisableInstallInCheck should only be used on modules. However, it was found"
+                          + " annotating foo.bar.FooEntryPoint")
+                  .onSource(entryPoint)
+                  .onLine(11);
+            });
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD b/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD
index a815ecd..ad919f5 100644
--- a/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD
+++ b/javatests/dagger/hilt/processor/internal/generatesrootinput/BUILD
@@ -15,24 +15,25 @@
 # Description:
 # Tests the functionality of GeneratesRootInputProcessor.
 
-load("//:build_defs.bzl", "DOCLINT_HTML_AND_SYNTAX")
-load("//:test_defs.bzl", "GenJavaTests")
+load("//java/dagger/testing/compile:macros.bzl", "compiler_test")
 
 package(default_visibility = ["//:src"])
 
-GenJavaTests(
+compiler_test(
     name = "GeneratesRootInputProcessorTest",
-    srcs = glob(["*.java"]),
-    functional = False,
-    javacopts = DOCLINT_HTML_AND_SYNTAX,
-    deps = [
-        "//:dagger_with_compiler",
+    srcs = ["GeneratesRootInputProcessorTest.java"],
+    compiler_deps = [
         "//java/dagger/hilt:generates_root_input",
+        "@androidsdk//:platforms/android-32/android.jar",
+        "@maven//:androidx_annotation_annotation",
+    ],
+    deps = [
+        "//java/dagger/hilt:generates_root_input",
+        "//java/dagger/hilt/android/testing/compile",
         "//java/dagger/hilt/processor/internal:base_processor",
         "//java/dagger/hilt/processor/internal/generatesrootinput:generates_root_inputs",
-        "//java/dagger/hilt/processor/internal/generatesrootinput:processor_lib",
-        "//third_party/java/auto:common",
-        "//third_party/java/compile_testing",
+        "//java/dagger/internal/codegen/xprocessing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/javapoet",
         "//third_party/java/junit",
         "//third_party/java/truth",
diff --git a/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java b/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java
index 48818d4..195b5fe 100644
--- a/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java
+++ b/javatests/dagger/hilt/processor/internal/generatesrootinput/GeneratesRootInputProcessorTest.java
@@ -17,24 +17,22 @@
 package dagger.hilt.processor.internal.generatesrootinput;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static com.google.testing.compile.Compiler.javac;
 
-import com.google.auto.common.MoreElements;
+import androidx.room.compiler.processing.XElement;
+import androidx.room.compiler.processing.XFiler.Mode;
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.XRoundEnv;
+import androidx.room.compiler.processing.util.Source;
 import com.google.common.truth.Correspondence;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
 import com.squareup.javapoet.ClassName;
 import com.squareup.javapoet.JavaFile;
 import com.squareup.javapoet.TypeSpec;
-import dagger.hilt.processor.internal.BaseProcessor;
+import dagger.hilt.GeneratesRootInput;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
+import dagger.hilt.processor.internal.BaseProcessingStep;
+import dagger.internal.codegen.xprocessing.XElements;
 import java.util.ArrayList;
 import java.util.List;
-import javax.annotation.processing.ProcessingEnvironment;
-import javax.annotation.processing.RoundEnvironment;
-import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.lang.model.element.Element;
-import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -45,55 +43,55 @@
   private static final int GENERATED_CLASSES = 5;
   private static final ClassName TEST_ANNOTATION = ClassName.get("test", "TestAnnotation");
 
-  private final List<Element> elementsToWaitFor = new ArrayList<>();
+  private final List<XElement> elementsToWaitFor = new ArrayList<>();
   private int generatedClasses = 0;
 
-  @SupportedAnnotationTypes("*")
-  public final class TestAnnotationProcessor extends BaseProcessor {
+  public final class TestAnnotationStep extends BaseProcessingStep {
     private GeneratesRootInputs generatesRootInputs;
 
-    @Override
-    public synchronized void init(ProcessingEnvironment processingEnv) {
-      super.init(processingEnv);
-      generatesRootInputs = new GeneratesRootInputs(processingEnv);
+    public TestAnnotationStep(XProcessingEnv env) {
+      super(env);
+      generatesRootInputs = new GeneratesRootInputs(env);
     }
 
     @Override
-    protected void postRoundProcess(RoundEnvironment roundEnv) throws Exception {
+    public void postProcess(XProcessingEnv processingEnv, XRoundEnv round) {
       if (generatedClasses > 0) {
-        elementsToWaitFor.addAll(generatesRootInputs.getElementsToWaitFor(roundEnv));
+        elementsToWaitFor.addAll(generatesRootInputs.getElementsToWaitFor(round));
       }
       if (generatedClasses < GENERATED_CLASSES) {
         TypeSpec typeSpec =
             TypeSpec.classBuilder("Foo" + generatedClasses++)
                 .addAnnotation(TEST_ANNOTATION)
                 .build();
-        JavaFile.builder("foo", typeSpec).build().writeTo(processingEnv.getFiler());
+        processingEnv.getFiler().write(JavaFile.builder("foo", typeSpec).build(), Mode.Isolating);
       }
     }
   }
 
   @Test
   public void succeeds_ComponentProcessorWaitsForAnnotationsWithGeneratesRootInput() {
-    JavaFileObject testAnnotation =
-        JavaFileObjects.forSourceLines(
+    Source testAnnotation =
+        HiltCompilerTests.javaSource(
             "test.TestAnnotation",
             "package test;",
-            "@dagger.hilt.GeneratesRootInput",
+            "@" + GeneratesRootInput.class.getCanonicalName(),
             "public @interface TestAnnotation {}");
 
-    Compilation compilation =
-        javac()
-            .withProcessors(new TestAnnotationProcessor(), new GeneratesRootInputProcessor())
-            .compile(testAnnotation);
-
-    assertThat(compilation).succeeded();
-    assertThat(elementsToWaitFor)
-        .comparingElementsUsing(
-            Correspondence.<Element, String>transforming(
-                element -> MoreElements.asType(element).getQualifiedName().toString(),
-                "has qualified name of"))
-        .containsExactly("foo.Foo0", "foo.Foo1", "foo.Foo2", "foo.Foo3", "foo.Foo4")
-        .inOrder();
+    HiltCompilerTests.hiltCompiler(testAnnotation)
+        .withProcessingSteps(TestAnnotationStep::new)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(0);
+              assertThat(elementsToWaitFor)
+                  .comparingElementsUsing(
+                      Correspondence.<XElement, String>transforming(
+                          element -> XElements.asTypeElement(element).getQualifiedName(),
+                          "has qualified name of"))
+                  .containsExactly("foo.Foo0", "foo.Foo1", "foo.Foo2", "foo.Foo3", "foo.Foo4")
+                  .inOrder();
+              elementsToWaitFor.clear();
+              generatedClasses = 0;
+            });
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/BUILD b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD
index 96fdab6..75f56ff 100644
--- a/javatests/dagger/hilt/processor/internal/originatingelement/BUILD
+++ b/javatests/dagger/hilt/processor/internal/originatingelement/BUILD
@@ -29,6 +29,7 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/compile_testing",
         "//third_party/java/junit",
         "//third_party/java/truth",
diff --git a/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java
index 5dc7a11..813fbef 100644
--- a/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java
+++ b/javatests/dagger/hilt/processor/internal/originatingelement/OriginatingElementProcessorTest.java
@@ -16,12 +16,10 @@
 
 package dagger.hilt.processor.internal.originatingelement;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
+import static dagger.hilt.android.testing.compile.HiltCompilerTests.hiltCompiler;
 
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
-import javax.tools.JavaFileObject;
+import androidx.room.compiler.processing.util.Source;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -31,14 +29,10 @@
 
   @Test
   public void originatingElementOnInnerClass_fails() {
-    JavaFileObject outer1 =
-        JavaFileObjects.forSourceLines(
-            "test.Outer1",
-            "package test;",
-            "",
-            "class Outer1 {}");
-    JavaFileObject outer2 =
-        JavaFileObjects.forSourceLines(
+    Source outer1 =
+        HiltCompilerTests.javaSource("test.Outer1", "package test;", "", "class Outer1 {}");
+    Source outer2 =
+        HiltCompilerTests.javaSource(
             "test.Outer2",
             "package test;",
             "",
@@ -48,26 +42,23 @@
             "  @OriginatingElement(topLevelClass = Outer1.class)",
             "  static class Inner {}",
             "}");
-    Compilation compilation = compiler().compile(outer1, outer2);
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@OriginatingElement should only be used to annotate top-level types, but found: "
-            + "test.Outer2.Inner");
+    hiltCompiler(outer1, outer2)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@OriginatingElement should only be used to annotate top-level types, but found: "
+                      + "test.Outer2.Inner");
+            });
   }
 
   @Test
   public void originatingElementValueWithInnerClass_fails() {
-    JavaFileObject outer1 =
-        JavaFileObjects.forSourceLines(
-            "test.Outer1",
-            "package test;",
-            "",
-            "class Outer1 {",
-            "  static class Inner {}",
-            "}");
-    JavaFileObject outer2 =
-        JavaFileObjects.forSourceLines(
+    Source outer1 =
+        HiltCompilerTests.javaSource(
+            "test.Outer1", "package test;", "", "class Outer1 {", "  static class Inner {}", "}");
+    Source outer2 =
+        HiltCompilerTests.javaSource(
             "test.Outer2",
             "package test;",
             "",
@@ -75,11 +66,13 @@
             "",
             "@OriginatingElement(topLevelClass = Outer1.Inner.class)",
             "class Outer2 {}");
-    Compilation compilation = compiler().compile(outer1, outer2);
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining(
-            "OriginatingElement.topLevelClass value should be a top-level class, but found: "
-            + "test.Outer1.Inner");
+    hiltCompiler(outer1, outer2)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "OriginatingElement.topLevelClass value should be a top-level class, but found: "
+                      + "test.Outer1.Inner");
+            });
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/root/BUILD b/javatests/dagger/hilt/processor/internal/root/BUILD
index ab85f60..0da11f1 100644
--- a/javatests/dagger/hilt/processor/internal/root/BUILD
+++ b/javatests/dagger/hilt/processor/internal/root/BUILD
@@ -42,7 +42,7 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
-        "//third_party/java/compile_testing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/guava/collect",
         "//third_party/java/junit",
         "//third_party/java/truth",
@@ -73,7 +73,7 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
-        "//third_party/java/compile_testing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/guava/collect",
         "//third_party/java/junit",
         "//third_party/java/truth",
@@ -94,10 +94,9 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
-        "//third_party/java/compile_testing",
+        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//third_party/java/guava/collect",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
diff --git a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java
index e4aa5ab..f7e9b3d 100644
--- a/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java
+++ b/javatests/dagger/hilt/processor/internal/root/MyAppPreviousCompilationTest.java
@@ -16,16 +16,20 @@
 
 package dagger.hilt.processor.internal.root;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+import static dagger.hilt.android.testing.compile.HiltCompilerTests.compileWithKapt;
+import static dagger.hilt.android.testing.compile.HiltCompilerTests.javaSource;
 
+import androidx.room.compiler.processing.util.DiagnosticMessage;
+import androidx.room.compiler.processing.util.Source;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.Compiler;
-import com.google.testing.compile.JavaFileObjects;
-import dagger.hilt.android.testing.compile.HiltCompilerTests;
-import javax.tools.JavaFileObject;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import javax.tools.Diagnostic.Kind;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -38,24 +42,24 @@
     return ImmutableList.copyOf(new Object[][] {{true}, {false}});
   }
 
+  @Rule public TemporaryFolder tempFolderRule = new TemporaryFolder();
+
   private final boolean disableCrossCompilationRootValidation;
 
   public MyAppPreviousCompilationTest(boolean disableCrossCompilationRootValidation) {
     this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation;
   }
 
-  private Compiler compiler() {
-    return HiltCompilerTests.compiler()
-        .withOptions(
-            String.format(
-                "-Adagger.hilt.disableCrossCompilationRootValidation=%s",
-                disableCrossCompilationRootValidation));
+  private ImmutableMap<String, String> processorOptions() {
+    return ImmutableMap.of(
+        "dagger.hilt.disableCrossCompilationRootValidation",
+        Boolean.toString(disableCrossCompilationRootValidation));
   }
 
   @Test
   public void testRootTest() {
-    JavaFileObject testRoot =
-        JavaFileObjects.forSourceLines(
+    Source testRoot =
+        javaSource(
             "test.TestRoot",
             "package test;",
             "",
@@ -64,15 +68,19 @@
             "@HiltAndroidTest",
             "public class TestRoot {}");
 
+    // TODO(danysantiago): Add KSP test once b/288966076 is resolved.
     // This test case should succeed independent of disableCrossCompilationRootValidation.
-    Compilation compilation = compiler().compile(testRoot);
-    assertThat(compilation).succeeded();
+    compileWithKapt(
+        ImmutableList.of(testRoot),
+        processorOptions(),
+        tempFolderRule,
+        result -> assertThat(result.getSuccess()).isTrue());
   }
 
   @Test
   public void appRootTest() {
-    JavaFileObject appRoot =
-        JavaFileObjects.forSourceLines(
+    Source appRoot =
+        javaSource(
             "test.AppRoot",
             "package test;",
             "",
@@ -82,19 +90,25 @@
             "@HiltAndroidApp(Application.class)",
             "public class AppRoot extends Hilt_AppRoot {}");
 
-    Compilation compilation = compiler().compile(appRoot);
-    if (disableCrossCompilationRootValidation) {
-      assertThat(compilation).succeeded();
-    } else {
-      assertThat(compilation).failed();
-      assertThat(compilation).hadErrorCount(1);
-      assertThat(compilation)
-          .hadErrorContaining(
-              "Cannot process new app roots when there are app roots from a "
-                  + "previous compilation unit:"
-                  + "\n    App roots in previous compilation unit: "
-                  + "dagger.hilt.processor.internal.root.MyAppPreviousCompilation.MyApp"
-                  + "\n    App roots in this compilation unit: test.AppRoot");
-    }
+    // TODO(danysantiago): Add KSP test once b/288966076 is resolved.
+    compileWithKapt(
+        ImmutableList.of(appRoot),
+        processorOptions(),
+        tempFolderRule,
+        result -> {
+          if (disableCrossCompilationRootValidation) {
+            assertThat(result.getSuccess()).isTrue();
+          } else {
+            List<DiagnosticMessage> errors = result.getDiagnostics().get(Kind.ERROR);
+            assertThat(errors).hasSize(1);
+            assertThat(errors.get(0).getMsg())
+                .contains(
+                    "Cannot process new app roots when there are app roots from a "
+                        + "previous compilation unit:"
+                        + "\n    App roots in previous compilation unit: "
+                        + "dagger.hilt.processor.internal.root.MyAppPreviousCompilation.MyApp"
+                        + "\n    App roots in this compilation unit: test.AppRoot");
+          }
+        });
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java
index 82251bd..795a4ea 100644
--- a/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java
+++ b/javatests/dagger/hilt/processor/internal/root/MyTestPreviousCompilationTest.java
@@ -16,16 +16,20 @@
 
 package dagger.hilt.processor.internal.root;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
+import static com.google.common.truth.Truth.assertThat;
+import static dagger.hilt.android.testing.compile.HiltCompilerTests.compileWithKapt;
+import static dagger.hilt.android.testing.compile.HiltCompilerTests.javaSource;
 
+import androidx.room.compiler.processing.util.DiagnosticMessage;
+import androidx.room.compiler.processing.util.Source;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.Compiler;
-import com.google.testing.compile.JavaFileObjects;
-import dagger.hilt.android.testing.compile.HiltCompilerTests;
-import javax.tools.JavaFileObject;
+import com.google.common.collect.ImmutableMap;
+import java.util.List;
+import javax.tools.Diagnostic.Kind;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -38,24 +42,24 @@
     return ImmutableList.copyOf(new Object[][] {{true}, {false}});
   }
 
+  @Rule public TemporaryFolder tempFolderRule = new TemporaryFolder();
+
   private final boolean disableCrossCompilationRootValidation;
 
   public MyTestPreviousCompilationTest(boolean disableCrossCompilationRootValidation) {
     this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation;
   }
 
-  private Compiler compiler() {
-    return HiltCompilerTests.compiler()
-        .withOptions(
-            String.format(
-                "-Adagger.hilt.disableCrossCompilationRootValidation=%s",
-                disableCrossCompilationRootValidation));
+  private ImmutableMap<String, String> processorOptions() {
+    return ImmutableMap.of(
+        "dagger.hilt.disableCrossCompilationRootValidation",
+        Boolean.toString(disableCrossCompilationRootValidation));
   }
 
   @Test
   public void testRootTest() {
-    JavaFileObject testRoot =
-        JavaFileObjects.forSourceLines(
+    Source testRoot =
+        javaSource(
             "test.TestRoot",
             "package test;",
             "",
@@ -64,25 +68,32 @@
             "@HiltAndroidTest",
             "public class TestRoot {}");
 
-    Compilation compilation = compiler().compile(testRoot);
-    if (disableCrossCompilationRootValidation) {
-      assertThat(compilation).succeeded();
-    } else {
-      assertThat(compilation).failed();
-      assertThat(compilation).hadErrorCount(1);
-      assertThat(compilation)
-          .hadErrorContaining(
-              "Cannot process new roots when there are test roots from a previous compilation unit:"
-                  + "\n    Test roots from previous compilation unit: "
-                  + "dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest"
-                  + "\n    All roots from this compilation unit: test.TestRoot");
-    }
+    // TODO(danysantiago): Add KSP test once b/288966076 is resolved.
+    compileWithKapt(
+        ImmutableList.of(testRoot),
+        processorOptions(),
+        tempFolderRule,
+        result -> {
+          if (disableCrossCompilationRootValidation) {
+            assertThat(result.getSuccess()).isTrue();
+          } else {
+            List<DiagnosticMessage> errors = result.getDiagnostics().get(Kind.ERROR);
+            assertThat(errors).hasSize(1);
+            assertThat(errors.get(0).getMsg())
+                .contains(
+                    "Cannot process new roots when there are test roots from a previous "
+                        + "compilation unit:\n"
+                        + "    Test roots from previous compilation unit: "
+                        + "dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest\n"
+                        + "    All roots from this compilation unit: test.TestRoot");
+          }
+        });
   }
 
   @Test
   public void appRootTest() {
-    JavaFileObject appRoot =
-        JavaFileObjects.forSourceLines(
+    Source appRoot =
+        javaSource(
             "test.AppRoot",
             "package test;",
             "",
@@ -92,18 +103,25 @@
             "@HiltAndroidApp(Application.class)",
             "public class AppRoot extends Hilt_AppRoot {}");
 
-    Compilation compilation = compiler().compile(appRoot);
-    if (disableCrossCompilationRootValidation) {
-      assertThat(compilation).succeeded();
-    } else {
-      assertThat(compilation).failed();
-      assertThat(compilation).hadErrorCount(1);
-      assertThat(compilation)
-          .hadErrorContaining(
-              "Cannot process new roots when there are test roots from a previous compilation unit:"
-                  + "\n    Test roots from previous compilation unit: "
-                  + "dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest"
-                  + "\n    All roots from this compilation unit: test.AppRoot");
-    }
+    // TODO(danysantiago): Add KSP test once b/288966076 is resolved.
+    compileWithKapt(
+        ImmutableList.of(appRoot),
+        processorOptions(),
+        tempFolderRule,
+        result -> {
+          if (disableCrossCompilationRootValidation) {
+            assertThat(result.getSuccess()).isTrue();
+          } else {
+            List<DiagnosticMessage> errors = result.getDiagnostics().get(Kind.ERROR);
+            assertThat(errors).hasSize(1);
+            assertThat(errors.get(0).getMsg())
+                .contains(
+                    "Cannot process new roots when there are test roots from a previous "
+                        + "compilation unit:\n"
+                        + "    Test roots from previous compilation unit: "
+                        + "dagger.hilt.processor.internal.root.MyTestPreviousCompilation.MyTest\n"
+                        + "    All roots from this compilation unit: test.AppRoot");
+          }
+        });
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java
index c07ee53..4d634cb 100644
--- a/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java
+++ b/javatests/dagger/hilt/processor/internal/root/RootProcessorErrorsTest.java
@@ -16,15 +16,11 @@
 
 package dagger.hilt.processor.internal.root;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-
+import androidx.room.compiler.processing.util.Source;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.Compiler;
-import com.google.testing.compile.JavaFileObjects;
+import com.google.common.collect.ImmutableMap;
 import dagger.hilt.android.testing.compile.HiltCompilerTests;
-import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -44,18 +40,16 @@
     this.disableCrossCompilationRootValidation = disableCrossCompilationRootValidation;
   }
 
-  private Compiler compiler() {
-    return HiltCompilerTests.compiler()
-        .withOptions(
-            String.format(
-                "-Adagger.hilt.disableCrossCompilationRootValidation=%s",
-                disableCrossCompilationRootValidation));
+  private ImmutableMap<String, String> processorOptions() {
+    return ImmutableMap.of(
+        "dagger.hilt.disableCrossCompilationRootValidation",
+        Boolean.toString(disableCrossCompilationRootValidation));
   }
 
   @Test
   public void multipleAppRootsTest() {
-    JavaFileObject appRoot1 =
-        JavaFileObjects.forSourceLines(
+    Source appRoot1 =
+        HiltCompilerTests.javaSource(
             "test.AppRoot1",
             "package test;",
             "",
@@ -65,8 +59,8 @@
             "@HiltAndroidApp(Application.class)",
             "public class AppRoot1 extends Hilt_AppRoot1 {}");
 
-    JavaFileObject appRoot2 =
-        JavaFileObjects.forSourceLines(
+    Source appRoot2 =
+        HiltCompilerTests.javaSource(
             "test.AppRoot2",
             "package test;",
             "",
@@ -76,20 +70,22 @@
             "@HiltAndroidApp(Application.class)",
             "public class AppRoot2 extends Hilt_AppRoot2 {}");
 
-    // This test case should fail independent of disableCrossCompilationRootValidation.
-    Compilation compilation = compiler().compile(appRoot1, appRoot2);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "Cannot process multiple app roots in the same compilation unit: "
-                + "test.AppRoot1, test.AppRoot2");
+    HiltCompilerTests.hiltCompiler(appRoot1, appRoot2)
+        .withProcessorOptions(processorOptions())
+        .compile(
+            subject -> {
+              // This test case should fail independent of disableCrossCompilationRootValidation.
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "Cannot process multiple app roots in the same compilation unit: "
+                      + "test.AppRoot1, test.AppRoot2");
+            });
   }
 
   @Test
   public void appRootWithTestRootTest() {
-    JavaFileObject appRoot =
-        JavaFileObjects.forSourceLines(
+    Source appRoot =
+        HiltCompilerTests.javaSource(
             "test.AppRoot",
             "package test;",
             "",
@@ -99,8 +95,8 @@
             "@HiltAndroidApp(Application.class)",
             "public class AppRoot extends Hilt_AppRoot {}");
 
-    JavaFileObject testRoot =
-        JavaFileObjects.forSourceLines(
+    Source testRoot =
+        HiltCompilerTests.javaSource(
             "test.TestRoot",
             "package test;",
             "",
@@ -109,14 +105,16 @@
             "@HiltAndroidTest",
             "public class TestRoot {}");
 
-    // This test case should fail independent of disableCrossCompilationRootValidation.
-    Compilation compilation = compiler().compile(appRoot, testRoot);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "Cannot process test roots and app roots in the same compilation unit:"
-                + "\n    App root in this compilation unit: test.AppRoot"
-                + "\n    Test roots in this compilation unit: test.TestRoot");
+    HiltCompilerTests.hiltCompiler(appRoot, testRoot)
+        .withProcessorOptions(processorOptions())
+        .compile(
+            subject -> {
+              // This test case should fail independent of disableCrossCompilationRootValidation.
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "Cannot process test roots and app roots in the same compilation unit:"
+                      + "\n    App root in this compilation unit: test.AppRoot"
+                      + "\n    Test roots in this compilation unit: test.TestRoot");
+            });
   }
 }
diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD
index 0436f99..50ffd2e 100644
--- a/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD
+++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/BUILD
@@ -32,9 +32,7 @@
     ],
     deps = [
         "//java/dagger/hilt/android/testing/compile",
-        "//third_party/java/compile_testing",
         "//third_party/java/junit",
-        "//third_party/java/truth",
     ],
 )
 
diff --git a/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java
index 04b5a24..de2c367 100644
--- a/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java
+++ b/javatests/dagger/hilt/processor/internal/uninstallmodules/UninstallModulesProcessorTest.java
@@ -16,11 +16,7 @@
 
 package dagger.hilt.processor.internal.uninstallmodules;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.hilt.android.testing.compile.HiltCompilerTests.compiler;
-
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
+import dagger.hilt.android.testing.compile.HiltCompilerTests;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -30,133 +26,125 @@
 
   @Test
   public void testInvalidModuleNoInstallIn_fails() {
-    Compilation compilation =
-        compiler()
-            .compile(
-                JavaFileObjects.forSourceLines(
-                    "test.MyTest",
-                    "package test;",
-                    "",
-                    "import dagger.hilt.android.testing.HiltAndroidTest;",
-                    "import dagger.hilt.android.testing.UninstallModules;",
-                    "",
-                    "@UninstallModules(InvalidModule.class)",
-                    "@HiltAndroidTest",
-                    "public class MyTest {}"),
-                JavaFileObjects.forSourceLines(
-                    "test.InvalidModule",
-                    "package test;",
-                    "",
-                    "import dagger.Module;",
-                    "import dagger.hilt.migration.DisableInstallInCheck;",
-                    "",
-                    "@DisableInstallInCheck",
-                    "@Module",
-                    "public class InvalidModule {}"));
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@UninstallModules should only include modules annotated with both @Module and "
-                + "@InstallIn, but found: [test.InvalidModule].");
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
+                "test.MyTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.HiltAndroidTest;",
+                "import dagger.hilt.android.testing.UninstallModules;",
+                "",
+                "@UninstallModules(InvalidModule.class)",
+                "@HiltAndroidTest",
+                "public class MyTest {}"),
+            HiltCompilerTests.javaSource(
+                "test.InvalidModule",
+                "package test;",
+                "",
+                "import dagger.Module;",
+                "import dagger.hilt.migration.DisableInstallInCheck;",
+                "",
+                "@DisableInstallInCheck",
+                "@Module",
+                "public class InvalidModule {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@UninstallModules should only include modules annotated with both @Module and "
+                      + "@InstallIn, but found: [test.InvalidModule].");
+            });
   }
 
   @Test
   public void testInvalidModuleNoModule_fails() {
-    Compilation compilation =
-        compiler()
-            .compile(
-                JavaFileObjects.forSourceLines(
-                    "test.MyTest",
-                    "package test;",
-                    "",
-                    "import dagger.hilt.android.testing.HiltAndroidTest;",
-                    "import dagger.hilt.android.testing.UninstallModules;",
-                    "",
-                    "@UninstallModules(InvalidModule.class)",
-                    "@HiltAndroidTest",
-                    "public class MyTest {}"),
-                JavaFileObjects.forSourceLines(
-                    "test.InvalidModule",
-                    "package test;",
-                    "",
-                    "public class InvalidModule {",
-                    "}"));
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@UninstallModules should only include modules annotated with both @Module and "
-                + "@InstallIn, but found: [test.InvalidModule].");
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
+                "test.MyTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.HiltAndroidTest;",
+                "import dagger.hilt.android.testing.UninstallModules;",
+                "",
+                "@UninstallModules(InvalidModule.class)",
+                "@HiltAndroidTest",
+                "public class MyTest {}"),
+            HiltCompilerTests.javaSource(
+                "test.InvalidModule", "package test;", "", "public class InvalidModule {", "}"))
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@UninstallModules should only include modules annotated with both @Module and "
+                      + "@InstallIn, but found: [test.InvalidModule].");
+            });
   }
 
   @Test
   public void testInvalidTest_fails() {
-    Compilation compilation =
-        compiler()
-            .compile(
-                JavaFileObjects.forSourceLines(
-                    "test.InvalidTest",
-                    "package test;",
-                    "",
-                    "import dagger.hilt.android.testing.UninstallModules;",
-                    "",
-                    "@UninstallModules(ValidModule.class)",
-                    "public class InvalidTest {}"),
-                JavaFileObjects.forSourceLines(
-                    "test.ValidModule",
-                    "package test;",
-                    "",
-                    "import dagger.Module;",
-                    "import dagger.hilt.InstallIn;",
-                    "import dagger.hilt.components.SingletonComponent;",
-                    "",
-                    "@Module",
-                    "@InstallIn(SingletonComponent.class)",
-                    "public class ValidModule {}"));
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@UninstallModules should only be used on test classes annotated with @HiltAndroidTest,"
-                + " but found: test.InvalidTest");
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
+                "test.InvalidTest",
+                "package test;",
+                "",
+                "import dagger.hilt.android.testing.UninstallModules;",
+                "",
+                "@UninstallModules(ValidModule.class)",
+                "public class InvalidTest {}"),
+            HiltCompilerTests.javaSource(
+                "test.ValidModule",
+                "package test;",
+                "",
+                "import dagger.Module;",
+                "import dagger.hilt.InstallIn;",
+                "import dagger.hilt.components.SingletonComponent;",
+                "",
+                "@Module",
+                "@InstallIn(SingletonComponent.class)",
+                "public class ValidModule {}"))
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  "@UninstallModules should only be used on test classes annotated with"
+                      + " @HiltAndroidTest, but found: test.InvalidTest");
+            });
   }
 
   @Test
   public void testInvalidTestModule_fails() {
-    Compilation compilation =
-        compiler()
-            .compile(
-                JavaFileObjects.forSourceLines(
-                    "test.MyTest",
-                    "package test;",
-                    "",
-                    "import dagger.Module;",
-                    "import dagger.hilt.InstallIn;",
-                    "import dagger.hilt.components.SingletonComponent;",
-                    "import dagger.hilt.android.testing.HiltAndroidTest;",
-                    "import dagger.hilt.android.testing.UninstallModules;",
-                    "",
-                    "@UninstallModules({",
-                    "    MyTest.PkgPrivateInvalidModule.class,",
-                    "    MyTest.PublicInvalidModule.class,",
-                    "})",
-                    "@HiltAndroidTest",
-                    "public class MyTest {",
-                    "  @Module",
-                    "  @InstallIn(SingletonComponent.class)",
-                    "  interface PkgPrivateInvalidModule {}",
-                    "",
-                    "  @Module",
-                    "  @InstallIn(SingletonComponent.class)",
-                    "  public interface PublicInvalidModule {}",
-                    "}"));
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    // TODO(bcorso): Consider unwrapping pkg-private modules before reporting the error.
-    assertThat(compilation)
-        .hadErrorContaining(
-            "@UninstallModules should not contain test modules, but found: "
-                + "[test.MyTest.PkgPrivateInvalidModule, test.MyTest.PublicInvalidModule]");
+    HiltCompilerTests.hiltCompiler(
+            HiltCompilerTests.javaSource(
+                "test.MyTest",
+                "package test;",
+                "",
+                "import dagger.Module;",
+                "import dagger.hilt.InstallIn;",
+                "import dagger.hilt.components.SingletonComponent;",
+                "import dagger.hilt.android.testing.HiltAndroidTest;",
+                "import dagger.hilt.android.testing.UninstallModules;",
+                "",
+                "@UninstallModules({",
+                "    MyTest.PkgPrivateInvalidModule.class,",
+                "    MyTest.PublicInvalidModule.class,",
+                "})",
+                "@HiltAndroidTest",
+                "public class MyTest {",
+                "  @Module",
+                "  @InstallIn(SingletonComponent.class)",
+                "  interface PkgPrivateInvalidModule {}",
+                "",
+                "  @Module",
+                "  @InstallIn(SingletonComponent.class)",
+                "  public interface PublicInvalidModule {}",
+                "}"))
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              // TODO(bcorso): Consider unwrapping pkg-private modules before reporting the error.
+              subject.hasErrorContaining(
+                  "@UninstallModules should not contain test modules, but found: "
+                      + "[test.MyTest.PkgPrivateInvalidModule, test.MyTest.PublicInvalidModule]");
+            });
   }
 }
diff --git a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java
index dc5d109..9ac3e27 100644
--- a/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java
+++ b/javatests/dagger/internal/codegen/AssistedFactoryErrorsTest.java
@@ -729,13 +729,16 @@
     CompilerTests.daggerCompiler(foo)
         .withProcessingOptions(compilerMode.processorOptions())
         .compile(
-            subject -> {
-              subject.hasErrorCount(1);
-              subject.hasErrorContaining(
-                      "A type with an @AssistedInject-annotated constructor cannot be scoped")
-                  .onSource(foo)
-                  .onLine(8);
-            });
+            subject ->
+                // Don't assert on the number of errors as Foo_Factory_Impl can also be created
+                // and have errors from the missing Foo_Factory.
+                // TODO(erichang): don't generate the factory impls if there are errors with the
+                // assisted type
+                subject
+                    .hasErrorContaining(
+                        "A type with an @AssistedInject-annotated constructor cannot be scoped")
+                    .onSource(foo)
+                    .onLine(8));
   }
 
   @Test
diff --git a/javatests/dagger/internal/codegen/BUILD b/javatests/dagger/internal/codegen/BUILD
index 9a5b7b8..5984e53 100644
--- a/javatests/dagger/internal/codegen/BUILD
+++ b/javatests/dagger/internal/codegen/BUILD
@@ -96,6 +96,7 @@
         "//java/dagger/internal/codegen/javapoet",
         "//java/dagger/internal/codegen/kotlin",
         "//java/dagger/internal/codegen/langmodel",
+        "//java/dagger/internal/codegen/model",
         "//java/dagger/internal/codegen/validation",
         "//java/dagger/internal/codegen/writing",
         "//java/dagger/internal/codegen/xprocessing",
diff --git a/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java b/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java
index 2fc88fc..7e81901 100644
--- a/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java
+++ b/javatests/dagger/internal/codegen/ComponentCreatorTestHelper.java
@@ -16,21 +16,17 @@
 
 package dagger.internal.codegen;
 
-import static dagger.internal.codegen.Compilers.compilerWithOptions;
 import static dagger.internal.codegen.base.ComponentCreatorKind.FACTORY;
 import static dagger.internal.codegen.binding.ErrorMessages.creatorMessagesFor;
 import static java.util.stream.Collectors.joining;
 
 import androidx.room.compiler.processing.util.Source;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
 import dagger.internal.codegen.base.ComponentCreatorAnnotation;
 import dagger.internal.codegen.base.ComponentCreatorKind;
 import dagger.internal.codegen.binding.ErrorMessages;
 import dagger.testing.compile.CompilerTests;
 import java.util.Arrays;
 import java.util.stream.Stream;
-import javax.tools.JavaFileObject;
 
 /**
  * Base class for component creator codegen tests that are written in terms of builders and
@@ -82,21 +78,8 @@
     return CompilerTests.javaSource(fullyQualifiedName, process(lines));
   }
 
-  /**
-   * Returns a Java file with the {@linkplain #process(String...)} processed} versions of the given
-   * lines.
-   */
-  JavaFileObject preprocessedJavaFile(String fullyQualifiedName, String... lines) {
-    return JavaFileObjects.forSourceString(fullyQualifiedName, process(lines));
-  }
-
   /** Returns a file builder for the current creator kind. */
   JavaFileBuilder javaFileBuilder(String qualifiedName) {
     return new JavaFileBuilder(qualifiedName).withSettings(compilerMode, creatorKind);
   }
-
-  /** Compiles the given files with the set compiler mode's javacopts. */
-  Compilation compile(JavaFileObject... files) {
-    return compilerWithOptions(compilerMode.javacopts()).compile(files);
-  }
 }
diff --git a/javatests/dagger/internal/codegen/ComponentProcessorTest.java b/javatests/dagger/internal/codegen/ComponentProcessorTest.java
index 9183639..e7f9b8e 100644
--- a/javatests/dagger/internal/codegen/ComponentProcessorTest.java
+++ b/javatests/dagger/internal/codegen/ComponentProcessorTest.java
@@ -153,10 +153,12 @@
                 .put("dagger.privateMemberValidation", "WARNING")
                 .buildOrThrow())
         .compile(
-            subject -> {
-              subject.hasErrorCount(1);
-              subject.hasErrorContaining("Dagger does not support injection into private classes");
-            });
+            subject ->
+                // Because it is just a warning until it is used, the factory still gets generated
+                // which has errors from referencing the private class, so there are extra errors.
+                // Hence we don't assert on the number of errors.
+                subject.hasErrorContaining(
+                    "Dagger does not support injection into private classes"));
   }
 
   @Test
diff --git a/javatests/dagger/internal/codegen/DaggerSuperficialValidationTest.java b/javatests/dagger/internal/codegen/DaggerSuperficialValidationTest.java
index 1fd8338..7092047 100644
--- a/javatests/dagger/internal/codegen/DaggerSuperficialValidationTest.java
+++ b/javatests/dagger/internal/codegen/DaggerSuperficialValidationTest.java
@@ -28,6 +28,7 @@
 import androidx.room.compiler.processing.XVariableElement;
 import androidx.room.compiler.processing.util.Source;
 import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import dagger.BindsInstance;
 import dagger.Component;
@@ -39,93 +40,108 @@
 import javax.inject.Singleton;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
 public class DaggerSuperficialValidationTest {
+  enum SourceKind {
+    JAVA,
+    KOTLIN
+  }
+
+  @Parameters(name = "sourceKind={0}")
+  public static ImmutableList<Object[]> parameters() {
+    return ImmutableList.of(new Object[] {SourceKind.JAVA}, new Object[] {SourceKind.KOTLIN});
+  }
+
+  private final SourceKind sourceKind;
+
+  public DaggerSuperficialValidationTest(SourceKind sourceKind) {
+    this.sourceKind = sourceKind;
+  }
+
   private static final Joiner NEW_LINES = Joiner.on("\n  ");
 
   @Test
   public void missingReturnType() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "abstract class TestClass {",
             "  abstract MissingType blah();",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => element (METHOD): blah()",
-                                "  => type (ERROR return type): %1$s"),
-                            isJavac ? "MissingType" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt",
+            "package test",
+            "",
+            "abstract class TestClass {",
+            "  abstract fun blah(): MissingType",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => element (METHOD): blah()",
+                          "  => type (ERROR return type): %1$s"),
+                      isJavac ? "MissingType" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void missingGenericReturnType() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "abstract class TestClass {",
             "  abstract MissingType<?> blah();",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => element (METHOD): blah()",
-                                "  => type (ERROR return type): %1$s"),
-                            isJavac ? "<any>" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt",
+            "package test",
+            "",
+            "abstract class TestClass {",
+            "  abstract fun blah(): MissingType<*>",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => element (METHOD): blah()",
+                          "  => type (ERROR return type): %1$s"),
+                      isJavac ? "<any>" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void missingReturnTypeTypeParameter() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass",
             "package test;",
@@ -133,295 +149,289 @@
             "import java.util.Map;",
             "import java.util.Set;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "abstract class TestClass {",
             "  abstract Map<Set<?>, MissingType<?>> blah();",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => element (METHOD): blah()",
-                                "  => type (DECLARED return type): "
-                                    + "java.util.Map<java.util.Set<?>,%1$s>",
-                                "  => type (ERROR type argument): %1$s"),
-                            isJavac ? "<any>" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt",
+            "package test",
+            "",
+            "abstract class TestClass {",
+            "  abstract fun blah(): Map<Set<*>, MissingType<*>>",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => element (METHOD): blah()",
+                          "  => type (DECLARED return type): "
+                              + "java.util.Map<java.util.Set<?>,%1$s>",
+                          "  => type (ERROR type argument): %1$s"),
+                      isJavac ? "<any>" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void missingTypeParameter() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass", //
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
-            "class TestClass<T extends MissingType> {}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => element (TYPE_PARAMETER): T",
-                                "  => type (ERROR bound type): %s"),
-                            isJavac ? "MissingType" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "class TestClass<T extends MissingType> {}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt", //
+            "package test",
+            "",
+            "class TestClass<T : MissingType>"),
+        (processingEnv, superficialValidation) -> {
+          if (isKAPT(processingEnv)) {
+            // TODO(b/268536260): Figure out why XProcessing Testing infra fails when using KAPT.
+            return;
+          }
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => element (TYPE_PARAMETER): T",
+                          "  => type (ERROR bound type): %s"),
+                      isJavac ? "MissingType" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void missingParameterType() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "abstract class TestClass {",
             "  abstract void foo(MissingType param);",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => element (METHOD): foo(%1$s)",
-                                "  => element (PARAMETER): param",
-                                "  => type (ERROR parameter type): %1$s"),
-                            isJavac ? "MissingType" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt",
+            "package test",
+            "",
+            "abstract class TestClass {",
+            "  abstract fun foo(param: MissingType);",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => element (METHOD): foo(%1$s)",
+                          "  => element (PARAMETER): param",
+                          "  => type (ERROR parameter type): %1$s"),
+                      isJavac ? "MissingType" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void missingAnnotation() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass", //
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "@MissingAnnotation",
-            "class TestClass {}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => annotation: @MissingAnnotation",
-                                "  => type (ERROR annotation type): %s"),
-                            isJavac ? "MissingAnnotation" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "class TestClass {}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt", //
+            "package test",
+            "",
+            "@MissingAnnotation",
+            "class TestClass"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => annotation type: MissingAnnotation",
+                          "  => type (ERROR annotation type): %s"),
+                      isJavac ? "MissingAnnotation" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void handlesRecursiveTypeParams() {
-    Source javaFileObject =
+    runSuccessfulTest(
         CompilerTests.javaSource(
             "test.TestClass", //
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
-            "class TestClass<T extends Comparable<T>> {}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                superficialValidation.validateElement(testClassElement);
-              }
-            })
-        .compile(subject -> subject.hasErrorCount(0));
+            "class TestClass<T extends Comparable<T>> {}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt", //
+            "package test",
+            "",
+            "class TestClass<T : Comparable<T>>"),
+        (processingEnv, superficialValidation) ->
+            superficialValidation.validateElement(processingEnv.findTypeElement("test.TestClass")));
   }
 
   @Test
   public void handlesRecursiveType() {
-    Source javaFileObject =
+    runSuccessfulTest(
         CompilerTests.javaSource(
             "test.TestClass",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "abstract class TestClass {",
             "  abstract TestClass foo(TestClass x);",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                superficialValidation.validateElement(testClassElement);
-              }
-            })
-        .compile(subject -> subject.hasErrorCount(0));
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt",
+            "package test",
+            "",
+            "abstract class TestClass {",
+            "  abstract fun foo(x: TestClass): TestClass",
+            "}"),
+        (processingEnv, superficialValidation) ->
+            superficialValidation.validateElement(processingEnv.findTypeElement("test.TestClass")));
   }
 
   @Test
   public void missingWildcardBound() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass",
             "package test;",
             "",
             "import java.util.Set;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "class TestClass {",
-            "  Set<? extends MissingType> extendsTest() {",
+            "  static final class Foo<T> {}",
+            "",
+            "  Foo<? extends MissingType> extendsTest() {",
             "    return null;",
             "  }",
             "",
-            "  Set<? super MissingType> superTest() {",
+            "  Foo<? super MissingType> superTest() {",
             "    return null;",
             "  }",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => element (METHOD): extendsTest()",
-                                "  => type (DECLARED return type): java.util.Set<? extends %1$s>",
-                                "  => type (WILDCARD type argument): ? extends %1$s",
-                                "  => type (ERROR extends bound type): %1$s"),
-                            isJavac ? "MissingType" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt",
+            "package test",
+            "",
+            "class TestClass {",
+            "  class Foo<T>",
+            "",
+            "  fun extendsTest(): Foo<out MissingType> = TODO()",
+            "",
+            "  fun superTest(): Foo<in MissingType> = TODO()",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => element (METHOD): extendsTest()",
+                          "  => type (DECLARED return type): test.TestClass.Foo<? extends %1$s>",
+                          "  => type (WILDCARD type argument): ? extends %1$s",
+                          "  => type (ERROR extends bound type): %1$s"),
+                      isJavac ? "MissingType" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void missingIntersection() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.TestClass",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
-            "class TestClass<T extends Number & Missing> {}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.TestClass",
-                                "  => element (TYPE_PARAMETER): T",
-                                "  => type (ERROR bound type): %s"),
-                            isJavac ? "Missing" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "class TestClass<T extends Number & Missing> {}"),
+        CompilerTests.kotlinSource(
+            "test.TestClass.kt",
+            "package test",
+            "",
+            "class TestClass<T> where T: Number, T: Missing"),
+        (processingEnv, superficialValidation) -> {
+          if (isKAPT(processingEnv)) {
+            // TODO(b/268536260): Figure out why XProcessing Testing infra fails when using KAPT.
+            return;
+          }
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.TestClass");
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.TestClass",
+                          "  => element (TYPE_PARAMETER): T",
+                          "  => type (ERROR bound type): %s"),
+                      isJavac ? "Missing" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void invalidAnnotationValue() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.Outer",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "final class Outer {",
             "  @interface TestAnnotation {",
             "    Class[] classes();",
@@ -429,192 +439,252 @@
             "",
             "  @TestAnnotation(classes = MissingType.class)",
             "  static class TestClass {}",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement =
-                    processingEnv.findTypeElement("test.Outer.TestClass");
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(testClassElement));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.Outer.TestClass",
-                                "  => annotation: @test.Outer.TestAnnotation(classes={<%1$s>})",
-                                "  => annotation value (TYPE_ARRAY): classes={<%1$s>}",
-                                "  => annotation value (TYPE): classes=<%1$s>"),
-                            isJavac ? "error" : "Error"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.Outer.kt",
+            "package test",
+            "",
+            "class Outer {",
+            "  annotation class TestAnnotation(",
+            "    val classes: Array<kotlin.reflect.KClass<*>>",
+            "  )",
+            "",
+            "  @TestAnnotation(classes = [MissingType::class])",
+            "  class TestClass {}",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.Outer.TestClass");
+          if (processingEnv.getBackend() == XProcessingEnv.Backend.KSP
+              && sourceKind == SourceKind.KOTLIN) {
+            // TODO(b/269364338): When using kotlin source with KSP the MissingType annotation value
+            // appears to be missing so validating this element does not cause the expected failure.
+            superficialValidation.validateElement(testClassElement);
+            return;
+          }
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(testClassElement));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.Outer.TestClass",
+                          "  => annotation type: test.Outer.TestAnnotation",
+                          "  => annotation: @test.Outer.TestAnnotation(classes={<%1$s>})",
+                          "  => annotation value (TYPE_ARRAY): classes={<%1$s>}",
+                          "  => annotation value (TYPE): classes=<%1$s>"),
+                      isJavac ? "error" : "Error"));
+        });
   }
 
   @Test
   public void invalidAnnotationValueOnParameter() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.Outer",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "final class Outer {",
             "  @interface TestAnnotation {",
             "    Class[] classes();",
             "  }",
             "",
             "  static class TestClass {",
-            "    TestClass(@TestAnnotation(classes = Foo) String strParam) {}",
+            "    TestClass(@TestAnnotation(classes = MissingType.class) String strParam) {}",
             "  }",
-            "}");
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement testClassElement =
-                    processingEnv.findTypeElement("test.Outer.TestClass");
-                XConstructorElement constructor = testClassElement.getConstructors().get(0);
-                XVariableElement parameter = constructor.getParameters().get(0);
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () -> superficialValidation.validateElement(parameter));
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        NEW_LINES.join(
-                            "Validation trace:",
-                            "  => element (CLASS): test.Outer.TestClass",
-                            "  => element (CONSTRUCTOR): TestClass(java.lang.String)",
-                            "  => element (PARAMETER): strParam",
-                            "  => annotation: @test.Outer.TestAnnotation(classes={<error>})",
-                            "  => annotation value (TYPE_ARRAY): classes={<error>}",
-                            "  => annotation value (TYPE): classes=<error>"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.Outer.kt",
+            "package test",
+            "",
+            "class Outer {",
+            "  annotation class TestAnnotation(",
+            "    val classes: Array<kotlin.reflect.KClass<*>>",
+            "  )",
+            "",
+            "  class TestClass(",
+            "      @TestAnnotation(classes = [MissingType::class]) strParam: String",
+            "  )",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          if (sourceKind == SourceKind.KOTLIN) {
+            // TODO(b/268536260): Figure out why XProcessing Testing infra fails when using KAPT.
+            // TODO(b/269364338): When using kotlin source the MissingType annotation value appears
+            // to be missing so validating this element does not cause the expected failure.
+            return;
+          }
+          XTypeElement testClassElement = processingEnv.findTypeElement("test.Outer.TestClass");
+          XConstructorElement constructor = testClassElement.getConstructors().get(0);
+          XVariableElement parameter = constructor.getParameters().get(0);
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () -> superficialValidation.validateElement(parameter));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.Outer.TestClass",
+                          "  => element (CONSTRUCTOR): TestClass(java.lang.String)",
+                          "  => element (PARAMETER): strParam",
+                          "  => annotation type: test.Outer.TestAnnotation",
+                          "  => annotation: @test.Outer.TestAnnotation(classes={<%1$s>})",
+                          "  => annotation value (TYPE_ARRAY): classes={<%1$s>}",
+                          "  => annotation value (TYPE): classes=<%1$s>"),
+                      isJavac ? "error" : "Error"));
+        });
   }
 
   @Test
   public void invalidSuperclassInTypeHierarchy() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.Outer",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "final class Outer {",
             "  Child<Long> getChild() { return null; }",
-            "",
             "  static class Child<T> extends Parent<T> {}",
-            "",
             "  static class Parent<T> extends MissingType<T> {}",
-            "}");
-
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement outerElement = processingEnv.findTypeElement("test.Outer");
-                XMethodElement getChildMethod = outerElement.getDeclaredMethods().get(0);
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () ->
-                            superficialValidation.validateTypeHierarchyOf(
-                                "return type", getChildMethod, getChildMethod.getReturnType()));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.Outer",
-                                "  => element (METHOD): getChild()",
-                                "  => type (DECLARED return type): "
-                                    + "test.Outer.Child<java.lang.Long>",
-                                "  => type (DECLARED supertype): test.Outer.Parent<java.lang.Long>",
-                                "  => type (ERROR supertype): %s"),
-                            isJavac ? "MissingType<T>" : "error.NonExistentClass"));
-              }
-            })
-        .compile(subject -> subject.hasError());
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.Outer.kt",
+            "package test",
+            "",
+            "class Outer {",
+            "  fun getChild(): Child<Long> = TODO()",
+            "  class Child<T> : Parent<T>",
+            "  open class Parent<T> : MissingType<T>",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement outerElement = processingEnv.findTypeElement("test.Outer");
+          XMethodElement getChildMethod = outerElement.getDeclaredMethods().get(0);
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () ->
+                      superficialValidation.validateTypeHierarchyOf(
+                          "return type", getChildMethod, getChildMethod.getReturnType()));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.Outer",
+                          "  => element (METHOD): getChild()",
+                          "  => type (DECLARED return type): test.Outer.Child<java.lang.Long>",
+                          "  => type (DECLARED supertype): test.Outer.Parent<java.lang.Long>",
+                          "  => type (ERROR supertype): %s"),
+                      isJavac ? "MissingType<T>" : "error.NonExistentClass"));
+        });
   }
 
   @Test
   public void invalidSuperclassTypeParameterInTypeHierarchy() {
-    Source javaFileObject =
+    runTest(
         CompilerTests.javaSource(
             "test.Outer",
             "package test;",
             "",
-            "@javax.inject.Singleton", // TODO(b/249322175): Used to trigger processing step
             "final class Outer {",
             "  Child getChild() { return null; }",
-            "",
             "  static class Child extends Parent<MissingType> {}",
-            "",
             "  static class Parent<T> {}",
-            "}");
+            "}"),
+        CompilerTests.kotlinSource(
+            "test.Outer.kt",
+            "package test",
+            "",
+            "class Outer {",
+            "  fun getChild(): Child = TODO()",
+            "  class Child : Parent<MissingType>()",
+            "  open class Parent<T>",
+            "}"),
+        (processingEnv, superficialValidation) -> {
+          XTypeElement outerElement = processingEnv.findTypeElement("test.Outer");
+          XMethodElement getChildMethod = outerElement.getDeclaredMethods().get(0);
+          if (isKAPT(processingEnv)) {
+            // https://youtrack.jetbrains.com/issue/KT-34193/Kapt-CorrectErrorTypes-doesnt-work-for-generics
+            // There's no way to work around this bug in KAPT so validation doesn't catch this case.
+            superficialValidation.validateTypeHierarchyOf(
+                "return type", getChildMethod, getChildMethod.getReturnType());
+            return;
+          }
+          ValidationException exception =
+              assertThrows(
+                  ValidationException.KnownErrorType.class,
+                  () ->
+                      superficialValidation.validateTypeHierarchyOf(
+                          "return type", getChildMethod, getChildMethod.getReturnType()));
+          // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
+          boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
+          assertThat(exception)
+              .hasMessageThat()
+              .contains(
+                  String.format(
+                      NEW_LINES.join(
+                          "Validation trace:",
+                          "  => element (CLASS): test.Outer",
+                          "  => element (METHOD): getChild()",
+                          "  => type (DECLARED return type): test.Outer.Child",
+                          "  => type (DECLARED supertype): test.Outer.Parent<%1$s>",
+                          "  => type (ERROR type argument): %1$s"),
+                      isJavac ? "MissingType" : "error.NonExistentClass"));
+        });
+  }
 
-    CompilerTests.daggerCompiler(javaFileObject)
-        .withProcessingSteps(
-            () -> new AssertingStep() {
-              @Override
-              void runAssertions(
-                  XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation) {
-                XTypeElement outerElement = processingEnv.findTypeElement("test.Outer");
-                XMethodElement getChildMethod = outerElement.getDeclaredMethods().get(0);
-                ValidationException exception =
-                    assertThrows(
-                        ValidationException.KnownErrorType.class,
-                        () ->
-                            superficialValidation.validateTypeHierarchyOf(
-                                "return type", getChildMethod, getChildMethod.getReturnType()));
-                // TODO(b/248552462): Javac and KSP should match once this bug is fixed.
-                boolean isJavac = processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC;
-                assertThat(exception)
-                    .hasMessageThat()
-                    .contains(
-                        String.format(
-                            NEW_LINES.join(
-                                "Validation trace:",
-                                "  => element (CLASS): test.Outer",
-                                "  => element (METHOD): getChild()",
-                                "  => type (DECLARED return type): test.Outer.Child",
-                                "  => type (DECLARED supertype): test.Outer.Parent<%1$s>",
-                                "  => type (ERROR type argument): %1$s"),
-                            isJavac ? "MissingType" : "error.NonExistentClass"));
-              }
-            })
+  private void runTest(
+      Source.JavaSource javaSource,
+      Source.KotlinSource kotlinSource,
+      AssertionHandler assertionHandler) {
+    CompilerTests.daggerCompiler(sourceKind == SourceKind.JAVA ? javaSource : kotlinSource)
+        .withProcessingSteps(() -> new AssertingStep(assertionHandler))
+        // We're expecting compiler errors that we assert on in the assertionHandler.
         .compile(subject -> subject.hasError());
   }
 
-  private abstract static class AssertingStep implements XProcessingStep {
+  private void runSuccessfulTest(
+      Source.JavaSource javaSource,
+      Source.KotlinSource kotlinSource,
+      AssertionHandler assertionHandler) {
+    CompilerTests.daggerCompiler(sourceKind == SourceKind.JAVA ? javaSource : kotlinSource)
+        .withProcessingSteps(() -> new AssertingStep(assertionHandler))
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  private boolean isKAPT(XProcessingEnv processingEnv) {
+    return processingEnv.getBackend() == XProcessingEnv.Backend.JAVAC
+        && sourceKind == SourceKind.KOTLIN;
+  }
+
+  private interface AssertionHandler {
+    void runAssertions(
+        XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation);
+  }
+
+  private static final class AssertingStep implements XProcessingStep {
+    private final AssertionHandler assertionHandler;
     private boolean processed = false;
 
+    AssertingStep(AssertionHandler assertionHandler) {
+      this.assertionHandler = assertionHandler;
+    }
+
     @Override
     public final ImmutableSet<String> annotations() {
-      // TODO(b/249322175): Replace this with "*" after this bug is fixed.
-      // For now, we just trigger off of annotations in the other sources in the test, but ideally
-      // this should support "*" similar to javac's Processor.
-      return ImmutableSet.of("javax.inject.Singleton");
+      return ImmutableSet.of("*");
     }
 
     @Override
@@ -624,11 +694,7 @@
         processed = true; // only process once.
         TestComponent component =
             DaggerDaggerSuperficialValidationTest_TestComponent.factory().create(env);
-        try {
-          runAssertions(env, component.superficialValidation());
-        } catch (Exception e) {
-          throw new RuntimeException(e);
-        }
+        assertionHandler.runAssertions(env, component.superficialValidation());
       }
       return ImmutableSet.of();
     }
@@ -636,10 +702,6 @@
     @Override
     public void processOver(
         XProcessingEnv env, Map<String, ? extends Set<? extends XElement>> elementsByAnnotation) {}
-
-    abstract void runAssertions(
-        XProcessingEnv processingEnv, DaggerSuperficialValidation superficialValidation)
-        throws Exception;
   }
 
   @Singleton
diff --git a/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java b/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java
index 07f8159..b45357c 100644
--- a/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java
+++ b/javatests/dagger/internal/codegen/DuplicateBindingsValidationTest.java
@@ -16,18 +16,14 @@
 
 package dagger.internal.codegen;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.internal.codegen.Compilers.daggerCompiler;
 import static dagger.internal.codegen.TestUtils.message;
 import static org.junit.Assume.assumeFalse;
 
+import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.util.Source;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
 import dagger.testing.compile.CompilerTests;
-import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -1227,11 +1223,10 @@
   }
 
   // Tests the format of the error for a somewhat complex binding method.
-  // TODO(b/241293838): Convert this test to use XProcessing Testing after fixing this bug.
   @Test
   public void formatTest() {
-    JavaFileObject modules =
-        JavaFileObjects.forSourceLines(
+    Source modules =
+        CompilerTests.javaSource(
             "test.Modules",
             "package test;",
             "",
@@ -1269,8 +1264,8 @@
             "    }",
             "  }",
             "}");
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -1283,17 +1278,23 @@
             "interface TestComponent {",
             "  @Modules.Foo(bar = String.class) String foo();",
             "}");
-    Compilation compilation = daggerCompiler().compile(modules, component);
-    assertThat(compilation).failed();
-    assertThat(compilation).hadErrorCount(1);
-    assertThat(compilation)
-        .hadErrorContaining(
-            message(
-                "String is bound multiple times:",
-                "    @Provides @Singleton @Modules.Foo(bar = String.class) String "
-                    + "Modules.Module1.foo(int, ImmutableList<Boolean>)",
-                "    @Provides @Singleton @Modules.Foo(bar = String.class) String "
-                    + "Modules.Module2.foo(int, ImmutableList<Boolean>)"))
-        .inFile(component);
+    CompilerTests.daggerCompiler(modules, component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  String.format(
+                      String.join(
+                          "\n",
+                          "String is bound multiple times:",
+                          "    @Provides @Singleton @Modules.Foo(%1$s) String "
+                              + "Modules.Module1.foo(int, ImmutableList<Boolean>)",
+                          "    @Provides @Singleton @Modules.Foo(%1$s) String "
+                              + "Modules.Module2.foo(int, ImmutableList<Boolean>)"),
+                      // TODO(b/241293838): KSP and java should match after this is fixed.
+                      CompilerTests.backend(subject) == XProcessingEnv.Backend.KSP
+                          ? "bar=String"
+                          : "bar = String.class"));
+            });
   }
 }
diff --git a/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java b/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java
index 3e7992b..1cd936e 100644
--- a/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java
+++ b/javatests/dagger/internal/codegen/FrameworkTypeMapperTest.java
@@ -17,11 +17,11 @@
 package dagger.internal.codegen;
 
 import static com.google.common.truth.Truth.assertThat;
-import static dagger.spi.model.RequestKind.INSTANCE;
-import static dagger.spi.model.RequestKind.LAZY;
-import static dagger.spi.model.RequestKind.PRODUCED;
-import static dagger.spi.model.RequestKind.PRODUCER;
-import static dagger.spi.model.RequestKind.PROVIDER;
+import static dagger.internal.codegen.model.RequestKind.INSTANCE;
+import static dagger.internal.codegen.model.RequestKind.LAZY;
+import static dagger.internal.codegen.model.RequestKind.PRODUCED;
+import static dagger.internal.codegen.model.RequestKind.PRODUCER;
+import static dagger.internal.codegen.model.RequestKind.PROVIDER;
 
 import dagger.internal.codegen.binding.FrameworkType;
 import dagger.internal.codegen.binding.FrameworkTypeMapper;
diff --git a/javatests/dagger/internal/codegen/IgnoreProvisionKeyWildcardsTest.java b/javatests/dagger/internal/codegen/IgnoreProvisionKeyWildcardsTest.java
new file mode 100644
index 0000000..008a0a8
--- /dev/null
+++ b/javatests/dagger/internal/codegen/IgnoreProvisionKeyWildcardsTest.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen;
+
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.util.CompilationResultSubject;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import dagger.testing.compile.CompilerTests;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IgnoreProvisionKeyWildcardsTest {
+  enum SourceKind { JAVA, KOTLIN }
+
+  @Parameters(name = "sourceKind={0}, isIgnoreProvisionKeyWildcardsEnabled={1}")
+  public static ImmutableList<Object[]> parameters() {
+    return ImmutableList.of(
+        new Object[] {SourceKind.JAVA, false},
+        new Object[] {SourceKind.KOTLIN, false},
+        new Object[] {SourceKind.JAVA, true},
+        new Object[] {SourceKind.KOTLIN, true}
+    );
+  }
+
+  private static final Joiner NEW_LINES = Joiner.on("\n");
+  private static final Joiner NEW_LINES_FOR_ERROR_MSG = Joiner.on("\n      ");
+
+  private final boolean isIgnoreProvisionKeyWildcardsEnabled;
+  private final SourceKind sourceKind;
+  private final ImmutableMap<String, String> processingOptions;
+
+  public IgnoreProvisionKeyWildcardsTest(
+      SourceKind sourceKind,
+      boolean isIgnoreProvisionKeyWildcardsEnabled) {
+    this.sourceKind = sourceKind;
+    this.isIgnoreProvisionKeyWildcardsEnabled = isIgnoreProvisionKeyWildcardsEnabled;
+    processingOptions =
+        isIgnoreProvisionKeyWildcardsEnabled
+            ? ImmutableMap.of("dagger.ignoreProvisionKeyWildcards", "enabled")
+            : ImmutableMap.of();
+  }
+
+  @Test
+  public void testProvidesUniqueBindingsWithDifferentTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Foo<? extends Bar> fooExtends();",
+            "  Foo<Bar> foo();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Provides static Foo<? extends Bar> fooExtends() { return null; }",
+            "  @Provides static Foo<Bar> foo() { return null; }",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun fooExtends(): Foo<out Bar>",
+            "  fun foo(): Foo<Bar>",
+            "}",
+            "@Module",
+            "object MyModule {",
+            "  @Provides fun fooExtends(): Foo<out Bar> = TODO()",
+            "  @Provides fun foo(): Foo<Bar> = TODO()",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                NEW_LINES_FOR_ERROR_MSG.join(
+                    "Foo<? extends Bar> is bound multiple times:",
+                    "        @Provides Foo<Bar> MyModule.foo()",
+                    "        @Provides Foo<? extends Bar> MyModule.fooExtends()",
+                    "    in component: [MyComponent]"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesUniqueBindingsWithMatchingWildcardArguments() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Map<Foo<? extends Bar>, Foo<? extends Bar>> mapFooExtendsBarFooExtendsBar();",
+            "  Map<Foo<? extends Bar>, Foo<Bar>> mapFooExtendsBarFooBar();",
+            "  Map<Foo<Bar>, Foo<? extends Bar>> mapFooBarFooExtendsBar();",
+            "  Map<Foo<Bar>, Foo<Bar>> mapFooBarFooBar();",
+            "}",
+            "@Module",
+            "class MyModule {",
+            "  @Provides",
+            "  Map<Foo<? extends Bar>, Foo<? extends Bar>> mapFooExtendsBarFooExtendsBar() {",
+            "    return null; ",
+            "  }",
+            "  @Provides",
+            "  Map<Foo<? extends Bar>, Foo<Bar>> mapFooExtendsBarFooBar() {",
+            "    return null;",
+            "  }",
+            "  @Provides",
+            "  Map<Foo<Bar>, Foo<? extends Bar>> mapFooBarFooExtendsBar() {",
+            "    return null;",
+            "  }",
+            "  @Provides",
+            "  Map<Foo<Bar>, Foo<Bar>> mapFooBarFooBar() {",
+            "    return null;",
+            "  }",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun mapFooExtendsBarFooExtendsBar(): Map<Foo<out Bar>, Foo<out Bar>>",
+            "  fun mapFooExtendsBarFooBar(): Map<Foo<out Bar>, Foo<Bar>>",
+            "  fun mapFooBarFooExtendsBar(): Map<Foo<Bar>, Foo<out Bar>>",
+            "  fun mapFooBarFooBar(): Map<Foo<Bar>, Foo<Bar>>",
+            "}",
+            "@Module",
+            "class MyModule {",
+            "  @Provides",
+            "  fun mapFooExtendsBarFooExtendsBar(): Map<Foo<out Bar>, Foo<out Bar>> = TODO()",
+            "  @Provides",
+            "  fun mapFooExtendsBarFooBar(): Map<Foo<out Bar>, Foo<Bar>> = TODO()",
+            "  @Provides",
+            "  fun mapFooBarFooExtendsBar(): Map<Foo<Bar>, Foo<out Bar>> = TODO()",
+            "  @Provides",
+            "  fun mapFooBarFooBar(): Map<Foo<Bar>, Foo<Bar>> = TODO()",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                NEW_LINES_FOR_ERROR_MSG.join(
+                    "Map<Foo<? extends Bar>,Foo<? extends Bar>> is bound multiple times:",
+                    "        @Provides Map<Foo<Bar>,Foo<Bar>> MyModule.mapFooBarFooBar()",
+                    "        @Provides Map<Foo<Bar>,Foo<? extends Bar>> "
+                        + "MyModule.mapFooBarFooExtendsBar()",
+                    "        @Provides Map<Foo<? extends Bar>,Foo<Bar>> "
+                        + "MyModule.mapFooExtendsBarFooBar()",
+                    "        @Provides Map<Foo<? extends Bar>,Foo<? extends Bar>> "
+                        + "MyModule.mapFooExtendsBarFooExtendsBar()",
+                    "    in component: [MyComponent]"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesMultibindsSetDeclarationsWithDifferentTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Set<Foo<? extends Bar>> setExtends();",
+            "  Set<Foo<Bar>> set();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Multibinds Set<Foo<? extends Bar>> setExtends();",
+            "  @Multibinds Set<Foo<Bar>> set();",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun setExtends(): Set<Foo<out Bar>>",
+            "  fun set(): Set<Foo<Bar>>",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Multibinds fun setExtends(): Set<Foo<out Bar>>",
+            "  @Multibinds fun set(): Set<Foo<Bar>>",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                NEW_LINES_FOR_ERROR_MSG.join(
+                    "Set<Foo<? extends Bar>> has incompatible bindings or declarations:",
+                    "    Set bindings and declarations:",
+                    "        @Multibinds Set<Foo<Bar>> MyModule.set()",
+                    "        @Multibinds Set<Foo<? extends Bar>> MyModule.setExtends()",
+                    "    in component: [MyComponent]"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesMultibindsSetContributionsWithDifferentTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Set<Foo<? extends Bar>> setExtends();",
+            "  Set<Foo<Bar>> set();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Provides @IntoSet static Foo<? extends Bar> setExtends() { return null; }",
+            "  @Provides @IntoSet static Foo<Bar> set() { return null; }",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun setExtends(): Set<Foo<out Bar>>",
+            "  fun set(): Set<Foo<Bar>>",
+            "}",
+            "@Module",
+            "object MyModule {",
+            "  @Provides @IntoSet fun setExtends(): Foo<out Bar> = TODO()",
+            "  @Provides @IntoSet fun set(): Foo<Bar> = TODO()",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                String.format(
+                    NEW_LINES_FOR_ERROR_MSG.join(
+                        "Set<Foo<? extends Bar>> has incompatible bindings or declarations:",
+                        "    Set bindings and declarations:",
+                        "        %1$s Foo<Bar> MyModule.set()",
+                        "        %1$s Foo<? extends Bar> MyModule.setExtends()",
+                        "    in component: [MyComponent]"),
+                    isKapt(subject) ? "@IntoSet @Provides" : "@Provides @IntoSet"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesMultibindsSetContributionAndMultibindsWithDifferentVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Set<Foo<? extends Bar>> setExtends();",
+            "  Set<Foo<Bar>> set();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Provides @IntoSet static Foo<? extends Bar> setExtends() { return null; }",
+            "  @Multibinds Set<Foo<Bar>> mulitbindSet();",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun setExtends(): Set<Foo<out Bar>>",
+            "  fun set(): Set<Foo<Bar>>",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Multibinds abstract fun mulitbindSet(): Set<Foo<Bar>>",
+            "",
+            "  companion object {",
+            "    @Provides @IntoSet fun setExtends(): Foo<out Bar> = TODO()",
+            "  }",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                String.format(
+                    NEW_LINES_FOR_ERROR_MSG.join(
+                        "Set<Foo<? extends Bar>> has incompatible bindings or declarations:",
+                        "    Set bindings and declarations:",
+                        "        @Multibinds Set<Foo<Bar>> MyModule.mulitbindSet()",
+                        "        %s Foo<? extends Bar> %s.setExtends()",
+                        "    in component: [MyComponent]"),
+                    isKapt(subject) ? "@IntoSet @Provides" : "@Provides @IntoSet",
+                    sourceKind == SourceKind.KOTLIN ? "MyModule.Companion" : "MyModule"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesIntoSetAndElementsIntoSetContributionsWithDifferentVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Set<Foo<? extends Bar>> setExtends();",
+            "  Set<Foo<Bar>> set();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Provides @IntoSet static Foo<? extends Bar> setExtends() { return null; }",
+            "",
+            "  @Provides",
+            "  @ElementsIntoSet",
+            "  static Set<Foo<Bar>> set() { return null; }",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun setExtends(): Set<Foo<out Bar>>",
+            "  fun set(): Set<Foo<Bar>>",
+            "}",
+            "@Module",
+            "object MyModule {",
+            "  @Provides @IntoSet fun setExtends(): Foo<out Bar> = TODO()",
+            "  @Provides @ElementsIntoSet fun set(): Set<Foo<Bar>> = TODO()",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                String.format(
+                    NEW_LINES_FOR_ERROR_MSG.join(
+                        "Set<Foo<? extends Bar>> has incompatible bindings or declarations:",
+                        "    Set bindings and declarations:",
+                        "        %s Set<Foo<Bar>> MyModule.set()",
+                        "        %s Foo<? extends Bar> MyModule.setExtends()",
+                        "    in component: [MyComponent]"),
+                    isKapt(subject) ? "@ElementsIntoSet @Provides" : "@Provides @ElementsIntoSet",
+                    isKapt(subject) ? "@IntoSet @Provides" : "@Provides @IntoSet"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesMultibindsSetContributionsWithSameTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Set<Foo<Bar>> set();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Provides @IntoSet static Foo<Bar> set1() { return null; }",
+            "  @Provides @IntoSet static Foo<Bar> set2() { return null; }",
+            "  @Provides @ElementsIntoSet static Set<Foo<Bar>> set3() { return null; }",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun set(): Set<Foo<Bar>>",
+            "}",
+            "@Module",
+            "object MyModule {",
+            "  @Provides @IntoSet fun set1(): Foo<Bar> = TODO()",
+            "  @Provides @IntoSet fun set2(): Foo<Bar> = TODO()",
+            "  @Provides @ElementsIntoSet fun set3(): Set<Foo<Bar>> = TODO()",
+            "}"),
+        subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
+  public void testProvidesMultibindsMapDeclarationValuesWithDifferentTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Map<String, Foo<? extends Bar>> mapExtends();",
+            "  Map<String, Foo<Bar>> map();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Multibinds Map<String, Foo<? extends Bar>> mapExtends();",
+            "  @Multibinds Map<String, Foo<Bar>> map();",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun mapExtends(): Map<String, Foo<out Bar>>",
+            "  fun map(): Map<String, Foo<Bar>>",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Multibinds fun mapExtends():Map<String, Foo<out Bar>>",
+            "  @Multibinds fun map(): Map<String, Foo<Bar>>",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                NEW_LINES_FOR_ERROR_MSG.join(
+                    "Map<String,Foo<? extends Bar>> has incompatible bindings or declarations:",
+                    "    Map bindings and declarations:",
+                    "        @Multibinds Map<String,Foo<Bar>> MyModule.map()",
+                    "        @Multibinds Map<String,Foo<? extends Bar>> MyModule.mapExtends()",
+                    "    in component: [MyComponent]"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesMultibindsMapDeclarationKeysWithDifferentTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Map<Foo<? extends Bar>, String> mapExtends();",
+            "  Map<Foo<Bar>, String> map();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Multibinds Map<Foo<? extends Bar>, String> mapExtends();",
+            "  @Multibinds Map<Foo<Bar>, String> map();",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun mapExtends(): Map<Foo<out Bar>, String>",
+            "  fun map(): Map<Foo<Bar>, String>",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Multibinds fun mapExtends():Map<Foo<out Bar>, String>",
+            "  @Multibinds fun map(): Map<Foo<Bar>, String>",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                NEW_LINES_FOR_ERROR_MSG.join(
+                    "Map<Foo<? extends Bar>,String> has incompatible bindings or declarations:",
+                    "    Map bindings and declarations:",
+                    "        @Multibinds Map<Foo<Bar>,String> MyModule.map()",
+                    "        @Multibinds Map<Foo<? extends Bar>,String> MyModule.mapExtends()",
+                    "    in component: [MyComponent]"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesMultibindsMapContributionsWithDifferentTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Map<String, Foo<? extends Bar>> mapExtends();",
+            "  Map<String, Foo<Bar>> map();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @Provides",
+            "  @IntoMap",
+            "  @StringKey(\"fooExtends\")",
+            "  static Foo<? extends Bar> fooExtends() { return null; }",
+            "",
+            "  @Provides",
+            "  @IntoMap",
+            "  @StringKey(\"foo\")",
+            "  static Foo<Bar> foo() { return null; }",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun mapExtends(): Map<String, Foo<out Bar>>",
+            "  fun map(): Map<String, Foo<Bar>>",
+            "}",
+            "@Module",
+            "object MyModule {",
+            "  @Provides",
+            "  @IntoMap",
+            "  @StringKey(\"fooExtends\")",
+            "  fun fooExtends(): Foo<out Bar> = TODO()",
+            "",
+            "  @Provides",
+            "  @IntoMap",
+            "  @StringKey(\"foo\")",
+            "  fun foo(): Foo<Bar> = TODO()",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorContaining(
+                String.format(
+                    NEW_LINES_FOR_ERROR_MSG.join(
+                        "Map<String,Foo<? extends Bar>> has incompatible bindings or declarations:",
+                        "    Map bindings and declarations:",
+                        "        %s Foo<Bar> MyModule.foo()",
+                        "        %s Foo<? extends Bar> MyModule.fooExtends()",
+                        "    in component: [MyComponent]"),
+                    isKapt(subject)
+                        ? "@StringKey(\"foo\") @IntoMap @Provides"
+                        : "@Provides @IntoMap @StringKey(\"foo\")",
+                    isKapt(subject)
+                        ? "@StringKey(\"fooExtends\") @IntoMap @Provides"
+                        : "@Provides @IntoMap @StringKey(\"fooExtends\")"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  @Test
+  public void testProvidesOptionalDeclarationWithDifferentTypeVariances() {
+    compile(
+        /* javaComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = MyModule.class)",
+            "interface MyComponent {",
+            "  Optional<Foo<? extends Bar>> fooExtends();",
+            "  Optional<Foo<Bar>> foo();",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @BindsOptionalOf Foo<? extends Bar> fooExtends();",
+            "  @BindsOptionalOf Foo<Bar> foo();",
+            "}"),
+        /* kotlinComponentClass = */
+        NEW_LINES.join(
+            "@Component(modules = [MyModule::class])",
+            "interface MyComponent {",
+            "  fun fooExtends(): Optional<Foo<out Bar>>",
+            "  fun foo(): Optional<Foo<Bar>>",
+            "}",
+            "@Module",
+            "interface MyModule {",
+            "  @BindsOptionalOf fun fooExtends(): Foo<out Bar>",
+            "  @BindsOptionalOf fun foo(): Foo<Bar>",
+            "}"),
+        subject -> {
+          if (isIgnoreProvisionKeyWildcardsEnabled) {
+            subject.hasErrorCount(1);
+            subject.hasErrorContaining(
+                NEW_LINES_FOR_ERROR_MSG.join(
+                    "Optional<Foo<? extends Bar>> is bound multiple times:",
+                    "    @BindsOptionalOf Foo<Bar> MyModule.foo()",
+                    "    @BindsOptionalOf Foo<? extends Bar> MyModule.fooExtends()",
+                    "in component: [MyComponent]"));
+          } else {
+            subject.hasErrorCount(0);
+          }
+        });
+  }
+
+  private void compile(
+      String javaComponentClass,
+      String kotlinComponentClass,
+      Consumer<CompilationResultSubject> onCompilationResult) {
+    if (sourceKind == SourceKind.JAVA) {
+      // Compile with Java sources
+      CompilerTests.daggerCompiler(
+              CompilerTests.javaSource(
+                  "test.MyComponent",
+                  "package test;",
+                  "",
+                  "import dagger.BindsOptionalOf;",
+                  "import dagger.Component;",
+                  "import dagger.Module;",
+                  "import dagger.Provides;",
+                  "import dagger.multibindings.ElementsIntoSet;",
+                  "import dagger.multibindings.IntoSet;",
+                  "import dagger.multibindings.IntoMap;",
+                  "import dagger.multibindings.Multibinds;",
+                  "import dagger.multibindings.StringKey;",
+                  "import java.util.Map;",
+                  "import java.util.Optional;",
+                  "import java.util.Set;",
+                  "import javax.inject.Inject;",
+                  "import javax.inject.Provider;",
+                  "",
+                  javaComponentClass,
+                  "",
+                  "interface Foo<T> {}",
+                  "",
+                  "class Bar {}"))
+          .withProcessingOptions(processingOptions)
+          .compile(onCompilationResult);
+    }
+
+    if (sourceKind == SourceKind.KOTLIN) {
+      // Compile with Kotlin sources
+      CompilerTests.daggerCompiler(
+              CompilerTests.kotlinSource(
+                  "test.MyComponent.kt",
+                  // TODO(bcorso): See if there's a better way to fix the following error.
+                  //
+                  //   Error: Cannot inline bytecode built with JVM target 11 into bytecode that is
+                  //          being built with JVM target 1.8
+                  "@file:Suppress(\"INLINE_FROM_HIGHER_PLATFORM\")",
+                  "package test",
+                  "",
+                  "import dagger.BindsOptionalOf",
+                  "import dagger.Component",
+                  "import dagger.Module",
+                  "import dagger.Provides",
+                  "import dagger.multibindings.ElementsIntoSet",
+                  "import dagger.multibindings.IntoSet",
+                  "import dagger.multibindings.IntoMap",
+                  "import dagger.multibindings.Multibinds",
+                  "import dagger.multibindings.StringKey",
+                  "import java.util.Optional;",
+                  "import javax.inject.Inject",
+                  "import javax.inject.Provider",
+                  "",
+                  kotlinComponentClass,
+                  "",
+                  "interface Foo<T>",
+                  "",
+                  "class Bar"))
+          .withProcessingOptions(processingOptions)
+          .compile(onCompilationResult);
+    }
+  }
+
+  private boolean isKapt(CompilationResultSubject subject) {
+    return sourceKind == SourceKind.KOTLIN
+        && CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC;
+  }
+}
diff --git a/javatests/dagger/internal/codegen/KeyFactoryTest.java b/javatests/dagger/internal/codegen/KeyFactoryTest.java
index 829e2b5..f11f375 100644
--- a/javatests/dagger/internal/codegen/KeyFactoryTest.java
+++ b/javatests/dagger/internal/codegen/KeyFactoryTest.java
@@ -23,9 +23,7 @@
 import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import androidx.room.compiler.processing.XAnnotation;
 import androidx.room.compiler.processing.XConstructorElement;
-import androidx.room.compiler.processing.XFieldElement;
 import androidx.room.compiler.processing.XMethodElement;
 import androidx.room.compiler.processing.XProcessingEnv;
 import androidx.room.compiler.processing.XType;
@@ -37,15 +35,11 @@
 import dagger.Provides;
 import dagger.internal.codegen.binding.KeyFactory;
 import dagger.internal.codegen.javac.JavacPluginModule;
+import dagger.internal.codegen.model.Key;
 import dagger.multibindings.ElementsIntoSet;
 import dagger.multibindings.IntoSet;
 import dagger.producers.ProducerModule;
 import dagger.producers.Produces;
-import dagger.spi.model.DaggerAnnotation;
-import dagger.spi.model.DaggerExecutableElement;
-import dagger.spi.model.DaggerType;
-import dagger.spi.model.DaggerTypeElement;
-import dagger.spi.model.Key;
 import java.lang.annotation.Retention;
 import java.util.Set;
 import javax.inject.Inject;
@@ -83,7 +77,6 @@
     Key key =
         keyFactory.forInjectConstructorWithResolvedType(
             constructor.getEnclosingElement().getType());
-    assertThat(key).isEqualTo(Key.builder(DaggerType.from(typeElement.getType())).build());
     assertThat(key.toString()).isEqualTo("dagger.internal.codegen.KeyFactoryTest.InjectedClass");
   }
 
@@ -94,12 +87,10 @@
 
   @Test
   public void forProvidesMethod() {
-    XType stringType = processingEnv.requireType(String.class.getCanonicalName());
     XTypeElement moduleElement =
         processingEnv.requireTypeElement(ProvidesMethodModule.class.getCanonicalName());
     XMethodElement providesMethod = getOnlyElement(moduleElement.getDeclaredMethods());
     Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement);
-    assertThat(key).isEqualTo(Key.builder(DaggerType.from(stringType)).build());
     assertThat(key.toString()).isEqualTo("java.lang.String");
   }
 
@@ -139,17 +130,7 @@
         processingEnv.requireTypeElement(QualifiedProvidesMethodModule.class.getCanonicalName());
     XMethodElement providesMethod = getOnlyElement(moduleElement.getDeclaredMethods());
     Key provisionKey = keyFactory.forProvidesMethod(providesMethod, moduleElement);
-
-    XType type = processingEnv.requireType(String.class.getCanonicalName());
-    XTypeElement injectableElement =
-        processingEnv.requireTypeElement(QualifiedFieldHolder.class.getCanonicalName());
-    XFieldElement injectionField = getOnlyElement(injectableElement.getDeclaredFields());
-    XAnnotation qualifier = getOnlyElement(injectionField.getAllAnnotations());
-    Key injectionKey =
-        Key.builder(DaggerType.from(type)).qualifier(DaggerAnnotation.from(qualifier)).build();
-
-    assertThat(provisionKey).isEqualTo(injectionKey);
-    assertThat(injectionKey.toString())
+    assertThat(provisionKey.toString())
         .isEqualTo(
             "@dagger.internal.codegen.KeyFactoryTest.TestQualifier({"
                 + "@dagger.internal.codegen.KeyFactoryTest.InnerAnnotation("
@@ -199,20 +180,10 @@
 
   @Test
   public void forProvidesMethod_sets() {
-    XTypeElement setElement = processingEnv.requireTypeElement(Set.class.getCanonicalName());
-    XType stringType = processingEnv.requireType(String.class.getCanonicalName());
-    XType setOfStringsType = processingEnv.getDeclaredType(setElement, stringType);
     XTypeElement moduleElement =
         processingEnv.requireTypeElement(SetProvidesMethodsModule.class.getCanonicalName());
     for (XMethodElement providesMethod : moduleElement.getDeclaredMethods()) {
       Key key = keyFactory.forProvidesMethod(providesMethod, moduleElement);
-      assertThat(key)
-          .isEqualTo(
-              Key.builder(DaggerType.from(setOfStringsType))
-                  .multibindingContributionIdentifier(
-                      DaggerTypeElement.from(moduleElement),
-                      DaggerExecutableElement.from(providesMethod))
-                  .build());
       assertThat(key.toString())
           .isEqualTo(
               String.format(
@@ -269,12 +240,10 @@
   }
 
   @Test public void forProducesMethod() {
-    XType stringType = processingEnv.requireType(String.class.getCanonicalName());
     XTypeElement moduleElement =
         processingEnv.requireTypeElement(ProducesMethodsModule.class.getCanonicalName());
     for (XMethodElement producesMethod : moduleElement.getDeclaredMethods()) {
       Key key = keyFactory.forProducesMethod(producesMethod, moduleElement);
-      assertThat(key).isEqualTo(Key.builder(DaggerType.from(stringType)).build());
       assertThat(key.toString()).isEqualTo("java.lang.String");
     }
   }
@@ -291,20 +260,10 @@
   }
 
   @Test public void forProducesMethod_sets() {
-    XTypeElement setElement = processingEnv.requireTypeElement(Set.class.getCanonicalName());
-    XType stringType = processingEnv.requireType(String.class.getCanonicalName());
-    XType setOfStringsType = processingEnv.getDeclaredType(setElement, stringType);
     XTypeElement moduleElement =
         processingEnv.requireTypeElement(SetProducesMethodsModule.class.getCanonicalName());
     for (XMethodElement producesMethod : moduleElement.getDeclaredMethods()) {
       Key key = keyFactory.forProducesMethod(producesMethod, moduleElement);
-      assertThat(key)
-          .isEqualTo(
-              Key.builder(DaggerType.from(setOfStringsType))
-                  .multibindingContributionIdentifier(
-                      DaggerTypeElement.from(moduleElement),
-                      DaggerExecutableElement.from(producesMethod))
-                  .build());
       assertThat(key.toString())
           .isEqualTo(
               String.format(
diff --git a/javatests/dagger/internal/codegen/MapRequestRepresentationWithGuavaTest.java b/javatests/dagger/internal/codegen/MapRequestRepresentationWithGuavaTest.java
index d9a2469..0219e59 100644
--- a/javatests/dagger/internal/codegen/MapRequestRepresentationWithGuavaTest.java
+++ b/javatests/dagger/internal/codegen/MapRequestRepresentationWithGuavaTest.java
@@ -16,16 +16,10 @@
 
 package dagger.internal.codegen;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.internal.codegen.Compilers.compilerWithOptions;
-
 import androidx.room.compiler.processing.util.Source;
 import com.google.common.collect.ImmutableList;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
 import dagger.testing.compile.CompilerTests;
 import dagger.testing.golden.GoldenFileRule;
-import javax.tools.JavaFileObject;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,8 +43,8 @@
 
   @Test
   public void mapBindings() throws Exception {
-    JavaFileObject mapModuleFile =
-        JavaFileObjects.forSourceLines(
+    Source mapModuleFile =
+        CompilerTests.javaSource(
             "test.MapModule",
             "package test;",
             "",
@@ -70,8 +64,8 @@
             "  @Provides @IntoMap @LongKey(1) static long provideLong1() { return 1; }",
             "  @Provides @IntoMap @LongKey(2) static long provideLong2() { return 2; }",
             "}");
-    JavaFileObject subcomponentModuleFile =
-        JavaFileObjects.forSourceLines(
+    Source subcomponentModuleFile =
+        CompilerTests.javaSource(
             "test.SubcomponentMapModule",
             "package test;",
             "",
@@ -89,8 +83,8 @@
             "  @Provides @IntoMap @LongKey(4) static long provideLong4() { return 4; }",
             "  @Provides @IntoMap @LongKey(5) static long provideLong5() { return 5; }",
             "}");
-    JavaFileObject componentFile =
-        JavaFileObjects.forSourceLines(
+    Source componentFile =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -110,8 +104,8 @@
             "",
             "  Sub sub();",
             "}");
-    JavaFileObject subcomponent =
-        JavaFileObjects.forSourceLines(
+    Source subcomponent =
+        CompilerTests.javaSource(
             "test.Sub",
             "package test;",
             "",
@@ -124,14 +118,13 @@
             "  Map<Long, Long> longs();",
             "  Map<Long, Provider<Long>> providerLongs();",
             "}");
-
-    Compilation compilation =
-        compilerWithOptions(compilerMode.javacopts())
-            .compile(mapModuleFile, componentFile, subcomponentModuleFile, subcomponent);
-    assertThat(compilation).succeeded();
-    assertThat(compilation)
-        .generatedSourceFile("test.DaggerTestComponent")
-        .hasSourceEquivalentTo(goldenFileRule.goldenFile("test.DaggerTestComponent"));
+    CompilerTests.daggerCompiler(mapModuleFile, componentFile, subcomponentModuleFile, subcomponent)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(0);
+              subject.generatedSource(goldenFileRule.goldenSource("test/DaggerTestComponent"));
+            });
   }
 
   @Test
diff --git a/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java b/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java
index 79c0dbf..db9b61c 100644
--- a/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java
+++ b/javatests/dagger/internal/codegen/MembersInjectionValidationTest.java
@@ -16,14 +16,8 @@
 
 package dagger.internal.codegen;
 
-import static com.google.testing.compile.CompilationSubject.assertThat;
-import static dagger.internal.codegen.Compilers.daggerCompiler;
-
 import androidx.room.compiler.processing.util.Source;
-import com.google.testing.compile.Compilation;
-import com.google.testing.compile.JavaFileObjects;
 import dagger.testing.compile.CompilerTests;
-import javax.tools.JavaFileObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -61,11 +55,10 @@
             });
   }
 
-  // TODO(b/246320661): Use XProcessing testing once this bug is fixed.
   @Test
   public void membersInjectPrimitive() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -75,12 +68,14 @@
             "interface TestComponent {",
             "  void inject(int primitive);",
             "}");
-    Compilation compilation = daggerCompiler().compile(component);
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Cannot inject members into int")
-        .inFile(component)
-        .onLineContaining("void inject(int primitive);");
+    CompilerTests.daggerCompiler(component)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining("Cannot inject members into int")
+                  .onSource(component)
+                  .onLineContaining("void inject(int primitive);");
+            });
   }
 
   @Test
@@ -263,11 +258,10 @@
             });
   }
 
-  // TODO(b/245934092): Update this test after this bug is fixed on XProcessing side.
   @Test
   public void missingMembersInjectorForKotlinProperty() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -278,8 +272,8 @@
             "interface TestComponent {",
             "  void inject(KotlinInjectedQualifier injected);",
             "}");
-    JavaFileObject module =
-        JavaFileObjects.forSourceLines(
+    Source module =
+        CompilerTests.javaSource(
             "test.TestModule",
             "package test;",
             "",
@@ -293,17 +287,28 @@
             "  @Named(\"TheString\")",
             "  String theString() { return \"\"; }",
             "}");
-    Compilation compilation = daggerCompiler().compile(component, module);
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Unable to read annotations on an injected Kotlin property.");
+    CompilerTests.daggerCompiler(component, module)
+        .compile(
+            subject -> {
+              switch (CompilerTests.backend(subject)) {
+                case KSP:
+                  // KSP works fine in this case so we shouldn't expect any errors here.
+                  subject.hasErrorCount(0);
+                  break;
+                case JAVAC:
+                  subject.hasErrorCount(2);
+                  subject.hasErrorContaining(
+                      "Unable to read annotations on an injected Kotlin property.");
+                  subject.hasErrorContaining("KotlinInjectedQualifier cannot be provided");
+                  break;
+              }
+            });
   }
 
-  // TODO(b/245934092): Update this test after this bug is fixed on XProcessing side.
   @Test
   public void memberInjectionForKotlinObjectFails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -314,10 +319,22 @@
             "interface TestComponent {",
             "  void inject(KotlinObjectWithMemberInjection injected);",
             "}");
-    Compilation compilation = daggerCompiler().compile(component, testModule.toJFO());
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Dagger does not support injection into Kotlin objects");
+    CompilerTests.daggerCompiler(component, testModule)
+        .compile(
+            subject -> {
+              switch (CompilerTests.backend(subject)) {
+                case KSP:
+                  subject.hasErrorCount(2);
+                  break;
+                case JAVAC:
+                  subject.hasErrorCount(3);
+                  subject.hasErrorContaining(
+                      "Dagger does not support injection into static fields");
+                  break;
+              }
+              subject.hasErrorContaining("Dagger does not support injection into Kotlin objects");
+              subject.hasErrorContaining("KotlinObjectWithMemberInjection cannot be provided");
+            });
   }
 
   @Test
@@ -345,11 +362,10 @@
             });
   }
 
-  // TODO(b/245934092): Update this test after this bug is fixed on XProcessing side.
   @Test
   public void memberInjectionForKotlinClassWithCompanionObjectFails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -361,17 +377,29 @@
             "  void inject(KotlinClassWithMemberInjectedCompanion injected);",
             "  void injectCompanion(KotlinClassWithMemberInjectedCompanion.Companion injected);",
             "}");
-    Compilation compilation = daggerCompiler().compile(component, testModule.toJFO());
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Dagger does not support injection into static fields");
+    CompilerTests.daggerCompiler(component, testModule)
+        .compile(
+            subject -> {
+              switch (CompilerTests.backend(subject)) {
+                case KSP:
+                  subject.hasErrorCount(4);
+                  subject.hasErrorContaining(
+                      "Dagger does not support injection into Kotlin objects");
+                  break;
+                case JAVAC:
+                  subject.hasErrorCount(2);
+                  break;
+              }
+              subject.hasErrorContaining("Dagger does not support injection into static fields");
+              subject.hasErrorContaining(
+                  "KotlinClassWithMemberInjectedCompanion cannot be provided");
+            });
   }
 
-  // TODO(b/245792321): Update this test once this bug is fixed.
   @Test
   public void setterMemberInjectionForKotlinClassWithCompanionObjectFails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -382,17 +410,30 @@
             "interface TestComponent {",
             "  void inject(KotlinClassWithSetterMemberInjectedCompanion.Companion injected);",
             "}");
-    Compilation compilation = daggerCompiler().compile(component, testModule.toJFO());
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Dagger does not support injection into Kotlin objects");
+    CompilerTests.daggerCompiler(component, testModule)
+        .compile(
+            subject -> {
+              switch (CompilerTests.backend(subject)) {
+                case KSP:
+                  // TODO(b/268257007): The KSP results should match KAPT once this bug is fixed.
+                  subject.hasErrorCount(3);
+                  subject.hasErrorContaining(
+                      "Dagger does not support injection into static methods");
+                  break;
+                case JAVAC:
+                  subject.hasErrorCount(2);
+                  break;
+              }
+              subject.hasErrorContaining("Dagger does not support injection into Kotlin objects");
+              subject.hasErrorContaining(
+                  "KotlinClassWithSetterMemberInjectedCompanion.Companion cannot be provided");
+            });
   }
 
-  // TODO(b/245934092): Update this test after this bug is fixed on XProcessing side.
   @Test
   public void memberInjectionForKotlinClassWithNamedCompanionObjectFails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -405,17 +446,29 @@
             "  void injectCompanion(KotlinClassWithMemberInjectedNamedCompanion.TheCompanion"
                 + " injected);",
             "}");
-    Compilation compilation = daggerCompiler().compile(component, testModule.toJFO());
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Dagger does not support injection into static fields");
+    CompilerTests.daggerCompiler(component, testModule)
+        .compile(
+            subject -> {
+              switch (CompilerTests.backend(subject)) {
+                case KSP:
+                  subject.hasErrorCount(4);
+                  subject.hasErrorContaining(
+                      "Dagger does not support injection into Kotlin objects");
+                  break;
+                case JAVAC:
+                  subject.hasErrorCount(2);
+                  break;
+              }
+              subject.hasErrorContaining("Dagger does not support injection into static fields");
+              subject.hasErrorContaining(
+                  "KotlinClassWithMemberInjectedNamedCompanion cannot be provided");
+            });
   }
 
-  // TODO(b/245792321): Update this test once this bug is fixed.
   @Test
   public void setterMemberInjectionForKotlinClassWithNamedCompanionObjectFails() {
-    JavaFileObject component =
-        JavaFileObjects.forSourceLines(
+    Source component =
+        CompilerTests.javaSource(
             "test.TestComponent",
             "package test;",
             "",
@@ -427,10 +480,25 @@
             "  void inject(",
             "      KotlinClassWithSetterMemberInjectedNamedCompanion.TheCompanion injected);",
             "}");
-    Compilation compilation = daggerCompiler().compile(component, testModule.toJFO());
-    assertThat(compilation).failed();
-    assertThat(compilation)
-        .hadErrorContaining("Dagger does not support injection into Kotlin objects");
+    CompilerTests.daggerCompiler(component, testModule)
+        .compile(
+            subject -> {
+              switch (CompilerTests.backend(subject)) {
+                case KSP:
+                  // TODO(b/268257007): The KSP results should match KAPT once this bug is fixed.
+                  subject.hasErrorCount(3);
+                  subject.hasErrorContaining(
+                      "Dagger does not support injection into static methods");
+                  break;
+                case JAVAC:
+                  subject.hasErrorCount(2);
+                  break;
+              }
+              subject.hasErrorContaining("Dagger does not support injection into Kotlin objects");
+              subject.hasErrorContaining(
+                  "KotlinClassWithSetterMemberInjectedNamedCompanion.TheCompanion "
+                      + "cannot be provided");
+            });
   }
 
   private final Source testModule =
diff --git a/javatests/dagger/internal/codegen/MissingBindingValidationTest.java b/javatests/dagger/internal/codegen/MissingBindingValidationTest.java
index b25ac9d..919700e 100644
--- a/javatests/dagger/internal/codegen/MissingBindingValidationTest.java
+++ b/javatests/dagger/internal/codegen/MissingBindingValidationTest.java
@@ -16,9 +16,14 @@
 
 package dagger.internal.codegen;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.room.compiler.processing.util.DiagnosticMessage;
 import androidx.room.compiler.processing.util.Source;
 import com.google.common.collect.ImmutableList;
 import dagger.testing.compile.CompilerTests;
+import java.util.List;
+import javax.tools.Diagnostic;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -1298,4 +1303,493 @@
                   "[foo.Sub] foo.Sub.getObject() [Parent → Child1 → foo.Sub]");
             });
   }
+
+  @Test
+  public void requestWildcardTypeWithNonWildcardTypeBindingProvided_failsWithMissingBinding() {
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "  Child getChild();",
+            "}");
+    Source child =
+        CompilerTests.javaSource(
+            "test.Child",
+            "package test;",
+            "",
+            "import dagger.Subcomponent;",
+            "",
+            "@Subcomponent(modules = ChildModule.class)",
+            "interface Child {}");
+    Source childModule =
+        CompilerTests.javaSource(
+            "test.ChildModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import java.util.Set;",
+            "import java.util.HashSet;",
+            "",
+            "@Module",
+            "interface ChildModule {",
+            "  @Provides",
+            "  static Set<? extends Bar> provideBar() {",
+            "    return new HashSet<Bar>();",
+            "  }",
+            "}");
+    Source fooSrc =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "",
+            "import javax.inject.Inject;",
+            "import java.util.Set;",
+            "",
+            "class Foo {",
+            "  @Inject Foo(Set<? extends Bar> bar) {}",
+            "}");
+    Source barSrc =
+        CompilerTests.javaSource("test.Bar", "package test;", "", "public interface Bar {}");
+    Source moduleSrc =
+        CompilerTests.javaSource(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import dagger.multibindings.ElementsIntoSet;",
+            "import java.util.Set;",
+            "import java.util.HashSet;",
+            "",
+            "@Module",
+            "public class TestModule {",
+            "   @ElementsIntoSet",
+            "   @Provides",
+            "   Set<Bar> provideBars() {",
+            "     return new HashSet<Bar>();",
+            "   }",
+            "}");
+
+    CompilerTests.daggerCompiler(component, child, childModule, fooSrc, barSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("Found similar bindings:")
+                  .onSource(component)
+                  .onLineContaining("interface MyComponent");
+              subject.hasErrorContaining("Set<Bar> in [MyComponent");
+              subject.hasErrorContaining("Set<? extends Bar> in [MyComponent → Child]");
+            });
+  }
+
+  @Test
+  public void
+      injectParameterDoesNotSuppressWildcardGeneration_conflictsWithNonWildcardTypeBinding() {
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "}");
+    Source fooSrc =
+        CompilerTests.kotlinSource(
+            "Foo.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class Foo @Inject constructor(val bar: Set<Bar>) {}");
+    Source barSrc =
+        CompilerTests.javaSource("test.Bar", "package test;", "", "public interface Bar {}");
+    Source moduleSrc =
+        CompilerTests.javaSource(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import dagger.multibindings.ElementsIntoSet;",
+            "import java.util.Set;",
+            "import java.util.HashSet;",
+            "",
+            "@Module",
+            "public class TestModule {",
+            "   @ElementsIntoSet",
+            "   @Provides",
+            "   Set<Bar> provideBars() {",
+            "     return new HashSet<Bar>();",
+            "   }",
+            "}");
+
+    CompilerTests.daggerCompiler(component, fooSrc, barSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("Set<Bar> in [MyComponent]")
+                  .onSource(component)
+                  .onLineContaining("interface MyComponent");
+            });
+  }
+
+  @Test
+  public void injectWildcardTypeWithNonWildcardTypeBindingProvided_failsWithMissingBinding() {
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "}");
+    Source fooSrc =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "",
+            "import javax.inject.Inject;",
+            "import java.util.Set;",
+            "",
+            "class Foo {",
+            "  @Inject Set<? extends Bar> bar;",
+            "  @Inject Foo() {}",
+            "}");
+    Source barSrc =
+        CompilerTests.javaSource("test.Bar", "package test;", "", "public interface Bar {}");
+    Source moduleSrc =
+        CompilerTests.javaSource(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import dagger.multibindings.ElementsIntoSet;",
+            "import java.util.Set;",
+            "import java.util.HashSet;",
+            "",
+            "@Module",
+            "public class TestModule {",
+            "   @ElementsIntoSet",
+            "   @Provides",
+            "   Set<Bar> provideBars() {",
+            "     return new HashSet<Bar>();",
+            "   }",
+            "}");
+
+    CompilerTests.daggerCompiler(component, fooSrc, barSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("Found similar bindings:")
+                  .onSource(component)
+                  .onLineContaining("interface MyComponent");
+              subject.hasErrorContaining("Set<Bar> in [MyComponent]");
+            });
+  }
+
+  @Test
+  public void requestFinalClassWithWildcardAnnotation_missingWildcardTypeBinding() {
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "}");
+    Source fooSrc =
+        CompilerTests.kotlinSource(
+            "test.Foo.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class Foo @Inject constructor(val bar: List<Bar>) {}");
+    Source barSrc =
+        CompilerTests.javaSource("test.Bar", "package test;", "", "public final class Bar {}");
+    Source moduleSrc =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "import dagger.multibindings.ElementsIntoSet",
+            "",
+            "@Module",
+            "object TestModule {",
+            "   @Provides fun provideBars(): List<@JvmWildcard Bar> = setOf()",
+            "}");
+
+    CompilerTests.daggerCompiler(component, fooSrc, barSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("Found similar bindings:")
+                  .onSource(component)
+                  .onLineContaining("interface MyComponent");
+              subject.hasErrorContaining("List<? extends Bar> in [MyComponent]");
+            });
+  }
+
+  @Test
+  public void multipleTypeParameters_notSuppressWildcardType_failsWithMissingBinding() {
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "}");
+    Source fooSrc =
+        CompilerTests.kotlinSource(
+            "test.Foo.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class Foo @Inject constructor(val bar: Bar<Baz, Baz, Set<Baz>>) {}");
+    Source barSrc =
+        CompilerTests.kotlinSource(
+            "test.Bar.kt", "package test", "", "class Bar<out T1, T2, T3> {}");
+
+    Source bazSrc =
+        CompilerTests.javaSource("test.Baz", "package test;", "", "public interface Baz {}");
+
+    Source moduleSrc =
+        CompilerTests.javaSource(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import java.util.Set;",
+            "",
+            "@Module",
+            "public class TestModule {",
+            "   @Provides",
+            "   Bar<Baz, Baz, Set<Baz>> provideBar() {",
+            "     return new Bar<Baz, Baz, Set<Baz>>();",
+            "   }",
+            "}");
+
+    CompilerTests.daggerCompiler(component, fooSrc, barSrc, bazSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("Bar<Baz,Baz,Set<Baz>> in [MyComponent]")
+                  .onSource(component)
+                  .onLineContaining("interface MyComponent");
+            });
+  }
+
+  @Test
+  public void missingBindingWithoutQualifier_warnAboutSimilarTypeWithQualifierExists() {
+    Source qualifierSrc =
+        CompilerTests.javaSource(
+            "test.MyQualifier",
+            "package test;",
+            "",
+            "import javax.inject.Qualifier;",
+            "",
+            "@Qualifier",
+            "@interface MyQualifier {}");
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "}");
+    Source fooSrc =
+        CompilerTests.kotlinSource(
+            "Foo.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class Foo @Inject constructor(val bar: Set<Bar>) {}");
+    Source barSrc =
+        CompilerTests.javaSource("test.Bar", "package test;", "", "public interface Bar {}");
+    Source moduleSrc =
+        CompilerTests.javaSource(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import dagger.multibindings.ElementsIntoSet;",
+            "import java.util.Set;",
+            "import java.util.HashSet;",
+            "",
+            "@Module",
+            "public class TestModule {",
+            "   @ElementsIntoSet",
+            "   @Provides",
+            "   @MyQualifier",
+            "   Set<Bar> provideBars() {",
+            "     return new HashSet<Bar>();",
+            "   }",
+            "}");
+
+    CompilerTests.daggerCompiler(qualifierSrc, component, fooSrc, barSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining("MissingBinding");
+              List<DiagnosticMessage> diagnostics =
+                  subject.getCompilationResult().getDiagnostics().get(Diagnostic.Kind.ERROR);
+              assertThat(diagnostics).hasSize(1);
+              assertThat(diagnostics.get(0).getMsg())
+                  .doesNotContain("bindings with similar types exists in the graph");
+            });
+  }
+
+  @Test
+  public void missingWildcardTypeWithObjectBound_providedRawType_warnAboutSimilarTypeExists() {
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "}");
+    Source fooSrc =
+        CompilerTests.kotlinSource(
+            "test.Foo.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class Foo @Inject constructor(val bar: Bar<Object>) {}");
+    Source barSrc =
+        CompilerTests.kotlinSource("test.Bar.kt", "package test", "", "class Bar<out T1> {}");
+    Source moduleSrc =
+        CompilerTests.javaSource(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import java.util.Set;",
+            "",
+            "@Module",
+            "public class TestModule {",
+            "   @Provides",
+            "   Bar provideBar() {",
+            "     return new Bar<Object>();",
+            "   }",
+            "}");
+
+    CompilerTests.daggerCompiler(component, fooSrc, barSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining("Found similar bindings:");
+              subject.hasErrorContaining("Bar in [MyComponent]");
+            });
+  }
+
+  @Test
+  public void missingWildcardType_providedRawType_warnAboutSimilarTypeExists() {
+    Source component =
+        CompilerTests.javaSource(
+            "test.MyComponent",
+            "package test;",
+            "",
+            "import dagger.Component;",
+            "import java.util.Set;",
+            "",
+            "@Component(modules = TestModule.class)",
+            "interface MyComponent {",
+            "  Foo getFoo();",
+            "}");
+    Source fooSrc =
+        CompilerTests.kotlinSource(
+            "test.Foo.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class Foo @Inject constructor(val bar: Bar<String>) {}");
+    Source barSrc =
+        CompilerTests.kotlinSource("test.Bar.kt", "package test", "", "class Bar<out T1> {}");
+    Source moduleSrc =
+        CompilerTests.javaSource(
+            "test.TestModule",
+            "package test;",
+            "",
+            "import dagger.Module;",
+            "import dagger.Provides;",
+            "import java.util.Set;",
+            "",
+            "@Module",
+            "public class TestModule {",
+            "   @Provides",
+            "   Bar provideBar() {",
+            "     return new Bar<Object>();",
+            "   }",
+            "}");
+
+    CompilerTests.daggerCompiler(component, fooSrc, barSrc, moduleSrc)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining("MissingBinding");
+              List<DiagnosticMessage> diagnostics =
+                  subject.getCompilationResult().getDiagnostics().get(Diagnostic.Kind.ERROR);
+              assertThat(diagnostics).hasSize(1);
+              assertThat(diagnostics.get(0).getMsg())
+                  .doesNotContain("bindings with similar types exists in the graph");
+            });
+  }
 }
diff --git a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java
index 814652a..cc8e3b4 100644
--- a/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java
+++ b/javatests/dagger/internal/codegen/ModuleFactoryGeneratorTest.java
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2014 The Dagger Authors.
  *
@@ -557,6 +556,31 @@
             });
   }
 
+  @Test
+  public void privateModule_kotlin() {
+    Source moduleFile =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "private class TestModule {",
+            "  @Provides fun provideInt(): Int = 1",
+            "}");
+
+    CompilerTests.daggerCompiler(moduleFile)
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject
+                  .hasErrorContaining("Modules cannot be private")
+                  .onSource(moduleFile);
+            });
+  }
 
   @Test
   public void enclosedInPrivateModule() {
diff --git a/javatests/dagger/internal/codegen/XExecutableTypesTest.java b/javatests/dagger/internal/codegen/XExecutableTypesTest.java
new file mode 100644
index 0000000..faca16b
--- /dev/null
+++ b/javatests/dagger/internal/codegen/XExecutableTypesTest.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XTypeElement;
+import androidx.room.compiler.processing.util.Source;
+import dagger.internal.codegen.xprocessing.XExecutableTypes;
+import dagger.testing.compile.CompilerTests;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class XExecutableTypesTest {
+
+  @Test
+  public void subsignatureMethodNamesAreIgnored() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  void p(String s) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  void q(String s) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isTrue();
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isTrue();
+            });
+  }
+
+  @Test
+  public void subsignatureReturnTypesAreIgnored() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  List m(Collection c) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  Set m(Collection c) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isTrue();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isTrue();
+            });
+  }
+
+  @Test
+  public void subsignatureStaticIsIgnored() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  void m(Collection i) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  static void m(Collection i) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isTrue();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isTrue();
+            });
+  }
+
+  @Test
+  public void subsignatureWithAndWithoutTypeArguments() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  <T> void m(Collection<T> i) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  void m(Collection i) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isTrue();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isFalse();
+            });
+  }
+
+  @Test
+  public void subsignatureDifferentNumberOfTypeArguments() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  <T, Q> void m(Collection i) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  <T> void m(Collection i) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isFalse();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isFalse();
+            });
+  }
+
+  @Test
+  public void subsignatureDifferentTypeArgumentBounds() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  <T extends Foo> void m(Collection<T> i) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  <T> void m(Collection<T> i) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isFalse();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isFalse();
+            });
+  }
+
+  @Test
+  public void subsignatureWithGenericClasses() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  void m(Collection i) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  void m(Collection<String> i) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isFalse();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isTrue();
+            });
+  }
+
+  @Test
+  public void subsignatureSameSignature() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  <T> List<T> toList(Collection<T> c) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar extends Foo {",
+            "  <T> List<T> toList(Collection<T> c) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isTrue();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isTrue();
+            });
+  }
+
+  @Test
+  public void subsignatureSameSignatureUnrelatedClasses() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  <T> List<T> toList(Collection<T> c) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  <T> List<T> toList(Collection<T> c) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isTrue();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isTrue();
+            });
+  }
+
+  @Test
+  public void subsignatureWildcards() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  void toList(Collection<Object> c) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  void toList(Collection<? extends Foo> c) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isFalse();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isFalse();
+            });
+  }
+
+  @Test
+  public void subsignatureBounded() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  void toList(Collection<Foo> c) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  <T extends Foo> void toList(Collection<T> c) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isFalse();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isFalse();
+            });
+  }
+
+  @Test
+  public void subsignatureGenericMethodAndWildcard() {
+    Source foo =
+        CompilerTests.javaSource(
+            "test.Foo",
+            "package test;",
+            "import java.util.*;",
+            "class Foo {",
+            "  <T> List<T> toList(Collection<T> c) { throw new RuntimeException(); }",
+            "}");
+    Source bar =
+        CompilerTests.javaSource(
+            "test.Bar",
+            "package test;",
+            "import java.util.*;",
+            "class Bar {",
+            "  <T extends Foo> List<T> toList(Collection<T> c) { throw new RuntimeException(); }",
+            "}");
+    CompilerTests.invocationCompiler(foo, bar)
+        .compile(
+            invocation -> {
+              XTypeElement fooType = invocation.getProcessingEnv().requireTypeElement("test.Foo");
+              XMethodElement m1 = fooType.getDeclaredMethods().get(0);
+
+              XTypeElement barType = invocation.getProcessingEnv().requireTypeElement("test.Bar");
+              XMethodElement m2 = barType.getDeclaredMethods().get(0);
+
+              assertThat(XExecutableTypes.isSubsignature(m2, m1)).isFalse();
+              assertThat(XExecutableTypes.isSubsignature(m1, m2)).isFalse();
+            });
+  }
+}
diff --git a/javatests/dagger/internal/codegen/XTypesStripTypeNameTest.java b/javatests/dagger/internal/codegen/XTypesStripTypeNameTest.java
new file mode 100644
index 0000000..3a3da57
--- /dev/null
+++ b/javatests/dagger/internal/codegen/XTypesStripTypeNameTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen;
+
+import static androidx.room.compiler.processing.util.ProcessorTestExtKt.runProcessorTest;
+import static com.google.common.truth.Truth.assertThat;
+import static dagger.internal.codegen.extension.DaggerCollectors.onlyElement;
+import static dagger.internal.codegen.xprocessing.XTypes.stripVariances;
+
+import androidx.room.compiler.processing.XMethodElement;
+import androidx.room.compiler.processing.XProcessingEnvConfig;
+import androidx.room.compiler.processing.XTypeElement;
+import androidx.room.compiler.processing.util.Source;
+import androidx.room.compiler.processing.util.XTestInvocation;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.squareup.javapoet.TypeName;
+import dagger.testing.compile.CompilerTests;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class XTypesStripTypeNameTest {
+  @Test
+  public void fooExtendsBar() {
+    assertStrippedWildcardTypeNameEquals(
+        /* source = */
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  Foo<? extends Bar<? extends Baz>> method();",
+            "",
+            "  interface Foo<T> {}",
+            "  interface Bar<T> {}",
+            "  interface Baz {}",
+            "}"),
+        /* strippedTypeName = */ "Foo<Bar<Baz>>");
+  }
+
+  @Test
+  public void fooSuperBar() {
+    assertStrippedWildcardTypeNameEquals(
+        /* source = */
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  Foo<? super Bar<? super Baz>> method();",
+            "",
+            "  interface Foo<T> {}",
+            "  interface Bar<T> {}",
+            "  interface Baz {}",
+            "}"),
+        /* strippedTypeName = */ "Foo<Bar<Baz>>");
+  }
+
+  @Test
+  public void multipleParameters() {
+    assertStrippedWildcardTypeNameEquals(
+        /* source = */
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  Foo<Bar<? extends Baz>, Bar<? super Baz>> method();",
+            "",
+            "  interface Foo<T1, T2> {}",
+            "  interface Bar<T> {}",
+            "  interface Baz {}",
+            "}"),
+        /* strippedTypeName = */ "Foo<Bar<Baz>, Bar<Baz>>");
+  }
+
+  @Test
+  public void multipleParametersSameArgument() {
+    assertStrippedWildcardTypeNameEquals(
+        /* source = */
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  Foo<Bar<? extends Baz>, Bar<? extends Baz>> method();",
+            "",
+            "  interface Foo<T1, T2> {}",
+            "  interface Bar<T> {}",
+            "  interface Baz {}",
+            "}"),
+        /* strippedTypeName = */ "Foo<Bar<Baz>, Bar<Baz>>");
+  }
+
+  @Test
+  public void multipleParametersCrossReferencing() {
+    assertStrippedWildcardTypeNameEquals(
+        /* source = */
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  Foo<Bar<? extends Baz>, Bar<? extends Bar<? extends Baz>>> method();",
+            "",
+            "  interface Foo<T1, T2 extends Bar<? extends T1>> {}",
+            "  interface Bar<T> {}",
+            "  interface Baz {}",
+            "}"),
+        /* strippedTypeName = */ "Foo<Bar<Baz>, Bar<Bar<Baz>>>");
+  }
+
+  @Test
+  public void selfReferencing() {
+    assertStrippedWildcardTypeNameEquals(
+        /* source = */
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  <T extends Foo<T>> Foo<T> method();",
+            "",
+            "  interface Foo<T extends Foo<T>> {}",
+            "}"),
+        /* strippedTypeName = */ "Foo<T>");
+  }
+
+  @Test
+  public void arrayType() {
+    assertStrippedWildcardTypeNameEquals(
+        /* source = */
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  Foo<? extends Bar<? extends Baz>>[] method();",
+            "",
+            "  interface Foo<T> {}",
+            "  interface Bar<T> {}",
+            "  interface Baz {}",
+            "}"),
+        /* strippedTypeName = */ "Foo<Bar<Baz>>[]");
+  }
+
+  @Test
+  public void typeVariableSameVariableName() {
+    runTest(
+        CompilerTests.javaSource(
+            "Subject",
+            "interface Subject {",
+            "  <T extends Bar> Foo<T> method1();",
+            "  <T extends Baz> Foo<T> method2();",
+            "",
+            "  interface Foo<T> {}",
+            "  interface Bar {}",
+            "  interface Baz {}",
+            "}"),
+        invocation -> {
+          XTypeElement subject = invocation.getProcessingEnv().requireTypeElement("Subject");
+          TypeName method1ReturnTypeName =
+              getDeclaredMethod(subject, "method1").getReturnType().getTypeName();
+          TypeName method2ReturnTypeName =
+              getDeclaredMethod(subject, "method2").getReturnType().getTypeName();
+          assertThat(method1ReturnTypeName).isEqualTo(method2ReturnTypeName);
+          return null;
+        });
+  }
+
+  private static void assertStrippedWildcardTypeNameEquals(Source source, String strippedTypeName) {
+    runTest(
+        source,
+        invocation -> {
+          XTypeElement subject = invocation.getProcessingEnv().requireTypeElement("Subject");
+          TypeName returnTypeName =
+              getDeclaredMethod(subject, "method").getReturnType().getTypeName();
+          assertThat(stripVariances(returnTypeName).toString().replace("Subject.", ""))
+              .isEqualTo(strippedTypeName);
+          return null;
+        });
+  }
+
+  private static void runTest(Source source, Function<XTestInvocation, Void> handler) {
+    runProcessorTest(
+        ImmutableList.of(source),
+        /* classpath = */ ImmutableList.of(),
+        /* options = */ ImmutableMap.of(),
+        /* javacArguments = */ ImmutableList.of(),
+        /* kotlincArguments = */ ImmutableList.of(),
+        /* config = */ new XProcessingEnvConfig.Builder().build(),
+        /* handler = */ invocation -> {
+          handler.apply(invocation);
+          return null;
+        });
+  }
+
+  private static XMethodElement getDeclaredMethod(XTypeElement typeElement, String jvmName) {
+    return typeElement.getDeclaredMethods().stream()
+        .filter(method -> method.getJvmName().contentEquals(jvmName))
+        .collect(onlyElement());
+  }
+}
diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD
index 1ed0425..ce7197a 100644
--- a/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD
+++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/BUILD
@@ -27,6 +27,7 @@
     javacopts = DOCLINT_HTML_AND_SYNTAX,
     deps = [
         "//java/dagger/internal/codegen/bindinggraphvalidation",
+        "//java/dagger/internal/codegen/xprocessing",
         "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
         "//java/dagger/testing/compile",
         "//javatests/dagger/internal/codegen:compilers",
diff --git a/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationKotlinTest.java b/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationKotlinTest.java
new file mode 100644
index 0000000..6b63b7c
--- /dev/null
+++ b/javatests/dagger/internal/codegen/bindinggraphvalidation/NullableBindingValidationKotlinTest.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2023 The Dagger Authors.
+ *
+ * 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 dagger.internal.codegen.bindinggraphvalidation;
+
+import static dagger.internal.codegen.bindinggraphvalidation.NullableBindingValidator.nullableToNonNullable;
+
+import androidx.room.compiler.processing.XProcessingEnv;
+import androidx.room.compiler.processing.util.Source;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import dagger.internal.codegen.CompilerMode;
+import dagger.testing.compile.CompilerTests;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NullableBindingValidationKotlinTest {
+  @Parameters(name = "{0}")
+  public static ImmutableList<Object[]> parameters() {
+    return CompilerMode.TEST_PARAMETERS;
+  }
+
+  private final CompilerMode compilerMode;
+
+  public NullableBindingValidationKotlinTest(CompilerMode compilerMode) {
+    this.compilerMode = compilerMode;
+  }
+
+  @Test
+  public void nullCheckForConstructorParameters() {
+    Source a =
+        CompilerTests.kotlinSource(
+            "test.A.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class A @Inject constructor(string: String)");
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "class TestModule {",
+            "  @Provides fun provideString(): String? = null",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun a(): A",
+            "}");
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  nullableToNonNullable(
+                      "String",
+                      CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
+                          ? "@Nullable @Provides String TestModule.provideString()"
+                          // TODO(b/268550160): For KSP, we should be including the kotlin typename
+                          // in the error message, e.g. "String?" otherwise the message doesn't make
+                          // a lot of sense.
+                          : "@Provides String TestModule.provideString()"));
+            });
+
+    // but if we disable the validation, then it compiles fine.
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(
+            ImmutableMap.<String, String>builder()
+                .putAll(compilerMode.processorOptions())
+                .put("dagger.nullableValidation", "WARNING")
+                .buildOrThrow())
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
+  public void nullCheckForMembersInjectParam() {
+    Source a =
+        CompilerTests.kotlinSource(
+            "test.A.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class A @Inject constructor() {",
+            "  @Inject fun register(string: String) {}",
+            "}");
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "class TestModule {",
+            "  @Provides fun provideString(): String? = null",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun a(): A",
+            "}");
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  nullableToNonNullable(
+                      "String",
+                      CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
+                          ? "@Nullable @Provides String TestModule.provideString()"
+                          // TODO(b/268550160): For KSP, we should be including the kotlin typename
+                          // in the error message, e.g. "String?" otherwise the message doesn't make
+                          // a lot of sense.
+                          : "@Provides String TestModule.provideString()"));
+            });
+
+    // but if we disable the validation, then it compiles fine.
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(
+            ImmutableMap.<String, String>builder()
+                .putAll(compilerMode.processorOptions())
+                .put("dagger.nullableValidation", "WARNING")
+                .buildOrThrow())
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
+  public void nullCheckForVariable() {
+    Source a =
+        CompilerTests.kotlinSource(
+            "test.A.kt",
+            "package test",
+            "",
+            "import javax.inject.Inject",
+            "",
+            "class A @Inject constructor() {",
+            "  @Inject lateinit var string: String",
+            "}");
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "class TestModule {",
+            "  @Provides fun provideString():String? = null",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun a(): A",
+            "}");
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  nullableToNonNullable(
+                      "String",
+                      CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
+                          ? "@Nullable @Provides String TestModule.provideString()"
+                          // TODO(b/268550160): For KSP, we should be including the kotlin typename
+                          // in the error message, e.g. "String?" otherwise the message doesn't make
+                          // a lot of sense.
+                          : "@Provides String TestModule.provideString()"));
+            });
+
+    // but if we disable the validation, then it compiles fine.
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(
+            ImmutableMap.<String, String>builder()
+                .putAll(compilerMode.processorOptions())
+                .put("dagger.nullableValidation", "WARNING")
+                .buildOrThrow())
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test public void nullCheckForComponentReturn() {
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "class TestModule {",
+            "  @Provides fun provideString():String? = null",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun string(): String",
+            "}");
+    CompilerTests.daggerCompiler(module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  nullableToNonNullable(
+                      "String",
+                      CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
+                          ? "@Nullable @Provides String TestModule.provideString()"
+                          // TODO(b/268550160): For KSP, we should be including the kotlin typename
+                          // in the error message, e.g. "String?" otherwise the message doesn't make
+                          // a lot of sense.
+                          : "@Provides String TestModule.provideString()"));
+            });
+
+    // but if we disable the validation, then it compiles fine.
+    CompilerTests.daggerCompiler(module, component)
+        .withProcessingOptions(
+            ImmutableMap.<String, String>builder()
+                .putAll(compilerMode.processorOptions())
+                .put("dagger.nullableValidation", "WARNING")
+                .buildOrThrow())
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
+  public void nullCheckForOptionalInstance() {
+    Source a =
+        CompilerTests.kotlinSource(
+            "test.A.kt",
+            "package test",
+            "",
+            "import com.google.common.base.Optional",
+            "import javax.inject.Inject",
+            "",
+            "class A @Inject constructor(optional: Optional<String>)");
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.BindsOptionalOf",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "abstract class TestModule {",
+            "  @BindsOptionalOf abstract fun optionalString(): String",
+            "",
+            "  companion object {",
+            "    @Provides fun provideString():String? = null",
+            "  }",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun a(): A",
+            "}");
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  nullableToNonNullable(
+                      "String",
+                      CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
+                          ? "@Nullable @Provides String TestModule.Companion.provideString()"
+                          // TODO(b/268550160): For KSP, we should be including the kotlin typename
+                          // in the error message, e.g. "String?" otherwise the message doesn't make
+                          // a lot of sense.
+                          : "@Provides String TestModule.Companion.provideString()"));
+            });
+  }
+
+  @Test
+  public void nullCheckForOptionalProvider() {
+    Source a =
+        CompilerTests.kotlinSource(
+            "test.A.kt",
+            "package test",
+            "",
+            "import com.google.common.base.Optional",
+            "import javax.inject.Inject",
+            "import javax.inject.Provider",
+            "",
+            "class A @Inject constructor(optional: Optional<Provider<String>>)");
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.BindsOptionalOf",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "abstract class TestModule {",
+            "  @BindsOptionalOf abstract fun optionalString(): String",
+            "",
+            "  companion object {",
+            "    @Provides fun provideString():String? = null",
+            "  }",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun a(): A",
+            "}");
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
+  public void nullCheckForOptionalLazy() {
+    Source a =
+        CompilerTests.kotlinSource(
+            "test.A.kt",
+            "package test",
+            "",
+            "import com.google.common.base.Optional",
+            "import dagger.Lazy",
+            "import javax.inject.Inject",
+            "",
+            "class A @Inject constructor(optional: Optional<Lazy<String>>)");
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.BindsOptionalOf",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "abstract class TestModule {",
+            "  @BindsOptionalOf abstract fun optionalString(): String",
+            "",
+            "  companion object {",
+            "    @Provides fun provideString():String? = null",
+            "  }",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun a(): A",
+            "}");
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
+  public void nullCheckForOptionalProviderOfLazy() {
+    Source a =
+        CompilerTests.kotlinSource(
+            "test.A.kt",
+            "package test",
+            "",
+            "import com.google.common.base.Optional",
+            "import dagger.Lazy",
+            "import javax.inject.Inject",
+            "import javax.inject.Provider",
+            "",
+            "class A @Inject constructor(optional: Optional<Provider<Lazy<String>>>)");
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.BindsOptionalOf",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "abstract class TestModule {",
+            "  @BindsOptionalOf abstract fun optionalString(): String",
+            "",
+            "  companion object {",
+            "    @Provides fun provideString():String? = null",
+            "  }",
+            "}");
+    Source component =
+        CompilerTests.kotlinSource(
+            "test.TestComponent.kt",
+            "package test",
+            "",
+            "import dagger.Component",
+            "",
+            "@Component(modules = [TestModule::class])",
+            "interface TestComponent {",
+            "  fun a(): A",
+            "}");
+    CompilerTests.daggerCompiler(a, module, component)
+        .withProcessingOptions(compilerMode.processorOptions())
+        .compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
+  public void moduleValidation() {
+    Source module =
+        CompilerTests.kotlinSource(
+            "test.TestModule.kt",
+            "package test",
+            "",
+            "import dagger.Binds",
+            "import dagger.Module",
+            "import dagger.Provides",
+            "",
+            "@Module",
+            "abstract class TestModule {",
+            "  @Binds abstract fun bindObject(string: String): Object",
+            "",
+            "  companion object {",
+            "    @Provides fun nullableString():String? = null",
+            "  }",
+            "}");
+    CompilerTests.daggerCompiler(module)
+        .withProcessingOptions(
+            ImmutableMap.<String, String>builder()
+                .putAll(compilerMode.processorOptions())
+                .put("dagger.fullBindingGraphValidation", "ERROR")
+                .buildOrThrow())
+        .compile(
+            subject -> {
+              subject.hasErrorCount(1);
+              subject.hasErrorContaining(
+                  nullableToNonNullable(
+                      "String",
+                      CompilerTests.backend(subject) == XProcessingEnv.Backend.JAVAC
+                          ? "@Nullable @Provides String TestModule.Companion.nullableString()"
+                          // TODO(b/268550160): For KSP, we should be including the kotlin typename
+                          // in the error message, e.g. "String?" otherwise the message doesn't make
+                          // a lot of sense.
+                          : "@Provides String TestModule.Companion.nullableString()"));
+            });
+  }
+}
diff --git a/javatests/dagger/internal/codegen/goldens/ComponentProcessorTest_membersInjectionInsideProvision_DEFAULT_MODE_test.DaggerSimpleComponent b/javatests/dagger/internal/codegen/goldens/ComponentProcessorTest_membersInjectionInsideProvision_DEFAULT_MODE_test.DaggerSimpleComponent
deleted file mode 100644
index e4fa976..0000000
--- a/javatests/dagger/internal/codegen/goldens/ComponentProcessorTest_membersInjectionInsideProvision_DEFAULT_MODE_test.DaggerSimpleComponent
+++ /dev/null
@@ -1,59 +0,0 @@
-package test;
-
-import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import dagger.internal.DaggerGenerated;
-import javax.annotation.processing.Generated;
-
-@DaggerGenerated
-@Generated(
-    value = "dagger.internal.codegen.ComponentProcessor",
-    comments = "https://dagger.dev"
-)
-@SuppressWarnings({
-    "unchecked",
-    "rawtypes",
-    "KotlinInternal",
-    "KotlinInternalInJava"
-})
-final class DaggerSimpleComponent {
-  private DaggerSimpleComponent() {
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static SimpleComponent create() {
-    return new Builder().build();
-  }
-
-  static final class Builder {
-    private Builder() {
-    }
-
-    public SimpleComponent build() {
-      return new SimpleComponentImpl();
-    }
-  }
-
-  private static final class SimpleComponentImpl implements SimpleComponent {
-    private final SimpleComponentImpl simpleComponentImpl = this;
-
-    private SimpleComponentImpl() {
-
-
-    }
-
-    @Override
-    public SomeInjectedType createAndInject() {
-      return injectSomeInjectedType(SomeInjectedType_Factory.newInstance());
-    }
-
-    @CanIgnoreReturnValue
-    private SomeInjectedType injectSomeInjectedType(SomeInjectedType instance) {
-      SomeInjectedType_MembersInjector.injectInjectedField(instance, new SomeInjectableType());
-      return instance;
-    }
-  }
-}
-
diff --git a/javatests/dagger/internal/codegen/goldens/ComponentProcessorTest_membersInjectionInsideProvision_FAST_INIT_MODE_test.DaggerSimpleComponent b/javatests/dagger/internal/codegen/goldens/ComponentProcessorTest_membersInjectionInsideProvision_FAST_INIT_MODE_test.DaggerSimpleComponent
deleted file mode 100644
index e4fa976..0000000
--- a/javatests/dagger/internal/codegen/goldens/ComponentProcessorTest_membersInjectionInsideProvision_FAST_INIT_MODE_test.DaggerSimpleComponent
+++ /dev/null
@@ -1,59 +0,0 @@
-package test;
-
-import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import dagger.internal.DaggerGenerated;
-import javax.annotation.processing.Generated;
-
-@DaggerGenerated
-@Generated(
-    value = "dagger.internal.codegen.ComponentProcessor",
-    comments = "https://dagger.dev"
-)
-@SuppressWarnings({
-    "unchecked",
-    "rawtypes",
-    "KotlinInternal",
-    "KotlinInternalInJava"
-})
-final class DaggerSimpleComponent {
-  private DaggerSimpleComponent() {
-  }
-
-  public static Builder builder() {
-    return new Builder();
-  }
-
-  public static SimpleComponent create() {
-    return new Builder().build();
-  }
-
-  static final class Builder {
-    private Builder() {
-    }
-
-    public SimpleComponent build() {
-      return new SimpleComponentImpl();
-    }
-  }
-
-  private static final class SimpleComponentImpl implements SimpleComponent {
-    private final SimpleComponentImpl simpleComponentImpl = this;
-
-    private SimpleComponentImpl() {
-
-
-    }
-
-    @Override
-    public SomeInjectedType createAndInject() {
-      return injectSomeInjectedType(SomeInjectedType_Factory.newInstance());
-    }
-
-    @CanIgnoreReturnValue
-    private SomeInjectedType injectSomeInjectedType(SomeInjectedType instance) {
-      SomeInjectedType_MembersInjector.injectInjectedField(instance, new SomeInjectableType());
-      return instance;
-    }
-  }
-}
-
diff --git a/javatests/dagger/internal/codegen/goldens/ComponentShardTest_testNewShardCreated_FAST_INIT_MODE_dagger.internal.codegen.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/ComponentShardTest_testNewShardCreated_FAST_INIT_MODE_dagger.internal.codegen.DaggerTestComponent
index 06c68ee..81056e2 100644
--- a/javatests/dagger/internal/codegen/goldens/ComponentShardTest_testNewShardCreated_FAST_INIT_MODE_dagger.internal.codegen.DaggerTestComponent
+++ b/javatests/dagger/internal/codegen/goldens/ComponentShardTest_testNewShardCreated_FAST_INIT_MODE_dagger.internal.codegen.DaggerTestComponent
@@ -252,4 +252,5 @@
       }
     }
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_DEFAULT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_DEFAULT_MODE_test.DaggerTestComponent
index 3a29729..9c25066 100644
--- a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_DEFAULT_MODE_test.DaggerTestComponent
+++ b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_DEFAULT_MODE_test.DaggerTestComponent
@@ -71,4 +71,5 @@
       return OtherEntryPoint_Factory.newInstance(fooImpl());
     }
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_FAST_INIT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_FAST_INIT_MODE_test.DaggerTestComponent
index fd92c6d..a18b8a4 100644
--- a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_FAST_INIT_MODE_test.DaggerTestComponent
+++ b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoopScoped_FAST_INIT_MODE_test.DaggerTestComponent
@@ -93,4 +93,5 @@
       }
     }
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_DEFAULT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_DEFAULT_MODE_test.DaggerTestComponent
index 8e03211..c1345ad 100644
--- a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_DEFAULT_MODE_test.DaggerTestComponent
+++ b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_DEFAULT_MODE_test.DaggerTestComponent
@@ -67,4 +67,5 @@
       return OtherEntryPoint_Factory.newInstance(fooImpl());
     }
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_FAST_INIT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_FAST_INIT_MODE_test.DaggerTestComponent
index 137cbcf..8e2e959 100644
--- a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_FAST_INIT_MODE_test.DaggerTestComponent
+++ b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_inaccessibleTypeBoundInALoop_FAST_INIT_MODE_test.DaggerTestComponent
@@ -89,4 +89,5 @@
       }
     }
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_DEFAULT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_DEFAULT_MODE_test.DaggerTestComponent
index 19d8352..73dda53 100644
--- a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_DEFAULT_MODE_test.DaggerTestComponent
+++ b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_DEFAULT_MODE_test.DaggerTestComponent
@@ -59,4 +59,5 @@
       return bindProvider.get();
     }
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_FAST_INIT_MODE_test.DaggerTestComponent b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_FAST_INIT_MODE_test.DaggerTestComponent
index 922d293..84ae12f 100644
--- a/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_FAST_INIT_MODE_test.DaggerTestComponent
+++ b/javatests/dagger/internal/codegen/goldens/InaccessibleTypeBindsTest_scopedInaccessibleTypeBound_FAST_INIT_MODE_test.DaggerTestComponent
@@ -85,4 +85,5 @@
       }
     }
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/InjectConstructorFactoryGeneratorTest_noDeps_test.SimpleType_Factory b/javatests/dagger/internal/codegen/goldens/InjectConstructorFactoryGeneratorTest_noDeps_test.SimpleType_Factory
index 8804127..67f856c 100644
--- a/javatests/dagger/internal/codegen/goldens/InjectConstructorFactoryGeneratorTest_noDeps_test.SimpleType_Factory
+++ b/javatests/dagger/internal/codegen/goldens/InjectConstructorFactoryGeneratorTest_noDeps_test.SimpleType_Factory
@@ -36,4 +36,5 @@
   private static final class InstanceHolder {
     private static final SimpleType_Factory INSTANCE = new SimpleType_Factory();
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/MembersInjectionTest_componentWithNestingAndGeneratedType_FAST_INIT_MODE_test.OuterType_B_MembersInjector b/javatests/dagger/internal/codegen/goldens/MembersInjectionTest_componentWithNestingAndGeneratedType_FAST_INIT_MODE_test.OuterType_B_MembersInjector
index e15bf9b..72b876e 100644
--- a/javatests/dagger/internal/codegen/goldens/MembersInjectionTest_componentWithNestingAndGeneratedType_FAST_INIT_MODE_test.OuterType_B_MembersInjector
+++ b/javatests/dagger/internal/codegen/goldens/MembersInjectionTest_componentWithNestingAndGeneratedType_FAST_INIT_MODE_test.OuterType_B_MembersInjector
@@ -39,4 +39,5 @@
   public static void injectA(Object instance, Object a) {
     ((OuterType.B) instance).a = (OuterType.A) a;
   }
-}
\ No newline at end of file
+}
+
diff --git a/javatests/dagger/internal/codegen/goldens/embersInjectionTest_injectConstructorAndMembersInjection_DEFAULT_MODE_test.AllInjections_MembersInjector b/javatests/dagger/internal/codegen/goldens/embersInjectionTest_injectConstructorAndMembersInjection_DEFAULT_MODE_test.AllInjections_MembersInjector
deleted file mode 100644
index 479cf07..0000000
--- a/javatests/dagger/internal/codegen/goldens/embersInjectionTest_injectConstructorAndMembersInjection_DEFAULT_MODE_test.AllInjections_MembersInjector
+++ /dev/null
@@ -1,52 +0,0 @@
-package test;
-
-import dagger.MembersInjector;
-import dagger.internal.DaggerGenerated;
-import dagger.internal.InjectedFieldSignature;
-import dagger.internal.QualifierMetadata;
-import javax.annotation.processing.Generated;
-import javax.inject.Provider;
-
-@QualifierMetadata
-@DaggerGenerated
-@Generated(
-    value = "dagger.internal.codegen.ComponentProcessor",
-    comments = "https://dagger.dev"
-)
-@SuppressWarnings({
-    "unchecked",
-    "rawtypes",
-    "KotlinInternal",
-    "KotlinInternalInJava"
-})
-public final class AllInjections_MembersInjector implements MembersInjector<AllInjections> {
-  private final Provider<String> sProvider;
-
-  private final Provider<String> sProvider2;
-
-  public AllInjections_MembersInjector(Provider<String> sProvider, Provider<String> sProvider2) {
-    this.sProvider = sProvider;
-    this.sProvider2 = sProvider2;
-  }
-
-  public static MembersInjector<AllInjections> create(Provider<String> sProvider,
-      Provider<String> sProvider2) {
-    return new AllInjections_MembersInjector(sProvider, sProvider2);
-  }
-
-  @Override
-  public void injectMembers(AllInjections instance) {
-    injectS(instance, sProvider.get());
-    injectS2(instance, sProvider2.get());
-  }
-
-  @InjectedFieldSignature("test.AllInjections.s")
-  public static void injectS(Object instance, String s) {
-    ((AllInjections) instance).s = s;
-  }
-
-  public static void injectS2(Object instance, String s) {
-    ((AllInjections) instance).s(s);
-  }
-}
-
diff --git a/javatests/dagger/internal/codegen/kotlin/BUILD b/javatests/dagger/internal/codegen/kotlin/BUILD
index 0c217ab..f84b559 100644
--- a/javatests/dagger/internal/codegen/kotlin/BUILD
+++ b/javatests/dagger/internal/codegen/kotlin/BUILD
@@ -48,36 +48,6 @@
 )
 
 kt_compiler_test(
-    name = "KspDescriptorTest",
-    srcs = ["KspDescriptorTest.java"],
-    compiler_deps = [
-        "//java/dagger:core",
-        "//java/dagger/internal/codegen:package_info",
-        "//java/dagger/internal/codegen:processor",
-        "//java/dagger/internal/codegen/base",
-        "//java/dagger/internal/codegen/binding",
-        "//java/dagger/internal/codegen/bindinggraphvalidation",
-        "//java/dagger/internal/codegen/compileroption",
-        "//java/dagger/internal/codegen/javapoet",
-        "//java/dagger/internal/codegen/langmodel",
-        "//java/dagger/internal/codegen/validation",
-        "//java/dagger/internal/codegen/writing",
-        "//java/dagger/model/testing",
-        "//java/dagger/producers",
-        "//java/dagger/spi",
-    ],
-    deps = [
-        "//java/dagger/internal/codegen:package_info",
-        "//java/dagger/internal/codegen/extension",
-        "//java/dagger/internal/codegen/xprocessing",
-        "//java/dagger/internal/codegen/xprocessing:xprocessing-testing",
-        "//third_party/java/guava/collect",
-        "//third_party/java/junit",
-        "//third_party/java/truth",
-    ],
-)
-
-kt_compiler_test(
     name = "ComponentValidationKtTest",
     srcs = ["ComponentValidationKtTest.java"],
     compiler_deps = [
diff --git a/javatests/dagger/internal/codegen/kotlin/KspComponentProcessorTest.java b/javatests/dagger/internal/codegen/kotlin/KspComponentProcessorTest.java
index 56ecdaf..cc4f5a1 100644
--- a/javatests/dagger/internal/codegen/kotlin/KspComponentProcessorTest.java
+++ b/javatests/dagger/internal/codegen/kotlin/KspComponentProcessorTest.java
@@ -93,6 +93,34 @@
   }
 
   @Test
+  public void
+      testComponentReferencingGeneratedTypeInCompanionObject_successfullyGeneratedComponent()
+          throws Exception {
+    Source componentSrc =
+        CompilerTests.kotlinSource(
+            "MyComponent.kt",
+            "package test",
+            "",
+            "import dagger.BindsInstance",
+            "import dagger.Component",
+            "",
+            "@Component",
+            "interface MyComponent {",
+            " @Component.Builder",
+            " interface Builder {",
+            "   @BindsInstance fun text(text: String): Builder",
+            "   fun build(): MyComponent",
+            " }",
+            "",
+            " companion object {",
+            "   fun getComponent(text: String) = DaggerMyComponent.builder().text(text).build()",
+            " }",
+            "}");
+
+    CompilerTests.daggerCompiler(componentSrc).compile(subject -> subject.hasErrorCount(0));
+  }
+
+  @Test
   public void injectBindingComponentTest() throws Exception {
     Source componentSrc =
         CompilerTests.kotlinSource(
diff --git a/javatests/dagger/internal/codegen/kotlin/KspDescriptorTest.java b/javatests/dagger/internal/codegen/kotlin/KspDescriptorTest.java
deleted file mode 100644
index d2ce2dc..0000000
--- a/javatests/dagger/internal/codegen/kotlin/KspDescriptorTest.java
+++ /dev/null
@@ -1,448 +0,0 @@
-/*
- * Copyright (C) 2022 The Dagger Authors.
- *
- * 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 dagger.internal.codegen.kotlin;
-
-import static com.google.common.truth.Truth.assertThat;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
-
-import androidx.room.compiler.processing.XFieldElement;
-import androidx.room.compiler.processing.XMethodElement;
-import androidx.room.compiler.processing.util.Source;
-import androidx.room.compiler.processing.util.XTestInvocation;
-import com.google.common.collect.ImmutableSet;
-import dagger.internal.codegen.xprocessing.XElements;
-import dagger.testing.compile.CompilerTests;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public final class KspDescriptorTest {
-  private static final Source describeAnnotationSrc =
-      Source.Companion.kotlin(
-          "Describe.kt",
-          String.join(
-              "\n",
-              "package test",
-              "",
-              "import kotlin.annotation.Target",
-              "import kotlin.annotation.AnnotationTarget.FIELD",
-              "import kotlin.annotation.AnnotationTarget.FUNCTION",
-              "",
-              "@Target(FIELD, FUNCTION)",
-              "annotation class Describe"));
-
-  @Test
-  public void testFieldDescriptor() {
-    Source dummySrc =
-        CompilerTests.kotlinSource(
-            "DummyClass.kt",
-            "package test",
-            "",
-            "class DummyClass {",
-            " @Describe val field1: Int = 0",
-            " @Describe val field2: String = \"\"",
-            " @Describe val field3: List<String> = listOf()",
-            "}");
-
-    CompilerTests.invocationCompiler(dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              if (invocation.isKsp()) {
-                assertThat(getFieldDescriptor(invocation))
-                    .containsExactly(
-                        "field1:I", "field2:Lkotlin/String;", "field3:Lkotlin/collections/List;");
-              } else {
-                assertThat(getFieldDescriptor(invocation))
-                    .containsExactly(
-                        "field1:I", "field2:Ljava/lang/String;", "field3:Ljava/util/List;");
-              }
-            });
-  }
-
-  @Test
-  public void testFieldDescriptorWithJavaSource() {
-    Source dummySrc =
-        CompilerTests.javaSource(
-            "test.DummyClass",
-            "package test;",
-            "",
-            "import java.util.List;",
-            "",
-            "class DummyClass {",
-            " @Describe int field1;",
-            " @Describe String field2;",
-            " @Describe List<String> field3;",
-            "}");
-
-    CompilerTests.invocationCompiler(dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              if (invocation.isKsp()) {
-                assertThat(getFieldDescriptor(invocation))
-                    .containsExactly(
-                        "field1:I",
-                        "field2:Lkotlin/String;",
-                        "field3:Lkotlin/collections/MutableList;");
-              } else {
-                assertThat(getFieldDescriptor(invocation))
-                    .containsExactly(
-                        "field1:I", "field2:Ljava/lang/String;", "field3:Ljava/util/List;");
-              }
-            });
-  }
-
-  @Test
-  public void testMethodDescriptor() {
-    Source dummySrc =
-        CompilerTests.kotlinSource(
-            "DummyClass.kt",
-            "package test;",
-            "",
-            "class DummyClass {",
-            " @Describe fun method1() {}",
-            " @Describe fun method2(yesOrNo: Boolean, number: Int) {}",
-            " @Describe fun method3(letter: Char) {}",
-            " @Describe fun method4(realNumber1: Double, realNumber2: Float) {}",
-            " @Describe fun method5(bigNumber1: Long, littleNumber: Short) {}",
-            " @Describe fun method6(somthing: Any) {}",
-            " @Describe fun method7(): Any? = null",
-            " @Describe fun method8(): Map<String, Any> = mapOf()",
-            "}");
-
-    CompilerTests.invocationCompiler(dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              if (invocation.isKsp()) {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1()V",
-                        "method2(ZI)V",
-                        "method3(C)V",
-                        "method4(DF)V",
-                        "method5(JS)V",
-                        "method6(Lkotlin/Any;)V",
-                        "method7()Lkotlin/Any;",
-                        "method8()Lkotlin/collections/Map;");
-              } else {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1()V",
-                        "method2(ZI)V",
-                        "method3(C)V",
-                        "method4(DF)V",
-                        "method5(JS)V",
-                        "method6(Ljava/lang/Object;)V",
-                        "method7()Ljava/lang/Object;",
-                        "method8()Ljava/util/Map;");
-              }
-            });
-  }
-
-  @Test
-  public void methodDescriptorWithJavaSource() {
-    Source dummySrc =
-        CompilerTests.javaSource(
-            "test.DummyClass",
-            "package test;",
-            "",
-            "import java.util.ArrayList;",
-            "import java.util.List;",
-            "import java.util.Map;",
-            "",
-            "class DummyClass {",
-            " @Describe void method1() {}",
-            " @Describe void method2(boolean yesOrNo, int number) {}",
-            " @Describe void method3(char letter) {}",
-            " @Describe void method4(double realNumber1, float realNumber2) {}",
-            " @Describe void method5(long bigNumber1, short littleNumber) {}",
-            " @Describe void method6(Object somthing) {}",
-            " @Describe Object method7() { return null; }",
-            " @Describe List<String> method8(ArrayList<Integer> list) { return null; }",
-            " @Describe Map<String, Object> method9() { return null; }",
-            "}");
-
-    CompilerTests.invocationCompiler(dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              if (invocation.isKsp()) {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1()V",
-                        "method2(ZI)V",
-                        "method3(C)V",
-                        "method4(DF)V",
-                        "method5(JS)V",
-                        "method6(Lkotlin/Any;)V",
-                        "method7()Lkotlin/Any;",
-                        "method8(Ljava/util/ArrayList;)Lkotlin/collections/MutableList;",
-                        "method9()Lkotlin/collections/MutableMap;");
-              } else {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1()V",
-                        "method2(ZI)V",
-                        "method3(C)V",
-                        "method4(DF)V",
-                        "method5(JS)V",
-                        "method6(Ljava/lang/Object;)V",
-                        "method7()Ljava/lang/Object;",
-                        "method8(Ljava/util/ArrayList;)Ljava/util/List;",
-                        "method9()Ljava/util/Map;");
-              }
-            });
-  }
-
-  @Test
-  public void testArraysMethodDescriptor() {
-    Source customClassSrc =
-        CompilerTests.kotlinSource("CustomClass.kt", "package test", "class CustomClass {}");
-
-    Source dummySrc =
-        CompilerTests.kotlinSource(
-            "DummyClass.kt",
-            "package test",
-            "",
-            "class DummyClass {",
-            " @Describe fun method1(param: Array<CustomClass>) {}",
-            " @Describe fun method2(): Array<CustomClass> = arrayOf()",
-            " @Describe fun method3(param: Array<Int>) {}",
-            "}");
-
-    CompilerTests.invocationCompiler(customClassSrc, dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              if (invocation.isKsp()) {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1([Ltest/CustomClass;)V",
-                        "method2()[Ltest/CustomClass;",
-                        "method3([Lkotlin/Int;)V");
-              } else {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1([Ltest/CustomClass;)V",
-                        "method2()[Ltest/CustomClass;",
-                        "method3([Ljava/lang/Integer;)V");
-              }
-            });
-  }
-
-  @Test
-  public void testArraysMethodDescriptorJavaSource() {
-    Source customClassSrc =
-        CompilerTests.kotlinSource("CustomClass.kt", "package test", "class CustomClass {}");
-
-    Source dummySrc =
-        CompilerTests.javaSource(
-            "test.DummyClass",
-            "package test;",
-            "",
-            "class DummyClass {",
-            " @Describe void method1(CustomClass [] param) {}",
-            " @Describe CustomClass[] method2() { return null; }",
-            " @Describe void method3(int[] array) {}",
-            " @Describe void method4(int... array) {}",
-            "}");
-
-    CompilerTests.invocationCompiler(customClassSrc, dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              if (invocation.isKsp()) {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1([Ltest/CustomClass;)V",
-                        "method2()[Ltest/CustomClass;",
-                        "method3([I)V",
-                        "method4([I)V");
-              } else {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1([Ltest/CustomClass;)V",
-                        "method2()[Ltest/CustomClass;",
-                        "method3([I)V",
-                        "method4([I)V");
-              }
-            });
-  }
-
-  @Test
-  public void testInnerClassMethodDescriptorJavaSource() {
-    Source customClassSrc =
-        CompilerTests.javaSource(
-            "test.CustomClass",
-            "package test;",
-            "",
-            "public class CustomClass {",
-            " class InnerData {}",
-            " static class StaticInnerData {}",
-            " enum EnumData { VALUE1, VALUE2 }",
-            "}");
-
-    Source dummySrc =
-        CompilerTests.javaSource(
-            "test.DummyClass",
-            "package test;",
-            "",
-            "class DummyClass {",
-            " @Describe void method1(CustomClass.InnerData data) {}",
-            " @Describe void method2(CustomClass.StaticInnerData data) {}",
-            " @Describe void method3(CustomClass.EnumData data) {}",
-            " @Describe CustomClass.StaticInnerData method4() { return null; }",
-            "}");
-
-    CompilerTests.invocationCompiler(customClassSrc, dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              assertThat(getMethodDescriptor(invocation))
-                  .containsExactly(
-                      "method1(Ltest/CustomClass$InnerData;)V",
-                      "method2(Ltest/CustomClass$StaticInnerData;)V",
-                      "method3(Ltest/CustomClass$EnumData;)V",
-                      "method4()Ltest/CustomClass$StaticInnerData;");
-            });
-  }
-
-  @Test
-  public void testNestedFieldDescriptor() {
-    Source dummySrc =
-        CompilerTests.kotlinSource(
-            "DummyClass.kt",
-            "package test",
-            "",
-            "class DummyClass {",
-            "  class Nested1 {",
-            "   class Nested2 {",
-            "     @Describe val field1: DummyClass? = null",
-            "     @Describe val field2: Nested2? = null",
-            "   }",
-            " }",
-            "}");
-
-    CompilerTests.invocationCompiler(dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              if (invocation.isKsp()) {
-                assertThat(getFieldDescriptor(invocation))
-                    .containsExactly(
-                        "field1:Ltest/DummyClass;", "field2:Ltest/DummyClass$Nested1$Nested2;");
-              } else {
-                assertThat(getFieldDescriptor(invocation))
-                    .containsExactly(
-                        "field1:Ltest/DummyClass;", "field2:Ltest/DummyClass$Nested1$Nested2;");
-              }
-            });
-  }
-
-  @Test
-  public void testGenericTypeMethodDescriptor() {
-    Source dummySrc =
-        CompilerTests.kotlinSource(
-            "DummyClass.kt",
-            "package test",
-            "",
-            "public class DummyClass<T> {",
-            " @Describe fun method1(something: T) {}",
-            " @Describe fun method2(): T? = null",
-            " @Describe fun <O : String> method3(): List<O> = listOf()",
-            " @Describe fun method4(): Map<T, String> = mapOf()",
-            " @Describe fun method5(): List<Map<String, T>> = listOf()",
-            " @Describe fun <I, O : I> method6(input: I): O? = null",
-            " @Describe fun <I, O : String> method7(input: I): O? = null",
-            " @Describe fun <P> method8(): P? where P : Collection<String>, P : Comparable<String> "
-                + " = null",
-            "}");
-
-    CompilerTests.invocationCompiler(dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              // TODO(b/231169600): Add ksp test when generic type is supported.
-              if (!invocation.isKsp()) {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1(Ljava/lang/Object;)V",
-                        "method2()Ljava/lang/Object;",
-                        "method3()Ljava/util/List;",
-                        "method4()Ljava/util/Map;",
-                        "method5()Ljava/util/List;",
-                        "method6(Ljava/lang/Object;)Ljava/lang/Object;",
-                        "method7(Ljava/lang/Object;)Ljava/lang/String;",
-                        "method8()Ljava/util/Collection;");
-              }
-            });
-  }
-
-  @Test
-  public void testGenericTypeMethodDescriptorWithJavaSource() {
-    Source dummySrc =
-        CompilerTests.javaSource(
-            "test.DummyClass",
-            "package test;",
-            "",
-            "import java.util.ArrayList;",
-            "import java.util.Collection;",
-            "import java.util.List;",
-            "import java.util.Map;",
-            "import java.util.Set;",
-            "",
-            "public class DummyClass<T> {",
-            " @Describe void method1(T something) {}",
-            " @Describe T method2() { return null; }",
-            " @Describe List<? extends String> method3() { return null; }",
-            " @Describe Map<T, String> method4() { return null; }",
-            " @Describe ArrayList<Map<String, T>> method5() { return null; }",
-            " @Describe <I, O extends I> O method6(I input) { return null; }",
-            " @Describe static <I, O extends String> O method7(I input) { return null; }",
-            " @Describe static <P extends Collection<String> & Comparable<String>> P method8() {"
-                + " return null; }",
-            " @Describe static <P extends String & List<Character>> P method9() { return null; }",
-            "}");
-
-    CompilerTests.invocationCompiler(dummySrc, describeAnnotationSrc)
-        .compile(
-            invocation -> {
-              // TODO(b/231169600): Add ksp test when generic type is supported.
-              if (!invocation.isKsp()) {
-                assertThat(getMethodDescriptor(invocation))
-                    .containsExactly(
-                        "method1(Ljava/lang/Object;)V",
-                        "method2()Ljava/lang/Object;",
-                        "method3()Ljava/util/List;",
-                        "method4()Ljava/util/Map;",
-                        "method5()Ljava/util/ArrayList;",
-                        "method6(Ljava/lang/Object;)Ljava/lang/Object;",
-                        "method7(Ljava/lang/Object;)Ljava/lang/String;",
-                        "method8()Ljava/util/Collection;",
-                        "method9()Ljava/lang/String;");
-              }
-            });
-  }
-
-  private ImmutableSet<String> getFieldDescriptor(XTestInvocation invocation) {
-    return invocation.getRoundEnv().getElementsAnnotatedWith("test.Describe").stream()
-        .filter(element -> element instanceof XFieldElement)
-        .map(element -> XElements.getFieldDescriptor((XFieldElement) element))
-        .collect(toImmutableSet());
-  }
-
-  private ImmutableSet<String> getMethodDescriptor(XTestInvocation invocation) {
-    return invocation.getRoundEnv().getElementsAnnotatedWith("test.Describe").stream()
-        .filter(element -> element instanceof XMethodElement)
-        .map(element -> XElements.getMethodDescriptor((XMethodElement) element))
-        .collect(toImmutableSet());
-  }
-}
diff --git a/resources/META-INF/services/javax.annotation.processing.Processor b/resources/META-INF/services/javax.annotation.processing.Processor
deleted file mode 100644
index 069807e..0000000
--- a/resources/META-INF/services/javax.annotation.processing.Processor
+++ /dev/null
@@ -1 +0,0 @@
-dagger.internal.codegen.ComponentProcessor
diff --git a/test_defs.bzl b/test_defs.bzl
index 2046778..467f4e9 100644
--- a/test_defs.bzl
+++ b/test_defs.bzl
@@ -31,6 +31,7 @@
     "Shards": ["-Adagger.keysPerComponentShard=2"],
     "FastInit": ["-Adagger.fastInit=enabled"],
     "FastInit_Shards": ["-Adagger.fastInit=enabled", "-Adagger.keysPerComponentShard=2"],
+    "IgnoreProvisionKeyWildcards": ["-Adagger.ignoreProvisionKeyWildcards=enabled"],
 }
 
 def GenKtLibrary(
diff --git a/third_party/kotlin/build_extensions/BUILD b/third_party/kotlin/build_extensions/BUILD
new file mode 100644
index 0000000..8c46830
--- /dev/null
+++ b/third_party/kotlin/build_extensions/BUILD
@@ -0,0 +1,18 @@
+# Copyright (C) 2023 The Dagger Authors.
+#
+# 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.
+
+# Description:
+#   Build extension helpers for Kotlin.
+
+package(default_visibility = ["//:src"])
diff --git a/third_party/kotlin/build_extensions/rules.bzl b/third_party/kotlin/build_extensions/rules.bzl
new file mode 100644
index 0000000..f1d9b7c
--- /dev/null
+++ b/third_party/kotlin/build_extensions/rules.bzl
@@ -0,0 +1,20 @@
+# Copyright (C) 2023 The Dagger Authors.
+#
+# 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.
+
+"""Convenience wrapper for kt_android_library."""
+
+load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", io_kt_android_library = "kt_android_library")
+
+def kt_android_library(**kwargs):
+    io_kt_android_library(**kwargs)
diff --git a/tools/bazel_compat.bzl b/tools/bazel_compat.bzl
index e9354da..bc8b04a 100644
--- a/tools/bazel_compat.bzl
+++ b/tools/bazel_compat.bzl
@@ -15,7 +15,7 @@
 """Macros for building with Bazel.
 """
 
-load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library")
+load("//third_party/kotlin/build_extensions:rules.bzl", "kt_android_library")
 
 def compat_kt_android_library(name, **kwargs):
     bazel_kt_android_library(name, kwargs)
diff --git a/tools/maven.bzl b/tools/maven.bzl
index f842c88..bc76386 100644
--- a/tools/maven.bzl
+++ b/tools/maven.bzl
@@ -20,6 +20,11 @@
 load("@google_bazel_common//tools/javadoc:javadoc.bzl", "javadoc_library")
 load("@google_bazel_common//tools/jarjar:jarjar.bzl", "jarjar_library")
 
+SHADED_MAVEN_DEPS = [
+    "com.google.auto:auto-common",
+    "org.jetbrains.kotlinx:kotlinx-metadata-jvm",
+]
+
 def pom_file(name, targets, artifact_name, artifact_id, packaging = None, **kwargs):
     default_pom_file(
         name = name,
@@ -34,7 +39,12 @@
             "{artifact_id}": artifact_id,
             "{packaging}": packaging or "jar",
         },
-        excluded_artifacts = ["com.google.auto:auto-common"],
+        # NOTE: The shaded maven dependencies are excluded from every Dagger pom file.
+        # Thus, if a Dagger artifact needs the dependencies it must jarjar the dependency
+        # into the artifact itself using the gen_maven_artifact.shaded_deps or get it from
+        # a transitive Dagger artifact as a dependency. In addition, the artifact must add
+        # the shade rules in the deploy scripts, e.g. deploy-dagger.sh.
+        excluded_artifacts = SHADED_MAVEN_DEPS,
         **kwargs
     )
 
@@ -54,7 +64,6 @@
         javadoc_exclude_packages = None,
         javadoc_android_api_level = None,
         shaded_deps = None,
-        shaded_rules = None,
         manifest = None,
         lint_deps = None,
         proguard_specs = None):
@@ -282,7 +291,8 @@
     actual_maven_deps = [_strip_artifact_version(artifact) for artifact in maven_nearest_artifacts]
     _validate_list(
         "artifact_target_maven_deps",
-        actual_maven_deps,
+        # Exclude shaded maven deps from this list since they're not actual dependencies.
+        [dep for dep in actual_maven_deps if dep not in SHADED_MAVEN_DEPS],
         expected_maven_deps,
         ctx.attr.banned_maven_deps,
     )
diff --git a/tools/shader/build.gradle b/tools/shader/build.gradle
index aa9ba79..51218ba 100644
--- a/tools/shader/build.gradle
+++ b/tools/shader/build.gradle
@@ -84,6 +84,9 @@
 }
 
 shadowJar {
+    // This transform is needed so that services get relocated/shaded as well.
+    mergeServiceFiles()
+
     archiveClassifier = "" // postfix for output jar
     configurations = [project.configurations.shaded]
     transform(ManifestMerger.class)
diff --git a/util/deploy-dagger.sh b/util/deploy-dagger.sh
index 7f59b46..9249b91 100755
--- a/util/deploy-dagger.sh
+++ b/util/deploy-dagger.sh
@@ -51,10 +51,8 @@
   gwt/libgwt.jar \
   ""
 
-# This artifact uses the shaded classes from dagger-spi, so we use the same
-# shading rules so that our references to those classes are shaded the same way.
 _deploy \
-  "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler,dagger.spi.shaded.androidx.room.compiler" \
+  "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler,dagger.spi.shaded.androidx.room.compiler;kotlinx.metadata,dagger.spi.shaded.kotlinx.metadata;androidx.room,dagger.spi.shaded.androidx.room" \
   java/dagger/internal/codegen/artifact.jar \
   java/dagger/internal/codegen/pom.xml \
   java/dagger/internal/codegen/artifact-src.jar \
@@ -70,7 +68,7 @@
   ""
 
 _deploy \
-  "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler,dagger.spi.shaded.androidx.room.compiler" \
+  "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler,dagger.spi.shaded.androidx.room.compiler;kotlinx.metadata,dagger.spi.shaded.kotlinx.metadata;androidx.room,dagger.spi.shaded.androidx.room" \
   java/dagger/spi/artifact.jar \
   java/dagger/spi/pom.xml \
   java/dagger/spi/artifact-src.jar \
diff --git a/util/deploy-hilt.sh b/util/deploy-hilt.sh
index 325ef13..cdf4b6b 100755
--- a/util/deploy-hilt.sh
+++ b/util/deploy-hilt.sh
@@ -52,7 +52,7 @@
   ""
 
 _deploy \
-  "com.google.auto.common,dagger.hilt.android.shaded.auto.common" \
+  "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler,dagger.spi.shaded.androidx.room.compiler;kotlinx.metadata,dagger.spi.shaded.kotlinx.metadata;androidx.room,dagger.spi.shaded.androidx.room" \
   java/dagger/hilt/processor/artifact.jar \
   java/dagger/hilt/processor/pom.xml \
   java/dagger/hilt/processor/artifact-src.jar \
@@ -60,7 +60,7 @@
   ""
 
 _deploy \
-  "com.google.auto.common,dagger.hilt.android.shaded.auto.common" \
+  "com.google.auto.common,dagger.spi.shaded.auto.common;androidx.room.compiler,dagger.spi.shaded.androidx.room.compiler;kotlinx.metadata,dagger.spi.shaded.kotlinx.metadata;androidx.room,dagger.spi.shaded.androidx.room" \
   java/dagger/hilt/android/processor/artifact.jar \
   java/dagger/hilt/android/processor/pom.xml \
   java/dagger/hilt/android/processor/artifact-src.jar \
diff --git a/util/install-maven.sh b/util/install-maven.sh
new file mode 100755
index 0000000..5541cc8
--- /dev/null
+++ b/util/install-maven.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+set -eux
+
+function install-maven-version {
+  local VERSION=$1
+
+  if [[ ! "$VERSION" =~ ^3\. ]]; then
+    echo 'Version must begin with "3."'
+    exit 2
+  fi
+
+  pushd "$(mktemp -d)"
+  # Download the maven version
+  curl https://archive.apache.org/dist/maven/maven-3/${VERSION}/binaries/apache-maven-${VERSION}-bin.tar.gz --output apache-maven-${VERSION}-bin.tar.gz
+
+  # Unzip the contents to the /usr/share/ directory
+  sudo tar xvf apache-maven-${VERSION}-bin.tar.gz -C /usr/share/
+  popd
+
+  # Replace old symlink with new one
+  sudo unlink /usr/bin/mvn
+  sudo ln -s /usr/share/apache-maven-${VERSION}/bin/mvn /usr/bin/mvn
+}
+
+if [ $# -lt 1 ]; then
+  echo "usage $0 <version>"
+  exit 1;
+fi
+
+install-maven-version $1
+
+
diff --git a/util/latest-dagger-version.sh b/util/latest-dagger-version.sh
index ebb7a8d..305a748 100755
--- a/util/latest-dagger-version.sh
+++ b/util/latest-dagger-version.sh
@@ -4,15 +4,16 @@
 
 function github-rest-api {
   local GITHUB_REST_API=$1
-
   local GITHUB_API_HEADER_ACCEPT="Accept: application/vnd.github.v3+json"
+  # Grab the GH_TOKEN or else default to an empty string.
+  local GITHUB_TOKEN="${GH_TOKEN:-}"
 
-  if [ -z "$GH_TOKEN" ]; then
+  if [ -z "$GITHUB_TOKEN" ]; then
     curl -s $GITHUB_REST_API -H $GITHUB_API_HEADER_ACCEPT
   else
     curl -s $GITHUB_REST_API \
       -H $GITHUB_API_HEADER_ACCEPT \
-      -H "authorization: Bearer $GH_TOKEN"
+      -H "authorization: Bearer $GITHUB_TOKEN"
   fi
 }
 
diff --git a/util/run-local-gradle-android-tests.sh b/util/run-local-gradle-android-tests.sh
index 23eb864..0b3a5f5 100755
--- a/util/run-local-gradle-android-tests.sh
+++ b/util/run-local-gradle-android-tests.sh
@@ -3,13 +3,24 @@
 set -ex
 
 readonly AGP_VERSION_INPUT=$1
-readonly ANDROID_GRADLE_PROJECTS=(
+readonly COMMON_GRADLE_ARGS="--no-daemon --stacktrace --configuration-cache"
+
+readonly JAVA_ANDROID_GRADLE_PROJECTS=(
     "javatests/artifacts/dagger-android/simple"
     "javatests/artifacts/hilt-android/simple"
+)
+for project in "${JAVA_ANDROID_GRADLE_PROJECTS[@]}"; do
+    echo "Running gradle tests for $project with AGP $AGP_VERSION_INPUT"
+    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project assembleDebug $COMMON_GRADLE_ARGS
+    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug --continue $COMMON_GRADLE_ARGS
+done
+
+readonly KOTLIN_ANDROID_GRADLE_PROJECTS=(
     "javatests/artifacts/hilt-android/simpleKotlin"
 )
-for project in "${ANDROID_GRADLE_PROJECTS[@]}"; do
+for project in "${KOTLIN_ANDROID_GRADLE_PROJECTS[@]}"; do
     echo "Running gradle tests for $project with AGP $AGP_VERSION_INPUT"
-    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project assembleDebug --no-daemon --stacktrace --configuration-cache
-    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testDebug  --continue --no-daemon --stacktrace --configuration-cache
+    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project assembleDebug $COMMON_GRADLE_ARGS
+    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testWithKaptDebugUnitTest --continue $COMMON_GRADLE_ARGS
+    AGP_VERSION=$AGP_VERSION_INPUT ./$project/gradlew -p $project testWithKspDebugUnitTest --continue $COMMON_GRADLE_ARGS
 done
diff --git a/util/run-local-gradle-tests.sh b/util/run-local-gradle-tests.sh
index ecbbd35..bb0c458 100755
--- a/util/run-local-gradle-tests.sh
+++ b/util/run-local-gradle-tests.sh
@@ -4,6 +4,7 @@
 
 readonly GRADLE_PROJECTS=(
     "javatests/artifacts/dagger"
+    "javatests/artifacts/dagger-ksp"
     "javatests/artifacts/hilt-android/pluginMarker"
 )
 for project in "${GRADLE_PROJECTS[@]}"; do