Getting started: LiveObjects in Java

This guide shows how to integrate Ably LiveObjects into your Java application.

You will learn how to:

  • Create an Ably account and get an API key for authentication.
  • Install the Ably Pub/Sub SDK.
  • Create a channel with LiveObjects functionality enabled.
  • Use the PathObject API to access objects on a channel.
  • Create, update and subscribe to changes on LiveObjects data structures: LiveMap and LiveCounter.

Authentication

An API key is required to authenticate with Ably. API keys are used either to authenticate directly with Ably using basic authentication, or to generate tokens for untrusted clients using token authentication.

Sign up for a free account and create your own API key in the dashboard or use the Control API to create an API key programmatically.

API keys and tokens have a set of capabilities assigned to them that specify which operations can be performed on which resources. The following capabilities are available for LiveObjects:

  • object-subscribe - grants clients read access to LiveObjects, allowing them to get the channel object and subscribe to updates.
  • object-publish - grants clients write access to LiveObjects, allowing them to perform mutation operations on objects.

To use LiveObjects, an API key must have at least the object-subscribe capability. With only this capability, clients will have read-only access, preventing them from calling mutation methods on LiveObjects.

For the purposes of this guide, make sure your API key includes both object-subscribe and object-publish capabilities to allow full read and write access.

Install Ably Pub/Sub SDK

LiveObjects is available as part of the Ably Pub/Sub SDK via the dedicated Objects plugin. The plugin is discovered from the classpath at runtime. No code registration is needed; adding the dependency is enough.

Install for Maven

<dependency> <groupId>io.ably</groupId> <artifactId>ably-java</artifactId> <version>1.8.0</version> </dependency> <dependency> <groupId>io.ably</groupId> <artifactId>liveobjects</artifactId> <version>1.8.0</version> <scope>runtime</scope> </dependency>
Copied!

Install for Gradle

implementation 'io.ably:ably-java:1.8.0' runtimeOnly 'io.ably:liveobjects:1.8.0'
Copied!

For Android platform, use ably-android instead of ably-java

implementation 'io.ably:ably-android:1.8.0' runtimeOnly 'io.ably:liveobjects:1.8.0'
Copied!

Instantiate a client

Instantiate an Ably Realtime client from the Pub/Sub SDK:

Java

1

AblyRealtime realtime = new AblyRealtime(new ClientOptions("demokey:*****"));
API key:
DEMO ONLY

A ClientOptions object may be passed to the Pub/Sub SDK instance to further customize the connection, however at a minimum you must set an API key. The LiveObjects plugin is picked up automatically when io.ably:liveobjects is on the runtime classpath. If the plugin is missing, reading the channel.object field is still safe, but calling any method on it (for example channel.object.get()) fails with error code 40019 and a message telling you which dependency to add.

Create a channel

LiveObjects is managed and persisted on channels. To use LiveObjects, you must first create a channel with the correct channel mode flags:

  • OBJECT_SUBSCRIBE - required to access objects on a channel.
  • OBJECT_PUBLISH - required to create and modify objects on a channel.
Java

1

2

3

4

5

6

ChannelOptions opts = new ChannelOptions();
opts.modes = new ChannelMode[] {
    ChannelMode.object_publish,
    ChannelMode.object_subscribe
};
Channel channel = realtime.channels.get("test-channel", opts);

Get the channel object

The channel.object field gives access to the LiveObjects API for a channel.

Use channel.object.get() to obtain the channel object. The channel object is a LiveMap that always exists on a channel and acts as the top-level entry point for accessing and persisting objects. It is returned as a LiveMapPathObject, which provides a path-based API for accessing and manipulating the object hierarchy.

Every LiveObjects mutation is asynchronous: channel.object.get() returns a CompletableFuture<LiveMapPathObject> that completes once the LiveObjects state is synchronized with the Ably system; reads are synchronous and local. The recommended pattern is to compose these futures rather than block on them: hold the future returned by get() and chain each step onto it:

Java

1

2

3

4

5

6

7

8

9

10

// Recommended: keep the future reference explicit and compose it
CompletableFuture<LiveMapPathObject> objectFuture = channel.object.get();

// Get the object, then create your first object - without blocking.
// thenCompose sequences dependent operations (each returns its own future);
// finish with thenRun/exceptionally.
objectFuture
    .thenCompose(rootObject -> rootObject.set("visits", LiveMapValue.of(LiveCounter.create(0))))
    .thenRun(() -> System.out.println("Channel object ready and counter created"))
    .exceptionally(err -> { System.err.println("Failed to set up objects: " + err); return null; });

The remaining steps in this guide call .join() to keep each example short and focused on the LiveObjects API. It blocks until the operation completes and returns the result directly:

Java

1

2

// For brevity, the rest of this guide blocks with join()
LiveMapPathObject rootObject = channel.object.get().join();

Create and assign objects

You can create and assign objects using the LiveMap.create() and LiveCounter.create() static methods. These methods return a description of the object to create; the actual object is created when you assign it to a key with set(). Every value passed to set() is wrapped in LiveMapValue.of(...):

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// Create a LiveCounter with initial value 0
LiveCounter visits = LiveCounter.create(0);

// Assign it to the 'visits' key on the channel object
rootObject.set("visits", LiveMapValue.of(visits)).join();

// Create a LiveMap with initial entries
LiveMap reactions = LiveMap.create(Map.of(
    "likes", LiveMapValue.of(0),
    "hearts", LiveMapValue.of(0)));

// Assign it to the 'reactions' key on the channel object
rootObject.set("reactions", LiveMapValue.of(reactions)).join();

// Infer types for the assigned objects
LiveCounterPathObject visitsCounter = rootObject.get("visits").asLiveCounter();
LiveMapPathObject reactionsMap = rootObject.get("reactions").asLiveMap();

rootObject.get(key) returns a general PathObject that doesn't yet know the type of the value it points to. Call one of the as* methods, such as asLiveCounter() or asLiveMap(), to work with the value as a specific type. These casts are always safe to call: they never throw an exception, even if the value at the path has a different type. Learn more about Type inference.

Subscribe to updates

Subscribe to realtime updates using the subscribe() method on a PathObject. You will be notified when the object at that path is updated by other clients or by you:

Java

1

2

3

4

5

6

7

// Subscribe to counter updates
visitsCounter.subscribe(event ->
    System.out.println("Visits counter updated: " + event.getObject().asLiveCounter().value()));

// Subscribe to map updates
reactionsMap.subscribe(event ->
    System.out.println("Reactions updated: " + event.getObject().compactJson()));

The subscription callback receives an event that exposes:

  • getObject(): A PathObject pointing to the path where the change occurred
  • getMessage(): The ObjectMessage that carried the operation that led to the change (nullable)

The subscribe() method also returns a Subscription object. When you no longer want to receive updates, call subscription.unsubscribe() on it:

Java

1

2

3

4

5

6

// Keep the Subscription returned by subscribe()
Subscription subscription = visitsCounter.subscribe(event ->
    System.out.println("Visits counter updated: " + event.getObject().asLiveCounter().value()));

// Later, stop receiving updates
subscription.unsubscribe();

Update objects

Update objects using mutation methods on PathObject. All subscribers (including you) will be notified of the changes:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

// Update counter
visitsCounter.increment(5).join();
// console: Visits counter updated: 5.0

visitsCounter.decrement(2).join();
// console: Visits counter updated: 3.0

// Update map
reactionsMap.set("likes", LiveMapValue.of(10)).join();
// console: Reactions updated: {"likes":10.0,"hearts":0.0}

reactionsMap.set("hearts", LiveMapValue.of(5)).join();
// console: Reactions updated: {"likes":10.0,"hearts":5.0}

reactionsMap.remove("likes").join();
// console: Reactions updated: {"hearts":5.0}

Next steps

This quickstart introduced the basic concepts of LiveObjects and demonstrated how the path-based API works. The next steps are to: