swiftlang / swiftlang/swift-java

Attempt to remove non-JNI local reference errors on Android

Open Beginner friendly
#663 4 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

android
Dominant language
Swift
Stars
1.2k
Forks
123
Avg merge
1d 7h
Merged PRs (30d)
16

Description

Using FlutterSwift with the latest swift-java and Android SDK, I see

2026-04-01 15:37:27.149  7457-7457  ktone.InfernoUI         com.lukktone.InfernoUI               W  Attempt to remove non-JNI local reference

in the Android logs.

Proposed fix is to remove DeleteLocalRef on incoming references in JavaObjectHolder.init to fix Android JNI warnings. (This is by @ampcode-com, I haven't had a chance to review, I hate AI-generated PRs as much as the next person but I haven't had the cycles to look into the root cause myself yet.)

Root Cause

JavaObjectHolder.init(object:environment:) promotes the incoming jobject to a global reference via NewGlobalRef, then checks if the original is a local reference and calls DeleteLocalRef on it. This is problematic because:

  1. JNI parameter references are not owned by native code. When Java calls a native method, the jobject parameters are local references managed by the JNI frame. The JNI specification states these are automatically cleaned up when the native method returns. Native code should not call DeleteLocalRef on them.

  2. ART's CheckJNI detects this. Android's runtime (ART) has CheckJNI enabled by default, which distinguishes between locally-created references (via FindClass, NewObject, etc.) and parameter references. Calling DeleteLocalRef on a parameter reference triggers the warning.

  3. GetObjectRefType returns JNILocalRefType for both cases, so the existing check cannot distinguish parameter references from locally-created references.

Fix

Remove the DeleteLocalRef call entirely. After NewGlobalRef creates a new strong reference to the Java object, the original local reference (whether a parameter or locally-created) will be cleaned up automatically when the JNI frame is popped. This is safe and correct per the JNI specification:

"Local references are valid for the duration of a native method call. They are freed automatically after the native method returns." — JNI Specification

The only scenario where explicit DeleteLocalRef is needed is to avoid local reference table overflow when creating many local references in a loop within a single native method call. JavaObjectHolder.init does not fall into this category.

Testing

Tested on Android 15 (API 36) emulator with a FlutterSwift application that makes frequent JNI calls (platform channel message handlers). Before the fix, every message handler invocation produced 1–3 "Attempt to remove non-JNI local reference" warnings. After the fix, zero warnings are emitted

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at JavaObjectHolder.init(object:environment:), where incoming jobject references are promoted and the warning is triggered. Reproduce the Android JNI calls on the Android 15 emulator with the FlutterSwift application, then verify that frequent platform-channel message handlers emit zero "Attempt to remove non-JNI local reference" warnings.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, swift
Domain
mobile-dev
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.