I Put a Full JVM Inside a Browser Tab. It "Works". Technically. Eventually.
There is an entire Linux kernel in there and it takes 55 seconds to print Hello World
UPDATE: This has really taken off! I have updated the code so it’s actually fast now, no longer using alpine linux and QEMU, compiled OpenJDK Zero to WebAssembly.
Ever wonder if you could run Java code in the browser without any server? Do you miss Java applets? Do you like waiting for relatively long periods of time for very little payoff?
At this point, I assume only crazy people are reading this, so go buy a hat so you can hold onto it.
Well do I have the project for you bukaroo! It’s JavaBox - no server, no JVM backend. Just sheer, unadulterated (actually what’s the opposite of unadulterated?) OpenJDK shoved into Alpine Linux shoved into a WASM blob shoved into a browser. It’s like a Russian doll of inefficiencies.
But it’s kinda cool though right? This is the result of someone saying “well I’d like to see you do this” and me saying “hold my beer” and then coming back to said person and saying “technically it works”, which it does, but I feel that I need to REALLY stress the word technically. But, by the letter of the law, I did in fact get it to work.
The Stack That Should Not Exist
Let me walk you through what happens when you open JavaBox in a browser tab. Your browser loads a Cloudflare Worker. That Worker serves a 227MB WebAssembly blob. That blob contains an Emscripten-compiled build of QEMU. QEMU boots a Linux kernel. The Linux kernel boots Alpine Linux 3.21. Alpine Linux has OpenJDK 21 on it. And OpenJDK compiles and runs your Java code.
If you’re counting, that’s six layers of abstraction between your browser and System.out.println("Hello World"). Your CPU is emulating x86_64 instructions through QEMU’s Tiny Code Generator, which itself has been compiled to WebAssembly, which your browser’s JavaScript engine is JIT-compiling down to native machine code. It’s emulation all the way down.
This is the kind of thing that makes computer scientists cry and also the kind of thing that makes me want to keep going.
The Problem With Doing This The Obvious Way
The obvious approach to running Java in the browser is to just boot the container, wait for a shell prompt, send javac Main.java through the terminal, wait, then send java Main, wait some more, and collect the output. I tried this. Every single javac invocation has to start a brand new JVM. Under QEMU TCG WebAssembly emulation, JVM startup alone takes over twelve minutes.
Twelve. Minutes. Per. Compile.
Nobody is waiting twelve minutes to see if their for loop has a semicolon in the right place. So I had to get creative.
CompileServer, or How I Learned to Stop Spawning and Love the Daemon
The fix was a persistent JVM daemon I wrote called CompileServer. It’s a Java program that boots once, loads the compiler API via javax.tools.JavaCompiler, and then sits there waiting for work over stdin. When you send it source code, it compiles in-process using the already-loaded compiler. When you want to run the compiled class, it uses a URLClassLoader to load and execute it. Same JVM. No restarts. No twelve minute tax.
The really fun part is how this survives the snapshot. container2wasm has this feature where it captures a QEMU snapshot after the container finishes booting. When the browser loads the WASM blob, it restores from that snapshot instead of booting from scratch. So CompileServer starts up during the build step on my machine, gets frozen into the snapshot, and then wakes back up inside your browser tab with the JVM already warm.
The protocol is dead simple. The browser sends JBOX_PING through the terminal. CompileServer responds with JBOX_PONG. That’s how the SDK knows the JVM is alive. Then to compile and run, it sends JBOX_COMPILE ClassName followed by the source code followed by JBOX_END. CompileServer compiles, runs, prints the output, and finishes with JBOX_EXIT:0 (or whatever the exit code was). The whole thing uses BufferedReader because anything fancier (like JLine) breaks after snapshot restore.
I want to be clear that this is not elegant engineering. This is duct tape and prayer. But it got compile+run down from twelve minutes to about 35 seconds, which is a 20x improvement that I’m choosing to be proud of.
The Numbers
Let’s talk performance because I think it’s important to be honest about where this stands.
Boot time from page load to CompileServer ready is about 20 seconds. That’s the QEMU snapshot restoring and Linux coming up. First compile and run of a HelloWorld takes about 35 seconds on top of that. So you’re looking at roughly 55 seconds from opening the page to seeing “Hello World” in your terminal.
Is that fast? No. Is it usable for a production IDE? Absolutely not. Is it faster than the twelve minute alternative? By a lot.
The guest gets 128MB of RAM with the JVM heap capped at 64MB via -Xmx64m. JIT compilation is disabled with -Xint because there’s no point in JIT-compiling Java bytecode when you’re already running under a software CPU emulator compiled to WebAssembly. The JIT’s output would just be more x86 instructions for QEMU to emulate. It’s turtles compiling turtles.
What We’re Thinking About Using It For
We’re exploring using JavaBox for a Java documentation site. The idea is that when you’re reading about, say, HashMap or generics or whatever Java concept, there would be a “Try It” button right there on the page. Click it. Edit the example code. Hit run. See the output. No installing a JDK. No setting up an IDE. No server processing your code somewhere.
The 55 second first-run time is a problem for this. But if someone lands on the docs page and starts reading, the container can boot in the background while they’re going through the explanation. By the time they actually click “Try It” and type something, CompileServer might already be warm. Maybe. That’s the theory anyway. We’ll see how it plays out.
The other idea is shareable snippets. You write some Java, get a URL, send it to someone. They open it and the code runs in their browser. No backend to maintain. No server costs that scale with users. Every user’s browser is doing its own compute. It’s serverless in the most literal and also most ridiculous sense of the word.
How To Try It
There’s a live demo up right now. Go to javabox-demo.brian-fec.workers.dev and wait. And then wait some more. And then you’ll see a terminal. Type some Java and compile it. Or don’t. I’m not your boss.
The code is on GitHub at github.com/bmarti44/javabox. The README has build instructions if you want to go through the whole process yourself, which involves Rancher Desktop, Colima (if you’re on Apple Silicon), container2wasm, and a level of patience that I did not originally possess but have since developed.
What I Learned
Building something that is technically impressive and practically useless is an underrated way to learn things. I now understand way more about QEMU, WebAssembly memory models, cross-origin isolation headers, and the Java compiler API than I ever expected to. I learned that SharedArrayBuffer requires specific HTTP headers that most dev servers don’t set. I learned that container2wasm’s snapshot feature is genuinely clever and that the c2w-net networking proxy does things I didn’t think were possible from a browser. I learned that the JVM is incredibly heavy to cold-start but surprisingly cooperative if you just keep it running.
I also learned that 227MB is a lot of data to send to a browser. Like, a lot a lot. I could compress it more. I could chunk it smarter. But at some point you just have to accept that you’ve put an entire operating system in a browser tab and move on with your life.
Is JavaBox going to replace server-side Java compilation? No. Is it going to compete with online IDEs that use actual servers? No. Is it a fun proof of concept that demonstrates something genuinely interesting about what browsers can do in 2026? I think so.
And if nothing else, next time someone says “you can’t run Java in the browser anymore,” I can send them a link. They’ll have to wait 55 seconds, but they’ll get there. Eventually.
Brian Martin is a Senior Principal Developer at Oracle. The code is at github.com/bmarti44/javabox. The live demo is at javabox-demo.brian-fec.workers.dev. If you’re interested in this kind of thing, subscribe and I’ll keep writing about the weird stuff I build.



Nice article!
It's crazy how far one can push Wasm to run all sorts of things in the browser.
There is a real potential for moving a lot of server side compute to the client.
I work on CheerpJ so I am probably biased, but I think that your "sharable snippets" idea
is already realized quite well by JavaFiddle (https://javafiddle.leaningtech.com).
As for the "Try it" button on a java docs site: that's something we have been wanting to do for a while as a showcase. If you end up building it, do post about it. And if you want to explore using CheerpJ for it, we would gladly help you out!
Nice :)
My friends and I also made a JVM that runs in the browser here: https://github.com/anematode/b-jvm