[HN Gopher] Lessons from Mixing Rust and Java: Fast, Safe, and P...
       ___________________________________________________________________
        
       Lessons from Mixing Rust and Java: Fast, Safe, and Practical
        
       Author : killme2008
       Score  : 108 points
       Date   : 2025-05-15 02:18 UTC (3 days ago)
        
 (HTM) web link (medium.com)
 (TXT) w3m dump (medium.com)
        
       | lukax wrote:
       | Do not write the bindings manually. Just use the amazing uniffi-
       | rs library from Mozilla.
       | 
       | https://github.com/mozilla/uniffi-rs
       | 
       | You can generate bindings for multiple languages. It supports
       | error handling on both sides and latest versions also support
       | native async integration.
       | 
       | I've used it to reuse the same Rust engine in iOS and Android
       | apps and write native UI.
       | 
       | https://github.com/koofr/vault
        
         | cadamsdotcom wrote:
         | Thanks! Anywhere you hit issues?
        
           | lukax wrote:
           | Not really. But I didn't use async (was not supported yet
           | when I started using it).
           | 
           | Bindings were easy, everything else (building, linking,...)
           | was a bit pain to setup because there no good examples.
        
         | 4hg4ufxhy wrote:
         | The bindings are inefficient doing excessive cloning. But if
         | performance is not a concern then its fine.
        
           | no_wizard wrote:
           | Couldn't you manually edit after generating so you could
           | thereby optimize the code as needed?
        
       | invalidname wrote:
       | Why didn't OP use Panama or the modern Java native APIs which are
       | far better than JNI?
        
         | cyberax wrote:
         | Android?
        
           | invalidname wrote:
           | Does OP target Android?
        
           | pjmlp wrote:
           | Proving the point about Android being Google's J++, and
           | Kotlin their C#.
        
       | sgt wrote:
       | Should be possible with Zig as well. Is this one up to date?
       | https://github.com/zig-java/jui
        
       | rusbus wrote:
       | Another useful library in this space that allows you to avoid
       | manually writing bindings is https://github.com/duchess-
       | rs/duchess
        
       | never_inline wrote:
       | 1. The article seems kind-of shallow. I didn't see any concrete
       | (qualitative or quantitative) remarks about the "fast" part. I
       | don't doubt you have reasons to do this - but I expected some
       | information on what component are you writing using Rust + JNI,
       | and how it helped? Or is it just a demo?
       | 
       | At some point, repeated calls into the JNI are counter-productive
       | to performance, since the JIT can not optimize them. Pinning
       | affecting garbage collection is another potential drawback if any
       | of your rust calls are long lived. If we don't measure and just
       | conclude "we are fast because rust is faster than Java, and we
       | took average of both speeds", it's a disservice.
       | 
       | 2. Also, I see unsafe for each call? I'd rather isolate this into
       | a class / different file, since in JNI only few types of calls
       | are possible. (method returning one of the primtive types, an
       | object or `void`). This is the approach I took in dart jnigen.
       | (Though there, the call is Dart -> Java, not Java -> Native
       | language).                   unsafe {
       | env.call_method_unchecked(                     java_logger,
       | logger_method,
       | ReturnType::Primitive(Primitive::Void),
       | &[JValue::from(format_msg(record)).as_jni()]           );
       | }
       | 
       | 3. I believe some details are missing here. What's native_add_one
       | mapped to? And how is tokio futures awaited from Java? I believe
       | that's the important part you should be presenting.
       | public CompletableFuture<Integer> add_one(int x) {
       | long futureId = native_add_one(x); // Call Rust
       | return AsyncRegistry.take(futureId); // Get CompletableFuture
       | }
       | 
       | 4. Also please don't use ChatGPT for writing anything. It totally
       | derails the reader by mentioning irrelevant details and long
       | winded corporate conclusion at the end of every sentence.
        
         | killme2008 wrote:
         | This article summarizes our experience from a commercial
         | project that runs on an in-vehicle Android system. In this
         | project, we needed to invoke Rust code(DB) from Java(App), so
         | we couldn't directly use the project's source code for
         | demonstration. Instead, we created a demo project:
         | https://github.com/GreptimeTeam/rust-java-demo
         | 
         | 1. I agree that using Rust doesn't necessarily mean faster
         | performance; it simply gives you the opportunity to implement
         | some compute-intensive modules in Rust, which is a possible
         | approach.
         | 
         | 2. This is a great suggestion, and we organized our project in
         | the same way. You don't need to use unsafe for every call.
         | However, if you want to call JNI APIs from Rust, unsafe is
         | required.
         | 
         | 3. Sorry, some details were missing here. We use
         | AsyncRegistry(Java) as an intermediary. Before initiating an
         | async operation in Rust, we need to call Java code in advance
         | to register a future and obtain a unique future ID. After the
         | async execution completes, we retrieve the registered future by
         | its ID, and then complete it or complete it exceptionally
         | depending on the async result. You can refer to this code:
         | https://github.com/GreptimeTeam/rust-java-demo/blob/90ffa0ba...
         | and https://github.com/GreptimeTeam/rust-java-
         | demo/blob/90ffa0ba...
         | 
         | 4. This article was not generated by AI; it's just that our
         | official blog has a fixed template at the end. Sorry for the
         | inconvenience.
        
           | never_inline wrote:
           | Thanks for the clarifications. Good if you mention the
           | background in the medium post. Otherwise it reads like a PoC
           | demo.
           | 
           | 5. How did you handle java local and global ref lifetimes in
           | rust callee? Was it assumed that java caller owns all the
           | refs and freed after the rust computation returns? Or did
           | your calls mostly involve byte buffers and primitive types?
           | That latter is a sweet spot but not always feasible.
        
       | bullen wrote:
       | I agree that Java + native is the way to go.
       | 
       | But does rust really give you an edge over C/C++?
       | 
       | Here is how you do JNI with C++: http://move.rupy.se/file/jvm.txt
       | 
       | So simple it's ridiculous!
       | 
       | Then you can use RegisterNatives to give C++ API to the Java side
       | instead of the stub (Java calls C++ .dll/.so) thing...
        
         | pjmlp wrote:
         | Your comment is exactly the reason why while I find Rust a cool
         | language, I would be using C++ instead.
         | 
         | That is the systems language most JVM implementations make use
         | of, alongside Java, and what is directly supported by JNI
         | tooling, including on Java IDEs mixed language debugging.
         | 
         | And in what concerns Java, _native_ is anyway the synonym for
         | _unsafe_.
         | 
         | However to each their own.
        
           | tialaramex wrote:
           | C++ is a perfectly good programming language if you never
           | make mistakes. So the problem is that of course you'll make
           | mistakes, and the people who wrote your compiler make
           | mistakes, and the C++ committee makes mistakes, and the
           | language's inventor makes mistakes, if you work in a team the
           | other team members and the tooling both also have mistakes.
           | Other than these issues, sound choice.
        
             | pjmlp wrote:
             | You can bash C++ as much as you feel like, if it makes you
             | happy.
             | 
             | I am well aware of the issues with C++'s flaws, sometimes
             | it is easier to deal with such flaws, than adding a new
             | layer into the C++ sandwich of debugging tools, build
             | toolchain and IDE tooling for a given language runtime, and
             | dragging a whole team for the ride as well.
             | 
             | What about removing LLVM dependency from Rust, switch to
             | Cranelift, so that rustc isn't hindered by C++ mistakes on
             | LLVM?
        
               | tialaramex wrote:
               | Sure, or perhaps since LLVM devs like niches so much they
               | might decide to RIIR.
               | 
               | For me the big advantage of Cranelift isn't that it's
               | written in Rust it's that they seem to have invested more
               | into coherent semantics. I do not want to write code
               | which is correct but is miscompiled because the compiler
               | internal semantics are nonsense and that's a small but
               | noticeable problem in LLVM.
               | 
               | You can write contorted (but safe) Rust which LLVM just
               | plainly miscompiles, the rust layer is like "Make local
               | variable A, and local variable B" Ok says LLVM, "and now
               | is the address of A the same as the address of B?" "No",
               | says LLVM those are different, not the same variable so
               | different addresses. OK says the Rust. Now, subtract the
               | smaller from the larger and tell me the number you got.
               | "Zero" triumphantly proclaims LLVM having deduced that we
               | don't need A or B so there's no need to store them
               | anywhere, but forgotten that it promised their addresses
               | aren't the same... Oops.
               | 
               | That example is silly, but it's hard to be sure how many
               | large codebases might tickle equivalent LLVM
               | miscompilation, which is not good.
        
               | mrkeen wrote:
               | Because those mistakes get fixed on LLVM devs' time, not
               | on parent's.
        
       | dbacar wrote:
       | WHich part of the Java app was slow for you? And did you check on
       | FFI (foreign function interface) ?
        
       | aeonik wrote:
       | I thought JNI was being deprecated in favor of the new FFM
       | interface.
       | 
       | https://openjdk.org/jeps/472
        
         | cogman10 wrote:
         | Notice the delivery version (24). That was literally just sent
         | out in March. The FFM interface was stabilized in 22. 21 is the
         | latest LTS version which is the furthest most orgs will go.
        
           | da_chicken wrote:
           | That doesn't mean you should happily go out and build a new
           | cathedral of technical debt.
        
         | immibis wrote:
         | This says:
         | 
         | > It is not a goal to deprecate JNI or to remove JNI from the
         | Java Platform.
         | 
         | It says they want to put a safety barrier in front of JNI, so
         | you'll have to explicitly indicate that you want a module to
         | use JNI.
        
           | aeonik wrote:
           | I know that's the goal, but I was doing deeper reading and
           | didn't understand the nuances here. It _felt_ deprecated, and
           | I remember reading that you should prefer FFM.
           | 
           | That's one of the reasons I posted it. A lot of knowledgeable
           | people here can chime in more details. And a sibling comment
           | did!
        
         | pron wrote:
         | FFM is the recommended alternative, but JNI is not being
         | deprecated. There are some things that JNI can do that FFM
         | can't, in particular - initiating calls from native code to
         | arbitrary Java methods. FFM only supports upcalls to Java in
         | the form of Java callbacks that are passed to the native code.
         | However, this is enough for the vast majority of Java-native
         | interactions, so FFM should be preferred in new code (it's much
         | easier to use than JNI).
        
       | koakuma-chan wrote:
       | I found mixing Bun and Rust works pretty well. Bun has gotten
       | many cool new things recently which feel great to use and it has
       | a nice FFI API. So I have Next.js apps running in Bun runtime and
       | anything CPU bound is written in Rust and called via FFI.
        
         | pjmlp wrote:
         | I would have expected Zig, given Bun, or using Deno instead, if
         | Rust as extension language.
        
           | koakuma-chan wrote:
           | > I would have expected Zig
           | 
           | You can use Zig instead if it meets your needs, but last time
           | I checked, the Zig ecosystem was lacking compared to Rust.
           | 
           | > or using Deno instead, if Rust as extension language
           | 
           | Deno? There was a thread the other day that Deno is dead. I
           | wouldn't even compare Deno and Bun because Bun is just waaay
           | better. Even before Bun already felt very polished and nice
           | to use, and now it even has native postgres and s3 clients.
           | And it doesn't try to sell you a KV database or some shit.
        
             | pjmlp wrote:
             | The point I was making was using the same language the VM
             | makes use of, instead of adding extra layers.
        
               | koakuma-chan wrote:
               | I mean, I guess there would be some point if you, e.g.,
               | forked bun and wrote new APIs in Zig. But if you are
               | using FFI it doesn't really matter because the interface
               | is C ABI anyway; there's no extra layers in using a
               | language different from the one your runtime uses.
        
         | never_inline wrote:
         | JS developer use tooling older than 3 years challenge. Level:
         | impossible.
        
           | koakuma-chan wrote:
           | It's not only JS. The tooling was just trash everywhere
           | before Rust started gaining traction. E.g. for Python there
           | is uv now, and I am definitely not going back to freaking
           | pip.
        
       | nilslice wrote:
       | Others should consider Chicory, enabling JVM apps to run wasm
       | code (compiled from Rust or many other languages) at high speed,
       | securely in-process.
       | 
       | JNI introduces a lot of other challenges and sacrifice in terms
       | of portability, memory and retaining observability and control
       | within the JVM.
       | 
       | Chicory even supports AOT compilation to translate wasm bytecode
       | to JVM bytecode for native performance.
       | 
       | https://github.com/dylibso/chicory
        
       ___________________________________________________________________
       (page generated 2025-05-18 23:01 UTC)