Code Walkthrough: End-to-End Java¶
⚠️ Partly outdated. Steps 1–3 reflect the current API. Step 4 (the client bootstrap) does not — it shows a manual
BinaryPackableRegistrar.registerAll()/WasmRmiClientChannel/WasmRmiClient.initializepath that is obsolete. The current bootstrap is a single call,Zeroz4jClient.connect(webSocketUrl, onReady), and registrars are discovered automatically throughMETA-INF/services. See Quickstart.
Zero impedance means your Java objects flow from the database to the browser without translation layers.
Step 1: Declare the Domain Model¶
Annotate your models with @DataModel — one annotation, nothing else. Compile-time serializers will be generated to write/read fields without reflection:
import com.zeroz4j.api.DataModel;
@DataModel
public class UserInfo {
private String name;
private int score;
public UserInfo() {} // Required
// Getters and setters...
}
Step 2: Declare the Remote Service Interface¶
Annotate your contract interfaces with @RmiService:
import com.zeroz4j.api.RmiService;
@RmiService
public interface UserService {
UserInfo getUserInfo(String username);
void updateScore(String username, int newScore);
}
Step 3: Server Persistence with EclipseStore¶
Because there is zero impedance, you don't need JPA or SQL to save your data. By simply depending on the zerozstack-store-eclipsestore module, EclipseStore is automatically wired up and ready to persist your object graph exactly as it is in memory.
If you want your entities to automatically synchronize memory state between the frontend and backend without a manual push, you can add @LiveSync to your models. However, database persistence is always strictly explicit to keep you in control.
import jakarta.enterprise.context.ApplicationScoped;
import com.zeroz4j.db.net.ZeroZDbNode;
import jakarta.inject.Inject;
@ApplicationScoped
public class UserServiceImpl implements UserService {
@Inject
private ZeroZDbNode db;
private DataRoot getRoot() {
return (DataRoot) db.localDb().root();
}
@Override
public UserInfo getUserInfo(String username) {
// Querying is just Java stream operations over the stored object graph
return getRoot().getUsers().stream()
.filter(u -> u.getName().equals(username))
.findFirst()
.orElse(null);
}
@Override
public void updateScore(String username, int newScore) {
UserInfo user = getRoot().getUsers().stream()
.filter(u -> u.getName().equals(username))
.findFirst().orElseThrow();
user.setScore(newScore);
// Persist the delta directly! No UPDATE statements.
// Saves are explicitly controlled by the developer.
storage.store(user);
}
}
Step 4: Implement the Client-Side Application (TeaVM/Wasm)¶
Initialize the WebSocket connection and call the generated RMI stub. The method execution will suspend processing cooperatively until response packet returns, keeping the UI fully responsive:
import com.zeroz4j.client.WasmRmiClient;
import com.zeroz4j.client.WasmRmiClientChannel;
import com.zeroz4j.generated.BinaryPackableRegistrar;
public class ClientApplication {
public static void main(String[] args) {
// 1. Register generated serializers
BinaryPackableRegistrar.registerAll();
// 2. Open WebSocket Channel
WasmRmiClientChannel channel = new WasmRmiClientChannel("ws://localhost:8080/wasm-rmi", () -> {
System.out.println("Connection opened!");
});
WasmRmiClient.initialize(channel);
// 3. Obtain Service Stub
UserService service = new UserService_Stub(); // Generated implementation
// 4. Invoke RMI calls seamlessly
UserInfo profile = service.getUserInfo("franz");
System.out.println("User Profile: " + profile);
}
}