[7.3.2] Support for using the java rules from `@rules_java` (#23779)

Co-authored-by: Googler <ahumesky@google.com>
Co-authored-by: Googler <gnish@google.com>
Co-authored-by: Googler <cushon@google.com>
Co-authored-by: Googler <cmita@google.com>
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index 6031a70..25baed9 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -42,12 +42,14 @@
 import com.google.devtools.build.lib.analysis.test.TestProvider.TestParams;
 import com.google.devtools.build.lib.analysis.test.TestTagsProvider;
 import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.RepositoryMapping;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.packages.AllowlistChecker;
 import com.google.devtools.build.lib.packages.Attribute;
 import com.google.devtools.build.lib.packages.BuildSetting;
+import com.google.devtools.build.lib.packages.BuiltinRestriction;
 import com.google.devtools.build.lib.packages.Info;
 import com.google.devtools.build.lib.packages.Provider;
 import com.google.devtools.build.lib.packages.TargetUtils;
@@ -344,7 +346,11 @@
       Label rdeLabel =
           ruleContext.getRule().getRuleClassObject().getRuleDefinitionEnvironmentLabel();
       // only allow native and builtins to override transitive validation propagation
-      if (rdeLabel != null && !rdeLabel.getRepository().getName().equals("_builtins")) {
+      if (rdeLabel != null
+          && BuiltinRestriction.isNotAllowed(
+              rdeLabel,
+              RepositoryMapping.ALWAYS_FALLBACK,
+              BuiltinRestriction.INTERNAL_STARLARK_API_ALLOWLIST)) {
         ruleContext.ruleError(rdeLabel + " cannot access the _transitive_validation private API");
         return;
       }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java
index db06d81..f54933e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationValue.java
@@ -83,13 +83,6 @@
   private static final Interner<ImmutableSortedMap<Class<? extends Fragment>, Fragment>>
       fragmentsInterner = BlazeInterners.newWeakInterner();
 
-  private static final ImmutableSet<BuiltinRestriction.AllowlistEntry> ANDROID_ALLOWLIST =
-      ImmutableSet.of(
-          BuiltinRestriction.allowlistEntry("", "third_party/bazel_rules/rules_android"),
-          BuiltinRestriction.allowlistEntry("build_bazel_rules_android", ""),
-          BuiltinRestriction.allowlistEntry("rules_android", ""),
-          BuiltinRestriction.allowlistEntry("", "tools/build_defs/android"));
-
   /** Global state necessary to build a BuildConfiguration. */
   public interface GlobalStateProvider {
     /** Computes the default shell environment for actions from the command line options. */
@@ -431,7 +424,7 @@
   @Override
   public boolean hasSeparateGenfilesDirectoryForStarlark(StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return hasSeparateGenfilesDirectory();
   }
 
@@ -535,7 +528,7 @@
 
   @Override
   public boolean isSiblingRepositoryLayoutForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return isSiblingRepositoryLayout();
   }
 
@@ -665,7 +658,7 @@
 
   @Override
   public boolean stampBinariesForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return stampBinaries();
   }
 
@@ -742,7 +735,7 @@
 
   @Override
   public boolean isToolConfigurationForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideAllowlist(thread, ANDROID_ALLOWLIST);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return isToolConfiguration();
   }
 
@@ -885,7 +878,7 @@
 
   @Override
   public boolean runfilesEnabledForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return runfilesEnabled();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkActionFactory.java
index 192dc70..13447a4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkActionFactory.java
@@ -13,13 +13,11 @@
 // limitations under the License.
 package com.google.devtools.build.lib.analysis.starlark;
 
-import static com.google.devtools.build.lib.analysis.starlark.StarlarkRuleContext.PRIVATE_STARLARKIFICATION_ALLOWLIST;
 import static com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions.EXPERIMENTAL_SIBLING_REPOSITORY_LAYOUT;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
@@ -111,16 +109,6 @@
   private static final Set<String> validResources =
       new HashSet<>(Arrays.asList(ResourceSet.CPU, ResourceSet.MEMORY, "local_test"));
 
-  // TODO(gnish): This is a temporary allowlist while new BuildInfo API becomes stable enough to
-  // become public.
-  // After at least some of the builtin rules have been switched to the new API delete this.
-  private static final ImmutableSet<BuiltinRestriction.AllowlistEntry>
-      PRIVATE_BUILDINFO_API_ALLOWLIST =
-          ImmutableSet.of(
-              BuiltinRestriction.allowlistEntry("", "test"), // for tests
-              BuiltinRestriction.allowlistEntry("", "tools/build_defs/build_info"),
-              BuiltinRestriction.allowlistEntry("bazel_tools", "tools/build_defs/build_info"));
-
   public StarlarkActionFactory(StarlarkActionContext context) {
     this.context = context;
   }
@@ -291,7 +279,7 @@
       throws EvalException {
     context.checkMutable("actions.symlink");
     if (useExecRootForSourceObject != Starlark.UNBOUND) {
-      BuiltinRestriction.failIfCalledOutsideAllowlist(thread, PRIVATE_STARLARKIFICATION_ALLOWLIST);
+      BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     }
     boolean useExecRootForSource =
         !Starlark.UNBOUND.equals(useExecRootForSourceObject)
@@ -468,7 +456,7 @@
       String outputFileName,
       StarlarkThread thread)
       throws InterruptedException, EvalException {
-    BuiltinRestriction.failIfCalledOutsideAllowlist(thread, PRIVATE_BUILDINFO_API_ALLOWLIST);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return transformBuildInfoFile(
         transformFuncObject, templateObject, outputFileName, true, thread);
   }
@@ -480,7 +468,7 @@
       String outputFileName,
       StarlarkThread thread)
       throws InterruptedException, EvalException {
-    BuiltinRestriction.failIfCalledOutsideAllowlist(thread, PRIVATE_BUILDINFO_API_ALLOWLIST);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return transformBuildInfoFile(
         transformFuncObject, templateObject, outputFileName, false, thread);
   }
@@ -1081,7 +1069,7 @@
   @Override
   public FileApi createShareableArtifact(String path, Object artifactRoot, StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideAllowlist(thread, PRIVATE_STARLARKIFICATION_ALLOWLIST);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     ArtifactRoot root =
         artifactRoot == Starlark.UNBOUND
             ? getRuleContext().getBinDirectory()
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
index e064cf6..b518c8c 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
@@ -31,7 +31,6 @@
 import com.google.devtools.build.lib.packages.AttributeTransitionData;
 import com.google.devtools.build.lib.packages.AttributeValueSource;
 import com.google.devtools.build.lib.packages.BuildType;
-import com.google.devtools.build.lib.packages.BuiltinRestriction;
 import com.google.devtools.build.lib.packages.BzlInitThreadContext;
 import com.google.devtools.build.lib.packages.LabelConverter;
 import com.google.devtools.build.lib.packages.Provider;
@@ -58,7 +57,6 @@
 import net.starlark.java.eval.Starlark;
 import net.starlark.java.eval.StarlarkFunction;
 import net.starlark.java.eval.StarlarkInt;
-import net.starlark.java.eval.StarlarkList;
 import net.starlark.java.eval.StarlarkThread;
 
 /**
@@ -512,20 +510,11 @@
       Object allowRules,
       Object cfg,
       Sequence<?> aspects,
-      Object flagsObject, // Sequence<String> expected
+      Sequence<?> flags,
       StarlarkThread thread)
       throws EvalException {
     checkContext(thread, "attr.label()");
 
-    if (flagsObject != Starlark.UNBOUND) {
-      BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
-    }
-    @SuppressWarnings("unchecked")
-    Sequence<String> flags =
-        flagsObject == Starlark.UNBOUND
-            ? StarlarkList.immutableOf()
-            : ((Sequence<String>) flagsObject);
-
     ImmutableAttributeFactory attribute =
         createAttributeFactory(
             BuildType.LABEL,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java
index 24ef380..3434b4e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java
@@ -121,15 +121,6 @@
 public final class StarlarkRuleContext
     implements StarlarkRuleContextApi<ConstraintValueInfo>, StarlarkActionContext {
 
-  public static final ImmutableSet<BuiltinRestriction.AllowlistEntry>
-      PRIVATE_STARLARKIFICATION_ALLOWLIST =
-          ImmutableSet.of(
-              BuiltinRestriction.allowlistEntry("", "test"), // for tests
-              BuiltinRestriction.allowlistEntry("", "third_party/bazel_rules/rules_android"),
-              BuiltinRestriction.allowlistEntry("build_bazel_rules_android", ""),
-              BuiltinRestriction.allowlistEntry("rules_android", ""),
-              BuiltinRestriction.allowlistEntry("", "tools/build_defs/android"));
-
   private static final String EXECUTABLE_OUTPUT_NAME = "executable";
 
   // This field is a copy of the info from ruleContext, stored separately so it can be accessed
@@ -1016,7 +1007,7 @@
   }
 
   private static void checkPrivateAccess(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideAllowlist(thread, PRIVATE_STARLARKIFICATION_ALLOWLIST);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java
index 0e17cc5..16b7a39f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java
@@ -103,7 +103,7 @@
     if (!supportFilesBuilder.isEmpty()
         || !reportedToActualSources.isEmpty()
         || !environmentPairs.isEmpty()) {
-      BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+      BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     }
     return createInstrumentedFilesInfo(
         starlarkRuleContext.getRuleContext(),
diff --git a/src/main/java/com/google/devtools/build/lib/packages/BuiltinRestriction.java b/src/main/java/com/google/devtools/build/lib/packages/BuiltinRestriction.java
index 407c89d..72e1975 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/BuiltinRestriction.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/BuiltinRestriction.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.packages;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.cmdline.BazelModuleContext;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.RepositoryMapping;
@@ -29,6 +30,42 @@
 // rule attributes rather than what .bzl you're in.
 public final class BuiltinRestriction {
 
+  /** The "default" allowlist for restricted APIs added to aid the Java to Starlark migration. */
+  public static final ImmutableList<BuiltinRestriction.AllowlistEntry>
+      INTERNAL_STARLARK_API_ALLOWLIST =
+          ImmutableList.of(
+              // Testing
+              BuiltinRestriction.allowlistEntry("", "test"),
+              BuiltinRestriction.allowlistEntry("", "bazel_internal/test_rules"),
+
+              // BuildInfo
+              BuiltinRestriction.allowlistEntry("", "tools/build_defs/build_info"),
+              BuiltinRestriction.allowlistEntry("bazel_tools", "tools/build_defs/build_info"),
+
+              // Android rules
+              BuiltinRestriction.allowlistEntry("", "bazel_internal/test_rules/cc"),
+              BuiltinRestriction.allowlistEntry("", "tools/build_defs/android"),
+              BuiltinRestriction.allowlistEntry("", "third_party/bazel_rules/rules_android"),
+              BuiltinRestriction.allowlistEntry("rules_android", ""),
+              BuiltinRestriction.allowlistEntry("build_bazel_rules_android", ""),
+
+              // Cc rules
+              BuiltinRestriction.allowlistEntry("", "third_party/bazel_rules/rules_cc"),
+              BuiltinRestriction.allowlistEntry("rules_cc", ""),
+
+              // Java rules
+              BuiltinRestriction.allowlistEntry("", "third_party/bazel_rules/rules_java"),
+              BuiltinRestriction.allowlistEntry("rules_java", ""),
+
+              // Rust rules
+              BuiltinRestriction.allowlistEntry(
+                  "", "third_party/bazel_rules/rules_rust/rust/private"),
+              BuiltinRestriction.allowlistEntry("", "third_party/crubit"),
+              BuiltinRestriction.allowlistEntry("rules_rust", "rust/private"),
+
+              // CUDA rules
+              BuiltinRestriction.allowlistEntry("", "third_party/gpus/cuda"));
+
   private BuiltinRestriction() {}
 
   /**
@@ -88,6 +125,18 @@
   }
 
   /**
+   * Throws {@code EvalException} if the call is made outside of the default allowlist or outside of
+   * builtins.
+   *
+   * @throws NullPointerException if there is no currently executing Starlark function, or the
+   *     innermost Starlark function's module is not a .bzl file
+   */
+  public static void failIfCalledOutsideDefaultAllowlist(StarlarkThread thread)
+      throws EvalException {
+    failIfCalledOutsideAllowlist(thread, INTERNAL_STARLARK_API_ALLOWLIST);
+  }
+
+  /**
    * Throws {@code EvalException} if the given {@link BazelModuleContext} is not within either 1)
    * the builtins repository, or 2) a package or subpackage of an entry in the given allowlist.
    */
@@ -103,11 +152,20 @@
   public static void failIfLabelOutsideAllowlist(
       Label label, RepositoryMapping repoMapping, Collection<AllowlistEntry> allowlist)
       throws EvalException {
-    if (label.getRepository().getName().equals("_builtins")) {
-      return;
-    }
-    if (allowlist.stream().noneMatch(e -> e.allows(label, repoMapping))) {
+    if (isNotAllowed(label, repoMapping, allowlist)) {
       throw Starlark.errorf("file '%s' cannot use private API", label.getCanonicalForm());
     }
   }
+
+  /**
+   * Returns true if the given {@link Label} is not within both 1) the builtins repository, or 2) a
+   * package or subpackage of an entry in the given allowlist.
+   */
+  public static boolean isNotAllowed(
+      Label label, RepositoryMapping repoMapping, Collection<AllowlistEntry> allowlist) {
+    if (label.getRepository().getName().equals("_builtins")) {
+      return false;
+    }
+    return allowlist.stream().noneMatch(e -> e.allows(label, repoMapping));
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
index a8361a2..d3f76b1 100755
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
@@ -138,18 +138,6 @@
   private static final ImmutableList<String> SUPPORTED_OUTPUT_TYPES =
       ImmutableList.of("executable", "dynamic_library", "archive");
 
-  private static final ImmutableList<BuiltinRestriction.AllowlistEntry>
-      PRIVATE_STARLARKIFICATION_ALLOWLIST =
-          ImmutableList.of(
-              BuiltinRestriction.allowlistEntry("", "bazel_internal/test_rules/cc"),
-              BuiltinRestriction.allowlistEntry("", "tools/build_defs/android"),
-              BuiltinRestriction.allowlistEntry("", "third_party/bazel_rules/rules_android"),
-              BuiltinRestriction.allowlistEntry(
-                  "", "rust/private"),
-              BuiltinRestriction.allowlistEntry("build_bazel_rules_android", ""),
-              BuiltinRestriction.allowlistEntry("rules_android", ""),
-              BuiltinRestriction.allowlistEntry("rules_rust", "rust/private"));
-
   // TODO(bazel-team): This only makes sense for the parameter in cc_common.compile()
   //  additional_include_scanning_roots which is technical debt and should go away.
   private static final BuiltinRestriction.AllowlistEntry MATCH_CLIF_ALLOWLISTED_LOCATION =
@@ -2050,7 +2038,7 @@
 
   public static void checkPrivateStarlarkificationAllowlist(StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideAllowlist(thread, PRIVATE_STARLARKIFICATION_ALLOWLIST);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
   }
 
   public static boolean isStarlarkCcCommonCalledFromBuiltins(StarlarkThread thread) {
@@ -2086,7 +2074,7 @@
       })
   public void checkPrivateApi(Object allowlistObject, StarlarkThread thread) throws EvalException {
     // Make sure that check_private_api is called either from builtins or allowlisted packages.
-    isCalledFromStarlarkCcCommon(thread);
+    checkPrivateStarlarkificationAllowlist(thread);
     BazelModuleContext bazelModuleContext =
         (BazelModuleContext) Module.ofInnermostEnclosingStarlarkFunction(thread, 1).getClientData();
     ImmutableList<BuiltinRestriction.AllowlistEntry> allowlist =
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
index 9192ec6..3a42d97 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppConfiguration.java
@@ -863,7 +863,7 @@
   private static void checkInExpandedApiAllowlist(StarlarkThread thread, String feature)
       throws EvalException {
     try {
-      BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+      BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     } catch (EvalException e) {
       throw Starlark.errorf("%s (feature '%s' in CppConfiguration)", e.getMessage(), feature);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
index 652594e..12a2155 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
@@ -830,7 +830,7 @@
 
   private JavaCompilationInfoProvider createCompilationInfoProvider() throws RuleErrorException {
     return new JavaCompilationInfoProvider.Builder()
-        .setJavacOpts(javacOpts)
+        .setJavacOpts(JavaHelper.detokenizeJavaOptions(javacOpts))
         .setBootClasspath(getBootClasspath())
         .setCompilationClasspath(getCompileTimeClasspath())
         .setRuntimeClasspath(getRuntimeClasspath())
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
index fc4f9db..326dad4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
@@ -73,6 +73,7 @@
   private final JavaTargetAttributes.Builder attributes;
   private JavaTargetAttributes builtAttributes;
   private final ImmutableList<String> customJavacOpts;
+  private NestedSet<String> javaBuilderJvmFlags = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
   private final JavaSemantics semantics;
   private final ImmutableList<Artifact> additionalInputsForDatabinding;
   private final StrictDepsMode strictJavaDeps;
@@ -139,6 +140,10 @@
     return javacOptsInterner.intern(javacOpts);
   }
 
+  public void javaBuilderJvmFlags(NestedSet<String> javaBuilderJvmFlags) {
+    this.javaBuilderJvmFlags = javaBuilderJvmFlags;
+  }
+
   public void enableJspecify(boolean enableJspecify) {
     this.enableJspecify = enableJspecify;
   }
@@ -347,7 +352,8 @@
     }
     builder.setSourcePathEntries(attributes.getSourcePath());
     builder.setToolsJars(javaToolchain.getTools());
-    builder.setJavaBuilder(javaToolchain.getJavaBuilder());
+    builder.setJavaBuilder(
+        javaToolchain.getJavaBuilder().withAdditionalJvmFlags(javaBuilderJvmFlags));
     if (!turbineAnnotationProcessing) {
       builder.setGenSourceOutput(outputs.genSource());
       builder.setAdditionalOutputs(attributes.getAdditionalOutputs());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationInfoProvider.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationInfoProvider.java
index 272c288..559b0f8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationInfoProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationInfoProvider.java
@@ -83,8 +83,7 @@
       Builder builder =
           new Builder()
               .setJavacOpts(
-                  Sequence.cast(info.getValue("javac_options"), String.class, "javac_options")
-                      .getImmutableList())
+                  Depset.cast(info.getValue("javac_options"), String.class, "javac_options"))
               .setBootClasspath(
                   NestedSetBuilder.wrap(
                       Order.NAIVE_LINK_ORDER,
@@ -112,13 +111,13 @@
 
   /** Builder for {@link JavaCompilationInfoProvider}. */
   public static class Builder {
-    private ImmutableList<String> javacOpts = ImmutableList.of();
+    private NestedSet<String> javacOpts = NestedSetBuilder.emptySet(Order.NAIVE_LINK_ORDER);
     private NestedSet<Artifact> runtimeClasspath;
     private NestedSet<Artifact> compilationClasspath;
     private NestedSet<Artifact> bootClasspath = NestedSetBuilder.emptySet(Order.STABLE_ORDER);
 
     @CanIgnoreReturnValue
-    public Builder setJavacOpts(@Nonnull ImmutableList<String> javacOpts) {
+    public Builder setJavacOpts(@Nonnull NestedSet<String> javacOpts) {
       this.javacOpts = javacOpts;
       return this;
     }
@@ -143,15 +142,21 @@
 
     public JavaCompilationInfoProvider build() throws RuleErrorException {
       return new AutoValue_JavaCompilationInfoProvider(
-          JavaCompilationHelper.internJavacOpts(javacOpts),
-          runtimeClasspath,
-          compilationClasspath,
-          bootClasspath);
+          javacOpts, runtimeClasspath, compilationClasspath, bootClasspath);
     }
   }
 
+  public abstract NestedSet<String> getJavacOpts();
+
   @Override
-  public abstract ImmutableList<String> getJavacOpts();
+  public Depset getJavacOptsStarlark() {
+    return Depset.of(String.class, getJavacOpts());
+  }
+
+  @Override
+  public ImmutableList<String> getJavacOptsList() {
+    return JavaCompilationHelper.internJavacOpts(JavaHelper.tokenizeJavaOptions(getJavacOpts()));
+  }
 
   @Nullable
   public abstract NestedSet<Artifact> runtimeClasspath();
@@ -203,7 +208,7 @@
       return false;
     }
     JavaCompilationInfoProvider other = (JavaCompilationInfoProvider) obj;
-    return Objects.equals(getJavacOpts(), other.getJavacOpts())
+    return getJavacOpts().shallowEquals(other.getJavacOpts())
         && Objects.equals(getRuntimeClasspath(), other.getRuntimeClasspath())
         && Objects.equals(getCompilationClasspath(), other.getCompilationClasspath())
         && bootClasspath().shallowEquals(other.bootClasspath());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java
index eb66c19..338ceb8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java
@@ -549,7 +549,7 @@
 
   @Override
   public boolean autoCreateJavaTestDeployJars(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return autoCreateDeployJarForJavaTests;
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java
index 3199074..015c589 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaStarlarkCommon.java
@@ -20,7 +20,6 @@
 import com.google.common.base.Predicates;
 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.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
@@ -68,9 +67,6 @@
         StarlarkRuleContext,
         StarlarkActionFactory> {
 
-  private static final ImmutableSet<BuiltinRestriction.AllowlistEntry>
-      PRIVATE_STARLARKIFACTION_ALLOWLIST =
-          ImmutableSet.of(BuiltinRestriction.allowlistEntry("", "bazel_internal/test_rules"));
   private final JavaSemantics javaSemantics;
 
   private static StrictDepsMode getStrictDepsMode(String strictDepsMode) {
@@ -185,6 +181,7 @@
       Depset compileTimeClasspath,
       Depset directJars,
       Object bootClassPathUnchecked,
+      Depset javaBuilderJvmFlags,
       Depset compileTimeJavaDeps,
       Depset javacOpts,
       String strictDepsMode,
@@ -260,6 +257,8 @@
             JavaToolchainProvider.PROVIDER.wrap(javaToolchain),
             Sequence.cast(additionalInputs, Artifact.class, "additional_inputs")
                 .getImmutableList());
+    compilationHelper.javaBuilderJvmFlags(
+        Depset.cast(javaBuilderJvmFlags, String.class, "javabuilder_jvm_flags"));
     compilationHelper.enableJspecify(enableJSpecify);
     compilationHelper.enableDirectClasspath(enableDirectClasspath);
     compilationHelper.createCompileAction(outputs);
@@ -315,7 +314,7 @@
   }
 
   protected static void checkPrivateAccess(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideAllowlist(thread, PRIVATE_STARLARKIFACTION_ALLOWLIST);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainTool.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainTool.java
index 2c08761..1d57886 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainTool.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainTool.java
@@ -129,4 +129,17 @@
       inputs.add(executable).addTransitive(toolchain.getJavaRuntime().javaBaseInputs());
     }
   }
+
+  public JavaToolchainTool withAdditionalJvmFlags(NestedSet<String> additionalJvmFlags) {
+    if (additionalJvmFlags.isEmpty()) {
+      return this;
+    }
+    return create(
+        tool(),
+        data(),
+        NestedSetBuilder.<String>stableOrder()
+            .addTransitive(jvmOpts())
+            .addTransitive(additionalJvmFlags)
+            .build());
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
index 5a0b9f3..b643210 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
@@ -221,7 +221,7 @@
 
   @StarlarkMethod(name = "archive", documented = false, useStarlarkThread = true)
   public Artifact archiveForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return archive();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java
index 5ac7ede..d1e88e3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/J2ObjcConfiguration.java
@@ -133,7 +133,7 @@
 
   @Override
   public boolean getRemoveDeadCodeForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return removeDeadCode;
   }
 
@@ -148,7 +148,7 @@
   @Override
   public boolean getExperimentalJ2ObjcHeaderMapForStarlark(StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return experimentalJ2ObjcHeaderMap;
   }
 
@@ -162,7 +162,7 @@
   @Override
   public boolean experimentalShorterHeaderPathforStarlark(StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return experimentalShorterHeaderPath;
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcConfiguration.java
index 87478e3..d5f99b9 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcConfiguration.java
@@ -209,7 +209,7 @@
   @Override
   public boolean targetShouldAlwayslink(StarlarkRuleContext ruleContext, StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
 
     AttributeMap attributes = ruleContext.getRuleContext().attributes();
     if (attributes.isAttributeValueExplicitlySpecified("alwayslink")) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoConfiguration.java
index b229553..4b2b147 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/proto/ProtoConfiguration.java
@@ -209,7 +209,7 @@
       documented = false)
   public boolean experimentalProtoDescriptorSetsIncludeSourceInfoForStarlark(StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return experimentalProtoDescriptorSetsIncludeSourceInfo();
   }
 
@@ -273,13 +273,13 @@
 
   @StarlarkMethod(name = "strict_proto_deps", useStarlarkThread = true, documented = false)
   public String strictProtoDepsForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return strictProtoDeps().toString();
   }
 
   @StarlarkMethod(name = "strict_public_imports", useStarlarkThread = true, documented = false)
   public String strictPublicImportsForStarlark(StarlarkThread thread) throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return options.strictPublicImports.toString();
   }
 
@@ -293,7 +293,7 @@
       documented = false)
   public List<String> ccProtoLibraryHeaderSuffixesForStarlark(StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return ccProtoLibraryHeaderSuffixes();
   }
 
@@ -307,7 +307,7 @@
       documented = false)
   public List<String> ccProtoLibrarySourceSuffixesForStarlark(StarlarkThread thread)
       throws EvalException {
-    BuiltinRestriction.failIfCalledOutsideBuiltins(thread);
+    BuiltinRestriction.failIfCalledOutsideDefaultAllowlist(thread);
     return ccProtoLibrarySourceSuffixes();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
index a8eae3f..6145749 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
@@ -346,11 +346,10 @@
             doc = ASPECTS_ARG_DOC),
         @Param(
             name = FLAGS_ARG,
-            defaultValue = "unbound",
             allowedTypes = {@ParamType(type = Sequence.class, generic1 = String.class)},
+            defaultValue = "[]",
             named = true,
             positional = false,
-            documented = false,
             doc = FLAGS_DOC)
       },
       useStarlarkThread = true)
@@ -365,7 +364,7 @@
       Object allowRules,
       Object cfg,
       Sequence<?> aspects,
-      Object flags, // Sequence<String> expected
+      Sequence<?> flags,
       StarlarkThread thread)
       throws EvalException;
 
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java
index d5cd183..c367383 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaBootstrap.java
@@ -20,7 +20,6 @@
 import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
 import com.google.devtools.build.lib.starlarkbuildapi.core.Bootstrap;
 import com.google.devtools.build.lib.starlarkbuildapi.core.ContextAndFlagGuardedValue;
-import net.starlark.java.eval.FlagGuardedValue;
 import net.starlark.java.eval.Starlark;
 
 /** {@link Bootstrap} for Starlark objects related to the java language. */
@@ -61,9 +60,6 @@
             Starlark.NONE,
             allowedRepositories));
 
-    builder.put(
-        ProguardSpecProviderApi.NAME,
-        FlagGuardedValue.onlyWhenExperimentalFlagIsTrue(
-            BuildLanguageOptions.EXPERIMENTAL_GOOGLE_LEGACY_API, proguardSpecProvider));
+    builder.put(ProguardSpecProviderApi.NAME, proguardSpecProvider);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java
index 3f11a84..4dbe47f 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCommonApi.java
@@ -541,6 +541,7 @@
         @Param(name = "compilation_classpath"),
         @Param(name = "direct_jars"),
         @Param(name = "bootclasspath"),
+        @Param(name = "javabuilder_jvm_flags"),
         @Param(name = "compile_time_java_deps"),
         @Param(name = "javac_opts"),
         @Param(name = "strict_deps_mode"),
@@ -570,6 +571,7 @@
       Depset compileTimeClasspath,
       Depset directJars,
       Object bootClassPath,
+      Depset javabuilderJvmFlags,
       Depset compileTimeJavaDeps,
       Depset javacOpts,
       String strictDepsMode,
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCompilationInfoProviderApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCompilationInfoProviderApi.java
index 3147b5b..47f510c 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCompilationInfoProviderApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/java/JavaCompilationInfoProviderApi.java
@@ -29,8 +29,13 @@
     doc = "Provides access to compilation information for Java rules.")
 public interface JavaCompilationInfoProviderApi<FileT extends FileApi> extends StarlarkValue {
 
-  @StarlarkMethod(name = "javac_options", structField = true, doc = "Options to java compiler.")
-  ImmutableList<String> getJavacOpts();
+  @StarlarkMethod(
+      name = "javac_options",
+      structField = true,
+      doc =
+          "A depset of options to java compiler. To get the exact list of options passed to javac"
+              + " in the correct order, use the tokenize_javacopts utility in rules_java")
+  Depset getJavacOptsStarlark();
 
   @StarlarkMethod(
       name = "javac_options_list",
@@ -39,9 +44,7 @@
           "A list of options to java compiler. This exists temporarily for migration purposes. "
               + "javac_options will return a depset in the future, and this method will be dropped "
               + "once all usages have been updated to handle depsets.")
-  default ImmutableList<String> getJavacOptsList() {
-    return getJavacOpts();
-  }
+  ImmutableList<String> getJavacOptsList();
 
   @StarlarkMethod(
       name = "runtime_classpath",
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java
index e1f94ce..a56bdf7 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java
@@ -74,7 +74,7 @@
       Object allowRules,
       Object cfg,
       Sequence<?> aspects,
-      Object flags,
+      Sequence<?> flags,
       StarlarkThread thread)
       throws EvalException {
     List<List<String>> allNameGroups = new ArrayList<>();
diff --git a/src/main/starlark/builtins_bzl/bazel/java/bazel_java_binary.bzl b/src/main/starlark/builtins_bzl/bazel/java/bazel_java_binary.bzl
index 8c28c0d..255650c0 100644
--- a/src/main/starlark/builtins_bzl/bazel/java/bazel_java_binary.bzl
+++ b/src/main/starlark/builtins_bzl/bazel/java/bazel_java_binary.bzl
@@ -14,6 +14,7 @@
 
 load(":common/cc/cc_helper.bzl", "cc_helper")
 load(":common/cc/semantics.bzl", cc_semantics = "semantics")
+load(":common/java/android_lint.bzl", "android_lint_subrule")
 load(":common/java/java_binary.bzl", "BASE_TEST_ATTRIBUTES", "BASIC_JAVA_BINARY_ATTRIBUTES", "basic_java_binary")
 load(":common/java/java_helper.bzl", "helper")
 load(":common/java/java_info.bzl", "JavaInfo")
@@ -292,6 +293,7 @@
         exec_groups = {
             "cpp_link": exec_group(toolchains = cc_helper.use_cpp_toolchain()),
         },
+        subrules = [android_lint_subrule],
     )
 
 _BASE_BINARY_ATTRS = merge_attrs(
diff --git a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
index 529d5cd..eee6f48 100644
--- a/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
+++ b/src/main/starlark/builtins_bzl/common/cc/cc_common.bzl
@@ -37,6 +37,8 @@
     ("rules_android", ""),
     ("", "rust/private"),
     ("rules_rust", "rust/private"),
+    ("", "third_party/bazel_rules/rules_cc"),
+    ("rules_cc", ""),
 ]
 
 _BUILTINS = [("_builtins", "")]
diff --git a/src/main/starlark/builtins_bzl/common/java/android_lint.bzl b/src/main/starlark/builtins_bzl/common/java/android_lint.bzl
index 3533c37..6d1f5f8 100644
--- a/src/main/starlark/builtins_bzl/common/java/android_lint.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/android_lint.bzl
@@ -17,7 +17,10 @@
 load(":common/java/java_helper.bzl", "helper")
 load(":common/java/java_semantics.bzl", "semantics")
 
-def android_lint_action(ctx, source_files, source_jars, compilation_info):
+def _tokenize_opts(opts_depset):
+    return helper.tokenize_javacopts(ctx = None, opts = opts_depset)
+
+def _android_lint_action(ctx, source_files, source_jars, compilation_info):
     """
     Creates an action that runs Android lint against Java source files.
 
@@ -96,8 +99,9 @@
     args.add("--target_label", ctx.label)
 
     javac_opts = compilation_info.javac_options
-    if (javac_opts):
-        args.add_all("--javacopts", javac_opts)
+    if javac_opts:
+        # wrap in a list so that map_each passes the depset to _tokenize_opts
+        args.add_all("--javacopts", [javac_opts], map_each = _tokenize_opts)
         args.add("--")
 
     args.add("--lintopts")
@@ -126,10 +130,14 @@
         tools = tools,
         arguments = args_list,
         execution_requirements = {"supports-workers": "1"},
-        toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
         env = {
             # TODO(b/279025786): replace with setting --XskipJarVerification in AndroidLintRunner
             "ANDROID_LINT_SKIP_BYTECODE_VERIFIER": "true",
         },
     )
     return android_lint_out
+
+android_lint_subrule = subrule(
+    implementation = _android_lint_action,
+    toolchains = [semantics.JAVA_TOOLCHAIN_TYPE],
+)
diff --git a/src/main/starlark/builtins_bzl/common/java/basic_java_library.bzl b/src/main/starlark/builtins_bzl/common/java/basic_java_library.bzl
index 9afe121..febc1d1 100644
--- a/src/main/starlark/builtins_bzl/common/java/basic_java_library.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/basic_java_library.bzl
@@ -17,7 +17,8 @@
 """
 
 load(":common/cc/cc_info.bzl", "CcInfo")
-load(":common/java/android_lint.bzl", "android_lint_action")
+load(":common/java/android_lint.bzl", "android_lint_subrule")
+load(":common/java/boot_class_path_info.bzl", "BootClassPathInfo")
 load(":common/java/compile_action.bzl", "compile_action")
 load(":common/java/java_common.bzl", "java_common")
 load(":common/java/java_common_internal_for_builtins.bzl", "target_kind")
@@ -74,7 +75,9 @@
         coverage_config = None,
         proguard_specs = None,
         add_exports = [],
-        add_opens = []):
+        add_opens = [],
+        bootclasspath = None,
+        javabuilder_jvm_flags = None):
     """
     Creates actions that compile and lint Java sources, sets up coverage and returns JavaInfo, InstrumentedFilesInfo and output groups.
 
@@ -108,6 +111,8 @@
         Proguard validation is done only when the parameter is set.
       add_exports: (list[str]) Allow this library to access the given <module>/<package>.
       add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+      bootclasspath: (Target) The JDK APIs to compile this library against.
+      javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder.
     Returns:
       (dict[str, Provider],
         {files_to_build: list[File],
@@ -146,6 +151,8 @@
         enable_compile_jar_action,
         add_exports = add_exports,
         add_opens = add_opens,
+        bootclasspath = bootclasspath[BootClassPathInfo] if bootclasspath else None,
+        javabuilder_jvm_flags = javabuilder_jvm_flags,
     )
     target = {"JavaInfo": java_info}
 
@@ -161,8 +168,7 @@
             for output in java_info.java_outputs
             if output.generated_source_jar != None
         ]
-        lint_output = android_lint_action(
-            ctx,
+        lint_output = android_lint_subrule(
             source_files,
             source_jars + generated_source_jars,
             compilation_info,
diff --git a/src/main/starlark/builtins_bzl/common/java/compile_action.bzl b/src/main/starlark/builtins_bzl/common/java/compile_action.bzl
index 1e26d64..7b9c20a 100644
--- a/src/main/starlark/builtins_bzl/common/java/compile_action.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/compile_action.bzl
@@ -16,8 +16,8 @@
 Java compile action
 """
 
-load(":common/java/java_semantics.bzl", "semantics")
 load(":common/java/java_common_internal_for_builtins.bzl", _compile_private_for_builtins = "compile")
+load(":common/java/java_semantics.bzl", "semantics")
 
 def _filter_strict_deps(mode):
     return "error" if mode in ["strict", "default"] else mode
@@ -56,7 +56,9 @@
         strict_deps = "ERROR",
         enable_compile_jar_action = True,
         add_exports = [],
-        add_opens = []):
+        add_opens = [],
+        bootclasspath = None,
+        javabuilder_jvm_flags = None):
     """
     Creates actions that compile Java sources, produce source jar, and produce header jar and returns JavaInfo.
 
@@ -117,6 +119,8 @@
         by non-library targets such as binaries that do not have dependants.
       add_exports: (list[str]) Allow this library to access the given <module>/<package>.
       add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+      bootclasspath: (BootClassPathInfo) The set of JDK APIs to compile this library against.
+      javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder.
 
     Returns:
       ((JavaInfo, {files_to_build: list[File],
@@ -152,6 +156,8 @@
         enable_compile_jar_action = enable_compile_jar_action,
         add_exports = add_exports,
         add_opens = add_opens,
+        bootclasspath = bootclasspath,
+        javabuilder_jvm_flags = javabuilder_jvm_flags,
     )
 
     compilation_info = struct(
@@ -159,7 +165,7 @@
         runfiles = [output_class_jar] if source_files or source_jars or resources else [],
         # TODO(ilist): collect compile_jars from JavaInfo in deps & exports
         compilation_classpath = java_info.compilation_info.compilation_classpath,
-        javac_options = java_info.compilation_info.javac_options_list,
+        javac_options = java_info.compilation_info.javac_options,
         plugins = _collect_plugins(deps, plugins),
     )
 
diff --git a/src/main/starlark/builtins_bzl/common/java/java_binary.bzl b/src/main/starlark/builtins_bzl/common/java/java_binary.bzl
index bdcd1db..4d2c424 100644
--- a/src/main/starlark/builtins_bzl/common/java/java_binary.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/java_binary.bzl
@@ -18,7 +18,7 @@
 load(":common/cc/cc_info.bzl", "CcInfo")
 load(":common/cc/semantics.bzl", cc_semantics = "semantics")
 load(":common/java/basic_java_library.bzl", "BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS", "basic_java_library", "collect_deps")
-load(":common/java/java_binary_deploy_jar.bzl", "create_deploy_archive")
+load(":common/java/boot_class_path_info.bzl", "BootClassPathInfo")
 load(":common/java/java_common.bzl", "java_common")
 load(
     ":common/java/java_common_internal_for_builtins.bzl",
@@ -26,7 +26,7 @@
     "get_runtime_classpath_for_archive",
 )
 load(":common/java/java_helper.bzl", "helper")
-load(":common/java/java_info.bzl", "JavaInfo", "JavaPluginInfo", "to_java_binary_info")
+load(":common/java/java_info.bzl", "JavaCompilationInfo", "JavaInfo", "JavaPluginInfo", "to_java_binary_info")
 load(":common/java/java_semantics.bzl", "semantics")
 load(":common/paths.bzl", "paths")
 load(":common/proto/proto_info.bzl", "ProtoInfo")
@@ -126,8 +126,10 @@
         coverage_config = coverage_config,
         add_exports = ctx.attr.add_exports,
         add_opens = ctx.attr.add_opens,
+        bootclasspath = ctx.attr.bootclasspath,
     )
     java_info = target["JavaInfo"]
+    compilation_info = java_info.compilation_info
     runtime_classpath = depset(
         order = "preorder",
         transitive = [
@@ -150,6 +152,13 @@
                 ),
             ],
         )
+        compilation_info = JavaCompilationInfo(
+            compilation_classpath = compilation_info.compilation_classpath,
+            runtime_classpath = runtime_classpath,
+            boot_classpath = compilation_info.boot_classpath,
+            javac_options = compilation_info.javac_options,
+            javac_options_list = compilation_info.javac_options_list,
+        )
 
     java_attrs = _collect_attrs(ctx, runtime_classpath, classpath_resources)
 
@@ -265,56 +274,10 @@
 
     _filter_validation_output_group(ctx, output_groups)
 
-    java_binary_info = to_java_binary_info(java_info)
-
-    internal_deploy_jar_info = InternalDeployJarInfo(
-        java_attrs = java_attrs,
-        launcher_info = struct(
-            runtime_jars = launcher_info.runtime_jars,
-            launcher = launcher_info.launcher,
-            unstripped_launcher = launcher_info.unstripped_launcher,
-        ),
-        shared_archive = shared_archive,
-        main_class = main_class,
-        coverage_main_class = coverage_main_class,
-        strip_as_default = strip_as_default,
-        stamp = ctx.attr.stamp,
-        hermetic = hasattr(ctx.attr, "hermetic") and ctx.attr.hermetic,
-        add_exports = add_exports,
-        add_opens = add_opens,
-        manifest_lines = ctx.attr.deploy_manifest_lines,
-    )
-
-    # "temporary" workaround for https://github.com/bazelbuild/intellij/issues/5845
-    extra_files = []
-    if is_test_rule_class and ctx.fragments.java.auto_create_java_test_deploy_jars():
-        extra_files.append(_auto_create_deploy_jar(ctx, internal_deploy_jar_info))
-
-    internal_deploy_jar_info = InternalDeployJarInfo(
-        java_attrs = java_attrs,
-        launcher_info = struct(
-            runtime_jars = launcher_info.runtime_jars,
-            launcher = launcher_info.launcher,
-            unstripped_launcher = launcher_info.unstripped_launcher,
-        ),
-        shared_archive = shared_archive,
-        main_class = main_class,
-        coverage_main_class = coverage_main_class,
-        strip_as_default = strip_as_default,
-        stamp = ctx.attr.stamp,
-        hermetic = hasattr(ctx.attr, "hermetic") and ctx.attr.hermetic,
-        add_exports = add_exports,
-        add_opens = add_opens,
-        manifest_lines = ctx.attr.deploy_manifest_lines,
-    )
-
-    # "temporary" workaround for https://github.com/bazelbuild/intellij/issues/5845
-    extra_files = []
-    if is_test_rule_class and ctx.fragments.java.auto_create_java_test_deploy_jars():
-        extra_files.append(_auto_create_deploy_jar(ctx, internal_deploy_jar_info))
+    java_binary_info = to_java_binary_info(java_info, compilation_info)
 
     default_info = struct(
-        files = depset(extra_files, transitive = [files]),
+        files = files,
         runfiles = runfiles,
         executable = executable,
     )
@@ -324,7 +287,23 @@
         "JavaInfo": java_binary_info,
         "InstrumentedFilesInfo": target["InstrumentedFilesInfo"],
         "JavaRuntimeClasspathInfo": java_common.JavaRuntimeClasspathInfo(runtime_classpath = java_info.transitive_runtime_jars),
-        "InternalDeployJarInfo": internal_deploy_jar_info,
+        "InternalDeployJarInfo": InternalDeployJarInfo(
+            java_attrs = java_attrs,
+            launcher_info = struct(
+                runtime_jars = launcher_info.runtime_jars,
+                launcher = launcher_info.launcher,
+                unstripped_launcher = launcher_info.unstripped_launcher,
+            ),
+            shared_archive = shared_archive,
+            main_class = main_class,
+            coverage_main_class = coverage_main_class,
+            strip_as_default = strip_as_default,
+            stamp = ctx.attr.stamp,
+            hermetic = hasattr(ctx.attr, "hermetic") and ctx.attr.hermetic,
+            add_exports = add_exports,
+            add_opens = add_opens,
+            manifest_lines = ctx.attr.deploy_manifest_lines,
+        ),
     }, default_info, jvm_flags
 
 def _collect_attrs(ctx, runtime_classpath, classpath_resources):
@@ -422,7 +401,7 @@
     else:
         allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist")
 
-    if not tool:  # On Mac oneversion tool is not available
+    if not tool or not allowlist:  # On Mac oneversion tool is not available
         return None
 
     output = ctx.actions.declare_file("%s-one-version.txt" % ctx.label.name)
@@ -430,11 +409,8 @@
     args = ctx.actions.args()
     args.set_param_file_format("shell").use_param_file("@%s", use_always = True)
 
-    one_version_inputs = []
     args.add("--output", output)
-    if allowlist:
-        args.add("--whitelist", allowlist)
-        one_version_inputs.append(allowlist)
+    args.add("--whitelist", allowlist)
     if one_version_level == "WARNING":
         args.add("--succeed_on_found_violations")
     args.add_all(
@@ -448,7 +424,7 @@
         progress_message = "Checking for one-version violations in %{label}",
         executable = tool,
         toolchain = semantics.JAVA_TOOLCHAIN_TYPE,
-        inputs = depset(one_version_inputs, transitive = [inputs]),
+        inputs = depset([allowlist], transitive = [inputs]),
         tools = [tool],
         outputs = [output],
         arguments = [args],
@@ -479,6 +455,7 @@
                attr_name not in [
                    "deploy_env",
                    "applicable_licenses",
+                   "package_metadata",
                    "plugins",
                    "translations",
                    # special ignored attributes
@@ -514,42 +491,6 @@
     else:
         return depset()
 
-# TODO: bazelbuild/intellij/issues/5845 - remove this once no longer required
-# this need not be completely identical to the regular deploy jar since we only
-# care about packaging the classpath
-def _auto_create_deploy_jar(ctx, info):
-    output = ctx.actions.declare_file(ctx.label.name + "_auto_deploy.jar")
-    java_attrs = info.java_attrs
-    runtime_classpath = depset(
-        direct = info.launcher_info.runtime_jars,
-        transitive = [
-            java_attrs.runtime_jars,
-            java_attrs.runtime_classpath_for_archive,
-        ],
-        order = "preorder",
-    )
-    create_deploy_archive(
-        ctx,
-        launcher = info.launcher_info.launcher,
-        main_class = info.main_class,
-        coverage_main_class = info.coverage_main_class,
-        resources = java_attrs.resources,
-        classpath_resources = java_attrs.classpath_resources,
-        runtime_classpath = runtime_classpath,
-        manifest_lines = info.manifest_lines,
-        build_info_files = [],
-        build_target = str(ctx.label),
-        output = output,
-        shared_archive = info.shared_archive,
-        one_version_level = ctx.fragments.java.one_version_enforcement_level,
-        one_version_allowlist = helper.check_and_get_one_version_attribute(ctx, "_one_version_allowlist"),
-        multi_release = ctx.fragments.java.multi_release_deploy_jars,
-        hermetic = info.hermetic,
-        add_exports = info.add_exports,
-        add_opens = info.add_opens,
-    )
-    return output
-
 BASIC_JAVA_BINARY_ATTRIBUTES = merge_attrs(
     BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS,
     {
@@ -593,6 +534,10 @@
             allow_files = False,
             # TODO(b/295221112): add back CcLauncherInfo
         ),
+        "bootclasspath": attr.label(
+            providers = [BootClassPathInfo],
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+        ),
         "neverlink": attr.bool(),
         "javacopts": attr.string_list(),
         "add_exports": attr.string_list(),
diff --git a/src/main/starlark/builtins_bzl/common/java/java_common.bzl b/src/main/starlark/builtins_bzl/common/java/java_common.bzl
index 4620396..be32659 100644
--- a/src/main/starlark/builtins_bzl/common/java/java_common.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/java_common.bzl
@@ -19,8 +19,11 @@
 load(":common/java/java_helper.bzl", "helper")
 load(
     ":common/java/java_info.bzl",
+    "JavaCompilationInfo",
     "JavaInfo",
+    "JavaPluginDataInfo",
     "JavaPluginInfo",
+    "to_java_binary_info",
     _java_info_add_constraints = "add_constraints",
     _java_info_make_non_strict = "make_non_strict",
     _java_info_merge = "merge",
@@ -289,6 +292,24 @@
     _java_common_internal.check_provider_instances([java_toolchain], "java_toolchain", JavaToolchainInfo)
     return java_toolchain.label
 
+def _internal_exports():
+    _builtins.internal.cc_common.check_private_api(allowlist = [
+        ("", "third_party/bazel_rules/rules_java"),
+        ("rules_java", ""),
+    ])
+    return struct(
+        incompatible_disable_non_executable_java_binary = _java_common_internal.incompatible_disable_non_executable_java_binary,
+        target_kind = _java_common_internal.target_kind,
+        compile = compile,
+        JavaCompilationInfo = JavaCompilationInfo,
+        collect_native_deps_dirs = _java_common_internal.collect_native_deps_dirs,
+        get_runtime_classpath_for_archive = _java_common_internal.get_runtime_classpath_for_archive,
+        to_java_binary_info = to_java_binary_info,
+        run_ijar_private_for_builtins = run_ijar,
+        expand_java_opts = _java_common_internal.expand_java_opts,
+        JavaPluginDataInfo = JavaPluginDataInfo,
+    )
+
 def _make_java_common():
     methods = {
         "provider": JavaInfo,
@@ -305,6 +326,7 @@
         "JavaRuntimeInfo": JavaRuntimeInfo,
         "BootClassPathInfo": BootClassPathInfo,
         "JavaRuntimeClasspathInfo": JavaRuntimeClasspathInfo,
+        "internal_DO_NOT_USE": _internal_exports,
     }
     if _java_common_internal._google_legacy_api_enabled():
         methods.update(
diff --git a/src/main/starlark/builtins_bzl/common/java/java_common_internal_for_builtins.bzl b/src/main/starlark/builtins_bzl/common/java/java_common_internal_for_builtins.bzl
index 08a5be5..57e7d93 100644
--- a/src/main/starlark/builtins_bzl/common/java/java_common_internal_for_builtins.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/java_common_internal_for_builtins.bzl
@@ -23,7 +23,7 @@
     "merge_plugin_info_without_outputs",
 )
 load(":common/java/java_semantics.bzl", "semantics")
-load(":common/java/sharded_javac.bzl", "experimental_sharded_javac", "use_sharded_javac")
+load(":common/java/java_toolchain.bzl", "JavaToolchainInfo")
 load(":common/paths.bzl", "paths")
 
 _java_common_internal = _builtins.internal.java_common_internal_do_not_use
@@ -46,6 +46,7 @@
         annotation_processor_additional_outputs = [],
         strict_deps = "ERROR",
         bootclasspath = None,
+        javabuilder_jvm_flags = None,
         sourcepath = [],
         resources = [],
         add_exports = [],
@@ -89,6 +90,7 @@
             'OFF', 'ERROR', 'WARN' and 'DEFAULT'.
         bootclasspath: (BootClassPathInfo) If present, overrides the bootclasspath associated with
             the provided java_toolchain. Optional.
+        javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder.
         sourcepath: ([File])
         resources: ([File])
         resource_jars: ([File])
@@ -111,15 +113,13 @@
     Returns:
         (JavaInfo)
     """
+    _java_common_internal.check_provider_instances([java_toolchain], "java_toolchain", JavaToolchainInfo)
     _java_common_internal.check_provider_instances(plugins, "plugins", JavaPluginInfo)
 
     plugin_info = merge_plugin_info_without_outputs(plugins + deps)
 
     all_javac_opts = []  # [depset[str]]
-    all_javac_opts.append(_java_common_internal.default_javac_opts(
-        java_toolchain = java_toolchain,
-        as_depset = True,
-    ))
+    all_javac_opts.append(java_toolchain._javacopts)
 
     all_javac_opts.append(ctx.fragments.java.default_javac_flags_depset)
     all_javac_opts.append(semantics.compatible_javac_options(
@@ -224,71 +224,46 @@
         compile_jar = output
         compile_deps_proto = None
 
-    if use_sharded_javac(ctx):
-        if compile_jar == output or not compile_jar:
-            fail("sharding requested without hjar/ijar compilation")
-        generated_source_jar = None
-        generated_class_jar = None
-        deps_proto = None
-        native_headers_jar = None
-        manifest_proto = None
-        experimental_sharded_javac(
-            ctx,
-            java_toolchain,
-            output,
-            compile_jar,
-            plugin_info,
-            compilation_classpath,
-            direct_jars,
-            bootclasspath,
-            compile_time_java_deps,
-            all_javac_opts,
-            strict_deps,
-            source_files,
-            source_jars,
-            resources,
-            resource_jars,
-        )
-    else:
-        native_headers_jar = helper.derive_output_file(ctx, output, name_suffix = "-native-header")
-        manifest_proto = helper.derive_output_file(ctx, output, extension_suffix = "_manifest_proto")
-        deps_proto = None
-        if ctx.fragments.java.generate_java_deps() and has_sources:
-            deps_proto = helper.derive_output_file(ctx, output, extension = "jdeps")
-        generated_class_jar = None
-        generated_source_jar = None
-        if uses_annotation_processing:
-            generated_class_jar = helper.derive_output_file(ctx, output, name_suffix = "-gen")
-            generated_source_jar = helper.derive_output_file(ctx, output, name_suffix = "-gensrc")
-        _java_common_internal.create_compilation_action(
-            ctx,
-            java_toolchain,
-            output,
-            manifest_proto,
-            plugin_info,
-            compilation_classpath,
-            direct_jars,
-            bootclasspath,
-            compile_time_java_deps,
-            all_javac_opts,
-            strict_deps,
-            ctx.label,
-            deps_proto,
-            generated_class_jar,
-            generated_source_jar,
-            native_headers_jar,
-            depset(source_files),
-            source_jars,
-            resources,
-            depset(resource_jars),
-            classpath_resources,
-            sourcepath,
-            injecting_rule_kind,
-            enable_jspecify,
-            enable_direct_classpath,
-            annotation_processor_additional_inputs,
-            annotation_processor_additional_outputs,
-        )
+    native_headers_jar = helper.derive_output_file(ctx, output, name_suffix = "-native-header")
+    manifest_proto = helper.derive_output_file(ctx, output, extension_suffix = "_manifest_proto")
+    deps_proto = None
+    if ctx.fragments.java.generate_java_deps() and has_sources:
+        deps_proto = helper.derive_output_file(ctx, output, extension = "jdeps")
+    generated_class_jar = None
+    generated_source_jar = None
+    if uses_annotation_processing:
+        generated_class_jar = helper.derive_output_file(ctx, output, name_suffix = "-gen")
+        generated_source_jar = helper.derive_output_file(ctx, output, name_suffix = "-gensrc")
+    _java_common_internal.create_compilation_action(
+        ctx,
+        java_toolchain,
+        output,
+        manifest_proto,
+        plugin_info,
+        compilation_classpath,
+        direct_jars,
+        bootclasspath,
+        depset(javabuilder_jvm_flags),
+        compile_time_java_deps,
+        all_javac_opts,
+        strict_deps,
+        ctx.label,
+        deps_proto,
+        generated_class_jar,
+        generated_source_jar,
+        native_headers_jar,
+        depset(source_files),
+        source_jars,
+        resources,
+        depset(resource_jars),
+        classpath_resources,
+        sourcepath,
+        injecting_rule_kind,
+        enable_jspecify,
+        enable_direct_classpath,
+        annotation_processor_additional_inputs,
+        annotation_processor_additional_outputs,
+    )
 
     create_output_source_jar = len(source_files) > 0 or source_jars != [output_source_jar]
     if not output_source_jar:
@@ -310,7 +285,7 @@
         direct_runtime_jars = []
 
     compilation_info = struct(
-        javac_options = all_javac_opts_list,
+        javac_options = all_javac_opts,
         javac_options_list = all_javac_opts_list,
         # needs to be flattened because the public API is a list
         boot_classpath = (bootclasspath.bootclasspath if bootclasspath else java_toolchain.bootclasspath).to_list(),
diff --git a/src/main/starlark/builtins_bzl/common/java/java_info.bzl b/src/main/starlark/builtins_bzl/common/java/java_info.bzl
index 1befe6e..d1936e3 100644
--- a/src/main/starlark/builtins_bzl/common/java/java_info.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/java_info.bzl
@@ -78,11 +78,13 @@
     },
 )
 
-_JavaCompilationInfo = provider(
+JavaCompilationInfo = provider(
     doc = "Compilation information in Java rules, for perusal of aspects and tools.",
     fields = {
         "boot_classpath": "Boot classpath for this Java target.",
-        "javac_options": "Options to the java compiler.",
+        "javac_options": """Depset of options to the java compiler. To get the
+            exact list of options passed to javac in the correct order, use the
+            tokenize_javacopts utility in rules_java""",
         "javac_options_list": """A list of options to java compiler. This exists
             temporarily for migration purposes. javac_options will return a depset
             in the future, and this method will be dropped once all usages have
@@ -92,14 +94,6 @@
     },
 )
 
-_EMPTY_COMPILATION_INFO = _JavaCompilationInfo(
-    compilation_classpath = depset(),
-    runtime_classpath = depset(),
-    boot_classpath = None,
-    javac_options = [],
-    javac_options_list = [],
-)
-
 def merge(
         providers,
         # private to @_builtins:
@@ -197,12 +191,12 @@
         )
     return _java_common_internal.wrap_java_info(_new_javainfo(**result))
 
-def to_java_binary_info(java_info):
+def to_java_binary_info(java_info, compilation_info):
     """Get a copy of the given JavaInfo with minimal info returned by a java_binary
 
     Args:
         java_info: (JavaInfo) A JavaInfo provider instance
-
+        compilation_info: (JavaCompilationInfo)
     Returns:
         (JavaInfo) A JavaInfo instance representing a java_binary target
     """
@@ -227,17 +221,6 @@
     if hasattr(java_info, "cc_link_params_info"):
         result.update(cc_link_params_info = java_info.cc_link_params_info)
 
-    compilation_info = _EMPTY_COMPILATION_INFO
-    if java_info.compilation_info:
-        compilation_info = java_info.compilation_info
-    elif java_info.transitive_compile_time_jars or java_info.transitive_runtime_jars:
-        compilation_info = _JavaCompilationInfo(
-            boot_classpath = None,
-            javac_options = [],
-            javac_options_list = [],
-            compilation_classpath = java_info.transitive_compile_time_jars,
-            runtime_classpath = java_info.transitive_runtime_jars,
-        )
     result["compilation_info"] = compilation_info
 
     java_outputs = [
@@ -471,8 +454,8 @@
     )
     if compilation_info:
         result.update(
-            compilation_info = _JavaCompilationInfo(
-                javac_options = _java_common_internal.intern_javac_opts(compilation_info.javac_options),
+            compilation_info = JavaCompilationInfo(
+                javac_options = compilation_info.javac_options,
                 javac_options_list = _java_common_internal.intern_javac_opts(compilation_info.javac_options_list),
                 boot_classpath = compilation_info.boot_classpath,
                 compilation_classpath = compilation_info.compilation_classpath,
diff --git a/src/main/starlark/builtins_bzl/common/java/java_library.bzl b/src/main/starlark/builtins_bzl/common/java/java_library.bzl
index f81e3a1a..89ca065 100644
--- a/src/main/starlark/builtins_bzl/common/java/java_library.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/java_library.bzl
@@ -17,7 +17,9 @@
 """
 
 load(":common/cc/cc_info.bzl", "CcInfo")
+load(":common/java/android_lint.bzl", "android_lint_subrule")
 load(":common/java/basic_java_library.bzl", "BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS", "basic_java_library", "construct_defaultinfo")
+load(":common/java/boot_class_path_info.bzl", "BootClassPathInfo")
 load(":common/java/java_info.bzl", "JavaInfo", "JavaPluginInfo")
 load(":common/java/java_semantics.bzl", "semantics")
 load(":common/rule_util.bzl", "merge_attrs")
@@ -35,7 +37,9 @@
         neverlink = False,
         proguard_specs = [],
         add_exports = [],
-        add_opens = []):
+        add_opens = [],
+        bootclasspath = None,
+        javabuilder_jvm_flags = None):
     """Implements java_library.
 
     Use this call when you need to produce a fully fledged java_library from
@@ -56,6 +60,8 @@
       proguard_specs: (list[File]) Files to be used as Proguard specification.
       add_exports: (list[str]) Allow this library to access the given <module>/<package>.
       add_opens: (list[str]) Allow this library to reflectively access the given <module>/<package>.
+      bootclasspath: (Target) The JDK APIs to compile this library against.
+      javabuilder_jvm_flags: (list[str]) Additional JVM flags to pass to JavaBuilder.
     Returns:
       (dict[str, provider]) A list containing DefaultInfo, JavaInfo,
         InstrumentedFilesInfo, OutputGroupsInfo, ProguardSpecProvider providers.
@@ -79,6 +85,8 @@
         proguard_specs = proguard_specs,
         add_exports = add_exports,
         add_opens = add_opens,
+        bootclasspath = bootclasspath,
+        javabuilder_jvm_flags = javabuilder_jvm_flags,
     )
 
     target["DefaultInfo"] = construct_defaultinfo(
@@ -108,6 +116,8 @@
         ctx.files.proguard_specs,
         ctx.attr.add_exports,
         ctx.attr.add_opens,
+        ctx.attr.bootclasspath,
+        ctx.attr.javabuilder_jvm_flags,
     ).values()
 
 JAVA_LIBRARY_IMPLICIT_ATTRS = BASIC_JAVA_LIBRARY_IMPLICIT_ATTRS
@@ -155,6 +165,11 @@
             providers = [JavaPluginInfo],
             cfg = "exec",
         ),
+        "bootclasspath": attr.label(
+            providers = [BootClassPathInfo],
+            flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+        ),
+        "javabuilder_jvm_flags": attr.string_list(),
         "javacopts": attr.string_list(),
         "neverlink": attr.bool(),
         "resource_strip_prefix": attr.string(),
@@ -180,6 +195,7 @@
         },
         fragments = ["java", "cpp"],
         toolchains = [semantics.JAVA_TOOLCHAIN],
+        subrules = [android_lint_subrule],
     )
 
 java_library = _make_java_library_rule()
diff --git a/src/main/starlark/builtins_bzl/common/java/java_plugin.bzl b/src/main/starlark/builtins_bzl/common/java/java_plugin.bzl
index ccf51b7..e0aaad7 100644
--- a/src/main/starlark/builtins_bzl/common/java/java_plugin.bzl
+++ b/src/main/starlark/builtins_bzl/common/java/java_plugin.bzl
@@ -16,11 +16,12 @@
 Definition of java_plugin rule.
 """
 
+load(":common/java/android_lint.bzl", "android_lint_subrule")
 load(":common/java/basic_java_library.bzl", "basic_java_library", "construct_defaultinfo")
-load(":common/java/java_library.bzl", "JAVA_LIBRARY_ATTRS", "JAVA_LIBRARY_IMPLICIT_ATTRS")
-load(":common/rule_util.bzl", "merge_attrs")
-load(":common/java/java_semantics.bzl", "semantics")
 load(":common/java/java_info.bzl", "JavaPluginInfo")
+load(":common/java/java_library.bzl", "JAVA_LIBRARY_ATTRS", "JAVA_LIBRARY_IMPLICIT_ATTRS")
+load(":common/java/java_semantics.bzl", "semantics")
+load(":common/rule_util.bzl", "merge_attrs")
 
 def bazel_java_plugin_rule(
         ctx,
@@ -138,4 +139,5 @@
     },
     fragments = ["java", "cpp"],
     toolchains = [semantics.JAVA_TOOLCHAIN],
+    subrules = [android_lint_subrule],
 )
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/TransitiveValidationPropagationTest.java b/src/test/java/com/google/devtools/build/lib/analysis/TransitiveValidationPropagationTest.java
index de0afb1..2685d74 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/TransitiveValidationPropagationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/TransitiveValidationPropagationTest.java
@@ -142,17 +142,17 @@
   @Test
   public void testTransitiveValidationOutputGroupNotAllowedForStarlarkRules() throws Exception {
     scratch.file(
-        "test/foo_rule.bzl",
+        "foobar/foo_rule.bzl",
         "def _impl(ctx):",
         "  return [OutputGroupInfo(_validation_transitive = depset())]",
         "foo_rule = rule(implementation = _impl)");
-    scratch.file("test/BUILD", "load('//test:foo_rule.bzl', 'foo_rule')", "foo_rule(name='foo')");
+    scratch.file("foobar/BUILD", "load('//foobar:foo_rule.bzl', 'foo_rule')", "foo_rule(name='foo')");
 
     AssertionError expected =
-        assertThrows(AssertionError.class, () -> getConfiguredTarget("//test:foo"));
+        assertThrows(AssertionError.class, () -> getConfiguredTarget("//foobar:foo"));
 
     assertThat(expected)
         .hasMessageThat()
-        .contains("//test:foo_rule.bzl cannot access the _transitive_validation private API");
+        .contains("//foobar:foo_rule.bzl cannot access the _transitive_validation private API");
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationStarlarkTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationStarlarkTest.java
index 824ee5e..14dd2c4 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationStarlarkTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildConfigurationStarlarkTest.java
@@ -90,8 +90,6 @@
 
     AssertionError e =
         assertThrows(AssertionError.class, () -> getConfiguredTarget("//example:custom"));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("file '//example:rule.bzl' cannot use private @_builtins API");
+    assertThat(e).hasMessageThat().contains("file '//example:rule.bzl' cannot use private API");
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/java/JavaConfiguredTargetsTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/java/JavaConfiguredTargetsTest.java
index 8e86933..509daee 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/rules/java/JavaConfiguredTargetsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/java/JavaConfiguredTargetsTest.java
@@ -103,71 +103,6 @@
     }
   }
 
-  @Test
-  public void experimentalShardedJavaLibrary_succeeds() throws Exception {
-    setBuildLanguageOptions("--experimental_java_library_export");
-    scratch.file(
-        "foo/rule.bzl",
-        //
-        "java_library = experimental_java_library_export_do_not_use.sharded_java_library(",
-        "  default_shard_size = 10",
-        ")");
-    scratch.file(
-        "foo/BUILD",
-        "load(':rule.bzl', 'java_library')",
-        "",
-        "java_library(",
-        "  name = 'lib1',",
-        "  srcs = ['1.java', '2.java', '3.java'],",
-        "  experimental_javac_shard_size = 1,",
-        ")",
-        "java_library(",
-        "  name = 'lib2',",
-        "  srcs = ['1.java', '2.java', '3.java'],",
-        "  experimental_javac_shard_size = 2,",
-        ")");
-
-    ImmutableList<Action> compileActionsWithShardSize1 =
-        getActions("//foo:lib1", JavaCompileAction.class);
-    ImmutableList<Action> compileActionsWithShardSize2 =
-        getActions("//foo:lib2", JavaCompileAction.class);
-
-    assertThat(compileActionsWithShardSize1).hasSize(3);
-    assertThat(compileActionsWithShardSize2).hasSize(2);
-  }
-
-  // regression test for b/297356812#comment31
-  @Test
-  public void experimentalShardedJavaLibrary_allOutputsHaveUniqueNames() throws Exception {
-    setBuildLanguageOptions("--experimental_java_library_export");
-    scratch.file(
-        "foo/rule.bzl",
-        //
-        "java_library = experimental_java_library_export_do_not_use.sharded_java_library(",
-        "  default_shard_size = 1",
-        ")");
-    scratch.file(
-        "foo/BUILD",
-        "load(':rule.bzl', 'java_library')",
-        "",
-        "java_library(",
-        "  name = 'lib',",
-        "  srcs = ['1.java', '2.java', '3.java'],",
-        ")");
-
-    ImmutableList<Artifact> outputs =
-        getActions("//foo:lib", JavaCompileAction.class).stream()
-            .map(ActionAnalysisMetadata::getPrimaryOutput)
-            .collect(toImmutableList());
-    ImmutableSet<String> uniqueFilenamesWithoutExtension =
-        outputs.stream()
-            .map(file -> MoreFiles.getNameWithoutExtension(file.getPath().getPathFile().toPath()))
-            .collect(toImmutableSet());
-
-    assertThat(outputs).hasSize(3);
-    assertThat(uniqueFilenamesWithoutExtension).hasSize(3);
-  }
-
   // regression test for https://github.com/bazelbuild/bazel/issues/20378
   @Test
   public void javaTestInvalidTestClassAtRootPackage() throws Exception {
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java
index ff3a7b1..abcea64 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CcToolchainProviderTest.java
@@ -741,23 +741,23 @@
   @Test
   public void testDwpFilesIsBlocked() throws Exception {
     scratch.file(
-        "test/dwp_files_rule.bzl",
+        "foobar/dwp_files_rule.bzl",
         "def _impl(ctx):",
         "  cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]",
         "  cc_toolchain.dwp_files()",
         "  return []",
         "dwp_files_rule = rule(",
         "  implementation = _impl,",
-        "  attrs = {'_cc_toolchain':" + " attr.label(default=Label('//test:alias'))},",
+        "  attrs = {'_cc_toolchain':" + " attr.label(default=Label('//foobar:alias'))},",
         ")");
     scratch.file(
-        "test/BUILD",
+        "foobar/BUILD",
         "load(':dwp_files_rule.bzl', 'dwp_files_rule')",
         "cc_toolchain_alias(name='alias')",
         "dwp_files_rule(name = 'target')");
     reporter.removeHandler(failFastHandler);
 
-    getConfiguredTarget("//test:target");
+    getConfiguredTarget("//foobar:target");
 
     assertContainsEvent("cannot use private API");
   }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationStarlarkTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationStarlarkTest.java
index d9dd2df..3146f75 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationStarlarkTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppConfigurationStarlarkTest.java
@@ -99,8 +99,7 @@
     assertThat(e)
         .hasMessageThat()
         .contains(
-            String.format(
-                "cannot use private @_builtins API (feature '%s' in CppConfiguration)", feature));
+            String.format("cannot use private API (feature '%s' in CppConfiguration)", feature));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java
index dd5c542..eb68e56 100755
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/StarlarkCcCommonTest.java
@@ -7684,9 +7684,7 @@
     AssertionError e =
         assertThrows(AssertionError.class, () -> getConfiguredTarget("//foo:custom"));
 
-    assertThat(e)
-        .hasMessageThat()
-        .contains("file '//foo:custom_rule.bzl' cannot use private @_builtins API");
+    assertThat(e).hasMessageThat().contains("file '//foo:custom_rule.bzl' cannot use private API");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionTestHelper.java b/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionTestHelper.java
index c355908..b10cb7b 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionTestHelper.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/java/JavaCompileActionTestHelper.java
@@ -86,10 +86,6 @@
     return getOptions(javac).getSourcePath();
   }
 
-  public static List<String> getBootclasspath(JavaCompileAction javac) throws Exception {
-    return getOptions(javac).getBootClassPath();
-  }
-
   /** Returns the JavaBuilder command line, up to the main class or deploy jar. */
   public static List<String> getJavacCommand(JavaCompileAction action) throws Exception {
     List<String> args = action.getCommandLines().allArguments();
diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/JavaInfoStarlarkApiTest.java b/src/test/java/com/google/devtools/build/lib/rules/java/JavaInfoStarlarkApiTest.java
index 5ec8324..36a3835 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/java/JavaInfoStarlarkApiTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/java/JavaInfoStarlarkApiTest.java
@@ -1110,7 +1110,10 @@
                 "compilation_info",
                 makeStruct(
                     ImmutableMap.of(
-                        "javac_options", StarlarkList.immutableOf("opt1", "opt2"),
+                        "javac_options",
+                            Depset.of(
+                                String.class,
+                                NestedSetBuilder.create(Order.NAIVE_LINK_ORDER, "opt1", "opt2")),
                         "boot_classpath", StarlarkList.immutableOf(createArtifact("cp.jar")))))
             .buildOrThrow();
     StarlarkInfo starlarkInfo = makeStruct(fields);
@@ -1119,7 +1122,7 @@
 
     assertThat(javaInfo).isNotNull();
     assertThat(javaInfo.getCompilationInfoProvider()).isNotNull();
-    assertThat(javaInfo.getCompilationInfoProvider().getJavacOpts())
+    assertThat(javaInfo.getCompilationInfoProvider().getJavacOptsList())
         .containsExactly("opt1", "opt2");
     assertThat(javaInfo.getCompilationInfoProvider().getBootClasspathList()).hasSize(1);
     assertThat(prettyArtifactNames(javaInfo.getCompilationInfoProvider().getBootClasspathList()))
@@ -1138,13 +1141,16 @@
             ImmutableMap.of(
                 "compilation_classpath", Depset.of(Artifact.class, compilationClasspath),
                 "runtime_classpath", Depset.of(Artifact.class, runtimeClasspath),
-                "javac_options", StarlarkList.immutableOf("opt1", "opt2"),
+                "javac_options",
+                    Depset.of(
+                        String.class,
+                        NestedSetBuilder.create(Order.NAIVE_LINK_ORDER, "opt1", "opt2")),
                 "boot_classpath", StarlarkList.immutableOf(bootClasspathArtifact)));
     JavaCompilationInfoProvider nativeCompilationInfo =
         new JavaCompilationInfoProvider.Builder()
             .setCompilationClasspath(compilationClasspath)
             .setRuntimeClasspath(runtimeClasspath)
-            .setJavacOpts(ImmutableList.of("opt1", "opt2"))
+            .setJavacOpts(NestedSetBuilder.create(Order.NAIVE_LINK_ORDER, "opt1", "opt2"))
             .setBootClasspath(
                 NestedSetBuilder.create(Order.NAIVE_LINK_ORDER, bootClasspathArtifact))
             .build();
diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java b/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java
index 912085e..4f705ac 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/java/JavaStarlarkApiTest.java
@@ -680,7 +680,7 @@
     assertThat(prettyArtifactNames(compilationInfo.getRuntimeClasspath().toList(Artifact.class)))
         .containsExactly("java/test/libdep.jar", "java/test/libcustom.jar");
 
-    assertThat(compilationInfo.getJavacOpts()).contains("-XDone");
+    assertThat(compilationInfo.getJavacOptsList()).contains("-XDone");
   }
 
   @Test
@@ -2182,7 +2182,7 @@
             prettyArtifactNames(
                 javaCompilationInfoProvider.getRuntimeClasspath().getSet(Artifact.class)))
         .containsExactly("foo/libmy_java_lib_a.jar");
-    assertThat(javaCompilationInfoProvider.getJavacOpts()).contains("opt1");
+    assertThat(javaCompilationInfoProvider.getJavacOpts().toList()).contains("opt1");
     assertThat(javaCompilationInfoProvider.getJavacOptsList()).contains("opt1");
   }
 
@@ -2953,7 +2953,7 @@
 
     reporter.removeHandler(failFastHandler);
     getConfiguredTarget("//a:r");
-    assertContainsEvent("got value of type 'ToolchainInfo', want 'JavaToolchainInfo'");
+    assertContainsEvent("got element of type ToolchainInfo, want JavaToolchainInfo");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcStarlarkTest.java b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcStarlarkTest.java
index 7eebb14..88873aec1 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcStarlarkTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/objc/ObjcStarlarkTest.java
@@ -1996,7 +1996,7 @@
 
     getConfiguredTarget("//foo:myrule");
 
-    assertContainsEvent("file '//foo:rule.bzl' cannot use private @_builtins API");
+    assertContainsEvent("file '//foo:rule.bzl' cannot use private API");
   }
 
   @Test
@@ -2015,7 +2015,7 @@
 
     getConfiguredTarget("//foo:myrule");
 
-    assertContainsEvent("file '//foo:rule.bzl' cannot use private @_builtins API");
+    assertContainsEvent("file '//foo:rule.bzl' cannot use private API");
   }
 
   @Test
@@ -2035,7 +2035,7 @@
 
     getConfiguredTarget("//foo:myrule");
 
-    assertContainsEvent("file '//foo:rule.bzl' cannot use private @_builtins API");
+    assertContainsEvent("file '//foo:rule.bzl' cannot use private API");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
index 8ab7a0f..3c00892 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
@@ -1145,7 +1145,7 @@
   public void testInstrumentedFilesInfo_coverageSupportAndEnvVarsArePrivateAPI() throws Exception {
     // Arrange
     scratch.file(
-        "test/starlark/extension.bzl",
+        "foobar/starlark/extension.bzl",
         "",
         "def custom_rule_impl(ctx):",
         "  return [",
@@ -1163,8 +1163,8 @@
         "  },",
         ")");
     scratch.file(
-        "test/starlark/BUILD",
-        "load('//test/starlark:extension.bzl', 'custom_rule')",
+        "foobar/starlark/BUILD",
+        "load('//foobar/starlark:extension.bzl', 'custom_rule')",
         "",
         "custom_rule(",
         "  name = 'foo',",
@@ -1173,10 +1173,10 @@
     reporter.removeHandler(failFastHandler);
 
     // Act
-    getConfiguredTarget("//test/starlark:foo");
+    getConfiguredTarget("//foobar/starlark:foo");
 
     // Assert
-    assertContainsEvent("file '//test/starlark:extension.bzl' cannot use private @_builtins API");
+    assertContainsEvent("file '//foobar/starlark:extension.bzl' cannot use private API");
   }
 
   @Test
diff --git a/tools/build_defs/build_info/BUILD b/tools/build_defs/build_info/BUILD
index 5e42573..2490c70 100644
--- a/tools/build_defs/build_info/BUILD
+++ b/tools/build_defs/build_info/BUILD
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 load(":bazel_cc_build_info.bzl", "bazel_cc_build_info")
+load(":bazel_java_build_info.bzl", "bazel_java_build_info")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -20,6 +21,10 @@
     name = "cc_build_info",
 )
 
+bazel_java_build_info(
+    name = "java_build_info",
+)
+
 filegroup(
     name = "embedded_tools",
     srcs = glob(["*"]),
diff --git a/tools/build_defs/build_info/BUILD.tools b/tools/build_defs/build_info/BUILD.tools
index 9053be4..b37799a 100644
--- a/tools/build_defs/build_info/BUILD.tools
+++ b/tools/build_defs/build_info/BUILD.tools
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 load(":bazel_cc_build_info.bzl", "bazel_cc_build_info")
+load(":bazel_java_build_info.bzl", "bazel_java_build_info")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -20,6 +21,10 @@
     name = "cc_build_info",
 )
 
+bazel_java_build_info(
+    name = "java_build_info",
+)
+
 filegroup(
     name = "bzl_srcs",
     srcs = glob(["*.bzl"]),
diff --git a/tools/build_defs/build_info/bazel_java_build_info.bzl b/tools/build_defs/build_info/bazel_java_build_info.bzl
new file mode 100644
index 0000000..c5bdf93
--- /dev/null
+++ b/tools/build_defs/build_info/bazel_java_build_info.bzl
@@ -0,0 +1,109 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""A rule for getting transliterated build info files for Java."""
+
+def _transform_date_string(date, timestamp):
+    # Date should always be in "yyyy MMM d HH mm ss EEE" format.
+    # For example: 2023 Jul 26 01 58 54 Fri
+    tokens = date.split(" ")
+    if len(tokens) != 7:
+        fail("date string does not have a proper format: " + date + "\nExpected: yyyy MMM d HH mm ss EEE(2023 Jul 26 01 58 54 Fri)")
+    date_format = "{day} {month} {day_num} {hour}:{minute}:{second} {year} ({timestamp})"
+    return date_format.format(
+        day = tokens[6],
+        month = tokens[1],
+        day_num = tokens[2],
+        hour = tokens[3],
+        minute = tokens[4],
+        second = tokens[5],
+        year = tokens[0],
+        timestamp = timestamp,
+    )
+
+def _add_backslashes_to_string(value):
+    tokens = value.split(":")
+    return "\\:".join(tokens)
+
+def _add_backslashes_to_dict_values(substitutions):
+    # We are doing this because the values of substitutions dict
+    # is written to .properties file for Java.
+    for k, v in substitutions.items():
+        substitutions[k] = _add_backslashes_to_string(v)
+    return substitutions
+
+def _transform_version(version_file_contents):
+    # We assume that FORMATTED_DATE and BUILD_TIMESTAMP are always present
+    # in the workspace status file.
+    if "BUILD_TIMESTAMP" not in version_file_contents.keys() or "FORMATTED_DATE" not in version_file_contents.keys():
+        fail("timestamp information is missing from workspace status file, BUILD_TIMESTAMP or FORMATTED_DATE keys not found.")
+    substitutions = {
+        "{build.time}": _transform_date_string(version_file_contents["FORMATTED_DATE"], version_file_contents["BUILD_TIMESTAMP"]),
+        "{build.timestamp}": version_file_contents["BUILD_TIMESTAMP"],
+        "{build.timestamp.as.int}": version_file_contents["BUILD_TIMESTAMP"],
+    }
+    return _add_backslashes_to_dict_values(substitutions)
+
+def _transform_info(info_file_contents):
+    return _add_backslashes_to_dict_values({"{build.label}": info_file_contents.get("BUILD_EMBED_LABEL", "")})
+
+def _get_build_info_files(actions, version_template, info_template, redacted_file, stamp):
+    outputs = []
+    if stamp:
+        version_file = actions.transform_version_file(transform_func = _transform_version, template = version_template, output_file_name = "volatile_file.properties")
+        info_file = actions.transform_info_file(transform_func = _transform_info, template = info_template, output_file_name = "non_volatile_file.properties")
+        outputs.append(info_file)
+        outputs.append(version_file)
+    else:
+        output_redacted_file = actions.declare_file("redacted_file.properties")
+        actions.symlink(output = output_redacted_file, target_file = redacted_file)
+        outputs.append(output_redacted_file)
+    return outputs
+
+def _impl(ctx):
+    output_groups = {
+        "non_redacted_build_info_files": depset(_get_build_info_files(
+            ctx.actions,
+            ctx.file._version_template,
+            ctx.file._info_template,
+            ctx.file._redacted_file,
+            True,
+        )),
+        "redacted_build_info_files": depset(_get_build_info_files(
+            ctx.actions,
+            ctx.file._version_template,
+            ctx.file._info_template,
+            ctx.file._redacted_file,
+            False,
+        )),
+    }
+    return OutputGroupInfo(**output_groups)
+
+bazel_java_build_info = rule(
+    implementation = _impl,
+    attrs = {
+        "_version_template": attr.label(
+            default = "@bazel_tools//tools/build_defs/build_info/templates:volatile_file.properties.template",
+            allow_single_file = True,
+        ),
+        "_info_template": attr.label(
+            default = "@bazel_tools//tools/build_defs/build_info/templates:non_volatile_file.properties.template",
+            allow_single_file = True,
+        ),
+        "_redacted_file": attr.label(
+            default = "@bazel_tools//tools/build_defs/build_info/templates:redacted_file.properties.template",
+            allow_single_file = True,
+        ),
+    },
+)
diff --git a/tools/build_defs/build_info/templates/BUILD b/tools/build_defs/build_info/templates/BUILD
index a355fdd..85dab06 100644
--- a/tools/build_defs/build_info/templates/BUILD
+++ b/tools/build_defs/build_info/templates/BUILD
@@ -17,6 +17,9 @@
         "non_volatile_file.h.template",
         "volatile_file.h.template",
         "redacted_file.h.template",
+        "non_volatile_file.properties.template",
+        "volatile_file.properties.template",
+        "redacted_file.properties.template",
     ],
     visibility = [
         "@bazel_tools//tools/build_defs/build_info:__pkg__",
diff --git a/tools/build_defs/build_info/templates/BUILD.tools b/tools/build_defs/build_info/templates/BUILD.tools
index ddbcab3..996d17d 100644
--- a/tools/build_defs/build_info/templates/BUILD.tools
+++ b/tools/build_defs/build_info/templates/BUILD.tools
@@ -17,6 +17,9 @@
         "non_volatile_file.h.template",
         "volatile_file.h.template",
         "redacted_file.h.template",
+        "non_volatile_file.properties.template",
+        "volatile_file.properties.template",
+        "redacted_file.properties.template",
     ],
     visibility = [
         "//tools/build_defs/build_info:__pkg__",
diff --git a/tools/build_defs/build_info/templates/non_volatile_file.properties.template b/tools/build_defs/build_info/templates/non_volatile_file.properties.template
new file mode 100644
index 0000000..eb96e20
--- /dev/null
+++ b/tools/build_defs/build_info/templates/non_volatile_file.properties.template
@@ -0,0 +1 @@
+build.label={build.label}
diff --git a/tools/build_defs/build_info/templates/redacted_file.properties.template b/tools/build_defs/build_info/templates/redacted_file.properties.template
new file mode 100644
index 0000000..ab107f1
--- /dev/null
+++ b/tools/build_defs/build_info/templates/redacted_file.properties.template
@@ -0,0 +1,3 @@
+build.time=Thu Jan 01 00\:00\:00 1970 (0)
+build.timestamp=Thu Jan 01 00\:00\:00 1970 (0)
+build.timestamp.as.int=0
diff --git a/tools/build_defs/build_info/templates/volatile_file.properties.template b/tools/build_defs/build_info/templates/volatile_file.properties.template
new file mode 100644
index 0000000..1724234
--- /dev/null
+++ b/tools/build_defs/build_info/templates/volatile_file.properties.template
@@ -0,0 +1,3 @@
+build.time={build.time}
+build.timestamp={build.timestamp}
+build.timestamp.as.int={build.timestamp.as.int}