Goal
Remove the experimental JavaInterop1 code-generation target and its runtime support. XAJavaInterop1 should be the only supported legacy Android binding/JCW target. The trimmable typemap Java generator remains a separate, targetless pipeline and should not be renamed to another AndroidCodegenTarget value.
Important scope distinction
There are two independent JavaInterop1 switches:
CodeGenerationTarget.JavaInterop1 controls the Java.Interop generator's generated C# binding shape.
JavaPeerStyle.JavaInterop1 controls Java Callable Wrapper (JCW) generation.
The trimmable typemap does not consume either enum. It generates its own Java sources and registers natives through TrimmableTypeMap.RegisterNativeMethods() and mono.android.Runtime.registerNatives(Class).
Current state and prerequisite
PR #12133:
- switches NativeAOT from
JavaPeerStyle.JavaInterop1 to XAJavaInterop1;
- makes the shared
_AndroidJcwCodegenTarget=XAJavaInterop1 default apply to NativeAOT;
- rejects
XamarinAndroid and JavaInterop1 through _AndroidJcwCodegenTarget;
- removes the NativeAOT-only
net.dot.jni.GCUserPeerable bridge path.
After that PR lands, no supported dotnet/android build path should generate JavaInterop1-style JCWs. Java.Interop's standalone generator, JCW emitter, SDK, tests, and expected-output baselines will still contain the legacy implementation and need separate cleanup.
Findings
The direct textual footprint is 123 JavaInterop1 references across 55 files, almost entirely under external/Java.Interop. Including target-specific expected output and conditional test scaffolding, the logical footprint is about 351 files:
- 305 Java.Interop test/support/baseline files;
- 40 production generator files;
- 3 JCW library files;
- 1 Java.Interop SDK file;
- 1 outer Android build default;
- 1 documentation file.
ManagedPeer
JavaPeerStyle.JavaInterop1 JCWs use:
net.dot.jni.ManagedPeer.registerNativeMembers(...)
net.dot.jni.ManagedPeer.construct(...)
XAJavaInterop1 JCWs instead use the mono.android.Runtime registration/activation path. Once PR #12133 lands and the standalone JavaInterop1 JCW emitter is disabled, ManagedPeer is no longer needed as a general JCW bridge.
ManagedPeer also currently bootstraps two built-in Java.Interop proxy classes:
JavaProxyObject calls ManagedPeer.registerNativeMembers() to register native equals, hashCode, and toString callbacks.
JavaProxyThrowable calls ManagedPeer.registerNativeMembers() with an empty method string, but its managed type has no native-registration callback. The call registers nothing and can be removed without replacement.
The trimmable typemap's TrimmableJavaProxyObject is not a replacement for the normal Java.Interop proxy contract. It intentionally uses Java identity semantics and has no native methods. The normal JavaProxyObject delegates equality, hash code, and string conversion to the wrapped managed object; that behavior should remain for standalone/non-trimmable Java.Interop.
Using an Android [Register] class and app JCW generation for core Java.Interop is not a good fit:
- Java.Interop is standalone/JVM-agnostic and builds
java-interop.jar itself.
- The core
Java.Interop project does not include the Android RegisterAttribute.
- Depending on the dotnet/android JCW pipeline would introduce an Android-specific/bootstrap dependency.
The smallest behavior-preserving replacement is for managed JavaProxyObject to register its three native methods lazily, immediately before constructing the first proxy instance, while retaining the existing handwritten Java class. The Java static initializer that calls ManagedPeer can then be removed. The trimmable identity proxy remains separate.
Revised implementation plan
1. Land the supported-consumer migration
2. Decouple built-in proxies from ManagedPeer
3. Remove remaining ManagedPeer consumers
This phase can happen before removing the much larger CodeGenerationTarget.JavaInterop1 C# generator implementation.
4. Delete ManagedPeer and its compatibility plumbing
JniRuntime.CreationOptions.JniAddNativeMethodRegistrationAttributePresent is broader than ManagedPeer and should remain.
5. Remove CodeGenerationTarget.JavaInterop1
Compatibility decision
Do not retain a runtime ManagedPeer compatibility shim. Once JavaInterop1 is rejected and removed, previously generated or handwritten Java bytecode that references ManagedPeer is unsupported. Issue #11635 can be resolved in that direction; optional build-time detection may provide a clearer diagnostic, but should not keep the runtime bridge.
Validation
- Build and run the Java.Interop runtime, generator, and JCW test suites.
- Add/retain focused coverage that normal
JavaProxyObject Java calls delegate equals, hashCode, and toString to the wrapped managed object.
- Retain exception-transition coverage for
JavaProxyThrowable.
- Run dotnet/android build tests that reject legacy JCW targets.
- Run representative non-trimmable CoreCLR/MonoVM device tests.
- Run trimmable CoreCLR and NativeAOT build/device tests.
- Inspect produced jars/APKs to confirm
net/dot/jni/ManagedPeer.class is absent.
- Confirm repository searches contain no
ManagedPeerNativeRegistration, ManagedPeer.construct, or ManagedPeer.registerNativeMembers references.
Completion criteria
XAJavaInterop1 is the only supported AndroidCodegenTarget/legacy JCW shape.
- The trimmable typemap remains an independent Java-generation path.
ManagedPeer and ManagedPeerNativeRegistration no longer exist.
- Built-in proxy marshalling continues to work without ManagedPeer.
CodeGenerationTarget.JavaInterop1, JavaPeerStyle.JavaInterop1, and all associated baselines/scaffolding are removed.
Goal
Remove the experimental
JavaInterop1code-generation target and its runtime support.XAJavaInterop1should be the only supported legacy Android binding/JCW target. The trimmable typemap Java generator remains a separate, targetless pipeline and should not be renamed to anotherAndroidCodegenTargetvalue.Important scope distinction
There are two independent
JavaInterop1switches:CodeGenerationTarget.JavaInterop1controls the Java.Interop generator's generated C# binding shape.JavaPeerStyle.JavaInterop1controls Java Callable Wrapper (JCW) generation.The trimmable typemap does not consume either enum. It generates its own Java sources and registers natives through
TrimmableTypeMap.RegisterNativeMethods()andmono.android.Runtime.registerNatives(Class).Current state and prerequisite
PR #12133:
JavaPeerStyle.JavaInterop1toXAJavaInterop1;_AndroidJcwCodegenTarget=XAJavaInterop1default apply to NativeAOT;XamarinAndroidandJavaInterop1through_AndroidJcwCodegenTarget;net.dot.jni.GCUserPeerablebridge path.After that PR lands, no supported dotnet/android build path should generate JavaInterop1-style JCWs. Java.Interop's standalone generator, JCW emitter, SDK, tests, and expected-output baselines will still contain the legacy implementation and need separate cleanup.
Findings
The direct textual footprint is 123
JavaInterop1references across 55 files, almost entirely underexternal/Java.Interop. Including target-specific expected output and conditional test scaffolding, the logical footprint is about 351 files:ManagedPeer
JavaPeerStyle.JavaInterop1JCWs use:XAJavaInterop1JCWs instead use themono.android.Runtimeregistration/activation path. Once PR #12133 lands and the standalone JavaInterop1 JCW emitter is disabled,ManagedPeeris no longer needed as a general JCW bridge.ManagedPeeralso currently bootstraps two built-in Java.Interop proxy classes:JavaProxyObjectcallsManagedPeer.registerNativeMembers()to register nativeequals,hashCode, andtoStringcallbacks.JavaProxyThrowablecallsManagedPeer.registerNativeMembers()with an empty method string, but its managed type has no native-registration callback. The call registers nothing and can be removed without replacement.The trimmable typemap's
TrimmableJavaProxyObjectis not a replacement for the normal Java.Interop proxy contract. It intentionally uses Java identity semantics and has no native methods. The normalJavaProxyObjectdelegates equality, hash code, and string conversion to the wrapped managed object; that behavior should remain for standalone/non-trimmable Java.Interop.Using an Android
[Register]class and app JCW generation for core Java.Interop is not a good fit:java-interop.jaritself.Java.Interopproject does not include the AndroidRegisterAttribute.The smallest behavior-preserving replacement is for managed
JavaProxyObjectto register its three native methods lazily, immediately before constructing the first proxy instance, while retaining the existing handwritten Java class. The Java static initializer that callsManagedPeercan then be removed. The trimmable identity proxy remains separate.Revised implementation plan
1. Land the supported-consumer migration
XAJavaInterop1for the legacy JCW pipeline._AndroidJcwCodegenTarget.2. Decouple built-in proxies from ManagedPeer
ManagedPeer.registerNativeMembers()static initializer from JavaJavaProxyObject.equals,hashCode, andtoStringimmediately before the first proxy allocation.JniType.RegisterNativeMethods()mechanism.ManagedPeer.registerNativeMembers()static initializer from JavaJavaProxyThrowable.JavaProxyObjectandJavaProxyThrowablemarshalling types.3. Remove remaining ManagedPeer consumers
JavaPeerStyle.JavaInterop1in the standalone JCW generator.ManagedPeer.registerNativeMembers()orManagedPeer.construct().ManagedPeer.construct().net/dot/jni/ManagedPeer.This phase can happen before removing the much larger
CodeGenerationTarget.JavaInterop1C# generator implementation.4. Delete ManagedPeer and its compatibility plumbing
Java.Interop.ManagedPeer.net.dot.jni.ManagedPeer.javaand remove it fromjava-interop.jar.Java.Interop.RuntimeFeature.ManagedPeerNativeRegistration.ManagedPeer.Init()and built-in type-manager mappings.ManagedPeerNativeRegistration.net.dot.jni.ManagedPeer.Java.Interop.ManagedPeer.JniRuntime.CreationOptions.JniAddNativeMethodRegistrationAttributePresentis broader than ManagedPeer and should remain.5. Remove
CodeGenerationTarget.JavaInterop1javainterop1CLI option.XAJavaInterop1behavior.JAVA_INTEROP1,TODO_JAVA_INTEROP1,TryJavaInterop1, and related conditional test support.out.ji,expected.ji, andCodeGeneratorExpectedResults/JavaInterop1baselines.JavaInterop1.Compatibility decision
Do not retain a runtime
ManagedPeercompatibility shim. OnceJavaInterop1is rejected and removed, previously generated or handwritten Java bytecode that referencesManagedPeeris unsupported. Issue #11635 can be resolved in that direction; optional build-time detection may provide a clearer diagnostic, but should not keep the runtime bridge.Validation
JavaProxyObjectJava calls delegateequals,hashCode, andtoStringto the wrapped managed object.JavaProxyThrowable.net/dot/jni/ManagedPeer.classis absent.ManagedPeerNativeRegistration,ManagedPeer.construct, orManagedPeer.registerNativeMembersreferences.Completion criteria
XAJavaInterop1is the only supportedAndroidCodegenTarget/legacy JCW shape.ManagedPeerandManagedPeerNativeRegistrationno longer exist.CodeGenerationTarget.JavaInterop1,JavaPeerStyle.JavaInterop1, and all associated baselines/scaffolding are removed.