ZeroZ Stack · The full-stack framework
In a conventional Java web application a single field travels from a Java class, through a JSON encoder, across HTTP, into a TypeScript interface and finally onto a DOM node, and at every one of those boundaries the compiler stops being able to help you. ZeroZ Stack does not try to make those boundaries cheaper, it removes them, so the whole trip happens in Java.
The cost
The Java object is converted into JSON text, sent over HTTP, and then parsed back into a JavaScript or TypeScript object in the browser. Nothing checks that the two ends still agree, so if a field is renamed on the server you usually find out from a user.
The browser DOM can only be changed from JavaScript or TypeScript, so even a pure Java team ends up with a second language, a second build tool chain and a second set of libraries, just for the screen.
Rename a field on the server and nothing breaks, not at compile time and not at deploy time. It breaks in the browser, for a user, as undefined. Add a nullable field and three separate artefacts have to agree about it, i.e. the entity, the DTO and the TypeScript interface, and usually one of them gets forgotten. And if you ask an AI agent to add a column end to end, it has to hold four languages in context at once, which is generally where it starts inventing endpoints that do not exist.
The solution
A ZeroZ Stack client is compiled Java that runs as WebAssembly in the browser. It calls an ordinary Java interface, the call travels as packed binary over a persistent WebSocket, and it arrives at a CDI bean on the server as the same object with the same type. There is no controller, no deserialiser and no mapping layer, basically because there is nothing left to map between.
01 · How it works
You write a model class, a service interface, its implementation and a UI class, all in Java and all sharing the exact same object. There is no HTTP mapping, no JSON translation and no ORM schema to write, and the four files below are the complete feature (a small chat message), not an abbreviated version of it.
// becomes serializable + persistable @DataModel public class ChatMessage implements BinaryPackable { private String author; private String text; public ChatMessage() {} // constructor, getters, setters… }
// the RPC contract — one interface @RmiService public interface ChatService { void sendMessage(ChatMessage msg); }
@ApplicationScoped public class ChatServiceImpl implements ChatService { @Inject ZeroZDbNode db; @Override public void sendMessage(ChatMessage msg) { // receives the exact object sent — persist it db.localDb().write(ctx -> { root.getMessages().add(msg); ctx.store(root.getMessages()); }); } }
public class ChatView extends Div { ChatView(ChatService chat) { Button b = new Button("Send"); b.onClick(e -> { // calls the backend over binary WS chat.sendMessage(new ChatMessage("Alice", "Hi!")); }); add(b); } }
→ scaffold the multi-module project with the Maven archetype, then mvn clean install and open http://localhost:8080
02 · The model
@LiveSync is about state synchronisation and signals are about reactivity, and it helps to keep the two apart. The first one keeps the object true on both sides, and the second one decides what the user interface does about a change. If you bind a synchronised object to a signal, a change made on the server redraws the UI without you writing any glue code.
Why binary
The client and the server talk to each other in a dense binary RPC protocol over a persistent WebSocket. This is not a compressed JSON, the text layer is simply gone, and with it the parsing, the repeated field names and the runtime type checks.
Under the hood
Performance in the browser is decided at compile time rather than hoped for at runtime.
During the Maven build an annotation processor scans for @DataModel and @RmiService and generates the _Serializer and _Stub classes. There is no runtime reflection, which matters because WebAssembly handles reflection poorly.
The client module is transpiled from Java bytecode, including the generated stubs and serialisers, directly into WasmGC, which then runs in the browser.
Wasm runs on the single UI thread of the browser, so it cannot block. An RMI call therefore suspends the coroutine, sends its frame, hands control back to the browser, and resumes exactly when the response arrives.
Every incoming WebSocket frame is handed to a virtual thread, so the server's I/O threads never block on a long query and thousands of persistent connections stay responsive.
The core is a transport-agnostic CDI engine and RMI dispatcher, and a separate server binding supplies the HTTP and WebSocket transport.
Unlike Vaadin, ZeroZ4j keeps no DOM state on the server at all. Components live entirely in the client's Wasm heap, so a user session costs the server nothing except its connection.
Deployment
The same application code runs in three ways. In embedded mode the store is private to the JVM and there is no socket in the path. In auto-server mode the first JVM to start owns the store and serves it, and the others become clients, without any configuration deciding which is which. Or you point every JVM at a dedicated database server and none of them owns data. The application code does not change between the three, only the mode does, so you can start small and move later.