Skip to content

Latest commit

 

History

History
184 lines (158 loc) · 9.87 KB

paper.md

File metadata and controls

184 lines (158 loc) · 9.87 KB
title Bytebox: Java on Cloudflare Workers
tags
Java
WebAssembly
WasmGC
serverless
edge computing
Cloudflare Workers
TeaVM
Gradle
authors
name orcid affiliation
Gregory Mitchell
0009-0002-3956-4818
1
affiliations
name index
Independent Researcher, Hanover, NH, United States
1
date 19 September 2026
bibliography paper.bib

Summary

Serverless platforms run small programs on demand, starting them when a request arrives and discarding them afterwards, so that no server has to be kept running between visits. Cloudflare Workers is one such platform, and it executes two kinds of program: JavaScript, and WebAssembly, a portable binary format that many languages can be compiled into [@cfwasm].

Java is not one of those two kinds. A Java program normally runs on a Java Virtual Machine, which expects a filesystem, operating-system services, real threads, and a process that outlives any single request. None of those exist on this platform.

Bytebox compiles a Java project into a deployable Worker. A developer writes an ordinary Java class, applies a Gradle plugin, and runs one command; the build emits a complete Worker — the WebAssembly module, its configuration, a JavaScript entry point and a package manifest — with nothing written by hand. Inside that Worker, Java code that was never written for this platform continues to work: dates and times, HTTP clients, sockets, regular expressions and string formatting all behave as they do elsewhere, because the build points each of those references at an implementation that the platform can actually run. Where no faithful implementation is possible, the build or the running program refuses instead of producing a result that looks right and behaves differently.

Statement of Need

Compiling Java to WebAssembly is a solved problem: TeaVM has done it for years, and its WebAssembly GC backend produces a module with garbage collection provided by the host engine [@teavm]. Running that module on Cloudflare Workers was not possible before this software, for reasons that are properties of the platform rather than of the compiler.

TeaVM's generated module imports seven functions from the wasm:js-string namespace [@jsstring], two of which take WebAssembly array references that JavaScript cannot construct. Those imports are satisfied only by passing an option to the module constructor, and the platform's own pre-compiled module path accepts no options, so a Worker built that way fails at instantiation. The obvious alternative — compiling the module when the request arrives — is refused: the runtime permits synchronous WebAssembly construction while a Worker is starting and prohibits it during request handling [@cfwebstandards]. TeaVM's own loader cannot be used either, because it compiles a feature-detection module from bytes, takes a Node.js branch whenever process is defined, and resolves package imports through a dynamic import of a string stored in a WebAssembly custom section.

Beyond loading, TeaVM's class library has no java.time at all, an HTTP stack wired to XMLHttpRequest, and a formatter that reaches locale data unavailable here. A third-party library does not stop using those APIs because the platform changed.

Bytebox supplies the loader that works, the platform bindings a Worker needs, the build that ties them together, and a retargeted class library beneath the unchanged Java programming model. For researchers, it also functions as an instrument: it is the artifact through which a managed language runtime can be deployed to, and measured on, a commercial serverless host. Given identical sources and settings the smallest sample emits byte-identical output and a larger one alternates between two outputs two bytes apart, so committed binaries serve as stable inputs for size and behaviour, and a measurement of one is attributable to the implementation once that noise floor is stated.

State of the Field

Managed and dynamic language runtimes have been brought to JavaScript-hosted WebAssembly environments before. Pyodide compiles CPython through Emscripten and supplies the absent host services — a synthesized filesystem and a JavaScript interoperation layer — from inside the module [@pyodide]. Blazor WebAssembly executes a .NET runtime in WebAssembly on a browser host [@blazor]. WALL-E integrates managed-language libraries through external library linking rather than nesting a runtime inside the module [@walle]. GraalVM Native Image takes a different target entirely, compiling Java ahead of time to a native executable for a specific operating system [@graalvm]. It sits with AWS Lambda SnapStart [@snapstart] and CRaC [@crac] among systems that cut JVM startup cost, by snapshotting or ahead-of-time compilation, while keeping a conventional operating-system host. Separately, WebAssembly has been evaluated as a serverless execution target independent of any particular language [@murphy; @lumos].

Pyodide and Blazor ship a language runtime into WebAssembly and emulate what the host does not provide; the startup-side systems keep a conventional host and change how the runtime reaches it. Bytebox starts from an existing Java-to-WebAssembly compiler and makes the opposite move where it can: the class library is bound to services the host already implements, work that cannot execute on the platform is removed at build time, and semantics that cannot be preserved are refused rather than approximated.

Software Design

The Gradle plugin derives the Worker from the Java project. A handler class declares what it handles by implementing interfaces — HTTP requests, cron triggers, inbound email, queue messages, trace events, Durable Object alarms — and the build derives the JavaScript exports, the configuration keys and the entry-point guards from the interfaces present, so an unimplemented trigger produces no code and no configuration.

The loader embeds the compiled module as binary data, constructs it synchronously while the Worker is starting, and instantiates it before any handler runs. It holds a gate through which every entry point passes, because one WebAssembly heap serves an isolate and an isolate serves many requests; without it, a second request could run on the same heap in the middle of the first request's work.

The class library is retargeted through the compiler's substitution mechanism rather than through a wrapper API, which is what allows an unmodified dependency to compile: java.time resolves to ThreeTen-Backport [@threeten] with zone rules read from the host's internationalization data, java.net to the platform's own HTTP and TCP facilities, java.util.regex to the host's regular-expression engine with differing constructs translated [@davis], and java.util.Formatter to digits computed in Java because the host's rounding answers a different question. JSON codecs and java.io serialization are generated at build time rather than discovered by reflection, since a closed-world compiler cannot prune a reflective closure. Constructs with no faithful equivalent are refused with the reason named: inbound sockets fail the build, and regular-expression and formatter forms throw when the program reaches them.

Java and npm dependencies coexist. A binding generator resolves TypeScript and JavaScript type information in descending order of reliability and falls back to a dynamic representation when no static shape can be established, so a package without published types still binds.

Fourteen samples in the repository each demonstrate one feature and double as the end-to-end check, and a verification step compiles every built module through the loader's own options before the build reports success.

Research Impact Statement

Bytebox produced the measurements in the accompanying study of runtime retargeting [@retargeting]. Among them: Worker startup tracks executable WebAssembly content rather than uploaded bytes, remaining flat at 8–10 ms while inert padding grew the bundle 640-fold; request CPU, not size or startup, is the budget a retargeted class library spends, measured at a 43 ms median against a documented 10 ms free-plan allowance without termination; the isolate memory ceiling measured 254 MiB accepted and 256 MiB refused against a documented 128 MB [@cflimits]; five of fourteen third-party Java libraries compile and run unmodified, with the refusals falling into three identifiable layers; and a regular-expression translator produced byte-identical results on every construct it accepts, across 48 pattern–input pairs; separately, (a+)+b over inputs of 20 to 30 characters cost 14 ms in total on the reference JVM and 11,010 ms on the host engine, showing that semantic agreement does not imply equivalent cost.

None of those measurements was obtainable without a way to deploy a managed runtime to this platform, which is the research role the software plays. The study names this repository at commit db114b1 as its artifact; the reproducible build makes the measurements repeatable by others, and the samples provide the workloads.

AI Usage Disclosure

Generative AI tools were used interactively during the development of Bytebox and preparation of this manuscript, including for technical discussion, implementation assistance, and proposing and revising manuscript prose. The author directed the research questions, system design, experimental evaluation, and overall argument of the paper; AI-generated suggestions and prose were reviewed, accepted, modified, or rejected by the author. The author takes responsibility for the implementation, experimental results, interpretations, claims, and final manuscript.

Acknowledgements

Bytebox builds on TeaVM, and on ThreeTen-Backport for its date and time implementation. It descends from Drupflare, an earlier project by the same author that runs PHP as WebAssembly inside a Cloudflare Durable Object, and shares its module-loading layer with cartridge, the interpreter-hosting library extracted from that project.

References