ZeroZ Stack documentation¶
ZeroZ Stack is a pure-Java full-stack framework. Your UI is Java compiled by TeaVM to run in the browser, your network layer is a binary RPC protocol over a persistent WebSocket, and your database is the JVM object graph on disk. You write no JavaScript, no JSON, no REST routes and no SQL.
Experimental
ZeroZ Stack is an experimental proof-of-concept at version 0.9.0. It is a working demonstration, not an industrialized production framework. Known gaps are listed in Limitations, and every page states its own limits where the feature is taught.
Compilation target
The client is written entirely in Java and compiled ahead-of-time by TeaVM. It currently targets TeaVM's JavaScript backend, deliberately: WasmGC does not yet provide functionality ZeroZ Stack depends on. WasmGC is the intended destination and the project will move to it once TeaVM's support is complete. Nothing in the code you write changes either way.
Three ways in¶
-
Learn it
Start from nothing and get a running application.
-
Decide
The framework's hardest question: which construct carries this piece of state?
-
Look it up
Annotations, APIs, the wire protocol, supported types.
The shape of an application¶
Three modules. The shared one compiles into both tiers, which is why a contract violation is a compile error rather than a runtime surprise.
// shared — the contract, compiled into client and server alike
@DataModel
public class ChatMessage {
private String author;
private String text;
public ChatMessage() { } // required: public no-arg constructor
public ChatMessage(String author, String text) {
this.author = author;
this.text = text;
}
// getters and setters for every serialized field
}
@RmiService
public interface ChatService {
void sendMessage(ChatMessage msg);
List<ChatMessage> getHistory();
}
// server — an ordinary CDI bean
@ApplicationScoped
public class ChatServiceImpl implements ChatService {
@Inject private EventPublisher events;
@Override
public void sendMessage(ChatMessage msg) {
events.publish(ChatEvents.MESSAGE_POSTED, msg);
}
}
// client — Java, compiled by TeaVM. The call suspends; it does not block.
ChatService chat = new ChatService_Stub();
sendButton.addClickListener(e -> chat.sendMessage(new ChatMessage(author, text)));
New in 0.8.0¶
-
Every control works without a mouse
Tabs, menus, links, copy buttons, splitters and drop boxes are all reachable with Tab and pressed with Enter, and every one of them says what it is. A build check fails when a new component forgets.
-
A field can say what it is, and why it refused
new TextField().withLabel("Email address")puts a real caption on the field. A failed check now writes its sentence under the field instead of turning the box red and saying nothing. -
Overlays that behave like overlays
A dialog takes over the page and Escape closes it. A drawer holds the keyboard while it is open. What sits above what is a named layer —
PAGEtoTOOLTIP— instead of a number somebody guessed. -
A record can cross the wire
public record Money(long amount, String currency) { }replaces ten lines of constructor, getters and setters. Asealedinterface travels as itself, so what arrives is the real type and not a cast. Fields inherited from a base class stop vanishing. -
A server inside a test, in a tenth of a second
Start one in the same process, open connections to it, and count what the browser was sent. Two servers can run side by side without sharing anything.
-
Leaving a screen shuts it down
replaceContentsswaps what is inside a container and tells everything on its way out. Before this,onDetachalmost never ran, so the screen you had just left kept its timers going. -
Text has a size and, separately, a strength
Five named sizes instead of describing one every time, and a second answer — full, quiet, faint — so a measurement can be small without being faded. Charts get four matching names for the words they draw inside the picture.
New in 0.7.0¶
-
Files from the person using the app
A drop-or-pick box with a progress bar and a cancel button for each file, and one Java class on the server that is handed each finished file. 25 MB per file by default.
-
Numbers instead of "whatever the container allows"
The biggest message the server accepts is 4 MB. One connection's messages are handled one at a time in the order they were sent, with 32 allowed to wait, and 256 messages or 8 MB may be waiting to go out. All six are settings, and the one in force is written to the log at startup.
-
Editing one thing at a time
LiveMutexmakes the second person wait instead of overwriting the first. A caller waits 30 seconds, callers are served in the order they arrived, and a dropped connection tells the holder its lock is gone. -
What a client may ask for
The server keeps a record of the objects it sent to each browser, and answers a re-read or a lock request only from that record. It survives a reconnect, holds 10,000 objects per browser, and is dropped after 24 hours idle.
-
Errors you can trace
An unexpected failure reaches the caller as one sentence and a short code, with the real message in the server log under the same code. Throw
ClientVisibleExceptionfor text the caller should read.
New in 0.6.0¶
-
State that belongs to somebody
Signals.sharedis one value for the whole server.Signals.scopedholds one per tenant, user or browser — and a client only ever sees its own.Scope.CLIENTneeds no login at all. -
URLs mapped to views
Real paths, nested layouts, typed parameters — and each route declares the data it needs, loaded before anything renders. The route table is generated at compile time.
-
Logging in for real
Authorization-code flow with PKCE against Keycloak in the browser, token verification at the handshake, and its claims becoming roles and a tenant.
-
Deployment into an application server
Take
zerozstack-server-jakartainstead of the Helidon binding and a WAR runs on WildFly, Payara, Open Liberty or TomEE — with RMI calls on container threads, sojava:complookups work inside a service. -
Installable, with push
One call and three tags make an application installable and push-capable. It does not make it work offline — nothing here can, and the page says so plainly rather than pretending.
Alongside those: a server-issued, HttpOnly client identity so an application with no login can
still keep one browser's state to itself, and a check on every handshake that the page opening it is
one of yours. Both are described in
Authentication and authorization.
If you read one page¶
Choosing how state moves. ZeroZ Stack gives you five ways to move state — local signals, RMI calls, server events, shared signals and LiveSync — and picking the wrong one is the most common source of trouble in ZeroZ Stack applications. The symptoms are rarely obvious: a feature works on your machine and fails for the second user, or works until someone reloads the page.
Working with an AI agent¶
The stack is designed so a coding agent holds one language and one model of your data instead of four.
To get the benefit, point your agent at AGENTS.md
in the repository root; it carries the build commands, the decision procedure and the silent-failure
list in a form an agent can act on. The repository also ships a context7.json, so that guidance
reaches agents working through Context7 once the library is indexed.