Skip to content

Follow-up to #59414: cache each Java TurboModule method's ID with its signature, by name - #59619

Merged
meta-codesync[bot] merged 3 commits into
react:mainfrom
hryhoriiK97:android-turbomodule-method-info-by-name
Oct 30, 2026
Merged

meta-codesync[bot] merged 3 commits into
react:mainfrom
hryhoriiK97:android-turbomodule-method-info-by-name

Conversation

@hryhoriiK97

@hryhoriiK97 hryhoriiK97 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

This is a follow-up to #59414, suggested by @javache in its review: keep a method's jmethodID in the same per-module cache as its parsed JNI signature, and index that cache by method name, instead of having two caches.

Before this change, a Java TurboModule method's ID was cached by its caller: every generated host function kept a static jmethodID cachedMethodId and passed it to invokeJavaMethod by reference, and JavaInteropTurboModule kept a methodIDs_ vector for the same purpose. JavaTurboModule then used that ID as the key of its MethodInfo cache (#59414).

With this change:

  • JavaTurboModule::MethodInfo also holds the method's jmethodID, and methodInfos_ is keyed by the method name (a std::string, looked up through a std::string_view without allocating). On a method's first call, getMethodInfo looks the method up with GetMethodID and parses its signature. A signature that doesn't match still turns into a JS error (redbox) before anything is called, and nothing is cached for it.
  • invokeJavaMethod gets an overload that takes the method name and JNI signature as C strings and no cachedMethodID. Generated code passes string literals, so a call no longer builds two std::strings; the JNI signature is usually longer than the small-string buffer, so that was a heap allocation on every call.
  • The previous overload (const std::string& name and signature, jmethodID&) is kept, for code generated by earlier codegen versions and for modules that call it directly, and marked [[deprecated]]. It forwards to the new one; cachedMethodID is no longer read or written. Libraries that ship pre-generated JNI code build it with -Wall -Werror (the codegen CMake template applies target_compile_reactnative_options), so their builds will fail on the deprecation warning until they regenerate that code.
  • The codegen template for Java module JNI files (GenerateModuleJniCpp) no longer emits static jmethodID cachedMethodId, and JavaInteropTurboModule drops methodIDs_.

Besides one cache instead of two, every generated host function gets smaller: RNTester's libreactnative.so (which includes FBReactNativeSpec) is 61 KB smaller and its libappmodules.so 9.4 KB smaller.

Changelog:

[ANDROID] [CHANGED] - Java TurboModules cache each method's ID with its parsed signature, by name, instead of in every generated host function

[ANDROID] [DEPRECATED] - JavaTurboModule::invokeJavaMethod with a cachedMethodID argument; use the overload without it

Test Plan:

  • yarn jest packages/react-native-codegen passes (64 suites, 3,130 tests); the GenerateModuleJniCpp snapshots are updated for the template change.
  • node scripts/clang-format.js --check reports no changes for the touched C++ files.
  • C++ API snapshots: the new invokeJavaMethod overload is added to the four ReactAndroid*Cxx.api snapshots by hand, where the generator sorts it; scripts/cxx-api can't run on current main locally (its codegen step fails with SyntaxError: Unexpected token '{' while processing FBReactNativeSpec, without this change too).
  • ./gradlew :packages:rn-tester:android:app:assembleRelease -PreactNativeArchitectures=arm64-v8a succeeds with this change; RNTester's generated FBReactNativeSpec and app module code use the new overload.
  • On a device. Two RNTester release APKs built from the same local checkout (main at f7de203 with local benchmark screens), without and with this change, installed alternately on a moto g57 power (Android 16), three installs each with two launches. A Playground screen times synchronous calls to NativeSampleTurboModule (20,000 calls each, 5,000 for void, callback and promise calls), median of the six launches:
Call (JNI signature) before this PR change
getBool ((Z)Z) 1.10 µs 1.15 µs +4%
getNumber ((D)D) 1.46 µs 1.53 µs +5%, ranges overlap
getString ((Ljava/lang/String;)Ljava/lang/String;) 2.05 µs 1.92 µs −6%
getArray, 3 items 10.34 µs 10.06 µs −3%
getObject, 3 keys 9.90 µs 9.58 µs −3%
getObject, 20 keys 35.67 µs 35.28 µs −1%
getValue(number, string, object) 13.94 µs 14.21 µs +2%, within noise
voidFunc 4.55 µs 4.59 µs within noise
getValueWithCallback 8.54 µs 7.99 µs −6.5%
getValueWithPromise 18.26 µs 17.38 µs −5%

Calls whose JNI signature is longer than the small-string buffer no longer allocate it, and are 5–6% cheaper. The cheapest calls, whose short signatures never allocated, now hash the method name instead of a pointer, about 50 ns more. Native library sizes are from the same two APKs.

🤖 Generated with Claude Code

JavaTurboModule now looks a method up on its first call and keeps its
jmethodID in the per-module MethodInfo cache with the parsed signature,
keyed by method name, instead of callers keeping the jmethodID themselves.
Generated host functions no longer hold a static jmethodID, and
JavaInteropTurboModule drops its methodIDs_ vector.

invokeJavaMethod takes the method name and signature as C strings, so
calls with literals don't build two std::strings each time. The overload
taking a cachedMethodID is kept for code generated by earlier codegen
versions and for modules that call it directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 29, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner labels Oct 29, 2026
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 29, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hryhoriiK97
hryhoriiK97 requested a review from javache October 29, 2026 15:03
# Conflicts:
#	packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp
@meta-codesync

meta-codesync Bot commented Oct 29, 2026

Copy link
Copy Markdown

@javache has imported this pull request. If you are a Meta employee, you can view this in D128349302.

@meta-codesync
meta-codesync Bot merged commit f78bdd4 into react:main Oct 30, 2026
101 checks passed
@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @hryhoriiK97 in f78bdd4

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants