Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
901e786
chore: sync Java client with Apify OpenAPI spec v2-2026-07-10T105921Z
claude Jul 10, 2026
0c3926a
docs: bump install-snippet version to 0.1.4 and reword changelog
claude Jul 10, 2026
d0217a6
feat: add lazy iteration helpers across all paginated collections
claude Jul 10, 2026
e365533
fix: terminate iteration on empty page, not short page or total
claude Jul 10, 2026
3a55660
chore: address review round 4 (hygiene, tests, docs)
claude Jul 10, 2026
333151d
docs: fix split DatasetClient table; CI dedupe; comment reflow
claude Jul 11, 2026
0266f6e
docs: warn iterateItems can stop early under server-side filters
claude Jul 11, 2026
38f24e0
chore: address review round 6 (publish gate, iterateKeys chunkSize, t…
claude Jul 11, 2026
594620b
test+feat: cover iterateKeys chunkSize; add single-arg iterate overloads
claude Jul 11, 2026
02c977e
fix: remove ambiguous iterateItems(options, Class) overload
claude Jul 11, 2026
a930fe9
test: hermetic coverage for dataset-items iteration
claude Jul 11, 2026
2f142cf
test: use a typed record instead of raw Map in dataset-items test
claude Jul 11, 2026
e7697a1
polish: KeysIterator honors isTruncated; normalize chunkSize; doc note
claude Jul 11, 2026
1efd733
fix: snapshot iterator filters for full options isolation
claude Jul 11, 2026
9c95d61
docs: unique actor name in example snippet; clarify iterator equivalence
claude Jul 11, 2026
2becb8b
docs: Maven Central + Gradle repositories note; lastRun() read-only c…
claude Jul 11, 2026
257f1bf
refactor: simplify PaginatedIterator.minForLimit to null-aware min
claude Jul 11, 2026
faf8c20
fix: versions().iterate() single-fetch on non-paginated endpoint
claude Jul 11, 2026
4c5f469
docs: clarify iterate limit(0) semantics and log-redirection exception
claude Jul 11, 2026
8da2646
docs: iterate limit(0) parity note on all offset options classes
claude Jul 11, 2026
a701fc6
docs: resolve 8 documentation review items at 8da2646
claude Jul 11, 2026
09b39b6
docs: resolve documentation items from a701fc6 reviews
claude Jul 11, 2026
b01549b
fix: revert ambiguous iterateItems overload; correct actor paths
claude Jul 11, 2026
191c770
docs: resolve documentation items from b01549b reviews
claude Jul 11, 2026
8a08622
docs: scope PaginatedIterator class-doc reuse claim accurately
claude Jul 11, 2026
640402f
feat: add StreamedLog log-redirection helper and standardize doc disc…
claude Jul 11, 2026
53b0505
docs: list ArrayList and Consumer imports for streamed-log snippet
claude Jul 11, 2026
7622ab6
fix: deliver last StreamedLog message on stop and prevent stop() hang
claude Jul 11, 2026
6c7a2e1
fix: address StreamedLog review items (format, javadoc, comment, fields)
claude Jul 11, 2026
5f4069f
fix: make StreamedLog.close idempotent, rename regression test, align…
claude Jul 11, 2026
de5ba23
test: add concurrent stop/close race test and fix comment accuracy
claude Jul 11, 2026
e78eeea
style: import AtomicReference instead of inline fully-qualified name
claude Jul 11, 2026
717044a
docs: describe option-object fields in storages/misc/actors
claude Jul 11, 2026
970fda8
fix: prevent StreamedLog self-join deadlock and eager prefix-lookup f…
claude Jul 11, 2026
390bd3a
docs: correct StreamedLog.stop() Javadoc for consumer self-call
claude Jul 11, 2026
00cab63
fix: harden StreamedLog against throwing consumers and racing readers
claude Jul 11, 2026
c4eb1ce
fix: unwind StreamedLog redirection on first consumer failure
claude Jul 11, 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
12 changes: 8 additions & 4 deletions .github/workflows/java-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ jobs:
- name: Static analysis (SpotBugs)
run: mvn -B -DskipTests compile spotbugs:check

# Offline unit tests (mock HTTP backend): prove the retry/error/signature logic without the API.
# Offline unit tests (mock HTTP backend): prove the retry/error/signature/pagination logic
# without the API. Selected by pattern — every hermetic test in the base package runs, and the
# token-gated integration/example/doc-snippet suites are excluded — so new hermetic tests are
# covered here automatically without editing this list.
- name: Unit tests
run: mvn -B test -Dtest='UnitHttpTest,ReviewFixesTest,ClientMetaTest,SignatureTest,ConfigTest,CompressionTest' -DfailIfNoTests=true
run: mvn -B test -Dtest='!*IntegrationTest,!ExamplesTest,!DocSnippetsTest' -DfailIfNoTests=true

# Fail fast if the integration-test secret is missing or empty. Without this guard the
# integration tests silently "pass" (JUnit assumptions skip them when APIFY_TOKEN is unset),
Expand All @@ -65,8 +68,9 @@ jobs:
env:
# The integration-test token is stored as a repository secret.
APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}
# Run every test except the example/doc-snippet harnesses (exercised by the step below).
run: mvn -B test -Dtest='!ExamplesTest,!DocSnippetsTest' -DfailIfNoTests=true
# Run only the live integration suites; the hermetic tests already ran in the offline step
# above and the example/doc-snippet harnesses run in the step below.
run: mvn -B test -Dtest='*IntegrationTest' -DfailIfNoTests=true

# Standalone CI step that verifies the documentation examples actually work end-to-end against
# the live API (ExamplesTest runs each example's main), and that every in-documentation Java
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/java-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ jobs:
- name: Static analysis (SpotBugs)
run: mvn -B -DskipTests compile spotbugs:check

# Same pattern-based selection as the integration workflow's offline gate: run every hermetic
# test and exclude the token-gated integration/example/doc-snippet suites, so new hermetic
# tests are gated on release automatically without editing this list.
- name: Unit tests
run: mvn -B test -Dtest='UnitHttpTest,ReviewFixesTest,ClientMetaTest,SignatureTest,ConfigTest' -DfailIfNoTests=true
run: mvn -B test -Dtest='!*IntegrationTest,!ExamplesTest,!DocSnippetsTest' -DfailIfNoTests=true

- name: Resolve version from pom.xml
id: version
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ replay_pid*
# Maven build output
target/

# Local build-classpath dumps (e.g. from `mvn dependency:build-classpath`)
cp.txt

# IDE
.idea/
*.iml
Expand Down
67 changes: 67 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,73 @@ All notable changes to the Apify Java client are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.0] - 2026-07-11

### Added

- `StreamedLog` log-redirection helper (matching the reference client's `getStreamedLog`):
`RunClient.getStreamedLog()` / `getStreamedLog(StreamedLogOptions)` return a `StreamedLog` that
follows the run's live log in a background thread and redirects each complete, timestamped message
to a destination. `StreamedLog` is `AutoCloseable` with `start()`/`stop()` lifecycle. Options:
`toLog(Consumer<String>)` (custom destination; default is a per-run prefixed `java.util.logging`
logger), `prefix(String)`, and `fromStart(boolean)` (skip pre-redirection log lines when false).

### Changed

- **Breaking:** `RunClient.getStreamedLog()` now returns a `StreamedLog` redirection helper instead
of a raw `InputStream`, aligning its public interface with the reference client. For raw stream
access use `run(id).log().stream(new LogOptions().raw(true))`.
- Standardized the "official, but experimental" disclaimer wording across the README and all
documentation pages.

### Fixed

- `StreamedLog`: the last complete log message is no longer dropped when stopping a live stream.
The final flush now runs in a `finally` block, so a stop that unblocks a blocked read with an
`IOException` still delivers the retained message.
- `StreamedLog`: `stop()`/`close()` can no longer hang. The log stream is now opened in `start()`
before the reader thread is launched, eliminating a startup race where `stop()` closed a
still-null stream and then waited forever on a read that never returned.
- Documentation: added `java.util.ArrayList` and `java.util.function.Consumer` to the stated
snippet import list in `docs/README.md` so the streamed-log example compiles as written.
- `StreamedLog.close()` is now fully idempotent: the running-check and stop happen atomically under
the monitor, so a double or concurrent `close()` can no longer throw `IllegalStateException`.
- Documentation: `docs/runs.md` streamed-log snippet now uses the `RUN_ID` placeholder, matching the
convention used across the other snippets.
- `StreamedLog`: a destination consumer that throws no longer escapes as an uncaught exception on the
background daemon thread. Matching the reference client, the failure is caught, redirection stops,
and a warning is logged.
- `StreamedLog`: the pending-message buffer is now local to each reader run instead of a shared
field, so a `start()` racing a still-draining reader can no longer corrupt shared parsing state.
- Documentation: `docs/README.md` now lists `dataset(id).getStatistics()` as returning
`Optional<JsonNode>`, matching the method table in `docs/storages.md`.

## [0.2.0] - 2026-07-10

### Added

- Lazy iteration helpers over every paginated collection, matching the reference JS client's
iterable `list()`: `iterate(options, chunkSize)` on the Actor, build, run, dataset,
key-value-store, request-queue, task, schedule, webhook, and webhook-dispatch collection clients;
and `DatasetClient.iterateItems(...)` for dataset items and `KeyValueStoreClient.iterateKeys(...)`
for store keys. The options' `limit` caps the total number of items yielded and `chunkSize` sets
the per-request page size. The non-paginated collections — `ActorVersionCollectionClient.iterate(options)`
and `ActorEnvVarCollectionClient.iterate()` — return the full list in a single fetch (no page size
to tune).

### Changed

- Verified the client against OpenAPI specification version `v2-2026-07-10T105921Z` and bumped
`Version.API_SPEC_VERSION` accordingly. The spec delta is forward-compatible: new `401`/`402`
error responses on several endpoints (handled generically by `ApifyApiException`) and relaxed
nullability/optionality on some response fields — already tolerated because the models use
nullable boxed field types (a JSON `null` deserializes to `null`) and an optional field simply
stays unset.
- **Breaking:** `StoreCollectionClient.iterate` now takes `iterate(StoreListOptions, Long chunkSize)`,
where the options' `limit` is the total-items cap and `chunkSize` is the page size. Previously
`limit` was the per-page size. This aligns Store iteration with the reference client and the new
collection iterators.

## [0.1.3] - 2026-07-10

### Added
Expand Down
86 changes: 61 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,54 +15,88 @@ queues, tasks, schedules, webhooks, the store, users and logs).

## Installation

Maven:
The client is published to [Maven Central](https://central.sonatype.com/artifact/com.apify/apify-client).

Maven (Maven Central is a default repository, so no extra configuration is needed):

```xml
<dependency>
<groupId>com.apify</groupId>
<artifactId>apify-client</artifactId>
<version>0.1.3</version>
<version>0.3.0</version>
</dependency>
```

Gradle:
Gradle — ensure `mavenCentral()` is in your `repositories`, then add the dependency:

```groovy
implementation 'com.apify:apify-client:0.1.3'
repositories {
mavenCentral()
}

dependencies {
implementation 'com.apify:apify-client:0.3.0'
}
```

## Quick start

A complete, copy-pasteable first program (save as `HelloApify.java`). Populate a `lib/` directory
with the client and its runtime dependencies (from a directory whose `pom.xml` declares the
dependency shown in [Installation](#installation) above), then compile and run against the JVM's
`lib/*` classpath wildcard — quote it so the shell does not expand it:
A complete, copy-pasteable first program (save as `HelloApify.java`). First scaffold a minimal
`pom.xml` next to it so Maven can resolve the client and its runtime dependencies:

```bash
# 1. Collect apify-client and its runtime dependencies (Jackson, brotli4j codecs, …) into lib/.
mvn dependency:copy-dependencies -DoutputDirectory=lib -DincludeScope=runtime

# 2. Compile and run. '.' is for the compiled HelloApify.class; lib/* is the JVM classpath wildcard.
javac -cp 'lib/*' HelloApify.java
java -cp '.:lib/*' HelloApify # Windows: java -cp ".;lib/*" HelloApify
```xml
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hello-apify</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>com.apify</groupId>
<artifactId>apify-client</artifactId>
<version>0.3.0</version>
</dependency>
</dependencies>
</project>
```

Create `HelloApify.java`:

```java
import com.apify.client.ApifyClient;
import com.apify.client.ActorRun;
import com.apify.client.ActorStartOptions;

class HelloApify {
public static void main(String[] args) {
ApifyClient client = ApifyClient.create("my-api-token");
// Your API token from https://console.apify.com/settings/integrations
ApifyClient client = ApifyClient.create(System.getenv("APIFY_TOKEN"));
ActorRun run = client.actor("apify/hello-world").call(null, new ActorStartOptions(), 120L);
System.out.println("Run " + run.getId() + " finished with status " + run.getStatus());
}
}
```

The remaining snippets below are fragments that assume a configured `client` (see the imports note
after the next block):
Then populate a `lib/` directory with the client and its runtime dependencies, and compile and run
against the JVM's `lib/*` classpath wildcard — quote it so the shell does not expand it:

```bash
# 1. Collect apify-client and its runtime dependencies (Jackson, brotli4j codecs, …) into lib/.
mvn dependency:copy-dependencies -DoutputDirectory=lib -DincludeScope=runtime

# 2. Compile and run. '.' is for the compiled HelloApify.class; lib/* is the JVM classpath wildcard.
javac -cp '.:lib/*' HelloApify.java # Windows: javac -cp ".;lib/*" HelloApify.java
java -cp '.:lib/*' HelloApify # Windows: java -cp ".;lib/*" HelloApify
```

The remaining snippets below are fragments that assume a configured `client` and these imports: all
public client types live in the `com.apify.client` package (e.g. `import com.apify.client.*;`); the
snippets also use `com.fasterxml.jackson.databind.JsonNode` (from the Jackson dependency) for untyped
data, `java.time.Duration` in the configuration examples, and standard JDK types such as
`java.util.Optional` and `java.util.Map` (`import java.util.*;`).

```java
ApifyClient client = ApifyClient.create("my-api-token");
Expand All @@ -77,10 +111,9 @@ PaginationList<JsonNode> items =
System.out.println("Items in this page: " + items.getCount());
```

All public client types live in the `com.apify.client` package (e.g. `import com.apify.client.*;`).
The snippets also use `com.fasterxml.jackson.databind.JsonNode` (from the Jackson dependency) for
untyped data, `java.time.Duration` in the configuration examples, and standard JDK types such as
`java.util.Optional` and `java.util.Map` (`import java.util.*;`).
The types used above — `PaginationList<T>`, `DatasetListItemsOptions`, and the per-resource clients —
are documented on the [resource pages](docs/README.md); `ApifyApiException` is covered under
[Error handling](#error-handling) below.

`ApifyClient.create` takes the token as an explicit argument — it does **not** read `APIFY_TOKEN` (or
any other environment variable) automatically. Read it yourself if you want that, e.g.
Expand Down Expand Up @@ -153,9 +186,12 @@ try {

## Versioning

- `Version.CLIENT_VERSION` — the semantic version of this client (`0.1.3`).
The public `com.apify.client.Version` class (`import com.apify.client.Version;`) exposes two
constants:

- `Version.CLIENT_VERSION` — the semantic version of this client (`0.3.0`).
- `Version.API_SPEC_VERSION` — the Apify OpenAPI specification version this client was verified
against (`v2-2026-07-08T143931Z`).
against (`v2-2026-07-10T105921Z`).

Changes to the public interface other than additive ones are considered breaking changes and follow
[Semantic Versioning](https://semver.org/).
Expand Down Expand Up @@ -194,7 +230,7 @@ Full documentation is in the [`docs/`](docs/README.md) directory, organized by r
- [Schedules](docs/schedules.md)
- [Webhooks & dispatches](docs/webhooks.md)
- [Store, users & logs](docs/misc.md)
- [Runnable examples](docs/examples.md)
- [Examples](docs/examples.md)

## Resources

Expand Down
35 changes: 26 additions & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This directory documents the public API of the Apify Java client, organized by r
lists the available methods with their parameters and short snippets. The snippets are code
fragments that assume a configured `client` and the imports listed below, not standalone `main`
programs; for complete, runnable programs see [examples.md](examples.md) and
[`src/test/java/com/apify/client/examples/`](../src/test/java/com/apify/client/examples). For an
[`src/test/java/com/apify/client/examples/`](https://github.com/apify/apify-client-java/tree/master/src/test/java/com/apify/client/examples). For an
overview, configuration, error handling and the full resource table, see the
[top-level README](../README.md).

Expand All @@ -32,8 +32,9 @@ empty `Optional` rather than an exception. API failures are thrown as `ApifyApiE
## Imports and dependencies

Snippets in these docs assume the client types are imported from `com.apify.client` (e.g.
`import com.apify.client.*;`) plus standard-library types (`java.util.List`, `java.util.Map`,
`java.util.Optional`, `java.util.Iterator`, `java.time.Duration`, `java.io.InputStream`).
`import com.apify.client.*;`) plus standard-library types (`java.util.List`, `java.util.ArrayList`,
`java.util.Map`, `java.util.Optional`, `java.util.Iterator`, `java.util.function.Consumer`,
`java.time.Duration`, `java.io.InputStream`).

Raw-JSON return values use Jackson's `com.fasterxml.jackson.databind.JsonNode`. Jackson is a
transitive dependency of this client, so it is already on your classpath.
Expand All @@ -44,9 +45,9 @@ A few methods return data whose shape is not modelled by this client and is inst
Jackson `JsonNode` (or accept an arbitrary `Object` serialized to JSON):

- Read: `me().monthlyUsage(...)`, `me().limits()`, `task(id).getInput()`,
`build(id).getOpenApiDefinition()`, `dataset(id).getStatistics()`, and the raw request-queue
operations (`listRequests`, `listAndLockHead`, `prolongRequestLock`, `unlockRequests`,
`batchDeleteRequests`).
`build(id).getOpenApiDefinition()`, `dataset(id).getStatistics()` (returned as
`Optional<JsonNode>`), and the raw request-queue operations (`listRequests`, `listAndLockHead`,
`prolongRequestLock`, `unlockRequests`, `batchDeleteRequests`).
- Write: `task(id).updateInput(...)` and `me().updateLimits(...)` accept an arbitrary
JSON-serializable value, as do definition/`update`/`create` arguments generally — a `Map`, a
`JsonNode`, or your own POJO.
Expand Down Expand Up @@ -84,8 +85,9 @@ PaginationList<Actor> page = client.actors().list(options);

## Common list options — `ListOptions`

Most `list` methods (builds, runs, tasks, schedules, webhooks, Actor versions) take the shared
`ListOptions`, which carries the standard pagination/ordering controls.
Most `list` methods (builds, tasks, schedules, webhooks, Actor versions) take the shared
`ListOptions`, which carries the standard pagination/ordering controls. Runs additionally take a
`RunListOptions` status filter — `runs().list(ListOptions, RunListOptions)`; see [Runs](runs.md).

| Method | Type | Meaning |
|---|---|---|
Expand All @@ -103,6 +105,21 @@ PaginationList<Build> builds = client.builds().list(new ListOptions().limit(50L)
`getCount()`, `isDesc()` and `getItems()`. Within-storage listers (`listKeys`, `listHead`) return
their own page/head containers instead.

## Iteration — `iterate` / `iterateItems` / `iterateKeys`

Each paginated collection also offers a lazy `Iterator` that fetches pages on demand: `iterate(...)`
on the collection clients, `DatasetClient.iterateItems(...)`, and `KeyValueStoreClient.iterateKeys(...)`
(request-queue requests use `RequestQueueClient.paginateRequests(...)`). The options' `limit` caps the
**total** number of items yielded; `null`/unset — or a non-positive value such as `0` — means no cap,
so every item is yielded. (This differs from `list(...)`, which sends `limit=0` to the server
verbatim rather than treating it as unbounded — the iteration behavior matches the reference JS
client.) The per-request page size is an optional
trailing `chunkSize` argument: the per-resource tables below show the `chunkSize` form, and each
iterator also has an overload that omits it (using the server's default page size). The page size
does not change which items a collection iterator yields; note the one exception in
[Storages](storages.md) — `iterateItems` combined with server-side item filters, where the page size
can affect the result.

## Resource pages

- [Actors, versions & environment variables](actors.md)
Expand All @@ -113,4 +130,4 @@ their own page/head containers instead.
- [Schedules](schedules.md)
- [Webhooks & dispatches](webhooks.md)
- [Store, users & logs](misc.md)
- [Runnable examples](examples.md)
- [Examples](examples.md)
Loading
Loading