#teavm
Estoy intentando portar mis juegos a la web con libGDX y TeaVM, veremos cómo queda.

#gamedev #html5games #indiedev #indiegamedev #java #libgdx #teavm #webgames
June 2, 2026 at 5:09 PM
i have a crazy #java project incoming

my goal is to make something like #teavm

will focus heavily on generated code readability and memory safety
March 17, 2025 at 12:49 PM
É uma amalgama de TeaVM (Java->JS), HTML5, TailwindCSS, JavaFX (por causa do WebView) e Helidon (um embedded HTTP server aleatório)

Ah e FireBug-Lite pq o JavaFX não tem DevTools :)
March 7, 2025 at 8:09 PM
the only one i know how to use is cheerpj and thats basically feed it a jar and it will shit out a web thing

teavm may be similar but i have no idea
July 5, 2025 at 5:43 AM
10月にTeaVMがWasmGC対応させることを発表したらしいので頑張ってほしいなぁ。
ちゃんと使えるようになるなら追加でSwing対応してくれると一気にWasmGC言語の勢力図が書き変わるんだけど。
December 3, 2024 at 12:25 PM
Applets Are Officially Going, But Java In the Browser Is Better Than Ever
"The entire java.applet package has been removed from JDK 26, which will release in March 2026," notes Inside Java. But long-time Slashdot reader AirHog links to this blog post reminding us that "Applets Are Officially Gone, But Java In The Browser Is Better Than Ever." This brings to an official end the era of applets, which began in 1996. However, for years it has been possible to build modern, interactive web pages in Java without needing applets or plugins. TeaVM provides fast, performant, and lightweight tooling to transpile Java to run natively in the browser... TeaVM, at its heart, transpiles Java code into JavaScript (or, these days, WASM). However, in order for Java code to be useful for web apps, much more is required, and TeaVM delivers. It includes a minifier, to shrink the generated code and obfuscate the intent, to complicate reverse-engineering. It has a tree-shaker to eliminate unused methods and classes, keeping your app download compact. It packages your code into a single file for easy distribution and inclusion in your HTML page. It also includes wrappers for all popular browser APIs, so you can invoke them from your Java code easily, with full IDE assistance and auto-correct. The blog post also touts Flavour, an open-source framework "for coding, packaging, and optimizing single-page apps implemented in Java... a full front-end toolkit with templates, routing, components, and more" to "build your modern single-page app using 100% Java." Read more of this story at Slashdot.
developers.slashdot.org
December 13, 2025 at 11:36 PM
Applets are officially gone, but Java in the browser is better than ever The end of an era, perhaps. Applets are officially, completely removed from Java 26, coming in March of 2026. This brings to...

#Java

Origin | Interest | Match
Applets are officially gone, but Java in the browser is better than ever
The end of an era, perhaps. > Applets are officially, completely removed from Java 26, coming in March of 2026. This brings to an official end the era of applets, which began in 1996. However, for years it has been possible to build modern, interactive web pages in Java without needing applets or plugins. TeaVM provides fast, performant, and lightweight tooling to transpile Java to run natively in the browser. And for a full front-end toolkit with templates, routing, components, and more, Flavour lets you build your modern single-page app using 100% Java. > > ↫ Andrew Oliver As consumers, we don’t really encounter Java that much anymore unless we play Minecraft, but that doesn’t mean Java no longer has a place in this world. In fact, it still consistently ranks in the top three of most popular programming languages, so any tools to make using Java easier, both for programmers and users, are welcome.
www.osnews.com
December 8, 2025 at 12:52 PM
Java on Cloudflare Workers
This post details bytebox, a dependency and runtime to get Java to run on Serverless Architecture via Cloudflare Workers. It uses TeaVM to compile Java into WebAssembly usable by the Cloudflare Workers runtime. It comes in multiple forms: you can use it as a Gradle plugin to output a deployable worker or a WebAssembly module, and comes as a NPM module to assemble workers by hand. Java dependencies are available via Maven Central and the Gradle Plugin Portal. An important note about Cloudflare Workers vs Java is that Cloudflare Workers is a single-threaded synchronous state, so all operations are blocking and concurrency is not possible from Java. `java.util.concurrent` and other threading operations are not included and will result in a compilation error from trying to use them. The GitHub repository lists the full scope of other platform limitations and out-of-scope items, including the use of `Process` and `ProcessBuilder`, dynamic class loading, and inbound TCP listening or UDP handlers with `DatagramSocket` or `ServerSocket`. Currently supported are Java 21 and 25 LTS versions. Documentation for both the Java API and the TypeScript API is available at bytebox.gmitch215.dev. ## Quick Start Copied from the README page: package com.example; import dev.gmitch215.bytebox.*; public class HelloWorker implements Worker { @Override public Response fetch(Request request, Env env, ExecutionCtx ctx) { return Bytebox.response("hello from Java"); } } In your Gradle configuration: plugins { java id("dev.gmitch215.bytebox") version "1.0.0" } repositories { mavenCentral() } dependencies { implementation("dev.gmitch215:bytebox-core:1.0.0") } // ... bytebox { handlerClass = "com.example.HelloWorker" // to handler class wrangler { name = "hello-world" // name of cfw compatibilityDate = "2026-08-22" // compatibility date of cfw } } The Gradle plugin will compile your code to WebAssembly using TeaVM and output the necessary worker scaffholding to use `wrangler dev` or `wrangler deploy`: ./gradlew buildWorker # build wrangler.jsonc, webassembly module, entrypoint ./gradlew workerDeploy # run wrangler deploy to logged-in account ## Bindings All bindings available on Cloudflare Workers are supported in bytebox. The Gradle plugin allows you to easily add one or multiple bindings for your generated `wrangler.jsonc`, and provides default names if you so choose. bytebox { bindings { kv() // KV kv("SESSIONS") { id = "abc123" } // an explicit name and a remote id d1() // DB r2() // BLOB durableObject("Counter") // DO_COUNTER } } You can also declare them like so: bytebox { bindings(KV, D1, D1, KV) // KV, DB, DB_2, KV_2 } A Gradle task named `bindingsReport` is available to get detailed information about the bindings you have declared in your project. ## JSON and Serialization Many Cloudflare Workers handle and respond with JSON outputs. `bytebox` comes with buult-in utilities to parse and handle JSON. To automatically serialize a class or a record, annotate it with `@JsonType`: @JsonType public record Order(String sku, int quantity, long total, List<String> tags) { // ... } `Bytebox.json` serializes it to JSON: Order order = request.json(Order.class); return Bytebox.json(order, Order.class); `java.io` is also supported normally, and requires no additional code. byte[] wire = Serial.encode(order); Order back = Serial.decode(wire, Order.class); ## Dependencies & Runtime ### Java Standard Libraries `bytebox` and TeaVM provide polyfills and runtime capabilities for you to use the majority of the Java Standard Library without any code modification. The Gradle plugin will automatically rewrite any class files or dependencies routed to a systems class that requires a polyfill, so no code modification is required. ZonedDateTime local = Instant.now().atZone(ZoneId.of("America/New_York")); HttpResponse<String> answer = HttpClient.newHttpClient() .send(HttpRequest.newBuilder(URI.create("https://example.com")).build(), ofString()); Libraries like `java.time`, `java.net`, `java.io`, `java.util.regex`, and many others can be written like you would normally. package com.example; import dev.gmitch215.bytebox.Bytebox; import dev.gmitch215.bytebox.Env; import dev.gmitch215.bytebox.ExecutionCtx; import dev.gmitch215.bytebox.Request; import dev.gmitch215.bytebox.Response; import dev.gmitch215.bytebox.Worker; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Ordinary Java, on a platform that has none of it. * * <p>Nothing here is a bytebox API. It is {@code java.time}, {@code java.net.http}, * {@code java.util.regex} and {@code String.format}, written the way they are written anywhere, and * the compiler points each reference at an implementation that works on this runtime. That is what * makes an unmodified library compile: the library does not know it is being retargeted. * * <p>What each one costs, and where each one differs from a JVM, is in the technical report. The two * differences worth knowing at the call site are here as comments. */ public class StandardLibraryWorker implements Worker { private static final Pattern SINCE = Pattern.compile("since=(?<date>\\d{4}-\\d{2}-\\d{2})"); private static final HttpClient CLIENT = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build(); @Override public Response fetch(Request request, Env env, ExecutionCtx ctx) { LocalDate since = LocalDate.of(2026, 1, 1); Matcher asked = SINCE.matcher(request.getUrl()); if (asked.find()) since = LocalDate.parse(asked.group("date")); // the clock is pinned between I/O, so this is the time the invocation began and does not move Instant now = Instant.now(); ZonedDateTime local = now.atZone(ZoneId.of("America/New_York")); long days = ChronoUnit.DAYS.between(since, local.toLocalDate()); StringBuilder body = new StringBuilder(); body.append(local.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)).append('\n'); // %,d groups every three digits here, which is right for the locales that group in threes body.append(String.format("%,d days since %s%n", days, since)); body.append(String.format("%-12s %8.2f%%%n", "elapsed", (days * 100.0) / 365)); body.append(upstream()).append('\n'); return Bytebox.response(body.toString()); } /** A request through the modern client, which is {@code fetch} underneath and suspends the fiber. */ private String upstream() { try { HttpResponse<String> answer = CLIENT.send( HttpRequest.newBuilder(URI.create("https://example.com/")) .header("Accept", "text/html") .timeout(Duration.ofSeconds(3)) .build(), HttpResponse.BodyHandlers.ofString() ); return String.format( "upstream %d, %,d bytes", answer.statusCode(), answer.body().length() ); } catch (IOException | InterruptedException failed) { return "upstream unreachable: " + failed.getMessage(); } } } ### npm Dependencies The `bytebox` Gradle plugin supports adding NPM dependencies to your worker, and comes with static analysis to generate supported types based on the npm module to use in your Java code. bytebox { npm("nanoid", "^5.0.9") npmBindings("nanoid") } `npm` installs the dependency, and `npmBindings` tells the plugin to read the `*.d.ts` declarations in the npm module to generate Java source files that you can use to work with the package. You can also use the `@JSBody` annotation that TeaVM provides with the `native` keyword to wire it directly: @JSBody( params = "size", imports = @JSBodyImport(alias = "nanoid", fromModule = "nanoid"), script = "return nanoid.nanoid(size);" ) private static native String id(int size); ### Third-Party Java Dependencies Dependencies can be declared as normal and they will be compiled in with the worker. dependencies { implementation("com.example:foo:1.0.0") } **Cloudflare Workers' free plan has a hard limit of 3 MiB after gzip** on the bundle, with paid increasing to 10 MiB after gzip, so installing heavy Java dependencies should be done with care. TeaVM only includes parts of the Java runtime that it detects is being used, and does not include the full runtime on compilation (unless everything is being used). The estimation for each runtime feature's cost is provided below. Feature | Added, gzipped ---|--- streams, collections, reflection | 3.0 to 3.8 KB each threads | 6.8 KB `java.net.Socket` | 9.8 KB `BigDecimal` | 12.7 KB `java.util.regex` | 13.3 KB `java.net.URL` and `HttpURLConnection` | 13.3 KB `java.time` with zones | 23.8 KB `String.format` | 33.0 KB `java.net.http` | 54.1 KB ### Cloudflare Runtime Libraries Cloudflare Workers provides runtime libraries like `cloudflare:sockets`, `cloudflare:mail`, and many other built-ins available to you. `bytebox` provides Java API around these out of the box so you can use them how you please. ## Examples Borrowed from the GitHub repository's snippets. ### Cron Job All triggers are supported in `bytebox`, and cron is not an exception. package com.example; import dev.gmitch215.bytebox.Bytebox; import dev.gmitch215.bytebox.Cron; import dev.gmitch215.bytebox.Env; import dev.gmitch215.bytebox.ExecutionCtx; import dev.gmitch215.bytebox.Scheduled; import dev.gmitch215.bytebox.builtin.Clock; /** * A Worker with no HTTP handler at all. * * <p>Implementing only {@code Scheduled} means the generated Worker exports only {@code scheduled}, * and the generated configuration carries only the trigger. Nothing is exported for a trigger the * handler does not implement. * * <p>An account gets 5 Cron Triggers on the free plan and 250 on paid, counted across every Worker * rather than per Worker. A scheduled invocation gets 15 minutes rather than a request's allowance, * and a throw is logged without a retry. */ public class NightlyWorker implements Scheduled { @Override public void scheduled(Cron cron, Env env, ExecutionCtx ctx) { Bytebox.log("fired for " + cron.expression() + ", due at " + Clock.iso(cron.scheduledAt())); env.kv().put("last-run", Clock.isoNow()); } } ### Durable Objects Durable Objects are supported out-of-the-box with bytebox. They can either be used directly or be implemented over as a class. package com.example; import dev.gmitch215.bytebox.Bytebox; import dev.gmitch215.bytebox.Env; import dev.gmitch215.bytebox.ExecutionCtx; import dev.gmitch215.bytebox.Request; import dev.gmitch215.bytebox.Response; import dev.gmitch215.bytebox.Worker; import dev.gmitch215.bytebox.binding.DurableObjectNamespace; import dev.gmitch215.bytebox.js.TSObject; /** * Routes to a Durable Object, which is where state that has to be exact belongs. * * <p>One instance per id, in one place, so two requests for the same id reach the same instance and * see each other's writes. That is the difference from the kv-counter sample, where two regions can * both read the same value and both write the next one. * * <p>The Durable Object class itself is JavaScript: it extends Cloudflare's own base class, which is * a JavaScript class the runtime instantiates. What Java owns is the routing and the calls. */ public class CounterWorker implements Worker { @Override public Response fetch(Request request, Env env, ExecutionCtx ctx) { DurableObjectNamespace counters = env.durableObject("DO_COUNTER"); // the id is derived from the path, so every path gets its own instance var counter = counters.byName(request.path()); TSObject count = counter.rpc("increment"); return Bytebox.response(request.path() + " is at " + count.asInt() + "\n"); } } ### Mail Routing Cloudflare Workers can receive inbound mail, allowing code to handle and reroute any incoming mail it receives. The `InboundMail` class provided as apart of the implementation surface will automatically handle it based on the method you call, whether it is `mail.forward`, `mail.reject`, or anything else you choose to do with it. package com.example; import dev.gmitch215.bytebox.Bytebox; import dev.gmitch215.bytebox.Env; import dev.gmitch215.bytebox.ExecutionCtx; import dev.gmitch215.bytebox.InboundMail; import dev.gmitch215.bytebox.Mail; /** * An inbound email router. * * <p>Cloudflare drops a message a handler returns without acting on, so {@code InboundMail} records * what was done and raises if nothing was. That makes the silent drop impossible to reach by * accident, and {@code drop()} is how to say the silence was meant. * * <p>Inbound messages are capped at 25 MiB. A reply needs the original to have passed DMARC, has to * come from the receiving domain, and is allowed once per message. */ public class MailRouter implements Mail { @Override public void email(InboundMail mail, Env env, ExecutionCtx ctx) { if (mail.rawSize() > 1_000_000) { mail.reject("messages over 1 MB are not accepted"); return; } String sender = mail.from(); if (env.kv().get("blocked:" + sender) != null) { mail.reject("this address is not accepted"); return; } // acting and then carrying on is the point: the disposition is recorded on the message Bytebox.log("routing " + mail.rawSize() + " bytes from " + sender); env.kv().put("last-sender", sender); mail.forward("inbox@example.com"); } } ### Queue Consumer Cloudflare Workers also supports using Queues as a trigger to handle multiple messages sent to the worker to be processed individually. package com.example; import dev.gmitch215.bytebox.Bytebox; import dev.gmitch215.bytebox.Consumer; import dev.gmitch215.bytebox.Env; import dev.gmitch215.bytebox.ExecutionCtx; import dev.gmitch215.bytebox.Message; import dev.gmitch215.bytebox.MessageBatch; import dev.gmitch215.bytebox.js.TSObject; /** * A Queue consumer that acknowledges each message separately. * * <p>An uncaught exception retries the whole batch, which is rarely what a partial failure wants. * Acknowledging and retrying per message is how one bad message stops holding up the rest. * * <p>A body is any structured-clone value, so it arrives as a {@code TSObject}. A body with a known * shape can be read with a codec instead; see the {@code @JSONType} annotation. */ public class OrderConsumer implements Consumer<TSObject> { @Override public void queue(MessageBatch<TSObject> batch, Env env, ExecutionCtx ctx) { var insert = env.d1().prepare("insert into orders (id, sku) values (?, ?)"); for (Message<TSObject> message : batch.messages()) { try { TSObject body = message.body(); insert.bind(message.id(), body.get("sku").asString()).run(); message.ack(); } catch (RuntimeException failure) { Bytebox.log("order " + message.id() + " failed: " + failure.getMessage()); // a fourth attempt is unlikely to go differently, so give up rather than loop if (message.attempts() >= 3) message.ack(); else message.retry(30); } } } } ### HTTP API Router `bytebox` comes with a `Router` class that makes it easy to use Java to make HTTP APIs. You can use them to build a tree of routes, similar to tools like Hono. import dev.gmitch215.bytebox.http.Router; public class Api implements Worker { private final Router routes = new Router() .filter(Api::requireToken) .get("/things", (request, env, ctx) -> Bytebox.json(env.d1().query("select * from things"))) .get("/things/:id", (request, env, ctx) -> one(env, request.param("id"))) .post("/things", Api::create); @Override public Response fetch(Request request, Env env, ExecutionCtx ctx) { return routes.handle(request, env, ctx); } // implement create(), requireToken(), and others below } ### TCP Worker The `cloudflare:sockets` module allows workers to send outbound TCP bytes to a specific hostname and port. No library is provided and only a raw `connectTls` function is provided, so libraries like edgeport can be used to wrap around SSH, SMTP, FTP, LDAP, and other TCP protocols you may need to use. Note that Cloudflare currently lists that it does not allow outbound TCP connections to its IP addresses or on port 22 directly. package com.example; import dev.gmitch215.bytebox.Bytebox; import dev.gmitch215.bytebox.Env; import dev.gmitch215.bytebox.ExecutionCtx; import dev.gmitch215.bytebox.Request; import dev.gmitch215.bytebox.Response; import dev.gmitch215.bytebox.Worker; import dev.gmitch215.bytebox.socket.Socket; import dev.gmitch215.bytebox.socket.Sockets; /** * A raw TCP client over {@code cloudflare:sockets}. * * <p>What this cannot reach decides whether it is the right tool. Cloudflare refuses a connection to * its own IP ranges, to localhost and to private addresses, and blocks port 25. So a Worker cannot * SMTP to Cloudflare Email Sending even with valid credentials; the email binding is the way out. * * <p>Every plan allows six simultaneous outgoing connections, so the socket is closed rather than * left to the runtime. Try-with-resources is why {@code Socket} is {@code AutoCloseable}. */ public class TcpWorker implements Worker { @Override public Response fetch(Request request, Env env, ExecutionCtx ctx) { String host = request.query("host", "example.com"); try (Socket socket = Sockets.connectTLS(host, 443)) { socket.write("HEAD / HTTP/1.0\r\nHost: " + host + "\r\nConnection: close\r\n\r\n"); String status = socket.readUntil("\r\n"); return Bytebox.response(host + " answered " + status + "\n"); } catch (RuntimeException refused) { // the refusal reads "proxy request failed, cannot connect to the specified address" return Bytebox.response("could not reach " + host + ": " + refused.getMessage(), 502); } } } ## Intermediate Example package example; import dev.gmitch215.bytebox.Env; import dev.gmitch215.bytebox.ExecutionCtx; import dev.gmitch215.bytebox.Request; import dev.gmitch215.bytebox.Response; import dev.gmitch215.bytebox.Worker; import dev.gmitch215.bytebox.Bytebox; import dev.gmitch215.bytebox.http.Filter; import dev.gmitch215.bytebox.http.Route; import dev.gmitch215.bytebox.http.Router; import java.util.Map; import java.util.Optional; /** * A moderately realistic ByteBox Worker demonstrating: * * <ul> * <li>HTTP routing</li> * <li>Path parameters</li> * <li>Query parameters</li> * <li>Route-specific middleware</li> * <li>Global middleware</li> * <li>Authentication</li> * <li>Request validation</li> * <li>JSON responses</li> * <li>404 handling</li> * <li>Basic error handling</li> * </ul> * * <p>The handlers deliberately use ordinary Java control flow. ByteBox's * Worker runtime handles suspension when a handler performs supported I/O.</p> */ public final class ApiWorker implements Worker { private final Router router; public ApiWorker() { this.router = buildRouter(); } /** * Worker entry point. * * <p>All requests are handed to the Router. The router is responsible * for matching the HTTP method/path and executing middleware before * invoking the selected handler.</p> */ @Override public Response fetch( Request request, Env env, ExecutionCtx ctx ) { try { return router.route(request, env, ctx); } catch (Exception e) { // In production, log the exception rather than returning its // details to the client. Bytebox.log("Unhandled request error: " + e); return Bytebox.json( """ { "error": "internal_server_error" } """, 500 ); } } /** * Constructs the application router. * * <p>Middleware registered with {@code use()} runs for matching requests * before the route handler. Route-specific filters can additionally be * supplied when registering an individual route.</p> */ private Router buildRouter() { Router router = Router.create(); /* * ------------------------------------------------------------------ * Global middleware * ------------------------------------------------------------------ */ // Add a request ID to every request and expose it through the // response headers. In a larger application this could instead // attach information to a request context abstraction. router.use(requestIdMiddleware()); // Reject unsupported HTTP methods before route dispatch. router.use(methodGuardMiddleware()); /* * ------------------------------------------------------------------ * Public routes * ------------------------------------------------------------------ */ router.get( "/", (request, path, env, ctx) -> Bytebox.json( """ { "name": "example-api", "status": "ok" } """ ) ); router.get( "/health", this::health ); /* * Query parameters can be read from the request URL. * * Example: * * GET /users?limit=20 * * The exact URL/query representation comes from ByteBox's Request * abstraction, while the rest of the handler remains ordinary Java. */ router.get( "/users", this::listUsers ); /* * ------------------------------------------------------------------ * Authenticated routes * ------------------------------------------------------------------ * * Authentication is applied only to this group of routes rather * than globally, so /health and / can remain public. */ router.get( "/me", authenticated(), this::currentUser ); router.get( "/users/:id", authenticated(), this::getUser ); router.post( "/users", authenticated(), this::createUser ); /* * ------------------------------------------------------------------ * Admin routes * ------------------------------------------------------------------ * * Multiple filters can be composed. Authentication runs first; * authorization runs only after authentication succeeds. */ router.delete( "/users/:id", authenticated(), requireRole("admin"), this::deleteUser ); /* * ------------------------------------------------------------------ * Wildcard example * ------------------------------------------------------------------ * * A wildcard route can be useful for proxy-like or asset-like * endpoints. */ router.get( "/debug/*", authenticated(), requireRole("admin"), this::debug ); /* * ------------------------------------------------------------------ * 404 * ------------------------------------------------------------------ * * Router invokes this handler when no registered route matches. */ router.notFound( (request, path, env, ctx) -> Bytebox.json( """ { "error": "not_found" } """, 404 ) ); return router; } /** * Simple request-ID middleware. * * <p>Middleware receives a {@link java.util.function.Supplier} for the * next handler. Calling {@code next.get()} continues the request.</p> */ private Filter requestIdMiddleware() { return (request, path, env, ctx, next) -> { String requestId = Bytebox.uuid(); Response response = next.get(); /* * The Response API exposes its headers, allowing middleware * to add information to an otherwise completed response. */ response.getHeaders().set("X-Request-ID", requestId); return response; }; } /** * Example global middleware that rejects methods which the application * does not intend to support. * * <p>Route matching still determines whether a particular endpoint * exists; this filter merely demonstrates application-level policy.</p> */ private Filter methodGuardMiddleware() { return (request, path, env, ctx, next) -> { String method = request.getMethod(); if (method == null) { return Bytebox.json( """ { "error": "invalid_request" } """, 400 ); } return switch (method) { case "GET", "POST", "PUT", "PATCH", "DELETE" -> next.get(); default -> Bytebox.json( """ { "error": "method_not_allowed" } """, 405 ); }; }; } /** * Authentication middleware. * * <p>This example intentionally keeps authentication simple. A real * implementation could validate a JWT, call an identity service, or * inspect a signed session cookie.</p> */ private Filter authenticated() { return (request, path, env, ctx, next) -> { String authorization = request.getHeaders().get("Authorization"); if (authorization == null || !authorization.startsWith("Bearer ")) { return Bytebox.json( """ { "error": "unauthorized" } """, 401 ); } String token = authorization.substring("Bearer ".length()); if (!isValidToken(token)) { return Bytebox.json( """ { "error": "invalid_token" } """, 401 ); } /* * ByteBox does not require middleware to be a particular * authentication framework. Application state can be carried * through whatever application-level context abstraction the * application uses. * * This example simply demonstrates successful middleware * continuation. */ return next.get(); }; } /** * Authorization middleware. * * <p>In a production application, the user's authenticated identity * would normally come from a request context established by the * authentication layer.</p> */ private Filter requireRole(String requiredRole) { return (request, path, env, ctx, next) -> { /* * Demo-only authorization decision. * * Replace this with the application's actual identity/context * lookup. */ String role = request.getHeaders().get("X-Debug-Role"); if (!requiredRole.equals(role)) { return Bytebox.json( """ { "error": "forbidden" } """, 403 ); } return next.get(); }; } /** * GET /health */ private Response health( Request request, Map<String, String> path, Env env, ExecutionCtx ctx ) { return Bytebox.json( """ { "status": "healthy" } """ ); } /** * GET /users * * <p>Demonstrates reading query parameters and producing a JSON * response. The application could replace the in-memory list with * a D1 query without changing the routing structure.</p> */ private Response listUsers( Request request, Map<String, String> path, Env env, ExecutionCtx ctx ) { String url = request.getUrl(); int limit = extractLimit(url); if (limit < 1 || limit > 100) { return Bytebox.json( """ { "error": "limit_must_be_between_1_and_100" } """, 400 ); } /* * Normally: * * env.d1() * .prepare("SELECT ... LIMIT ?") * .bind(limit) * .all(); * * would be used here. */ String json = """ { "users": [ { "id": 1, "name": "Ada" }, { "id": 2, "name": "Grace" } ], "limit": %d } """.formatted(limit); return Bytebox.json(json); } /** * GET /me */ private Response currentUser( Request request, Map<String, String> path, Env env, ExecutionCtx ctx ) { return Bytebox.json( """ { "id": 42, "name": "Example User", "role": "admin" } """ ); } /** * GET /users/:id * * <p>The {@code path} map contains the named route parameter: * * <pre> * /users/:id * ^ * └── path.get("id") * </pre> */ private Response getUser( Request request, Map<String, String> path, Env env, ExecutionCtx ctx ) { String id = path.get("id"); if (id == null || id.isBlank()) { return Bytebox.json( """ { "error": "missing_user_id" } """, 400 ); } if (!id.matches("\\d+")) { return Bytebox.json( """ { "error": "user_id_must_be_numeric" } """, 400 ); } /* * A real D1-backed implementation could do: * * var result = env.d1() * .prepare("SELECT id, name FROM users WHERE id = ?") * .bind(Long.parseLong(id)) * .first(); */ return Bytebox.json( """ { "id": %s, "name": "Example User" } """.formatted(id) ); } /** * POST /users * * <p>Demonstrates reading a request body as JSON. The actual application * would normally deserialize into a generated ByteBox JSON codec type.</p> */ private Response createUser( Request request, Map<String, String> path, Env env, ExecutionCtx ctx ) { String body = request.text(); if (body == null || body.isBlank()) { return Bytebox.json( """ { "error": "request_body_required" } """, 400 ); } /* * Example body: * * { * "name": "Linus" * } * * For a real application, prefer a @JSONType-generated codec instead * of manually parsing strings. */ if (!body.contains("\"name\"")) { return Bytebox.json( """ { "error": "name_required" } """, 422 ); } /* * A real implementation could insert into D1 here and return the * newly-created resource. */ Response response = Bytebox.json( """ { "id": 100, "name": "New User" } """, 201 ); response.getHeaders().set( "Location", "/users/100" ); return response; } /** * DELETE /users/:id * * <p>Authentication and admin authorization have already been handled * by route-specific middleware before this method executes.</p> */ private Response deleteUser( Request request, Map<String, String> path, Env env, ExecutionCtx ctx ) { String id = path.get("id"); if (id == null || !id.matches("\\d+")) { return Bytebox.json( """ { "error": "invalid_user_id" } """, 400 ); } /* * Real implementation: * * env.d1() * .prepare("DELETE FROM users WHERE id = ?") * .bind(Long.parseLong(id)) * .run(); */ return Bytebox.status(204); } /** * GET /debug/* * * <p>Demonstrates a wildcard route. The router places the matched * path information into the route parameter map.</p> */ private Response debug( Request request, Map<String, String> path, Env env, ExecutionCtx ctx ) { String wildcard = path.get("*"); return Bytebox.json( """ { "debug": true, "path": %s } """.formatted( jsonString(wildcard == null ? "" : wildcard) ) ); } /** * Demo token validator. * * <p>Replace this with actual signature verification / identity lookup. * No secret is embedded in application code.</p> */ private boolean isValidToken(String token) { return !token.isBlank() && token.length() >= 16; } /** * Minimal query-string extraction used only to keep the example focused * on Router rather than URL parsing. * * <p>For production code, use ByteBox's URL/request facilities rather * than relying on this deliberately small helper.</p> */ private int extractLimit(String url) { if (url == null) { return 20; } int queryStart = url.indexOf('?'); if (queryStart < 0) { return 20; } String query = url.substring(queryStart + 1); for (String parameter : query.split("&")) { String[] parts = parameter.split("=", 2); if (parts.length == 2 && parts[0].equals("limit")) { try { return Integer.parseInt(parts[1]); } catch (NumberFormatException ignored) { return -1; } } } return 20; } /** * Escapes a string sufficiently for this small JSON demonstration. * * <p>Use ByteBox's generated JSON codecs for real application models.</p> */ private String jsonString(String value) { if (value == null) { return "null"; } return "\"" + value .replace("\\", "\\\\") .replace("\"", "\\\"") .replace("\n", "\\n") .replace("\r", "\\r") .replace("\t", "\\t") + "\""; } } ## Conclusion This was a short demonstration about how to use bytebox to get Java to run on Cloudflare Workers. You can use it to interop between JavaScript and Java on serverless state to maximize the capabilities of your applications.
dev.to
August 31, 2026 at 7:12 AM
issue 書いた。
[[Feature Request] Make the SVG size limit configurable in the JavaScript build · Issue #2832 · plantuml/plantuml](github.com/plantuml/pla...)
[Feature Request] Make the SVG size limit configurable in the JavaScript build · Issue #2832 · plantuml/plantuml
Is your feature request related to a problem? Please describe. The JavaScript/TeaVM build currently has a hard-coded MAX_SVG_SIZE value of 4096 in PlantUMLBrowser. As a result, diagrams whose width...
github.com
August 26, 2026 at 9:21 AM
The JaBakkie Java source code is now available at gitlab.com/ewaldhorn/ja... - Good old Java running in the browser via TeaVM. Just for a laugh, but also potentially more useful...
July 22, 2026 at 8:26 AM
Judge all you want, but this is Java, running in the browser, via TeaVM! No jokes - nofuss.co.za/games/jabakk... - I even managed to give it some mobile support. And it was fun to build!
July 21, 2026 at 7:59 PM
is there any prior art using clojure with teavm?
November 30, 2023 at 4:07 PM
Applets Are Officially Going, But Java In the Browser Is Better Than Ever
"The entire java.applet package has been removed from JDK 26, which will release in March 2026," notes Inside Java. But long-time Slashdot reader AirHog links to this blog post reminding us that "Applets Are Officially Gone, But Java In The Browser Is Better Than Ever." This brings to an official end the era of applets, which began in 1996. However, for years it has been possible to build modern, interactive web pages in Java without needing applets or plugins. TeaVM provides fast, performant, and lightweight tooling to transpile Java to run natively in the browser... TeaVM, at its heart, transpiles Java code into JavaScript (or, these days, WASM). However, in order for Java code to be useful for web apps, much more is required, and TeaVM delivers. It includes a minifier, to shrink the generated code and obfuscate the intent, to complicate reverse-engineering. It has a tree-shaker to eliminate unused methods and classes, keeping your app download compact. It packages your code into a single file for easy distribution and inclusion in your HTML page. It also includes wrappers for all popular browser APIs, so you can invoke them from your Java code easily, with full IDE assistance and auto-correct. The blog post also touts Flavour, an open-source framework "for coding, packaging, and optimizing single-page apps implemented in Java... a full front-end toolkit with templates, routing, components, and more" to "build your modern single-page app using 100% Java." Read more of this story at Slashdot.
developers.slashdot.org
December 13, 2025 at 11:22 PM