Skip to content

[Java.Interop] Remove reflection-backed JNI managers - #13018

Merged
simonrozsival merged 4 commits into
mainfrom
simonrozsival-remove-reflection-jni-managers
Oct 6, 2026
Merged

simonrozsival merged 4 commits into
mainfrom
simonrozsival-remove-reflection-jni-managers

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

The Android production runtime uses the trimmable typemap managers rather than Java.Interop.JniRuntime.ReflectionJniValueManager or Java.Interop.JniRuntime.ReflectionJniTypeManager. Following the removal of JVM-only test runs in #12894, remove these unused production implementations and their remaining host-JVM consumers instead of retaining reflection-based runtime infrastructure solely for dormant tests.

Remove both managers, their generated helper tables and matching T4 templates, unshipped public API declarations, and obsolete suppressions. Delete the standalone TestJVM harness and host interop test project, while retaining the shared test sources compiled into the Android test assembly. Remove JniProxyRuntime, its custom manager test doubles, and its sole dependent test, JniRuntimeTest.Dispose_ClearsJniEnvironment, rather than preserving a second runtime implementation for that test. Delete the now-unnecessary JavaVMFixture base class and its remaining inheritance clauses, together with the always-skipped SafeHandle-dependent JniTransitionTests.Dispose_ClearsLocalReferences test. The remaining runtime tests use the active Android runtime. Update the solution, Android test project, and test documentation accordingly.

This branch is rebased onto main after #12894 merged. No issue is closed by this cleanup.

Validation

  • Passed after rebasing and again after removing the proxy runtime: ./dotnet-local.sh build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -c Debug -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar — zero warnings/errors.
  • Passed after rebasing: ./dotnet-local.sh build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -c Release -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar — zero warnings/errors.
  • Passed: git diff --check origin/main...HEAD; repository search found no remaining references to the removed managers, projects, proxy runtime, shared JVM fixture, capability flags, or dependent disposal tests.
  • Blocked: make prepare — NU1102: configured feeds could not restore Microsoft.NETCore.App.Ref version 10.0.13; full SDK build and device tests were not reached.
  • Blocked, including a fresh attempt after removing the shared fixture: ./dotnet-local.sh build tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj -c Debug -v quiet — NETSDK1147: the local Android workload is unavailable. The remaining interop tests have not been executed on Android.

Pull Request
title and
description
should follow the
commit-messages.md workflow documentation, and in particular should include:

  • Useful description of why the change is necessary.
  • Links to issues fixed
  • Unit tests

The Android runtime uses the trimmable typemap managers, and the
host-JVM test runs have been removed.  Delete the remaining reflection
managers, their generated helper tables, and the dormant JVM harness.

Keep runtime-disposal coverage with non-reflection test doubles, remove
unshipped API declarations and suppressions, and update test guidance.

Context: #12894

Co-authored-by: Copilot App <[email protected]>
Copilot AI balanced review requested due to automatic review settings October 6, 2026 11:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Full SDK validation remains blocked, and the modified runtime-disposal test has not been executed on Android.

Review effort: Balanced
Findings: None

What changed in this PR

Removes unused reflection-backed JNI infrastructure following #12894’s retirement of host-JVM tests, while retaining shared Android test coverage.

Changes:

  • Removes reflection managers, supporting tables, unshipped APIs, and obsolete suppressions.
  • Deletes host-JVM projects and adapts the runtime-disposal fixture.
  • Updates solution entries and test documentation.
File Description
tests/​Mono.Android-Tests/​Java.Interop-Tests/​Java.Interop-Tests.NET.csproj Removes obsolete source exclusion.
external/​Java.Interop/​tests/​TestJVM/​TestJVM.csproj Deletes host-JVM harness project.
external/​Java.Interop/​tests/​TestJVM/​TestJVM.cs Deletes host-JVM runtime harness.
external/​Java.Interop/​tests/​Java.Interop-Tests/​Java.Interop/​JniRuntimeTest.cs Replaces reflection managers with disposal-only test support.
external/​Java.Interop/​tests/​Java.Interop-Tests/​Java.Interop/​JavaVMFixture.Partial.cs Removes host-JVM initialization.
external/​Java.Interop/​tests/​Java.Interop-Tests/​Java.Interop/​JavaVMFixture.cs Deletes host-specific fixture implementation.
external/​Java.Interop/​tests/​Java.Interop-Tests/​Java.Interop-Tests.csproj Deletes standalone host test project.
external/​Java.Interop/​src/​Java.Interop/​PublicAPI.Unshipped.txt Removes deleted manager declarations.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JniRuntime.ReflectionJniValueManager.cs Deletes reflection-backed value manager.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JniRuntime.ReflectionJniTypeManager.cs Deletes reflection-backed type manager.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JniRuntime.JniTypeManager.cs Updates registration comment only.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JniObjectReferenceOptions.cs Removes reflection-only constant and obsolete comment.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JniBuiltinMarshalers.tt Removes obsolete table generation.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JniBuiltinMarshalers.cs Removes generated reflection lookup tables.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JavaPrimitiveArrays.tt Removes reflection array-table generation.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JavaPrimitiveArrays.cs Removes generated reflection array tables.
external/​Java.Interop/​src/​Java.Interop/​GlobalSuppressions.cs Removes suppressions for deleted types.
external/​Java.Interop/​Java.Interop.slnx Removes deleted project entries.
external/​Java.Interop/​Documentation/​BuildConfiguration.md Documents Android interop testing.
.github/​skills/​tests/​SKILL.md Replaces host-JVM testing guidance.
.github/​skills/​tests/​references/​test-catalog.md Updates test locations and prerequisites.

simonrozsival and others added 3 commits October 6, 2026 13:22
Remove JniProxyRuntime and its custom JNI manager test doubles rather
than preserving a second runtime implementation solely for a disposal
test.  Delete Dispose_ClearsJniEnvironment, its only consumer, and the
unused imports.  Retain tests using the active Android runtime.

Co-authored-by: Copilot App <[email protected]>
Keep test guidance focused on the supported Android and tooling tests,
without mentioning the removal of the standalone host-JVM harness.

Co-authored-by: Copilot App <[email protected]>
The shared JavaVMFixture no longer initializes a JVM.  Remove its
unused capability flags and the SafeHandle-dependent transition test,
which is always skipped because JniObjectReference has no gcHandle.

Drop the remaining inheritance clauses so Android interop tests use
the active runtime directly, without an empty shared fixture.

Co-authored-by: Copilot App <[email protected]>
@simonrozsival
simonrozsival added this pull request to stack #13020 October 6, 2026 11:31
@simonrozsival

Copy link
Copy Markdown
Member Author

@dalexsoto review

@dalexsoto dalexsoto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The complete 30-file deletion and integration review found no blocking issue. Android CoreCLR/NativeAOT factories explicitly retain trimmable managers; shipped APIs, public reference-option values and supported activation/lifecycle paths are unchanged. Generated/template parity and project/reference closure are complete, and removing the host fixture does not transfer VM ownership or change Android test initialization, isolation or rooting. Retirement of the standalone host/proxy and dormant disposal tests remains intentional; local SDK/device execution limits are not presented as reviewer validation.

@simonrozsival
simonrozsival merged commit 19b76be into main Oct 6, 2026
42 checks passed
@simonrozsival
simonrozsival deleted the simonrozsival-remove-reflection-jni-managers branch October 6, 2026 18:15
simonrozsival added a commit that referenced this pull request Oct 6, 2026
Keep core JNI test sources and Java fixtures beside their Android test
project after the standalone JVM harness removal.  Use the default C#
source glob and a single local fixture JAR, with the Android-safe GetThis
implementation for every type map.

Retain Android assertions, capability guards, and runtime exclusions.
Remove unsupported desktop remapping tests and their unused fixtures.
Update the interop documentation and test discovery guidance.

Context: #13018

Co-authored-by: Copilot App <[email protected]>
simonrozsival added a commit that referenced this pull request Oct 6, 2026
Keep core JNI test sources and Java fixtures beside their Android test
project after the standalone JVM harness removal.  Use the default C#
source glob and a single local fixture JAR, with the Android-safe GetThis
implementation for every type map.

Retain Android assertions, capability guards, and runtime exclusions.
Remove unsupported desktop remapping tests and their unused fixtures.
Update the interop documentation and test discovery guidance.

Context: #13018

Co-authored-by: Copilot App <[email protected]>
simonrozsival added a commit that referenced this pull request Oct 7, 2026
Core JNI interop tests now run exclusively in the Android device suite. Keeping their sources in the former standalone test directory leaves misleading ownership, external source globs, and separate desktop/Android Java fixture selection. This Android-only relocation puts the sources beside their actual test project and simplifies fixture compilation without changing supported Android test behavior.

Follow-up to the merged #13018. This PR now targets `main`; its diff contains only the Android test relocation and cleanup. It preserves the removal of the desktop harness, reflection-backed JNI managers, and obsolete `JavaVMFixture` scaffolding.

- Move core C# sources and Java fixtures to `tests/Mono.Android-Tests/Java.Interop-Tests/`; use the project's default `Compile` glob and a local `TestJarEntry` glob. Preserve the existing exclusion for tests of the internal `JNIEnv` layout.
- Remove `__ANDROID__` branches while retaining their Android assertions. Remove unsupported desktop-only custom-type-manager remapping tests and unused helper fixtures. Preserve `NO_MARSHAL_MEMBER_BUILDER_SUPPORT`, `NO_GC_BRIDGE_SUPPORT`, and applicable runtime/AOT exclusions.
- Consolidate `GetThis.java` onto the Android-safe implementation for every typemap. Remove duplicate `java-trimmable` selection and unused desktop test targets, and prevent implicit `AndroidJavaSource` compilation from duplicating fixture classes in the JAR.
- Update directly related documentation, instructions, and test catalog/skill guidance. Unrelated Java.Interop tooling suites are unchanged.

**Migration guidance:** Core JNI test additions belong in the new Android directory, including C# fixtures such as `ThrowableInputCleanupTests.cs` and `JavaPeerControlBlockTests.cs`, and Java fixtures under `java/net/dot/jni/test/`. Preserve coverage introduced by #13014 and #13015 when moving or updating these files. Other branches touching the old directory, including #13016, should rebase into the new location. Do not recreate the removed standalone desktop test project.

**Validation**

The following results were recorded during implementation. The installed-SDK build passed, but repository-SDK/device coverage remains blocked; marking this PR ready for review does not imply that coverage passed.

| Command / check | Outcome |
|---|---|
| `dotnet build tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj -c Release` | Passed using the installed Android SDK, including the local Java fixture JAR and AAR; 0 warnings, 0 errors. Repeated after rebasing onto the parent. This is compile coverage, not validation against a newly built repository SDK. |
| `dotnet msbuild tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj -p:Configuration=Release -getItem:Compile,TestJarEntry,AndroidJavaSource` | Passed during implementation: verified 40 local Compile items, all 12 Java fixtures, and no duplicate AndroidJavaSource items. |
| `javac --release 17 -d <session-artifacts>/java-fixture-classes tests/Mono.Android-Tests/Java.Interop-Tests/java/net/dot/jni/test/*.java external/Java.Interop/src/Java.Interop/java/net/dot/jni/GCUserPeerable.java external/Java.Interop/src/Java.Interop/java/net/dot/jni/ManagedPeer.java` | Passed for all 12 fixtures at the configured Java target version. |
| Android-preprocessed source comparison against parent | Passed for 39 migrated C# files during implementation; the remaining file intentionally removes desktop-only remapping coverage and its unused helpers. Capability guards are unchanged. |
| `jar tf tests/Mono.Android-Tests/Java.Interop-Tests/Jars/Mono.Android-Test-classes-trimmable.jar` and `javap -c -classpath tests/Mono.Android-Tests/Java.Interop-Tests/Jars/Mono.Android-Test-classes-trimmable.jar net.dot.jni.test.GetThis` | Passed: one GetThis class, implements GCUserPeerable, no desktop native registration/constructor calls. |
| `git diff HEAD^ HEAD --check` | Passed during implementation. |
| `make prepare CONFIGURATION=Release` | Blocked by NU1102: `Microsoft.NETCore.App.Ref` version `10.0.13` was unavailable in the configured feeds. The corresponding nuget.org package URL also returned HTTP 404. |
| `make all CONFIGURATION=Release` | Blocked by MSB4062: missing `bin/BuildRelease/net10.0/xa-prep-tasks.dll` after preparation failed. |
| `./dotnet-local.sh build -t:Install -c Release tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -p:AndroidTypeMapImplementation=llvm-ir` | Blocked by NETSDK1147: local SDK had no Android workload because repository SDK preparation/build could not complete. |
| `./dotnet-local.sh build -t:Install -c Release tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -p:AndroidTypeMapImplementation=trimmable` | Blocked by the same NETSDK1147 prerequisite. |

No on-device tests were run because the required local SDK/install builds failed, despite a connected arm64 emulator. Mono/CoreCLR/NativeAOT runtime behavior and both typemap device lanes remain unvalidated. No desktop JVM validation is required.

----

Pull Request
[title](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-summary) and
[description](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-body)
should follow the
[`commit-messages.md` workflow documentation](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md), and in particular should include:

- [x] Useful description of *why the change is necessary*.
- [ ] Links to issues fixed
- [ ] Unit tests

No issue is closed by this relocation. Existing Android tests compile; on-device test execution is blocked by the local SDK prerequisites described above.
simonrozsival added a commit that referenced this pull request Oct 7, 2026
Remove JavaInterop1 binding and JCW generation after NativeAOT moved
to XAJavaInterop1 and Android moved to the trimmable typemap.  Retire
ManagedPeer, GCUserPeerable, the old proxy classes, and java-interop.jar.

Use Android.Runtime.JavaProxyThrowable for both Android runtimes.
Preserve managed exception identity, typed peer lookup, and transferred
JNI reference ownership.  Retain the XA generator scenarios and port
array mutation coverage to generated Android bindings.

Validated with rebuilt Debug/Release SDKs and CoreCLR/NativeAOT device
suites, including 17 regression cases for typed lookup and ownership.

Context: #11727
Context: #12133
Context: #13018

Co-authored-by: Copilot App <[email protected]>
simonrozsival added a commit that referenced this pull request Oct 8, 2026
Remove JavaInterop1 binding and JCW generation after NativeAOT moved
to XAJavaInterop1 and Android moved to the trimmable typemap.  Retire
ManagedPeer, GCUserPeerable, the old proxy classes, and java-interop.jar.

Use Android.Runtime.JavaProxyThrowable for both Android runtimes.
Preserve managed exception identity, typed peer lookup, and transferred
JNI reference ownership.  Retain the XA generator scenarios and port
array mutation coverage to generated Android bindings.

Validated with rebuilt Debug/Release SDKs and CoreCLR/NativeAOT device
suites, including 17 regression cases for typed lookup and ownership.

Context: #11727
Context: #12133
Context: #13018

Co-authored-by: Copilot App <[email protected]>
simonrozsival added a commit that referenced this pull request Oct 9, 2026
…3016)

Refs #13010, finding 1. Independent PR against `main`; this does not close the multi-finding tracker.

`FinalizePeer()` clears the JNI reference before calling `Finalized()`. `JavaObject` and `JavaException` previously freed their native control block only when clearing a reference after the internal disposed state had already been set. Actual finalization therefore retained the native allocation; initially invalid peers could even allocate a block during clearing and retain it.

Release the cleared control block in a `finally` after `Dispose(false)` in both peer implementations, using the shared `FreeIfInvalid` helper. Preserve value-manager/context detachment ordering, the invalid-reference callback contract, and explicit-disposal behavior. Retain valid references reconstructed by derived callbacks, including when the callback throws and while reference-tracking contexts borrow the block. No native layout, bridge queue, or production synchronization changes.

Add **26 Android lifetime cases** covering global/local/invalid/no-block finalization, actual finalizers, throwing constructors/callbacks, managed resurrection, valid reconstruction/registration, and explicit disposal followed by finalization. Four additional global-reference deletion cases run under the existing dedicated, warmed-up `JniReferenceLeak` category. Ordinary-suite native-pointer/callback checks do not depend on VM-wide GREF totals. Document native-block ownership separately from JNI-reference deletion.

**Before/after evidence:** Before changing production code, 18 of the original 22 host cases failed with nonzero control-block pointers after finalization. The original 22 passed after the fix; expanded coverage subsequently passed on host, CoreCLR, and NativeAOT. The initial focused host GREF assertions passed, but VM-wide deltas are not per-reference ownership evidence in an ordinary shared suite. This proves a native allocation leak, **not the cause of intermittent GREF-count increases**.

### Review corrections

- `dd047b07af`: centralize invalid-block cleanup without changing behavior.
- `9596f9c6af`: address Alex's first blocker by moving both exact VM-total assertions out of ordinary lifetime tests. Azure build [1624644](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1624644) had three `ExplicitDispose(False)` assertion failures (235 expected/245 actual, 193/246, 193/245), verified against all 87 run summaries, failed results, and both stage-attempt logs. This was a PR test regression, not attribution of the VM-wide increases. Collection precedes cache warm-up in the dedicated category.
- `d25b714447` merges current `main` (`53c12fa441`); `909b509e8d` addresses Alex's second blocker. Main's #13018 removed `JavaVMFixture`, reflection-backed JNI managers, and the standalone host-JVM test project. The actual CI merge in [1625456](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1625456) consequently failed with `CS0246` in Debug, Release, NoAab, NativeAOT, and JniReferenceLeaks before execution. Adopt the supported no-base Android fixture convention and remove the obsolete host-manager conditional. Do not restore the retired harness.
- `094ed915a1`: address the local review suggestion by combining valid reconstruction with a throwing finalization callback for both peer types. Assert exception propagation, exactly one callback, retained JNI identity/native block/registry entry, then explicit disposal invalidates the reference, frees the block, and removes registration. Production code and callback/context ordering are unchanged. All current GitHub feedback was reread; there are no new unresolved inline threads or actionable comments beyond the already addressed blockers.
simonrozsival added a commit that referenced this pull request Oct 9, 2026
## Summary

Retire the experimental `JavaInterop1` binding/Java Callable Wrapper (JCW) generation paths and their obsolete runtime infrastructure. Keep **`XAJavaInterop1`** as the supported binding/legacy JCW shape, and keep the trimmable typemap's Java generator as a **separate, targetless pipeline**.

This implements the Android cleanup tracked by [#11727](#11727), including removal of `ManagedPeer`, `GCUserPeerable`, the handwritten Java.Interop proxies, and `java-interop.jar`. Preserve managed-object transport and consolidate exception transport onto the existing generated `Android.Runtime.JavaProxyThrowable` for both CoreCLR and NativeAOT.

The branch is rebased onto `main` commit `058e71b5867c63fd92a00ae545c7899f2b60ed6e`, including the Android test relocation in [#13019](#13019) and the retained-typemap R8 pipeline in [#12829](#12829). This remains a draft; local validation is documented below, not presented as a claim that CI is green. No tracking issue is auto-closed.

## Android compatibility: why this can be removed

### Normal .NET for Android and .NET MAUI codegen is unchanged

The standard .NET for Android application/binding pipeline, also used by .NET MAUI Android applications, uses **`XAJavaInterop1`**, not the experimental `JavaInterop1` shape. This PR preserves that supported code-generation path.

NativeAOT previously selected `JavaInterop1` for its JCWs. Android NativeAOT has remained an experimental runtime option, documented by [XA1040](https://github.com/dotnet/android/blob/e2230e9d5d/Documentation/docs-mobile/messages/xa1040.md), and **merged [#12133](#12133 already switched it to `XAJavaInterop1` and unified the Android GC bridge around `mono.android.IGCUserPeer`. Before this PoC, the SDK defaults already selected `XAJavaInterop1` for both `AndroidCodegenTarget` and `_AndroidJcwCodegenTarget`.

The old `JavaInterop1` option was explicitly documented as **experimental** in the [build-property documentation](https://github.com/dotnet/android/blob/e2230e9d5d/Documentation/docs-mobile/building-apps/build-properties.md#androidcodegentarget). Its historical NativeAOT-default description is stale after #12133 and is corrected here.

The supported Android paths have already migrated:

- [#12133](#12133) migrated NativeAOT's JCW shape and GC bridge.
- [#12152](#12152) removed the built-in proxies' dependency on `ManagedPeer` for native registration.
- [#12887](#12887) made the trimmable typemap the default and rejected legacy typemap selection.
- [#12890](#12890) removed the old native typemap/reflection-backed Android activation path.
- [#13018](#13018) removed the remaining reflection-backed JNI managers. Current Android runtimes use the trimmable managers.

All these prerequisites are merged. No supported current Android code-generation or activation path needs `ManagedPeer.construct` or JavaInterop1-style wrappers. Some obsolete helpers were still packaged or initialized through common runtime plumbing; removing that leftover machinery does not remove the codegen used by normal .NET for Android or .NET MAUI apps.

### Each removed runtime component has an Android replacement or no supported consumer

| Removed component | Android justification / replacement |
|---|---|
| `Java.Interop.ManagedPeer` and `net.dot.jni.ManagedPeer` | No supported generated JCW shape needs their reflective constructor/native-registration trampoline after #12133 and the trimmable migration. Remove initialization, feature-switch plumbing, built-in mappings, scanner exemptions, suppressions, and ProGuard retention together. |
| `net.dot.jni.GCUserPeerable`, `jiAddManagedReference`, and `jiClearManagedReferences` | Android GC processing uses `mono.android.IGCUserPeer`. Migrate surviving handwritten Android fixtures to that contract; remove the obsolete runtime field, host/managed marker lookups, and matching managed/native initialization-struct field. Android GC/reference tracking is retained. |
| `Java.Interop.JavaProxyObject` and `net.dot.jni.internal.JavaProxyObject` | No consumer remains in the active Android value-manager path. Keep the existing `TrimmableJavaProxyObject` and its Java identity semantics; the removed handwritten class is not its implementation. |
| `Java.Interop.JavaProxyThrowable` and `net.dot.jni.internal.JavaProxyThrowable` | NativeAOT still used this exception-transport path, so it is **replaced before removal**, not simply deleted as unused. Route NativeAOT through the existing generated `Android.Runtime.JavaProxyThrowable`, as CoreCLR already did. No third `TrimmableJavaProxyThrowable` is introduced. |
| `java-interop.jar` | After deleting its obsolete helpers, it has no remaining supported Android runtime content. Remove build/install wiring and references. Android fixture compilation references `android.jar` and `java_runtime.jar` instead. |
| Handwritten `SelfRegistration`, `TestType`, `TestTypeTests`, and the old Java.Interop `CallNonvirtualDerived2` fixture | These were unused or depended on the retired Java-to-managed constructor activation path; the obsolete test class was already excluded by capability guards. Keep the separate live generated-binding class with the same short name and preserve supported constructor/dispatch/exception scenarios. |

The removed managed runtime implementation types are internal. Public `JavaObject`, `JavaException`, `JniPeerMembers`, and `IJavaPeerable` remain. The existing Android throwable and managed-object proxy implementations also remain. The explicit public configuration-property removal below is documented separately.

### Supported construction and registration remain intact

Ordinary managed-to-Java construction does not need `ManagedPeer`: `JavaObject` allocates the Java object, associates the managed peer, then invokes the Java constructor through `StartCreateInstance`/`FinishCreateInstance`.

For Java-to-managed activation and callbacks, supported Android types use generated Android/trimmable dispatch and registration. The trimmable generator registers natives through `TrimmableTypeMap.RegisterNativeMethods()` / `mono.android.Runtime.registerNatives(Class)`, independently of either removed `JavaInterop1` enum value. No reflective compatibility shim is reintroduced.

## Implementation changes

### Generator and build surface

- Remove `CodeGenerationTarget.JavaInterop1`, `JavaPeerStyle.JavaInterop1`, their generator/source-writer/JCW branches, the `javainterop1` CLI selector, and unused SDK configuration selecting that shape.
- Preserve `XAJavaInterop1` output and the independent trimmable pipeline. This is not a rename of the typemap generator to another `AndroidCodegenTarget` value.
- Reject retired `AndroidCodegenTarget` values with actionable **XA4232** diagnostics for application and library builds. Retain the existing **XA4240** rejection for retired `_AndroidJcwCodegenTarget` values, and test rejection rather than silently falling back.
- Remove retired expected-output trees and conditional `JAVA_INTEROP1`/`TODO_JAVA_INTEROP1`/`TryJavaInterop1` scaffolding. Move shared XML inputs/support sources from `expected.ji` into `TestInputs` so supported tests do not depend on a retired baseline directory.
- Update directly related build-property/error documentation and the test catalog.

### Shared Android exception handling

Add a shared internal `AndroidExceptionManager` and make CoreCLR and NativeAOT delegate wrapping/unwrapping to it. `JniEnvironment.Exceptions.Throw(Exception)` dispatches to the active runtime's `RaisePendingException` implementation.

The Android implementation wraps ordinary managed exceptions in the generated Android proxy, recognizes existing Java exception peers, and returns the original managed exception when its proxy returns through JNI or appears as a Java cause. Typed value-manager lookup distinguishes peer from unboxed exception: requests for `Java.Lang.Throwable`, `JavaException`, or `IJavaPeerable` return the Java peer; requests for compatible managed exception/object types return the original managed value.

Shared lookup releases incoming JNI references in an option-aware `finally`, on successful and failing extraction paths. Borrowed references remain valid; transferred local/global references are released exactly once. Permanent regressions cover typed lookup and four extraction failure stages across all three ownership modes.

The base `JniRuntime.RaisePendingException` still throws an existing `JavaException`. Android overrides supply managed-exception wrapping; a runtime without an implementation receives an explicit `NotSupportedException` instead of silently depending on a deleted proxy. The exception contract is documented.

### Finish removing associated scaffolding

- **R8 roots:** remove eight keep entries for the four retired Java helpers from `proguard_typemap.cfg` and `proguard_typemap_coreclr.cfg`, along with their misleading comments. Preserve Android bootstrap/GC roots. Update the existing R8 response tests to assert that neither retained-typemap mode emits retired-helper roots.
- **Registration metadata:** retain `JniAddNativeMethodRegistrationAttribute` with warning-only `[Obsolete]`, explicitly stating that it has no effect. Delete the unused public `JniRuntime.CreationOptions.JniAddNativeMethodRegistrationAttributePresent` property rather than retaining a no-op setting; record its accessor removals with `*REMOVED*` entries in `PublicAPI.Unshipped.txt`. Its unread internal copy and Android assignment are also gone.
- **Dead scanner/diagnostic code:** remove the attribute-specific metadata pre-scan/flag, unused checker and commented rejection block, and unused **XA4251** logger plumbing. Remove XA4251 from the English resource and its designer accessor. Other live metadata matching helpers remain.
- **AOT profile:** delete the unreferenced `custom.aotprofile` ProjectTools fixture containing removed `ManagedPeer.Init`/`.cctor` records. Source searches found no direct or dynamically enumerated profile consumer; the resource wildcard was its remaining inclusion path. Rebuilt-assembly inspection confirms it is no longer embedded. Do not regenerate a profile for retired functionality.

No non-English `.resx` or generated localization files are modified.

### Reconciliation with current main

Preserve #13019's local C# compilation and Java fixture glob under `tests/Mono.Android-Tests/Java.Interop-Tests/`, including its `AndroidJavaSource` exclusion to prevent duplicate compilation. Apply fixture deletions and `IGCUserPeer` conversions at the relocated paths; do not restore the external test source directory/globs. Keep the Android-specific `GetThis` implementation and comments.

Preserve upstream native host startup/logging and assembly-store changes. Preserve #12829's retained-key R8 pipeline; do not reintroduce DGML generation or alter new targets to accommodate the old validation setup.

## Android coverage preserved and added

- **All 23** former `expected.ji` integration scenarios retain XA baselines and active runners. Restore `Java_Lang_Object` against XA and refresh its previously unused snapshot. Shared inputs are migrated, not deleted.
- Retained Common/XA/NRT baselines cover 71 of 72 retired unit-baseline names; the unmatched `WriteInterfaceFields` snapshot already had no test consumer before cleanup.
- Existing Android `ConstructorActivationTests`, generated-binding `BindingTests.VirtualMethodBinding`, and `ExceptionTests.ManagedJavaManaged_FinallyExecuted` retain activation, multi-level dispatch, and managed/Java/managed exceptions with Java `finally` execution.
- Port array-mutation scenarios to a real Java fixture and generated XA bindings: `int[]`, `int[][]`, `int[][][]`, with null/invalid/success results and complete copy-back assertions. These exercise generated `JNIEnv.CopyArray`, not handwritten replacement marshalling.
- Add GC-peer contract coverage for surviving handwritten fixtures and the active managed-object proxy, invoking actual add/clear operations.
- Add/strengthen exception identity, causes, unregistered throwable, Java-visible stack trace, callback identity, typed lookup, and throwable-array coverage.
- Add **17 permanent regressions** for typed/unboxed lookup and transferred-reference failures. All 17 passed on both runtimes after rebase, without skips.
- Require the generated Android throwable in both runtimes' packaging/R8 tests and reject old proxies. Existing R8 response tests now also reject all four obsolete helper roots in both rule modes.

Most deletions are retired generated baselines and duplicate scaffolding, not supported Android runtime scenarios.

## Local validation

### Latest property/scanner/resource/profile cleanup

The following checks ran against the final cleanup sources with a rebuilt Debug Android SDK. These are distinct from the earlier complete post-rebase device runs below.

| Command/check | Result |
|---|---|
| `bin/Debug/dotnet/dotnet build external/Java.Interop/src/Java.Interop/Java.Interop.csproj -c Debug -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar` and the same command with `-c Release` | **Passed**, 0 warnings/errors in both configurations; public API removal records accepted. |
| `bin/Debug/dotnet/dotnet build src/Mono.Android/Mono.Android.csproj -c Debug -v quiet` | **Passed**, 0 errors; no remaining source consumer of the deleted option. |
| `bin/Debug/dotnet/dotnet build build-tools/scripts/Prepare.proj -t:BuildDotNet -c Debug -v quiet` with Debug workload roots | **Passed**, 0 errors; existing warnings. An initial overlapping scanner-test build locked a shared intermediate DLL (MSB4018); the SDK retry passed after that build completed, without source workarounds. |
| `bin/Debug/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Debug -v quiet --filter 'FullyQualifiedName~TrimmableTypeMap\|FullyQualifiedName~TypeMapProguardTargetsTests'` | **73 passed, 4 existing prerequisite skips, 0 failures**. The filter uses the literal `|` OR operator. |
| `bin/Debug/dotnet/dotnet build src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj -c Debug -v quiet` | **Passed**, 0 errors; rebuilt task/resource and ProjectTools consumers. |
| Metadata inspection of rebuilt Debug/Release `Java.Interop.dll` and `Xamarin.ProjectTools.dll` | **Passed**: option absent from both configurations, attribute retained with warning-only `Obsolete`, deleted profile absent from manifest resources. |
| English resource XML parse, active source/reference checks, `git diff --check` | **Passed**; obsolete roots/diagnostic machinery absent and localization boundaries preserved. |

This combined invocation **passed 36 cases with 0 skips/failures**: all R8 task cases, both retained-rule R8 integration modes, CoreCLR/NativeAOT throwable packaging, and ProGuard builds.

```sh
bin/Debug/dotnet/dotnet test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll \
  --filter 'FullyQualifiedName~R8Tests|Name~RetainedTypeMapRulesDriveR8|Name~TrimmableTypeMap_PackagesAndroidJavaProxyThrowable|Name~BuildProguardEnabledProject' \
  -v quiet
```

### Full post-rebase runtime/device validation

Both configuration-specific SDKs were rebuilt after the rebase using `Prepare.proj -t:BuildDotNet -c Debug/Release`. Set matching `DOTNETSDK_WORKLOAD_MANIFEST_ROOTS="$PWD/bin/<configuration>/lib/sdk-manifests"` and `DOTNETSDK_WORKLOAD_PACK_ROOTS="$PWD/bin/<configuration>/lib"`, with `ANDROID_SERIAL=emulator-5554`. Explicit SDK paths avoid `dotnet-local.sh` preferring an existing Release SDK over Debug.

```sh
# Debug workload roots, from repository root.
bin/Debug/dotnet/dotnet build -t:Install -c Debug \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR \
  -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554' -v quiet
(
  cd tests/Mono.Android-Tests/Mono.Android-Tests
  ../../../bin/Debug/dotnet/dotnet test Mono.Android.NET-Tests.csproj \
    --no-build -c Debug --device emulator-5554 --report-trx \
    --results-directory ../../../bin/TestDebug/RebaseResults \
    -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR \
    -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554'
)
```

**Passed: 897 CoreCLR tests, 7 existing skips, 0 failures**, including relocated JNI tests and all 17 permanent regressions. This full CoreCLR suite also passed again during the subsequent deprecation step (results under `ObsoleteRegistrationResults`), before the latest host-only scanner/root/profile cleanup.

```sh
# Release workload roots, from repository root.
bin/Release/dotnet/dotnet build -t:Install -c Release \
  tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -m:1 -p:BuildInParallel=false \
  -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT \
  -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
  -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false \
  '-p:AdbTarget=-s emulator-5554' -v quiet
(
  cd tests/Mono.Android-Tests/Mono.Android-Tests
  ../../../bin/Release/dotnet/dotnet test Mono.Android.NET-Tests.csproj \
    --no-build -c Release --device emulator-5554 --report-trx \
    --results-directory ../../../bin/TestRelease/RebaseResults \
    -p:BuildInParallel=false \
    -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT \
    -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
    -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false \
    '-p:AdbTarget=-s emulator-5554'
)
```

**Passed: 842 NativeAOT tests, 7 existing skips, 0 failures**, including all 17 regressions. The current retained-key R8 pipeline produced **328 retained keys**, including the Android throwable; `r8.json` confirmed shrinking enabled. No old native-output-path override or DGML workaround was used, and no trimming/R8/AOT was disabled. Build-only `-m:1` is omitted from the test invocation because MTP rejects the forwarded `--m` option.

These complete device suites were not rerun for the latest scanner/resource/profile-only additions; the latest 36-case host integration run builds and verifies both CoreCLR and NativeAOT packages against the updated resources.

### Other post-rebase tooling and generated-XA coverage

| Command/check | Recorded result |
|---|---|
| `bin/Debug/dotnet/dotnet test external/Java.Interop/tests/generator-Tests/generator-Tests.csproj -c Debug -v quiet -p:JavaCPath=/usr/bin/javac -p:JarPath=/usr/bin/jar` | **478 passed**, 0 skipped. |
| Same command shape for `external/Java.Interop/tests/Java.Interop.Tools.JavaCallableWrappers-Tests/Java.Interop.Tools.JavaCallableWrappers-Tests.csproj` | **50 passed**, 0 skipped. |
| Same command shape for `external/Java.Interop/tests/Java.Interop.Tools.Generator-Tests/Java.Interop.Tools.Generator-Tests.csproj` | **18 passed**, 0 skipped. |
| `bin/Debug/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj -c Debug -v quiet --filter FullyQualifiedName~GenerateTrimmableTypeMapTests` | **16 passed**, 0 skipped after rebase; superseded by the broader latest 73-case run. |
| `bin/Debug/dotnet/dotnet test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll --filter 'FullyQualifiedName~InvalidConfigTests\|Name~TrimmableTypeMap_PackagesAndroidJavaProxyThrowable\|Name~BuildProguardEnabledProject\|Name~RetainedTypeMapRulesDriveR8' -v quiet` | **58 passed**, 0 skips/failures after rebase; latest related resource/packaging coverage is the 36-case run above. |

Generated-XA JCW device commands used `tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Xamarin.Android.JcwGen-Tests.csproj` with `build -t:Install`, then matching `test --no-build` from its directory, `--device emulator-5554 --report-trx`, and configuration-specific workload roots:

- **CoreCLR:** `-c Debug -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=CoreCLR -p:AndroidCodegenTarget=XAJavaInterop1 -p:RuntimeIdentifier=android-arm64 '-p:AdbTarget=-s emulator-5554'` — **46 passed, 2 existing explicit skips**, including arrays, dispatch, and managed/Java/finally exceptions.
- **NativeAOT arrays:** `-c Release -p:BuildInParallel=false -p:AndroidTypeMapImplementation=trimmable -p:AndroidRuntime=NativeAOT -p:PublishAot=true -p:AndroidCodegenTarget=XAJavaInterop1 -p:RuntimeIdentifier=android-arm64 -p:IncludeCategories=ArrayMutation -p:_AndroidEnableTypemapR8Trimming=true -p:_SkipNdkResolution=false '-p:AdbTarget=-s emulator-5554'`, plus build-only `-m:1` — **3 passed, 0 skips/failures**, with retained-key R8 trimming enabled.

Post-rebase artifact inspection verified all **10** Debug/Release runtime JARs omit the retired helpers, `java-interop.jar` is absent, the relocated fixture JAR omits deleted fixtures, and both signed runtime-test APKs contain the Android throwable without the retired helper descriptors.

Device coverage is arm64 only, not a complete ABI/device matrix. No CI success is claimed.

## Compatibility and follow-ups

- Old Java wrappers/handwritten code explicitly using the retired experimental `ManagedPeer`/JavaInterop1 contract must be regenerated/migrated. No compatibility shim is retained. [#11635](#11635) tracks that question; #11727 documents the removal direction. Normal .NET for Android/MAUI codegen remains intact.
- The unused public `JniAddNativeMethodRegistrationAttributePresent` configuration property is intentionally removed; the attribute itself remains warning-only obsolete. Existing code setting that property must remove the setting, which no longer controlled any Android runtime behavior.
- Developers should clean old intermediates when changing between pre-/post-removal trees. No permanent build invalidation/cleanup machinery is added solely for this transition.
- Broader single-target generator-plumbing simplification and other legacy public API deprecations/removals are separate follow-ups, not mixed into this PR.
- Successful shared exception lookup could additionally receive borrowed/global-reference success tests, complementing the existing failure matrix; this optional coverage is not claimed as implemented.

## Related issues and PRs

- [#11727](#11727): experimental JavaInterop1 retirement tracker.
- [#11635](#11635): old ManagedPeer bytecode compatibility discussion.
- [#12133](#12133), [#12152](#12152): merged NativeAOT/GC migration and proxy decoupling.
- [#12887](#12887), [#12890](#12890): merged trimmable default and legacy activation/native typemap removal.
- [#13018](#13018): merged reflection-manager removal.
- [#13019](#13019): merged Android test relocation, reconciled here.
- [#12829](#12829): retained-key R8 pipeline replacing DGML, exercised by validation and preserved by this cleanup.
- [#11480](#11480): **closed, unmerged** earlier trimmable-only ManagedPeer removal; useful artifact-check precedent, not a claimed prerequisite. No performance numbers from it are claimed here.
- [dotnet/java-interop#1490](dotnet/java-interop#1490): **closed, unmerged** ManagedPeer registration modernization; unnecessary once those Android consumers are removed.

----

Pull Request
[title](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-summary) and
[description](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md#commit-body)
should follow the
[`commit-messages.md` workflow documentation](https://github.com/dotnet/android/blob/main/Documentation/workflow/commit-messages.md), and in particular should include:

- [x] Useful description of *why the change is necessary*.
- [x] Links to issues fixed
- [x] Unit tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants