Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b8fd7eb
feat: init
Scarjit Aug 24, 2026
b27b296
feat: added oapi
Scarjit Aug 24, 2026
d1b513d
feat: add STACKIT authentication (AuthConfig, StackitAuthProvider, Pu…
Scarjit Aug 24, 2026
50183f4
feat: add data plane models (PublishMessage, Message, Ack/Nack)
Scarjit Aug 24, 2026
6debb04
feat: add retry logic with exponential backoff (RetryConfig, retry ut…
Scarjit Aug 24, 2026
87ac874
feat: add Publisher and Subscriber with HTTP client and retry
Scarjit Aug 24, 2026
cec47e4
feat: enhance PubSubClient with factory methods and environment support
Scarjit Aug 24, 2026
fae6050
feat: add Region and Environment enums for type safety
Scarjit Aug 24, 2026
ff37b66
feat: use UUID type for topicId and subscriptionId instead of String
Scarjit Aug 24, 2026
c5c9d62
fix: use Kotlin property syntax instead of Java getter (getAccessToke…
Scarjit Aug 24, 2026
1df8e7c
fix: explicitly type Duration constants to avoid platform type warnings
Scarjit Aug 24, 2026
26cfa17
feat: basic test infra
Scarjit Aug 24, 2026
a0c763d
feat: gh workflows
Scarjit Aug 24, 2026
54bf6dd
fix: alpha beta
Scarjit Aug 24, 2026
658ebe5
feat: readme, examples, MORE tests
Scarjit Aug 24, 2026
b86ef90
fix: redundant null checks. Added WERROR check
Scarjit Aug 24, 2026
b9e5db0
fix: better error types, types
Scarjit Aug 24, 2026
8ced85a
fix: increase coverage for subscriber to pass gate
Scarjit Aug 25, 2026
60d6e81
fix: remove test test
Scarjit Aug 25, 2026
9e2fee6
fix: base urls dev/qa/prod
Scarjit Aug 25, 2026
241f5ac
fix: integration tests atomic compare
Scarjit Aug 25, 2026
e91d06f
feat: split exmples for readability & simplify API
Scarjit Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: CI

on:
push:
branches: [ main ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]

jobs:
build:
name: Build & Test
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build and test with coverage
run: ./gradlew clean check

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: build/test-results/test/

deploy:
name: Deploy to Maven Central
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Publish to Maven Central
run: ./gradlew publishAllPublicationsToMavenRepository
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_SECRET_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSPHRASE }}
41 changes: 41 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Integration Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
integration:
name: Integration Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: 'gradle'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Run integration tests
env:
SA_KEY_B64: ${{ secrets.SA_KEY_B64 }}
STACKIT_PROJECT_ID: ${{ secrets.STACKIT_PROJECT_ID }}
run: ./gradlew test --tests "cloud.stackit.pubsub.sdk.integration.*"

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: integration-test-results
path: build/test-results/test/
49 changes: 48 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,48 @@
.idea
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### Kotlin ###
.kotlin

### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/

### Mac OS ###
.DS_Store

# Service account keys
*.json
10 changes: 10 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

132 changes: 131 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,131 @@
Welcome to pubsub-java-sdk
# pubsub-sdk-java

Kotlin SDK for the **STACKIT PubSub Data Plane API**.

This SDK covers the **data plane only** (publish, pull, ack, nack). Creating/deleting topics and subscriptions, and granting publisher/subscriber access, is done via the separate STACKIT PubSub **control plane** REST API or the STACKIT Portal.

## Install

Every GitHub Release publishes `pubsub-sdk-java` to Maven Central automatically (see `.github/workflows/ci.yml`). Once a release is cut:

**Maven** (`pom.xml`):
```xml
<dependency>
<groupId>cloud.stackit.sdk</groupId>
<artifactId>pubsub-sdk-java</artifactId>
<version>1.0.0</version>
</dependency>
```

**Gradle** (`build.gradle.kts`):
```kotlin
implementation("cloud.stackit.sdk:pubsub-sdk-java:1.0.0")
```

Requires JDK 21+. Runtime dependencies: `cloud.stackit.sdk:core`, `okhttp`, `gson`.

## Auth

Authentication goes through `AuthConfig` + `StackitAuthProvider`, backed by a STACKIT service account key.

```kotlin
import cloud.stackit.pubsub.sdk.auth.AuthConfig

val authConfig = AuthConfig(
serviceAccountKeyPath = "sa-key.json", // path to service account key JSON file
tokenEndpoint = "https://authorization.stackit.cloud/auth/token", // default shown
)
```

`AuthConfig` fields: `serviceAccountKey` (inline JSON string), `serviceAccountKeyPath` (file path), `privateKeyPath`, `tokenEndpoint` (default `https://authorization.stackit.cloud/auth/token`).

## Environments

The SDK supports three environments via the `Environment` enum:

| Environment | Dataplane URL pattern |
|-------------|----------------------|
| `PROD` (default) | `https://{topic_id}.pubsub.{region}.onstackit.cloud` |
| `QA` | `https://{topic_id}.pubsub.{region}.qa.onstackit.cloud` |
| `DEV` | `https://{topic_id}.pubsub.{region}.dev.onstackit.cloud` |

```kotlin
import cloud.stackit.pubsub.sdk.config.Environment

val client = PubSubClient(
topicId = UUID.fromString("your-topic-uuid"),
authConfig = authConfig,
environment = Environment.DEV // use DEV environment
)
```

## Quickstart

```kotlin
import cloud.stackit.pubsub.sdk.client.PubSubClient
import cloud.stackit.pubsub.sdk.auth.AuthConfig
import cloud.stackit.pubsub.sdk.models.PublishMessage
import java.util.UUID

fun main() {
val authConfig = AuthConfig(serviceAccountKeyPath = "sa-key.json")
val client = PubSubClient(
topicId = UUID.fromString("your-topic-uuid"),
authConfig = authConfig
)

// Publish
val publisher = client.getPublisher()
val response = publisher.publish(listOf(PublishMessage("aGVsbG8="))) // base64-encoded "hello"
println(response.messageIds)

// Subscribe
val subscriber = client.getSubscriber(UUID.fromString("your-subscription-uuid"))
val messages = subscriber.pull()
for (msg in messages) {
println(msg.text) // base64-decoded payload
}
subscriber.ack(messages.map { it.ackId })
// or: subscriber.nack(messages.map { it.ackId })
}
```

`client.baseDataplaneUrl` defaults to `https://{topic_id}.pubsub.eu01.onstackit.cloud`. Override with `region` or `environment` (e.g. `environment = Environment.DEV` for `https://{topic_id}.pubsub.eu01.dev.onstackit.cloud`).

`PublishMessage` takes a base64-encoded payload string. `Message.payload` (ByteArray) / `Message.text` (String) base64-decode it back.

Both `PubSubPublisher` and `PubSubSubscriber` expose call counters: `publisher.messagesSent` / `.publishCalls` / `.purgeCalls`, `subscriber.pullCalls` / `.ackCalls` / `.nackCalls`.

Other methods: `publisher.publishBatch(messages, batchSize = 100)`, `publisher.purgeTopic()`.

## Errors

All exceptions extend `PubSubAuthError` (sealed class):

| Exception | Raised when |
|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `PubSubAuthError.MissingCredentials` | No service account key configured (local validation) |
| `PubSubAuthError.TokenAcquisitionFailed` | HTTP 400/401/403/404/413, server errors (5xx), or token acquisition failure |
| | |

## Retries

`PubSubPublisher`/`PubSubSubscriber` retry automatically on network/timeout errors (`IOException`, `ConnectException`, `SocketTimeoutException`, `TimeoutException`) and HTTP 500/502/503/504, with exponential backoff + jitter. Configure via `retryConfig = RetryConfig(...)` passed to the publisher/subscriber constructor (or `getPublisher()`/`getSubscriber()` kwargs):

```kotlin
import cloud.stackit.pubsub.sdk.retry.RetryConfig

val retryConfig = RetryConfig(maxRetries = 3, baseDelay = 500L, maxDelay = 30000L, multiplier = 2.0)
```

400/401/403/404/413 are never retried.

## Examples

Standalone example programs demonstrating common patterns are in [`examples/`](examples/):

- `BasicExample.java` — publish/subscribe workflow
- `ErrorHandlingExample.java` — error handling patterns
- `BatchExample.java` — batch publishing
- `PurgeExample.java` — topic purging
- `RetryExample.java` — custom retry configuration
Loading
Loading