Skip to content
Merged
23 changes: 20 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ 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.14.0] - 2026-11-10

### Added

- `ZstdHttpCompressor` for `Content-Encoding: zstd` request bodies (optional `com.github.luben:zstd-jni` dependency).
- `ActorServerRequestOptions.parentRequestId(String)`, sent as the `X-Actor-Request-ID` header so a called multi-tenant Server Actor charges at the pricing tier of the incoming request.

### Fixed

- `StreamedLog.stop()` and `close()` no longer interrupt the reader thread, so a log consumer that is running or blocked is not interrupted and does not lose its message.
- `StreamedLog.close()` called from the log consumer no longer deadlocks while another thread is stopping the same `StreamedLog`, and `stop()` or `close()` from an interrupted thread still ends the reader.

### Changed

- Updated to Apify OpenAPI specification `v2-2026-11-09T143926Z`: `zstd` is an accepted `Content-Encoding`.
- Documented that `RunChargeOptions.requestId` is ignored by runs of single-tenant Server Actors.

## [0.13.0] - 2026-11-06

### Added
Expand Down Expand Up @@ -485,7 +502,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
`setStatusMessage` to the README resources table; noted `getExtra()` on every thin response model.
- Fixed a contradiction in `README.md`/`docs/README.md`: both described `docs/examples.md` as
"complete, runnable programs," but its own snippets are fragments in the same style as the
resource pages — the complete, runnable programs live under
resource pages; the complete, runnable programs live under
`src/test/java/com/apify/client/examples/`. Reworded both to match.
Added the missing `WebhookLastDispatch` import to the `com.apify.client.webhook` package-table row.
Corrected the "one raw-JSON field" framing in `docs/README.md`'s "Raw JSON values" section:
Expand Down Expand Up @@ -600,8 +617,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
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
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
Expand Down
91 changes: 54 additions & 37 deletions README.md

Large diffs are not rendered by default.

38 changes: 19 additions & 19 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ All snippets assume a configured client:
ApifyClient client = ApifyClient.create("my-api-token");
```

`ApifyClient.create` takes the token as an explicit argument — it does not read `APIFY_TOKEN`
`ApifyClient.create` takes the token as an explicit argument; it does not read `APIFY_TOKEN`
automatically. Use `ApifyClient.builder()` for non-default settings (base URL, retries, timeout tiers,
compression, default headers, user-agent suffix, custom HTTP transport). `client.withTimeout(...)`
returns a client that sends every request with a different timeout (see
Expand All @@ -25,7 +25,7 @@ Get your API token from the
[Apify Console → Settings → API & Integrations](https://console.apify.com/settings/integrations).

Every network-calling method returns a `CompletableFuture` (`java.util.concurrent.CompletableFuture`)
rather than blocking the calling thread — see [Asynchronous API](../README.md#asynchronous-api) in
rather than blocking the calling thread; see [Asynchronous API](../README.md#asynchronous-api) in
the top-level README. Methods that fetch a single resource complete with an `Optional<T>`: a missing
resource is reported by an empty `Optional` rather than an exception. API failures surface as the
future's exceptional completion, wrapping an `ApifyApiException` (see
Expand All @@ -34,14 +34,14 @@ future's exceptional completion, wrapping an `ApifyApiException` (see
## Imports and dependencies

Client types are **not** all in one package: `import com.apify.client.*;` only resolves
`ApifyClient`, its builder, and the shared kernel types below — a wildcard import does not reach
`ApifyClient`, its builder, and the shared kernel types below; a wildcard import does not reach
into sub-packages in Java. Every resource has its own sub-package for its client(s), model(s) and
option types, so import each resource you use from its own package:

| Package | Contains |
|---|---|
| `com.apify.client` (root) | `ApifyClient`, `ApifyClientBuilder`, `Version`, `PaginationList<T>`, `ListOptions`, `StorageListOptions`, `ApifyResource` |
| `com.apify.client.http` | `ApifyClientException`, `ApifyApiException`, `ApifyTransportException`, `HttpTransport`, `DefaultHttpTransport`, `HttpTimeoutException`, `Timeout`, `HttpCompressor`, `BrotliHttpCompressor`, `GzipHttpCompressor` |
| `com.apify.client.http` | `ApifyClientException`, `ApifyApiException`, `ApifyTransportException`, `HttpTransport`, `DefaultHttpTransport`, `HttpTimeoutException`, `Timeout`, `HttpCompressor`, `BrotliHttpCompressor`, `GzipHttpCompressor`, `ZstdHttpCompressor` |
| `com.apify.client.actor` | `Actor`, `ActorClient`, `ActorCollectionClient`, `ActorListOptions`, `ActorStartOptions`, `ActorCallOptions`, `ActorBuildOptions`, `ActorStandby`, `ActorVersion`, `SourceCodeFile`, `ActorVersionClient`, `ActorVersionCollectionClient`, `ActorEnvVar`, `ActorEnvVarClient`, `ActorEnvVarCollectionClient`, `ValidateInputOptions`, `ActorStats`, `ActorDefaultRunOptions` |
| `com.apify.client.build` | `Build`, `BuildClient`, `BuildCollectionClient`, `BuildActVersion`, `ActorDefinition`, `ActorDefinitionMeta`, `ActorDefinitionStorages`, `BuildMeta`, `BuildStats`, `BuildOptions`, `BuildUsage` |
| `com.apify.client.run` | `ActorRun`, `ActorRunStats`, `ActorRunOptions`, `ActorRunMeta`, `ActorRunStandby`, `ActorRunUsage`, `RunClient`, `RunCollectionClient`, `RunListOptions`, `LastRunOptions`, `MetamorphOptions`, `RunChargeOptions`, `RunResurrectOptions`, `SetStatusMessageOptions` |
Expand All @@ -58,7 +58,7 @@ option types, so import each resource you use from its own package:
For example, the [top-level README's dataset snippet](../README.md#quick-start) needs
`com.apify.client.ApifyClient`, `com.apify.client.PaginationList`,
`com.apify.client.dataset.DatasetListItemsOptions`, `com.apify.client.run.ActorRun`,
`com.apify.client.actor.ActorStartOptions` and `tools.jackson.databind.JsonNode` — five
`com.apify.client.actor.ActorStartOptions` and `tools.jackson.databind.JsonNode`; five
different packages for one short snippet.

Snippets in these docs also assume the standard-library types they use are imported
Expand Down Expand Up @@ -86,18 +86,18 @@ the run itself keeps running.
A few methods return data whose shape is not modelled by this client and is instead exposed as a
Jackson `JsonNode` (or accept an arbitrary `Object` serialized to JSON):

- Read, returning a required `JsonNode` (never absent; a 404 throws rather than returning empty —
- Read, returning a required `JsonNode` (never absent; a 404 throws rather than returning empty;
see [error handling](../README.md#error-handling)): `me().monthlyUsage(...)`, `me().limits()` (and the same on `user(id)`),
`dataset(id).getStatistics()`, `task(id).getInput()`.
- Read, returning `Optional<JsonNode>` (empty when the underlying resource has none):
`build(id).getOpenApiDefinition()`.
- Write: `task(id).updateInput(...)` (itself returning a required `JsonNode`, the updated input)
and `me().updateLimits(...)` accept an arbitrary JSON-serializable value, as do
definition/`update`/`create` arguments generally — a `Map`, a `JsonNode`, or your own POJO.
definition/`update`/`create` arguments generally; a `Map`, a `JsonNode`, or your own POJO.
- A few typed models still carry one or more raw-JSON fields where the shape is a discriminated
union (or otherwise not worth fully modelling): `RequestQueueRequest.getUserData()`,
`Webhook.getCondition()`, `Schedule.getActions()` (a `List<JsonNode>`), `Task.getInput()` (the
task's stored input, on the `Task` model itself — distinct from the live-fetching
task's stored input, on the `Task` model itself; distinct from the live-fetching
`task(id).getInput()` client call listed above), and `ActorRun`, which carries two:
`getPricingInfo()` and `getStorageIds()`.

Expand All @@ -106,22 +106,22 @@ Navigate a `JsonNode` with `node.get("field")`, `node.path("a").asText()`, etc.
The request-queue lock/list operations (`listRequests`, `listAndLockHead`, `prolongRequestLock`,
`unlockRequests`, `batchDeleteRequests`) return typed models (`RequestsList`,
`LockedRequestQueueHead`, `RequestLockInfo`, `UnlockRequestsResult`, `BatchDeleteResult`) rather
than raw `JsonNode` — see [Storages](storages.md#request-queues).
than raw `JsonNode`; see [Storages](storages.md#request-queues).

## What `ApifyResource` is

`ApifyResource` (root package) is the base class every response model extends — both the top-level
`ApifyResource` (root package) is the base class every response model extends; both the top-level
resources (`Actor`, `Dataset`, `Schedule`, `ActorRun`, and so on) and the nested value DTOs embedded
in them (`ActorRunStats`, `ActorRunMeta`, `BuildStats`, `WebhookStats`, `UserProfile`, `PricingInfo`,
and every other nested model in this client). It has no fields of its own beyond the `extra` map
described below; it exists purely so every model shares one place to capture unmodelled API fields,
and so code that only needs the common capability (rare — most call sites use a concrete model type
and so code that only needs the common capability (rare; most call sites use a concrete model type
directly) can accept `ApifyResource` rather than a specific model.

## Model fields and unmodeled data (`getExtra`)

Response models expose the commonly-used fields as typed getters. The API returns more fields than
are modelled; every model — including nested value DTOs, not only the top-level resources — also
are modelled; every model, including nested value DTOs, not only the top-level resources, also
carries a `getExtra()` map (`Map<String, Object>`, inherited from `ApifyResource` above) holding any
field not mapped to a typed getter, so nothing the API returns is lost, now or as the API grows new
fields later. For example a `me()` `User`'s private account details (email, plan, proxy settings,
Expand Down Expand Up @@ -154,11 +154,11 @@ ActorListOptions options = new ActorListOptions().my(true).limit(10L);
PaginationList<Actor> page = client.actors().list(options).join();
```

## Common list options — `ListOptions`
## Common list options: `ListOptions`

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).
`RunListOptions` status filter; `runs().list(ListOptions, RunListOptions)`; see [Runs](runs.md).

| Method | Type | Meaning |
|---|---|---|
Expand All @@ -170,7 +170,7 @@ Most `list` methods (builds, tasks, schedules, webhooks, Actor versions) take th
PaginationList<Build> builds = client.builds().list(new ListOptions().limit(50L).desc(true)).join();
```

## Pagination — `PaginationList<T>`
## Pagination: `PaginationList<T>`

`list` methods return a `PaginationList<T>` page with `getTotal()`, `getOffset()`, `getLimit()`,
`getCount()`, `isDesc()` and `getItems()`. `getCount()` is the number of entries on the page. On a
Expand All @@ -180,7 +180,7 @@ The next page starts at `offset + Math.min(count, limit)`; the cap matters only
can return more items than the rows scanned. Within-storage listers (`listKeys`, `listHead`) return
their own page/head containers instead.

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

Each paginated collection also offers a lazy `Flow.Publisher` (`java.util.concurrent.Flow.Publisher`,
the JDK's built-in Reactive Streams type) that fetches pages only as the subscriber signals demand:
Expand All @@ -190,13 +190,13 @@ the JDK's built-in Reactive Streams type) that fetches pages only as the subscri
that is the rows each page covers (`PaginationList.getCount()`, capped at the page limit that was
requested). That is the number of items yielded, unless a page's count differs from its items:
filtered or unwound dataset items, or a Store list that drops entries after counting them.
`null`/unset — or a non-positive value such as `0` — means no cap, so every item is yielded. (This
`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
unbounded.) The per-request
page size is an optional trailing `chunkSize` argument: the per-resource tables below show the
`chunkSize` form, and each publisher also has an overload that omits it (using the server's default
page size). The page size does not change which items a collection publisher yields; note the one
exception in [Storages](storages.md) — `iterateItems` combined with server-side item filters, where
exception in [Storages](storages.md); `iterateItems` combined with server-side item filters, where
the page size can affect the result.

Consume a publisher either by implementing `Flow.Subscriber<T>` yourself (see
Expand Down
Loading
Loading