Cettia is a full-featured real-time web application framework for Java that you can use to exchange events between server and client in real-time. It is meant for when you run into issues which are tricky to resolve with WebSocket, JSON, and switch statement per se:

  • Avoiding repetitive boilerplate code
  • Supporting environments where WebSocket is not available
  • Handling both text and binary data together
  • Recovering missed events
  • Providing multi-device user experience
  • Scaling out an application horizontally, and so on.

It offers a reliable full duplex message channel and elegant patterns to achieve better user experience in the real-time web, and is compatible with any web frameworks on the Java Virtual Machine.

Cettia is an open source project licensed under Apache License 2.0 and driven by the community, for the community. If you are interested and would like to be more involved, feel free to join the community and share your feedback.


Here are helpful resources produced by a growing community.




Here are release announcements for Cettia subprojects.

Getting Started

This is a summary of a tutorial, Building Real-Time Web Applications With Cettia, for quick start. You may want to read the tutorial for better understanding of Cettia. It covers the reason behind key design decisions that the Cettia team have made in the Cettia as well as the features required to create real-time oriented web applications with Cettia.

The result of the tutorial, the Cettia starter kit, is available in the GitHub repository. If you have Java 8+ and Maven 3+ installed, you can run the example by cloning or downloading the repository and typing the following maven command.

git clone https://github.com/cettia/cettia-starter-kit
cd cettia-starter-kit
mvn jetty:run

Then, open a browser and connect to http://localhost:8080.

Setting Up the Project


Add an io.cettia:cettia-server:1.2.0 (Javadoc) as a dependency of your application.


Then, you can accept and handle sockets that connect to the server through server.onsocket(socket -> {}).

Server server = new DefaultServer();
HttpTransportServer httpAction = new HttpTransportServer().ontransport(server);
WebSocketTransportServer wsAction = new WebSocketTransportServer().ontransport(server);

server.onsocket((ServerSocket socket) -> System.out.println(socket));

// javax.servlet.Servlet asityServlet = new AsityServlet().onhttp(httpAction);
// javax.websocket.Endpoint asityEndpoint = new AsityServerEndpoint().onwebsocket(wsAction);

Cettia is based on Asity and compatible with any web framework on the Java Virtual Machine. As you can see in the commend out code, the above application is able to run on any framework as long as you feed httpAction and wsAction with the framework's HTTP request-response exchange and WebSocket connection through bridges per framework provided by Asity like the above asityServlet and asityEndpoint. For the usage of bridge, see Asity's Run Anywhere section. Asity supports almost all popular web frameworks in Java: Servlet and Java API for WebSocket, Spring WebFlux, Spring MVC, Grizzly, Vert.x, Netty, Atmosphere, and so on.

The tutorial uses Servlet and Java API for WebSocket as a web framework and passes requests whose URI is /cettia to the Cettia server. In other means, the Cettia client can connect to this server through


Load the cettia object the way you want.

<script src="https://unpkg.com/cettia-client@1.0.1/cettia-browser.min.js"></script>
npm install cettia-client --save
var cettia = require("cettia-client/cettia-bundler");
npm install cettia-client --save
var cettia = require("cettia-client");

Then, you can open a socket pointing to the URI of the Cettia server with cettia.open(uri).

var socket = cettia.open("/cettia");

You may have to use an absolute URI,, if you use runtimes other than browser like Node.js. If everything is set up correctly, you should be able to see a socket log similar to the following in the server-side.


Socket Lifecycle

A socket always is in a specific state, such as opened or closed. Its state keeps changing based on the state of the underlying connection, firing one of built-in events. Just know that the communication is possible only in the opened state.


The state transition diagram of a server socket.


Tracking the state transition of the server socket.

server.onsocket(socket -> { // By 1
  Action<Void> log = v -> System.out.println(socket.state());
  socket.onopen(log); // By 3 and 5
  socket.onclose(log); // By 2 and 4
  socket.ondelete(log); // By 6


The state transition diagram of a client socket.


Tracking the state transition of the client socket.

var log = arg => console.log(socket.state(), arg);
socket.on("connecting", log); // By 1 and 6
socket.on("open", log); // By 3
socket.on("close", log); // By 2, 4, and 7
socket.on("waiting", log); // By 5

Attributes and Tags

In order to store information regarding socket like username in a socket and find sockets based on the stored information, Cettia provides attributes and tags per socket. They are analogous to data-* attributes and class attribute defined in HTML, respectively.


An attributes and its sugar methods on ServerSocket are as follows.

Map<String, Object> attributes()
Returns an attributes of the socket.
Object get(key)
Returns the value mapped to the given name.
ServerSocket set(key, value)
Associates the value with the given name in the socket.
ServerSocket remove(key)
Removes the mapping associated with the given name.


A tags and its sugar methods on ServerSocket are as follows.

Set<String> tags()
Returns a tags of the socket.
ServerSocket tag(tags...)
Attaches given tags to the socket.
ServerSocket untag(tags...)
Detaches given tags from the socket.

Sending and Receiving Events

A unit of exchange between the Cettia client and the Cettia server in real-time is the event. You can define and use your own events as long as the event name isn't duplicated with built-in events. Here's the echo event handler where any received echo event is sent back.


socket.on("echo", (Object data) -> socket.send("echo", data));


socket.on("echo", data => socket.send("echo", data));

In the server side, the allowed types for the event data are not just Object, but determined by Jackson, a JSON processor used by Cettia internally. If an event data is supposed to be one of the primitive types, you can cast and use it with the corresponding wrapper class, and if it’s supposed to be an object like List or Map and you prefer POJOs, you can convert and use it with JSON library like Jackson. It might look like this:

socket.on("event", data -> {
  Model model = objectMapper.convertValue(data, Model.class);
  Set<ConstraintViolation<Model>> violations = validator.validate(model);
  // ...

An event data can be basically anything as long as it is serializable, regardless of whether data is binary or text. If at least one of the properties of the event data is byte[] or ByteBuffer in the server, Buffer in Node or ArrayBuffer in the browser, the event data is internally treated as binary, and that binary property is given as a ByteBuffer in the server, a Buffer in Node, and an ArrayBuffer in the browser.

Disconnection Handling

Cettia defines the temporary disconnection as one that is followed by reconnection within 60 seconds, and designs a socket's lifecycle to be unaffected by temporary disconnections, to support environments where temporary disconnections happen frequently just like the mobile environment. Here's an example to send events failed due to disconnection on the next connection.

Queue<Object[]> queue = new ConcurrentLinkedQueue<>();
socket.oncache(args -> queue.offer(args));
socket.onopen(v -> {
  while (socket.state() == ServerSocket.State.OPENED && !queue.isEmpty()) {
    Object[] args = queue.poll();
    socket.send((String) args[0], args[1], (Action<?>) args[2], (Action<?>) args[3]);
socket.ondelete(v -> queue.forEach(args -> System.out.println(socket + " missed event - name: " + args[0] + ", data: " + args[1])));

The cache event above is fired with an argument array used to call the send method, if the socket has no active connection when the send method is called. If there has been no reconnection within one minute since disconnection, the delete event is fired and the lifecycle of socket ended. With the delete event, you can store the miseed events in a database and show them on the next visit.

Working with Sockets

The most common use case in a real-time web application is to push messages to certain clients, of course. Cettia supports this intuitively by enabling "find sockets and do something with them" without a separate concept like Topic and Broadcaster.

server.find(socket -> /* find sockets */).execute(socket -> /* do something with them */);

server.find(predicate) finds a certain set of sockets that matches the given predicate and returns an instance of fluent interface called Sentence. And sentence.execute(action) allows to deal with the sockets through the passed socket action. Here's an example to send a chat event to every socket in the server.

server.find(socket -> true).execute(socket -> socket.send("chat", "Hi, there"));

Along with server.find and sentence.execute, Cettia offers the following pre-defined predicates and socket actions through ServerSocketPredicates and Sentence, respectively, to make the code even more expressive and readable.


The following are static methods to create socket predicates defined in ServerSocketPredicates.

A predicate that always matches.
attr(String key, Object value)
A predicate that tests the socket attributes against the given key-value pair.
id(ServerSocket socket)
A predicate that tests the socket id against the given socket's id.
id(String id)
A predicate that tests the socket id against the given socket id.
tag(String... tags)
A predicate that tests the socket tags against the given tags.

Here’s an example to find sockets whose username is the same except the socket. Assume the attr and id are statically imported from the ServerSocketPredicates class.

ServerSocketPredicate p = attr("username", username).and(id(socket).negate());


Each method on Sentence is mapped to a pre-implemented common socket action, so if the method is executed, its mapped action is executed with sockets matching the sentence’s predicate. Here is a list of methods on the sentence.

Closes the socket.
send(String event)
Sends a given event without data through the socket.
send(String event, Object data)
Sends a given event with the given data through the socket.
tag(String... tags)
Attaches given tags to the socket.
untag(String... tags)
Detaches given tags from the socket.

Here’s an example of a sentence.


Scaling a Cettia Application

Last but not least is scaling an application. Any publish-subscribe messaging system can be used to scale a Cettia application horizontally, and it doesn't require any modification in the existing application. Here's an example of Hazelcast. Replace Server server = new DefaultServer(); with ClusteredServer server = new ClusteredServer();, and add the following dependencies to your application:


Then place the following Hazelcast configuration after ClusteredServer server = new ClusteredServer();.

HazelcastInstance hazelcast = HazelcastInstanceFactory.newHazelcastInstance(new Config());
ITopic<Map<String, Object>> topic = hazelcast.getTopic("cettia");
server.onpublish(message -> topic.publish(message));
topic.addMessageListener(message -> server.messageAction().on(message.getMessageObject()));

If you start up the server with different port such as 8090, you should see servers listening to 8080 and 8090 form a a cluster of Hazelcast nodes. This means that a chat event sent from a client connected to the server on 8080 propagates to clients connected to the server on 8090 as well as 8080.