From f68a90449c0d42cb4b58af3ab5a5610e0408cbbf Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 3 Nov 2026 15:11:45 +0530 Subject: [PATCH 1/3] docs: simplify the README --- README.md | 289 +++++++++++++++++++++++++----------------------------- 1 file changed, 131 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index bb104d5b4..4fba00c1d 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -![Ably Pub/Sub Java Header](images/javaSDK-github.png) +![Ably Pub/Sub Java Header](https://raw.githubusercontent.com/ably/ably-pubsub-java/main/images/javaSDK-github.png) [![Maven Central: server](https://img.shields.io/maven-central/v/io.ably.pubsub/server?label=io.ably.pubsub%3Aserver)](https://central.sonatype.com/artifact/io.ably.pubsub/server) [![Maven Central: device](https://img.shields.io/maven-central/v/io.ably.pubsub/device?label=io.ably.pubsub%3Adevice)](https://central.sonatype.com/artifact/io.ably.pubsub/device) -[![License](https://badgen.net/github/license/ably/ably-pubsub-java)](./LICENSE) +[![License](https://badgen.net/github/license/ably/ably-pubsub-java)](https://github.com/ably/ably-pubsub-java/blob/main/LICENSE) # Ably Pub/Sub Java SDK @@ -15,58 +15,31 @@ JVM desktop clients. The SDK is published as two packages, one for each side of the connection. You choose the package by where your code runs, and build your client through that package's entry point. -| Package | Use it for | Entry point | Platforms | -|---------|-----------|-------------|-----------| -| `io.ably.pubsub:server` | Servers and other trusted backend environments | `PubSubServer.httpClientBuilder()`, `PubSubServer.realtimeClientBuilder()` | JVM | -| `io.ably.pubsub:device` | Android apps and other end-user runtimes | `PubSubDevice.clientBuilder()` | Android, JVM | +| Package | Use it for | Entry point | +|---------|------------|-------------| +| `io.ably.pubsub:server` | Servers and other trusted backend environments | `PubSubServer.realtimeClientBuilder()`, `PubSubServer.httpClientBuilder()` | +| `io.ably.pubsub:device` | Android apps and other end-user runtimes, including JVM desktop apps | `PubSubDevice.clientBuilder()` | ## Requirements -| Platform | Requirement | -|----------|-------------| -| Java | 8 or later (the SDK targets Java 8 bytecode). | -| Android | Android 7.0 (API level 24) or later, for `io.ably.pubsub:device`. | -| Kotlin | Optional. The Java API is usable from Kotlin; the `device` package is built with Kotlin 2.1.10. | +Java 8 or later (the JVM, plus Android 7.0 / API level 24 or later for `io.ably.pubsub:device`). ## Installation -Use exactly one of `server` or `device` in an app; see [One package per app](#one-package-per-app). +Use exactly one of `io.ably.pubsub:server` or `io.ably.pubsub:device` in an app. Never depend on +`io.ably.pubsub:core` or `io.ably.pubsub:core-android` directly. ### Server -Gradle (Kotlin DSL): - ```kotlin dependencies { implementation("io.ably.pubsub:server:2.0.0") } ``` -Gradle (Groovy): - -```groovy -dependencies { - implementation 'io.ably.pubsub:server:2.0.0' -} -``` - -Maven: - -```xml - - io.ably.pubsub - server - 2.0.0 - -``` - ### Device -`device` is a Kotlin Multiplatform artifact, published as `device` (root), `device-android` and -`device-jvm`. A Gradle build declares only `io.ably.pubsub:device`; Gradle module metadata selects the -Android or JVM variant for you. - -Android, Gradle (Kotlin DSL): +On Android and on the JVM, Gradle picks the right variant of `io.ably.pubsub:device`: ```kotlin dependencies { @@ -74,23 +47,7 @@ dependencies { } ``` -Android, Gradle (Groovy): - -```groovy -dependencies { - implementation 'io.ably.pubsub:device:2.0.0' -} -``` - -JVM desktop client, Gradle (Kotlin DSL or Groovy; the coordinate is the same): - -```kotlin -dependencies { - implementation("io.ably.pubsub:device:2.0.0") -} -``` - -JVM desktop client, Maven. Maven cannot read Gradle module metadata, so name the JVM artifact directly: +Maven can't pick the variant, so a JVM desktop app names `device-jvm` directly: ```xml @@ -100,50 +57,38 @@ JVM desktop client, Maven. Maven cannot read Gradle module metadata, so name the ``` -### One package per app - -- Depend on exactly one of `io.ably.pubsub:server` or `io.ably.pubsub:device` in each app. -- Never depend on `io.ably.pubsub:core` or `io.ably.pubsub:core-android`. They are internal - implementation artifacts, and declaring them next to a package puts duplicate classes on the - classpath. -- If one module of your system runs on a device and another on a server, give each module its own - package. - -### Migrating from 1.x - -Remove the 1.x `io.ably:ably-java` and `io.ably:ably-android` dependencies when you add a 2.0 -package. See [UPGRADING.md](./UPGRADING.md) for the artifact mapping, package renames, behaviour -changes and removed APIs. - ## Quick start ### Server -Use an API key on a server. The following code connects, subscribes to a channel and publishes a -message to it. +Use an API key on a server. The samples read it from the `ABLY_API_KEY` environment variable; set it +to an API key from your [Ably dashboard](https://ably.com/accounts/any/apps/any/app_keys). The +following code connects, subscribes to a channel, publishes a message to it and waits for the +message to arrive. ```java +import java.util.concurrent.CompletableFuture; + import io.ably.pubsub.realtime.Channel; -import io.ably.pubsub.realtime.ConnectionEvent; import io.ably.pubsub.realtime.PubSubRealtimeClient; import io.ably.pubsub.server.PubSubServer; -import io.ably.pubsub.types.AblyException; +import io.ably.pubsub.types.Message; public class ServerQuickStart { - public static void main(String[] args) throws AblyException { + public static void main(String[] args) throws Exception { + String ablyApiKey = System.getenv("ABLY_API_KEY"); PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder() - .key("your-ably-api-key") + .key(ablyApiKey) .build(); - realtime.connection.on(ConnectionEvent.connected, change -> { - try { - Channel channel = realtime.channels.get("test-channel"); - channel.subscribe(message -> System.out.println("Received: " + message.data)); - channel.publish("test-event", "hello world"); - } catch (AblyException e) { - e.printStackTrace(); - } - }); + // Subscribing attaches the channel. A message published while connecting is queued. + Channel channel = realtime.channels.get("test-channel"); + CompletableFuture received = new CompletableFuture<>(); + channel.subscribe(received::complete); + channel.publish("test-event", "hello world"); + + System.out.println("Received: " + received.get().data); + realtime.close(); } } ``` @@ -157,8 +102,9 @@ import io.ably.pubsub.types.AblyException; public class ServerPublish { public static void main(String[] args) throws AblyException { + String ablyApiKey = System.getenv("ABLY_API_KEY"); PubSubHttpClient http = PubSubServer.httpClientBuilder() - .key("your-ably-api-key") + .key(ablyApiKey) .build(); http.channels.get("test-channel").publish("test-event", "hello world"); @@ -167,10 +113,43 @@ public class ServerPublish { ``` > [!IMPORTANT] -> Clients from `PubSubServer` declare themselves as servers. Authenticate them with an API key. -> Token authentication on a realtime connection works only with a token that carries the signed -> `x-ably-clientType=server` claim, which today means a JWT; otherwise Ably rejects the connection -> with error 40167. See [Server clients and token authentication](./UPGRADING.md#server-clients-and-token-authentication). +> Clients from `PubSubServer` declare themselves as servers. Authenticate them with an API key. A +> server client that must use a token needs an Ably JWT signed with your API key. The JWT must carry +> the `x-ably-clientType` claim set to `server`. Ably rejects any other token, such as an Ably Token +> or a JWT without the claim, with error 40167. See +> [Server clients and token authentication](https://github.com/ably/ably-pubsub-java/blob/main/UPGRADING.md#server-clients-and-token-authentication). + +Your server also issues the tokens that devices authenticate with. Return a token request from +the endpoint your devices use as their `authUrl`: + +```java +import io.ably.pubsub.http.Auth; +import io.ably.pubsub.http.PubSubHttpClient; +import io.ably.pubsub.server.PubSubServer; +import io.ably.pubsub.types.AblyException; + +public class ServerTokenRequest { + public static void main(String[] args) throws AblyException { + String ablyApiKey = System.getenv("ABLY_API_KEY"); + PubSubHttpClient http = PubSubServer.httpClientBuilder() + .key(ablyApiKey) + .build(); + + // Authenticate the caller first. Grant only the operations this user needs. + Auth.TokenParams params = new Auth.TokenParams(); + params.clientId = "me"; // the authenticated user's ID + // Without a capability, the token gets all of the API key's capabilities. + params.capability = "{\"test-channel\":[\"publish\",\"subscribe\",\"history\"]}"; + + Auth.TokenRequest tokenRequest = http.auth.createTokenRequest(params, null); + + // Return this JSON unchanged, with the content type application/json. + System.out.println(tokenRequest.asJson()); + } +} +``` + +See [token authentication](https://ably.com/docs/auth/token) for how devices use it. ### Device @@ -198,47 +177,52 @@ public class DeviceQuickStart { } ``` -The same in Kotlin: +To get tokens from your own code instead of a URL, pass an `Auth.TokenCallback` to +`authCallback(...)`. It can return a token string, a `TokenDetails` or a `TokenRequest`. -```kotlin -import io.ably.pubsub.device.PubSubDevice +## Migrating from 1.x -val realtime = PubSubDevice.clientBuilder() - .authUrl("https://your-server.example.com/ably-auth") - .clientId("me") - .build() +Version 2.0 splits the SDK into a server package and a device package, and builds clients with +builders. -val channel = realtime.channels.get("test-channel") -channel.subscribe { message -> println("Received: ${message.data}") } -channel.publish("test-event", "hello world") -``` +| | 1.x | 2.0 | +|---|---|---| +| Package | `io.ably:ably-java`, `io.ably:ably-android` | `io.ably.pubsub:server` or `io.ably.pubsub:device` | +| Java package | `io.ably.lib.*` | `io.ably.pubsub.*` | +| Realtime client | `new AblyRealtime(options)` | `PubSubServer.realtimeClientBuilder()…build()` or `PubSubDevice.clientBuilder()…build()` | +| HTTP client | `new AblyRest(options)` | `PubSubServer.httpClientBuilder()…build()` | + +See [UPGRADING.md](https://github.com/ably/ably-pubsub-java/blob/main/UPGRADING.md) for all other changes. ## Common configuration -Both entry points accept the same options through their builders, or through a `ClientOptions` -object passed to `PubSubServer.realtimeClientBuilder(options)`, `PubSubServer.httpClientBuilder(options)` -or `PubSubDevice.clientBuilder(options)`. +Set options on the builders, or pass a `ClientOptions` object to `realtimeClientBuilder(options)`, +`httpClientBuilder(options)` or `clientBuilder(options)`. ### Endpoint -`endpoint` selects the Ably host for both REST requests and the realtime connection. It replaces -the 1.x `environment`, `restHost` and `realtimeHost` options. +`endpoint` selects the Ably host for HTTP requests and the realtime connection. It replaces the +1.x `environment`, `restHost` and `realtimeHost` options. ```java +String ablyApiKey = System.getenv("ABLY_API_KEY"); PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder() - .key("your-ably-api-key") + .key(ablyApiKey) .endpoint("nonprod:sandbox") .build(); ``` -Never put a port in `endpoint`. Set `port` or `tlsPort` on `ClientOptions` instead. See -[Choose the host with `endpoint`](./UPGRADING.md#6-choose-the-host-with-endpoint) for how each value resolves. +- Never put a port in `endpoint`: `localhost:8080` is not a hostname and does not resolve. +- Set the port with `tlsPort`, or with `port` when TLS is off. +- With TLS off (`tls(false)`), use token authentication. + +See [Choose the host with `endpoint`](https://github.com/ably/ably-pubsub-java/blob/main/UPGRADING.md#6-choose-the-host-with-endpoint) for how the +old options map to `endpoint`. ### Network client -By default, the published packages use OkHttp for HTTP and WebSocket connections. To use the -built-in `HttpURLConnection` engine instead, swap the engine under the package you depend on. For -example, on a server: +The packages use OkHttp for HTTP and WebSocket connections. To use the built-in +`HttpURLConnection` engine instead, swap the engine. For example, on a server: ```kotlin dependencies { @@ -249,67 +233,51 @@ dependencies { } ``` -See [CONTRIBUTING.md](./CONTRIBUTING.md) for details of the engines. - ### Proxy Pass a `ProxyOptions` object to the builder to connect through a proxy. ```java import io.ably.pubsub.http.HttpAuth; -import io.ably.pubsub.realtime.PubSubRealtimeClient; -import io.ably.pubsub.server.PubSubServer; -import io.ably.pubsub.types.AblyException; import io.ably.pubsub.types.ProxyOptions; -public class AblyWithProxy { - public static void main(String[] args) throws AblyException { - ProxyOptions proxy = new ProxyOptions(); - proxy.host = "your-proxy-host"; - proxy.port = 8080; +ProxyOptions proxy = new ProxyOptions(); +proxy.host = "your-proxy-host"; +proxy.port = 8080; - // Optional: only if the proxy requires authentication. - proxy.username = "your-username"; - proxy.password = "your-password"; - proxy.prefAuthType = HttpAuth.Type.BASIC; +// Only if the proxy requires authentication. +proxy.username = "your-username"; +proxy.password = "your-password"; +proxy.prefAuthType = HttpAuth.Type.BASIC; - PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder() - .key("your-ably-api-key") - .proxy(proxy) - .build(); - } -} +String ablyApiKey = System.getenv("ABLY_API_KEY"); +PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder() + .key(ablyApiKey) + .proxy(proxy) + .build(); ``` ### Logging -Set `logLevel` to one of the constants in `io.ably.pubsub.util.Log` (`VERBOSE`, `DEBUG`, `INFO`, -`WARN`, `ERROR` or `NONE`; the default is `WARN`). Set `logHandler` to receive log output -yourself; by default it goes to standard output. +Set `logLevel` to a constant from `io.ably.pubsub.util.Log`: `VERBOSE`, `DEBUG`, `INFO`, `WARN` +(the default), `ERROR` or `NONE`. Log lines go to standard output unless you set a `logHandler`. ```java -import io.ably.pubsub.realtime.PubSubRealtimeClient; -import io.ably.pubsub.server.PubSubServer; -import io.ably.pubsub.types.AblyException; import io.ably.pubsub.util.Log; -public class AblyWithLogging { - public static void main(String[] args) throws AblyException { - PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder() - .key("your-ably-api-key") - .logLevel(Log.DEBUG) - .logHandler((severity, tag, msg, tr) -> System.err.println(tag + ": " + msg)) - .build(); - } -} +String ablyApiKey = System.getenv("ABLY_API_KEY"); +PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder() + .key(ablyApiKey) + .logLevel(Log.DEBUG) + .logHandler((severity, tag, msg, tr) -> System.err.println(tag + ": " + msg)) + .build(); ``` ## Live Objects -[Ably LiveObjects](https://ably.com/docs/liveobjects) provides realtime, collaborative data -structures that synchronize state across clients. It ships as a separate plugin, -`io.ably.pubsub:liveobjects`, that works with either package. Add it at runtime and the client -detects it on the classpath; no other setup is needed. +[Ably LiveObjects](https://ably.com/docs/liveobjects) ships as a separate plugin, +`io.ably.pubsub:liveobjects`, that works with either package. Add it at runtime and the client finds +it on the classpath. ```kotlin dependencies { @@ -318,22 +286,27 @@ dependencies { ``` Attach the channel with the `object_subscribe` and `object_publish` modes to use objects on it. See -the [LiveObjects documentation](https://ably.com/docs/liveobjects) and the -[Java quickstart](https://ably.com/docs/liveobjects/quickstart/java), and run the -[example app](./examples/README.md) for a working demo. +the [Java quickstart](https://ably.com/docs/liveobjects/quickstart/java) and the +[example app](https://github.com/ably/ably-pubsub-java/blob/main/examples/README.md). + +## Known limitations + +- `io.ably.pubsub:device` needs Android API level 24. Apps that support API levels 19 to 23 must + stay on 1.x (`io.ably:ably-android`) for now. ## Further reading - [Getting started with Pub/Sub using Java](https://ably.com/docs/getting-started/java) - [SDK setup for Java](https://ably.com/docs/getting-started/setup?lang=java) - [Ably Pub/Sub documentation](https://ably.com/docs/basics) and [examples](https://ably.com/examples?product=pubsub) -- [UPGRADING.md](./UPGRADING.md): migrating from 1.x -- [CHANGELOG.md](./CHANGELOG.md): release notes; all Ably releases are listed on [changelog.ably.com](https://changelog.ably.com) -- [examples/](./examples): the LiveObjects example app +- [Token authentication](https://ably.com/docs/auth/token) +- [UPGRADING.md](https://github.com/ably/ably-pubsub-java/blob/main/UPGRADING.md): migrating from 1.x +- [CHANGELOG.md](https://github.com/ably/ably-pubsub-java/blob/main/CHANGELOG.md): release notes; all Ably releases are listed on [changelog.ably.com](https://changelog.ably.com) +- [examples/](https://github.com/ably/ably-pubsub-java/tree/main/examples): the LiveObjects example app ## Contributing -Read [CONTRIBUTING.md](./CONTRIBUTING.md) before contributing. +Read [CONTRIBUTING.md](https://github.com/ably/ably-pubsub-java/blob/main/CONTRIBUTING.md) before contributing; it also describes the release process. ## Support, feedback and troubleshooting @@ -342,4 +315,4 @@ problems on [GitHub Issues](https://github.com/ably/ably-pubsub-java/issues). ## License -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE). +Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/ably/ably-pubsub-java/blob/main/LICENSE). From 9f5423a743bfa2080dca203a4a23896e96e38f8b Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 3 Nov 2026 20:51:33 +0530 Subject: [PATCH 2/3] Shorten the README server-token note, drop the core-package line and trim Further reading --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4fba00c1d..87e166be9 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,7 @@ Java 8 or later (the JVM, plus Android 7.0 / API level 24 or later for `io.ably. ## Installation -Use exactly one of `io.ably.pubsub:server` or `io.ably.pubsub:device` in an app. Never depend on -`io.ably.pubsub:core` or `io.ably.pubsub:core-android` directly. +Use exactly one of `io.ably.pubsub:server` or `io.ably.pubsub:device` in an app. ### Server @@ -113,11 +112,7 @@ public class ServerPublish { ``` > [!IMPORTANT] -> Clients from `PubSubServer` declare themselves as servers. Authenticate them with an API key. A -> server client that must use a token needs an Ably JWT signed with your API key. The JWT must carry -> the `x-ably-clientType` claim set to `server`. Ably rejects any other token, such as an Ably Token -> or a JWT without the claim, with error 40167. See -> [Server clients and token authentication](https://github.com/ably/ably-pubsub-java/blob/main/UPGRADING.md#server-clients-and-token-authentication). +> Server clients must authenticate with an API key. To use a token instead, see [Server clients and token authentication](https://ably.com/docs/auth/token#server-token-auth). Your server also issues the tokens that devices authenticate with. Return a token request from the endpoint your devices use as their `authUrl`: @@ -297,7 +292,6 @@ the [Java quickstart](https://ably.com/docs/liveobjects/quickstart/java) and the ## Further reading - [Getting started with Pub/Sub using Java](https://ably.com/docs/getting-started/java) -- [SDK setup for Java](https://ably.com/docs/getting-started/setup?lang=java) - [Ably Pub/Sub documentation](https://ably.com/docs/basics) and [examples](https://ably.com/examples?product=pubsub) - [Token authentication](https://ably.com/docs/auth/token) - [UPGRADING.md](https://github.com/ably/ably-pubsub-java/blob/main/UPGRADING.md): migrating from 1.x From 243aa13df675caa2254ef5b65cf415e9891141f0 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 3 Nov 2026 21:19:31 +0530 Subject: [PATCH 3/3] Show the README token request as an /ably-auth endpoint --- README.md | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 87e166be9..08f8822b7 100644 --- a/README.md +++ b/README.md @@ -114,32 +114,48 @@ public class ServerPublish { > [!IMPORTANT] > Server clients must authenticate with an API key. To use a token instead, see [Server clients and token authentication](https://ably.com/docs/auth/token#server-token-auth). -Your server also issues the tokens that devices authenticate with. Return a token request from -the endpoint your devices use as their `authUrl`: +The following endpoint issues token requests. Devices set `authUrl` to its URL, for example +`https://your-server.example.com/ably-auth`: ```java +package com.example; + import io.ably.pubsub.http.Auth; import io.ably.pubsub.http.PubSubHttpClient; import io.ably.pubsub.server.PubSubServer; import io.ably.pubsub.types.AblyException; - -public class ServerTokenRequest { - public static void main(String[] args) throws AblyException { +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +@RestController +public class AblyAuthServer { + private final PubSubHttpClient http; + + public AblyAuthServer() throws AblyException { String ablyApiKey = System.getenv("ABLY_API_KEY"); - PubSubHttpClient http = PubSubServer.httpClientBuilder() + http = PubSubServer.httpClientBuilder() .key(ablyApiKey) .build(); + } + @GetMapping(value = "/ably-auth", produces = MediaType.APPLICATION_JSON_VALUE) + public String ablyAuth() throws AblyException { // Authenticate the caller first. Grant only the operations this user needs. + String userId = "me"; // the signed-in user's ID, from your session Auth.TokenParams params = new Auth.TokenParams(); - params.clientId = "me"; // the authenticated user's ID + params.clientId = userId; // Without a capability, the token gets all of the API key's capabilities. params.capability = "{\"test-channel\":[\"publish\",\"subscribe\",\"history\"]}"; - Auth.TokenRequest tokenRequest = http.auth.createTokenRequest(params, null); + return http.auth.createTokenRequest(params, null).asJson(); + } - // Return this JSON unchanged, with the content type application/json. - System.out.println(tokenRequest.asJson()); + public static void main(String[] args) { + SpringApplication.run(AblyAuthServer.class, args); } } ``` @@ -148,7 +164,8 @@ See [token authentication](https://ably.com/docs/auth/token) for how devices use ### Device -Authenticate a device with tokens issued by your own server, through `authUrl` or `authCallback`. +Authenticate a device with tokens issued by your own server, such as the endpoint above, through +`authUrl` or `authCallback`. Never embed an API key in an app that runs on an end-user device. ```java