Ably is the platform that powers synchronized digital experiences in realtime. Whether attending an event in a virtual venue, receiving realtime financial information, or monitoring live car performance data – consumers simply expect realtime digital experiences as standard. Ably provides a suite of APIs to build, extend, and deliver powerful digital experiences in realtime for more than 250 million devices across 80 countries each month. Organizations like Bloomberg, HubSpot, Verizon, and Hopin depend on Ably’s platform to offload the growing complexity of business-critical realtime data synchronization at global scale. For more information, see the Ably documentation.
A Java Realtime and REST client library. This library currently targets the Ably client library features spec Version 1.2.
Include the library by adding an implementation
reference to dependencies
block in your Gradle build script.
For Java:
implementation 'io.ably:ably-java:1.2.44'
For Android:
implementation 'io.ably:ably-android:1.2.44'
The library is hosted on Maven Central, so you need to ensure that the repository is referenced also; IDEs will typically include this by default:
repositories {
mavenCentral()
}
We only support installation via Maven / Gradle from the Maven Central repository. If you want to use a standalone fat JAR (i.e. containing all dependencies), it can be generated via a Gradle task (see building below), creating a "Java" (JRE) library variant only. There is no standalone / self-contained AAR build option. Checkout requirements.
The library requires that the runtime environment is able to establish a safe TLS connection (TLS v1.2 or v1.3). It will fail to connect with a SecurityException
if this level of security is not available.
Please refer to the documentation for a full API reference.
The examples below assume a client has been created as follows:
AblyRealtime ably = new AblyRealtime("xxxxx");
AblyRealtime will attempt to connect automatically once new instance is created. Also, it offers API for listening connection state changes.
ably.connection.on(new ConnectionStateListener() {
@Override
public void onConnectionStateChanged(ConnectionStateChange state) {
System.out.println("New state is " + state.current.name());
switch (state.current) {
case connected: {
// Successful connection
break;
}
case failed: {
// Failed connection
break;
}
}
}
});
Given:
Channel channel = ably.channels.get("test");
Subscribe to all events:
channel.subscribe(new MessageListener() {
@Override
public void onMessage(Message message) {
System.out.println("Received `" + message.name + "` message with data: " + message.data);
}
});
or subscribe to certain events:
String[] events = new String[] {"event1", "event2"};
channel.subscribe(events, new MessageListener() {
@Override
public void onMessage(Message message) {
System.out.println("Received `" + message.name + "` message with data: " + message.data);
}
});
Subscribing to a channel in delta mode enables delta compression. This is a way for a client to subscribe to a channel so that message payloads sent contain only the difference (ie the delta) between the present message and the previous message on the channel.
Request a Vcdiff formatted delta stream using channel options when you get the channel:
Map<String, String> params = new HashMap<>();
params.put("delta", "vcdiff");
ChannelOptions options = new ChannelOptions();
options.params = params;
Channel channel = ably.channels.get("test", options);
Beyond specifying channel options, the rest is transparent and requires no further changes to your application. The message.data
instances that are delivered to your MessageListener
continue to contain the values that were originally published.
If you would like to inspect the Message
instances in order to identify whether the data
they present was rendered from a delta message from Ably then you can see if extras.getDelta().getFormat()
equals "vcdiff"
.
Data published to a channel (apart from strings or bytearrays) has to be instances of JsonElement to be encoded properly.
// Publishing message of type String
channel.publish("greeting", "Hello World!", new CompletionListener() {
@Override
public void onSuccess() {
System.out.println("Message successfully sent");
}
@Override
public void onError(ErrorInfo reason) {
System.err.println("Unable to publish message; err = " + reason.message);
}
});
// Publishing message of type JsonElement
JsonObject jsonElement = new JsonObject();
Map<String, String> inputMap = new HashMap<String, String>();
inputMap.put("name", "Joe");
inputMap.put("surename", "Doe");
for (Map.Entry<String, String> entry : inputMap.entrySet()) {
jsonElement.addProperty(entry.getKey(), entry.getValue());
}
channel.publish("greeting", message, new CompletionListener() {
@Override
public void onSuccess() {
System.out.println("Message successfully sent");
}
@Override
public void onError(ErrorInfo reason) {
System.err.println("Unable to publish message; err = " + reason.message);
}
});
PaginatedResult<Message> result = channel.history(null);
System.out.println(result.items().length + " messages received in first page");
while(result.hasNext()) {
result = result.getNext();
System.out.println(result.items().length + " messages received in next page");
}
channel.presence.enter("john.doe", new CompletionListener() {
@Override
public void onSuccess() {
// Successfully entered to the channel
}
@Override
public void onError(ErrorInfo reason) {
// Failed to enter channel
}
});
PaginatedResult<PresenceMessage> result = channel.presence.history(null);
System.out.println(result.items().length + " messages received in first page");
while(result.hasNext()) {
result = result.getNext();
System.out.println(result.items().length + " messages received in next page");
}
Channel
extends EventEmitter
that emits channel state changes, and listening those events is possible with ChannelStateListener
ChannelStateListener listener = new ChannelStateListener() {
@Override
public void onChannelStateChanged(ChannelStateChange stateChange) {
System.out.println("Channel state changed to " + stateChange.current.name());
if (stateChange.reason != null)
System.out.println("Channel state error" + stateChange.reason.message);
}
};
You can register using
channel.on(listener);
and after you are done listening channel state events, you can unregister using
channel.off(listener);
If you are interested with specific events, it is possible with providing extra ChannelState
value.
channel.on(ChannelState.attached, listener);
Callback that provides either tokens (TokenDetails
), or signed token requests (TokenRequest
), in response to a request with given token params.
ClientOptions options = new ClientOptions();
options.authCallback = new Auth.TokenCallback() {
@Override
public Object getTokenRequest(Auth.TokenParams params) {
System.out.println("Token Params: " + params);
// TODO: process params
return null; // TODO: return TokenDetails or TokenRequest or JWT string
}
};
AblyRealtime ablyRealtime = new AblyRealtime(options);
The examples below assume a client and/or channel has been created as follows:
AblyRest ably = new AblyRest("xxxxx");
Channel channel = ably.channels.get("test");
Given the message below
Message message = new Message("myEvent", "Hello");
Sharing synchronously,
channel.publish(message);
Sharing asynchronously,
channel.publishAsync(message, new CompletionListener() {
@Override
public void onSuccess() {
System.out.println("Message successfully received by Ably server.");
}
@Override
public void onError(ErrorInfo reason) {
System.err.println("Unable to publish message to Ably server; err = " + reason.message);
}
});
PaginatedResult<Message> result = channel.history(null);
System.out.println(result.items().length + " messages received in first page");
while(result.hasNext()) {
result = result.getNext();
System.out.println(result.items().length + " messages received in next page");
}
PaginatedResult<PresenceMessage> result = channel.presence.get(null);
System.out.println(result.items().length + " messages received in first page");
while(result.hasNext()) {
result = result.getNext();
System.out.println(result.items().length + " messages received in next page");
}
PaginatedResult<PresenceMessage> result = channel.presence.history(null);
System.out.println(result.items().length + " messages received in first page");
while(result.hasNext()) {
result = result.getNext();
System.out.println(result.items().length + " messages received in next page");
}
TokenDetails tokenDetails = ably.auth.requestToken(null, null);
System.out.println("Success; token = " + tokenRequest);
PaginatedResult<Stats> stats = ably.stats(null);
System.out.println(result.items().length + " messages received in first page");
while(result.hasNext()) {
result = result.getNext();
System.out.println(result.items().length + " messages received in next page");
}
long serviceTime = ably.time();
You can get log output from the library by modifying the log level:
import io.ably.lib.util.Log;
ClientOptions opts = new ClientOptions(key);
opts.logLevel = Log.VERBOSE;
AblyRest ably = new AblyRest(opts);
...
By default, log output will go to System.out
for the java library, and logcat for Android.
You can redirect the log output to a logger of your own by specifying a custom log handler:
import io.ably.lib.util.Log.LogHandler;
ClientOptions opts = new ClientOptions(key);
opts.logHandler = new LogHandler() {
public void println(int severity, String tag, String msg, Throwable tr) {
/* handle log output here ... */
}
};
AblyRest ably = new AblyRest(opts);
...
Note that any logger you specify in this way has global scope - it will set as a static of the library and will apply to all Ably library instances. If you need to release your custom logger so that it can be garbage-collected, you need to clear that static reference:
import io.ably.lib.util.Log;
Log.setHandler(null);
AblyRealtime will invoke all callbacks on background thread. If you are using Ably in Android application you must switch to main thread to update UI.
channel.presence.enter("john.doe", new CompletionListener() {
@Override
public void onSuccess() {
//If you are in Activity
runOnUiThread(new Runnable() {
@Override
public void run() {
//Update your UI here
}
});
//If you are in Fragment or other class
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//Update your UI here
}
});
}
});
See documentation for detail.
Ably provides two models for delivering push notifications to devices.
To publish a message to a channel including a push payload:
Message message = new Message("example", "realtime data");
message.extras = io.ably.lib.util.JsonUtils.object()
.add("push", io.ably.lib.util.JsonUtils.object()
.add("notification", io.ably.lib.util.JsonUtils.object()
.add("title", "Hello from Ably!")
.add("body", "Example push notification from Ably."))
.add("data", io.ably.lib.util.JsonUtils.object()
.add("foo", "bar")
.add("baz", "qux")));
rest.channels.get("pushenabled:foo").publishAsync(message, new CompletionListener() {
@Override
public void onSuccess() {}
@Override
public void onError(ErrorInfo errorInfo) {
// Handle error.
}
});
To publish a push payload directly to a registered device:
Param[] recipient = new Param[]{new Param("deviceId", "xxxxxxxxxxx");
JsonObject payload = io.ably.lib.util.JsonUtils.object()
.add("notification", io.ably.lib.util.JsonUtils.object()
.add("title", "Hello from Ably!")
.add("body", "Example push notification from Ably."))
.add("data", io.ably.lib.util.JsonUtils.object()
.add("foo", "bar")
.add("baz", "qux")));
rest.push.admin.publishAsync(recipient, payload, , new CompletionListener() {
@Override
public void onSuccess() {}
@Override
public void onError(ErrorInfo errorInfo) {
// Handle error.
}
});
See https://www.ably.com/docs/general/push/activate-subscribe for detail. In order to enable an app as a recipient of Ably push messages:
- register your app with Firebase Cloud Messaging (FCM) and configure the FCM credentials in the app dashboard;
- Implement a service extending
FirebaseMessagingService
and ensure it is declared in yourAndroidManifest.xml
, as per Firebase's guide: Edit your app manifest;- Override
onNewToken
, and provide Ably with the registration token:ActivationContext.getActivationContext(this).onNewRegistrationToken(RegistrationToken.Type.FCM, token);
. This method will be called whenever a new token is provided by Android.
- Override
- Activate the device for push notifications:
realtime.setAndroidContext(context);
realtime.push.activate();
When working in environments where outbound internet access is restricted, such as behind a corporate proxy, the Ably SDK allows you to configure a proxy server for HTTP and WebSocket connections.
You need to use OkHttp library for making HTTP calls and WebSocket connections in the Ably SDK to get proxy support both for your Rest and Realtime clients.
Add the following dependency to your build.gradle
file:
dependencies {
runtimeOnly("io.ably:network-client-okhttp:1.2.44")
}
After adding the required OkHttp dependency, you need to configure the proxy settings for your Ably client. This can be done by setting the proxy options in the ClientOptions
object when you instantiate the Ably SDK.
Here’s an example of how to configure and use a proxy:
import io.ably.lib.realtime.AblyRealtime;
import io.ably.lib.rest.AblyRest;
import io.ably.lib.transport.Defaults;
import io.ably.lib.types.ClientOptions;
import io.ably.lib.types.ProxyOptions;
import io.ably.lib.http.HttpAuth;
public class AblyWithProxy {
public static void main(String[] args) throws Exception {
// Configure Ably Client options
ClientOptions options = new ClientOptions();
// Setup proxy settings
ProxyOptions proxy = new ProxyOptions();
proxy.host = "your-proxy-host"; // Replace with your proxy host
proxy.port = 8080; // Replace with your proxy port
// Optional: If the proxy requires authentication
proxy.username = "your-username"; // Replace with proxy username
proxy.password = "your-password"; // Replace with proxy password
proxy.prefAuthType = HttpAuth.Type.BASIC; // Choose your preferred authentication type (e.g., BASIC or DIGEST)
// Attach the proxy settings to the client options
options.proxy = proxy;
// Create an instance of Ably using the configured options
AblyRest ably = new AblyRest(options);
// Alternatively, for real-time connections
AblyRealtime ablyRealtime = new AblyRealtime(options);
// Use the Ably client as usual
}
}
Visit https://www.ably.com/docs for a complete API reference and more examples.
- Ably Asset Tracking SDKs for Android
- Chat app using Spring Boot + Auth0 + Ably
- Spring + Ably Pub/Sub Demo with a Collaborative TODO list
For Java, JRE 8 or later is required. Note that the Java Unlimited JCE extensions must be installed in the Java runtime environment.
For Android, 4.4 KitKat (API level 19) or later is required.
Please visit http://support.ably.io/ for access to our knowledgebase and to ask for any assistance.
You can also view the community reported Github issues.
To see what has changed in recent versions of Bundler, see the CHANGELOG.
For guidance on how to contribute to this project, see CONTRIBUTING.md.