zfoo: where the Java class is the network schema
💡Extremely fast enterprise server framework, can be used in RPC, game server, web server.
At a glance
- What is it?
- zfoo bundles serialization, RPC, MongoDB mapping, Excel config loading, scheduling and an event bus into one Java 17 artifact, with almost all of the wiring expressed as annotations. The samples are short enough to copy, while the configuration keys, thread model and speed claims stay undocumented.
- Who is it for?
- Adopt zfoo when your team already runs Java 17 services and wants one artifact for codecs, RPC, MongoDB mapping, Excel config loading and scheduling, and leave it alone when you need documented configuration keys, a schema registry or published benchmark numbers, because the repository supplies none of those three.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 163 days ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- zfoo-project/zfoo2,008 ★ · -6
| Repository | 2026-10-15 | 2026-11-07 |
|---|---|---|
| zfoo-project/zfoo | 2k | 2k |
-6 Stars in the last 23 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
One aggregate artifact or one module, on JDK 17
zfoo answers a specific complaint: a Java backend arrives as six libraries stitched together, and every seam between them becomes code your team owns. A network layer for sockets, a serializer, a MongoDB mapping layer, a scheduler, a config loader and a monitoring agent each arrive with their own setup, and the glue between them is yours to write. zfoo replaces that list with a single `com.zfoo` artifact where the wiring lives in annotations on your own classes. The aggregate coordinate is `boot`, and the stated environment requirement is JDK 17+, with OpenJDK, Oracle JDK and native GraalVM named as supported.
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>boot</artifactId>
<version>4.1.4</version>
</dependency>Carrying only one piece is possible, because the same version is published per module. Take `protocol` when the wire format is all you need and the rest of the stack would be dead weight in your dependency tree.
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>protocol</artifactId>
<version>4.1.4</version>
</dependency>The repository tree lists nine module directories, and nothing says which of them `boot` bundles, so the contents of the aggregate are something you read out of your own resolved dependencies before committing to it. Java is the primary language of the repository, and every client side lives in a separate SDK project rather than here.
Registration turns your class into the wire schema
Serialization starts with a registration call instead of a schema file. You hand `ProtocolManager` the set of classes you intend to send, and it prepares the encoder and the decoder for them.
// protocol registration
ProtocolManager.initProtocol(Set.of(ComplexObject.class, ObjectA.class, ObjectB.class));
// serialization
ProtocolManager.write(byteBuf, complexObject);
// deserialization
var packet = ProtocolManager.read(byteBuf);The class is the entire schema. There is no separate schema document, no id table and no XML mapping in this example, so the bytes on the wire follow from the fields you declared and the registration set becomes the contract your clients have to agree with. That cuts both ways. Adding a field is a two-line change in Java, while renaming or reordering one changes the format for every Rust, C#, GDScript or Go client at the same moment, and no version negotiation appears anywhere in the documentation. Nothing says what `read` does when it meets a class that never appeared in `initProtocol`, so treat an unregistered type as unspecified rather than as a helpful exception you can catch and log.
Receivers bind from one annotation, consumers choose to block
Network services follow the same annotation-over-configuration logic. A provider method registers itself the moment you put `@PacketReceiver` on it, with the connection `Session` and the request object named in the signature, and there is no service descriptor file to keep in step with your interface.
// Service provider, only need to add an annotation to the method, the interface will be automatically registered
@PacketReceiver
public void atUserInfoAsk(Session session, UserInfoAsk ask) {
}Consumers pick between two call styles, and the difference is whether the calling thread waits. `syncAsk` returns the answer, and the comment above it states that the call blocks the current thread, so a handler built on it holds up everything else scheduled onto that thread. `asyncAsk` returns a future and the sample attaches its continuation with `whenComplete`.
// Consumers, synchronously requesting remote service, will block the current thread
var ask = UserInfoAsk.valueOf(userId);
var answer = NetContext.getCosumer().syncAsk(ask, UserInfoAnswer.class, userId).packet();
// Consumers, asynchronously requesting remote service, and will still execute logic in the current thread after the asynchronous
NetContext.getCosumer().asyncAsk(ask, UserInfoAnswer.class, userId)
.whenComplete(answer -> {
// do something
);Two details in that snippet reward a closer look. The accessor is spelled `NetContext.getCosumer()` in both examples, without the n that English expects, so copy the spelling as given instead of silently correcting it inside a wrapper of your own. And the comment on `asyncAsk` says the continuation still executes in the current thread, which the sample itself does not demonstrate; which thread your `whenComplete` body lands on decides whether it is safe to touch a session or write to a connection from inside it, so check that before you chain remote calls.
Hot swap passes raw class bytes and stops there
Replacing code in a running JVM is one static call here, and its input is the compiled bytes of the class you want swapped.
// Pass in the class file that needs to be updated
HotSwapUtils.hotswapClass(bytes);No agent configuration, no attach command and no restart appear anywhere in that flow, which is a smaller surface than a JVM attach normally demands. What the documentation does not give you is the other half: how `bytes` are produced and delivered to the process, whether by a file watcher, a remote push or your own build pipeline. The pointer it gives leads to a test source, `hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java`, so the working example lives in the test folder rather than in prose. The repository topics also carry `byte-buddy` and `javassist`, both names from the bytecode and class-generation toolkits, though nothing says which of them, if either, performs the redefinition here.
MongoDB shape comes from the entity class itself
Persistence is annotation-only as well. There is no SQL to write and no mapping file to maintain: an entity declares its identifier and its fields, and writes go through a cache object.
// You don't need to write sql and any configuration yourself, define a table in the database directly through annotation definitions
@EntityCache
public class UserEntity implements IEntity<Long> {
@Id
private long id;
private String name;
}
// update database data
entityCaches.update(userEntity);Read the block as two fragments rather than one class: the entity definition, then the call that follows it through an object named `entityCaches`. Note also what the entity does not carry. There is no collection name, no index annotation and no connection setting in the example, so where the mapping layer looks for your documents is decided somewhere the sample does not show, and the index annotation that the storage module uses for spreadsheets has no counterpart here on the query path. For anything holding real data the deeper point stands: the mapping is the field list, so a rename changes the key the next write stores, and no migration step, alias or version marker is documented for documents already filed under the old name. That turns a field rename into a planned outage rather than a refactor.
Excel sheets and cron strings become annotated classes
Configuration and scheduling use the same pattern. A class annotated `@Storage` is paired with a spreadsheet, an id annotation marks the key column and an index annotation marks a column you look records up by, so the header row drives the mapping, and json and csv are handled the same way.
@Storage
public class StudentResource {
@Id
private int id;
@Index
private String name;
private int age;
}Timed work is a cron string on a method, and the published example fires every second.
@Scheduler(cron = "0/1 * * * * ?")
public void cronSchedulerPerSecond() {
// do something
}Cross-module communication goes through an observer instead of direct calls. A method carrying `@EventReceiver` starts listening, and `EventBus.post` publishes to whoever registered.
// To receive an event, you only need to add an annotation to the method and the method will be automatically listen for the event
@EventReceiver
public void onMyNoticeEvent(MyNoticeEvent event) {
// do something
}
// fire an event
EventBus.post(MyNoticeEvent.valueOf("My event"));The operational claim sits one line above these examples: Excel, json and csv configuration is mapped automatically and can be hot swapped online, which would let a designer change a balance number without a redeploy. Neither the reload interval nor the behaviour when a sheet loses a column is written down, and those two gaps are what you would have to settle before spreadsheet editing reaches production.
Seventeen client languages claimed, three SDK repositories linked
The protocol section promises a client in C++, Rust, Java, Kotlin, Scala, JavaScript, TypeScript, ES, C#, Go, Php, Ruby, Lua, GDScript, Python, Dart and Swift. The tutorial section then links SDK repositories for a much smaller slice: C# and Lua for Unity and Godot, TypeScript and JavaScript for Cocos, web and H5, GDScript for Godot, plus a `godot-bird` demo project. Put the two lists side by side and the size of the gap becomes clear. A Unity or Godot client can be wired up from an existing SDK, while a C++, Go or Php client starts from a wire format described only by a serialization snippet. The repository topics also name `unreal-engine`, and no Unreal entry appears in the tutorial list at all. What the documentation offers for the remaining languages is a protocol document and the shared server layout, so interoperability is a design goal here rather than shipped client code you can inspect.
Extreme speed asserted, measured nowhere, and the hand-built stack as the other road
The opening description calls zfoo an Extremely fast enterprise server framework for RPC, game server and web server work, and the usage headings repeat the claim with the words ultimate performance. Nowhere in the repository does a number appear: no latency figure, no throughput figure, no hardware description, no comparison against another serializer or RPC library. The actor design, the lock free claim and built-in Cpu, memory, hard disk and network monitoring are all asserted in prose, and the sole evidence offered for the monitoring is a link to a test class at `monitor/src/test/java/com/zfoo/monitor/ApplicationTest.java`. Those two directories, `boot` and `monitor`, are also the only ones that never receive a code sample in the usage list, which matters before you plan an operations dashboard on top of them.
The other road is the assembly route: keep your own network library, generate a codec per message, maintain the id table by hand, write the database mapping layer yourself, and pick a scheduler and a config loader separately. zfoo removes that assembly cost by deriving codecs and service registration from your class definitions at startup, and pays for it by making your Java classes the wire contract while leaving the configuration surface, the thread model and the deployment knobs undocumented. Single server, microservice, cluster and gateway deployments are all named as supported, and `deploy.sh` sits at the top of the tree, but no script contents or flags are shown.
Version numbers tell their own story: the three newest releases, 4.1.4 on 2025-10-14 plus 4.1.3 and 4.1.2 on 2025-10-13, sit far behind the last push to the default branch main on 2026-05-29, so a build pinned at 4.1.4 runs October code. The repository carries the Apache License Version 2.0 grant and no contributor agreement file appears among the top-level entries.
Editorial conclusion
Adopt zfoo when your team already runs Java 17 services and wants one artifact for codecs, RPC, MongoDB mapping, Excel config loading and scheduling, and leave it alone when you need documented configuration keys, a schema registry or published benchmark numbers, because the repository supplies none of those three. Verify which thread your `asyncAsk` continuation runs on, read `hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java` end to end, then pin 4.1.4 in your build rather than tracking the main branch.
Frequently asked questions
What does zfoo need before a project can depend on it?
JDK 17 or newer, with OpenJDK, Oracle JDK and native GraalVM named as supported. The Maven coordinates are groupId `com.zfoo` with artifactId `boot` for the aggregate or `protocol` for a single module, both published at version 4.1.4.
Which game engines have a zfoo SDK?
The tutorial section links SDK repositories for C# and Lua aimed at Unity and Godot, TypeScript and JavaScript aimed at Cocos, web and H5, and GDScript for Godot. It names C++, Rust, Go, Php, Ruby, Python, Dart and Swift as protocol languages without linking an SDK for them.
How does zfoo hot update code without downtime?
You pass the bytes of the class you want replaced to `HotSwapUtils.hotswapClass`, and no extra configuration is involved. Where those bytes come from is not explained in prose, so the working reference is the test source at `hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java`.
Do you write SQL or mapping files to use the zfoo ORM?
No. An entity carries `@EntityCache`, implements `IEntity` and marks its key with an id annotation, and writes go through `entityCaches.update`. The mapping comes from the field names in the class itself, and no migration step is documented for renaming one.
How are Excel config files and timed jobs defined in zfoo?
A class annotated `@Storage` maps a spreadsheet onto its fields, with id and index annotations marking the key and lookup columns, and json and csv are mapped the same way. Timed work is a method carrying `@Scheduler` with a cron expression, and the published example fires once per second.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/zfoo-project-zfoo)Star history chart for your README
[](https://hysenlabs.com/projects/zfoo-project-zfoo)Was this useful?