Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/java-integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

# Offline unit tests (mock HTTP backend): prove the retry/error/signature logic without the API.
- name: Unit tests
run: mvn -B test -Dtest='UnitHttpTest,ReviewFixesTest,ClientMetaTest,SignatureTest,ConfigTest' -DfailIfNoTests=true
run: mvn -B test -Dtest='UnitHttpTest,ReviewFixesTest,ClientMetaTest,SignatureTest,ConfigTest,EnumTest' -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 Down
2 changes: 1 addition & 1 deletion .github/workflows/java-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
run: mvn -B -DskipTests compile spotbugs:check

- name: Unit tests
run: mvn -B test -Dtest='UnitHttpTest,ReviewFixesTest,ClientMetaTest,SignatureTest,ConfigTest' -DfailIfNoTests=true
run: mvn -B test -Dtest='UnitHttpTest,ReviewFixesTest,ClientMetaTest,SignatureTest,ConfigTest,EnumTest' -DfailIfNoTests=true

- name: Resolve version from pom.xml
id: version
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@ 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.2.0] - 2026-07-09

Idiomatic-Java refactor. This release contains breaking changes to the public interface.

### Changed

- `ActorRun.getStatus()` and `Build.getStatus()` now return a `RunStatus` enum instead of a `String`.
- `Webhook.getEventTypes()` now returns a `List<WebhookEventType>` instead of a `List<String>`.
- `ActorClient.lastRun(...)` and `TaskClient.lastRun(...)` now take a `RunStatus` instead of a
`String`; `LastRunOptions.status(...)`/`origin(...)` now take `RunStatus`/`RunOrigin` enums.
- `RunListOptions.status(...)` now takes a `List<RunStatus>` instead of a `List<String>`.
- `ActorStartOptions.forcePermissionLevel(...)` now takes a `PermissionLevel` enum instead of a
`String`.
- `StoreCollectionClient.iterate(...)` and `RequestQueueClient.paginateRequests(...)` now return a
lazy `Stream<T>` instead of an `Iterator<T>`.
- `RunClient.abort(Boolean)` is replaced by the overloads `abort()` (server default) and
`abort(boolean gracefully)`. Migration: replace `abort(null)` with `abort()`. Callers passing a
nullable `Boolean` variable are also affected — it now auto-unboxes to `abort(boolean)` and throws
a `NullPointerException` on `null` instead of falling through to the server default; use `abort()`
when the value is absent.

### Added

- `RunStatus`, `RunOrigin`, `PermissionLevel` and `WebhookEventType` enums (each parsing unrecognised
server values to an `UNKNOWN` constant where applicable), with `RunStatus.isTerminal()`.

## [0.1.1] - 2026-07-07

### Changed
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ Maven:
<dependency>
<groupId>com.apify</groupId>
<artifactId>apify-client</artifactId>
<version>0.1.1</version>
<version>0.2.0</version>
</dependency>
```

Gradle:

```groovy
implementation 'com.apify:apify-client:0.1.1'
implementation 'com.apify:apify-client:0.2.0'
```

## Quick start
Expand Down Expand Up @@ -122,7 +122,7 @@ try {

## Versioning

- `Version.CLIENT_VERSION` — the semantic version of this client (`0.1.1`).
- `Version.CLIENT_VERSION` — the semantic version of this client (`0.2.0`).
- `Version.API_SPEC_VERSION` — the Apify OpenAPI specification version this client was verified
against (`v2-2026-07-07T132551Z`).

Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ empty `Optional` rather than an exception. API failures are thrown as `ApifyApiE

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`).
`java.util.Optional`, `java.util.stream.Stream`, `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 Down
19 changes: 11 additions & 8 deletions docs/actors.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,27 @@ Actor created = client.actors().create(Map.of(
| `validateInput(Object input)` / `validateInput(Object input, ValidateInputOptions)` | Validate an input against the Actor's input schema. Returns `boolean`. `ValidateInputOptions` fields (optional): `build`, `contentType`. |
| `build(String versionNumber, ActorBuildOptions)` | Build a version. Returns `Build`. |
| `defaultBuild(Long waitForFinish)` | Resolve the default build. Returns `BuildClient`. |
| `lastRun(String status)` / `lastRun(LastRunOptions)` | A `RunClient` for the last run. |
| `lastRun(RunStatus status)` / `lastRun(LastRunOptions)` | A `RunClient` for the last run. |
| `builds()` / `runs()` / `versions()` | Nested collection clients. |
| `webhooks()` | Read-only nested webhook collection (`NestedWebhookCollectionClient`, list only). |
| `version(String)` | An `ActorVersionClient`. |

`ActorStartOptions` fields (all optional): `build`, `memoryMbytes`, `timeoutSecs`, `waitForFinish`,
`maxItems`, `maxTotalChargeUsd`, `contentType`, `restartOnError`, `forcePermissionLevel`
(`LIMITED_PERMISSIONS`/`FULL_PERMISSIONS`), and `webhooks(List<Object>)` — ad-hoc webhook definitions
(each a JSON-serializable `Map`, as in [Webhooks](webhooks.md)) that the client base64-encodes on the
wire.
(a `PermissionLevel` enum: `LIMITED_PERMISSIONS`/`FULL_PERMISSIONS`), and `webhooks(List<Object>)` —
ad-hoc webhook definitions (each a JSON-serializable `Map`, as in [Webhooks](webhooks.md)) that the
client base64-encodes on the wire.

`lastRun(String status)` filters only by status; `lastRun(LastRunOptions)` also accepts an origin
filter. `LastRunOptions` has fluent setters `status(String)` (e.g. `SUCCEEDED`, `RUNNING`) and
`origin(String)` (e.g. `API`, `WEB`, `SCHEDULER`); leave a setter uncalled to omit that filter.
`lastRun(RunStatus status)` filters only by status; `lastRun(LastRunOptions)` also accepts an origin
filter. `LastRunOptions` has fluent setters `status(RunStatus)` (e.g. `RunStatus.SUCCEEDED`,
`RunStatus.RUNNING`) and `origin(RunOrigin)` (e.g. `RunOrigin.API`, `RunOrigin.WEB`,
`RunOrigin.SCHEDULER`); leave a setter uncalled to omit that filter.

```java
Optional<ActorRun> last =
client.actor("apify/hello-world").lastRun(new LastRunOptions().status("SUCCEEDED").origin("API")).get();
client.actor("apify/hello-world")
.lastRun(new LastRunOptions().status(RunStatus.SUCCEEDED).origin(RunOrigin.API))
.get();
```

```java
Expand Down
7 changes: 4 additions & 3 deletions docs/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ builds) and a single build with `client.build(id)`.
| `getOpenApiDefinition()` | The build's OpenAPI definition. Returns `Optional<JsonNode>`. |
| `log()` | A `LogClient` for the build's log. |

`Build` fields: `getId()`, `getActId()`, `getStatus()`, `getStartedAt()`, `getFinishedAt()`,
`getBuildNumber()`, plus `isTerminal()` and `getExtra()`. The status is one of `READY`, `RUNNING`,
`SUCCEEDED`, `FAILED`, `TIMING-OUT`, `TIMED-OUT`, `ABORTING`, `ABORTED`.
`Build` fields: `getId()`, `getActId()`, `getStatus()` (a `RunStatus` enum), `getStartedAt()`,
`getFinishedAt()`, `getBuildNumber()`, plus `isTerminal()` and `getExtra()`. `RunStatus` is one of
`READY`, `RUNNING`, `SUCCEEDED`, `FAILED`, `TIMING_OUT`, `TIMED_OUT`, `ABORTING`, `ABORTED`, or
`UNKNOWN`.

```java
Build build = client.actor("me/my-actor").build("0.0", new ActorBuildOptions().tag("latest"));
Expand Down
11 changes: 4 additions & 7 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ try {

```java
client.actor("apify/hello-world").call(null, new ActorStartOptions(), 120L);
Optional<ActorRun> last = client.actor("apify/hello-world").lastRun("SUCCEEDED").get();
Optional<ActorRun> last = client.actor("apify/hello-world").lastRun(RunStatus.SUCCEEDED).get();
if (last.isPresent()) {
ActorRun run = last.get();
client.dataset(run.getDefaultDatasetId()).listItems(new DatasetListItemsOptions());
Expand All @@ -98,12 +98,9 @@ if (last.isPresent()) {
## Lazy iteration of Store Actors

```java
Iterator<ActorStoreListItem> it = client.store().iterate(new StoreListOptions().limit(10L));
int shown = 0;
while (shown < 5 && it.hasNext()) {
System.out.println(it.next().getName());
shown++;
}
client.store().iterate(new StoreListOptions().limit(10L))
.limit(5)
.forEach(item -> System.out.println(item.getName()));
```

## Run an Actor with log redirection
Expand Down
12 changes: 4 additions & 8 deletions docs/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@ Browse public Actors in the Apify Store.
| Method | Description |
|---|---|
| `list(StoreListOptions)` | A page of Store Actors. Returns `PaginationList<ActorStoreListItem>`. |
| `iterate(StoreListOptions)` | A lazy `Iterator<ActorStoreListItem>` fetching pages on demand. |
| `iterate(StoreListOptions)` | A lazy `Stream<ActorStoreListItem>` fetching pages on demand. |

`StoreListOptions` fields: `offset`, `limit`, `search`, `sortBy`, `category`, `username`,
`pricingModel` (`FREE`, `FLAT_PRICE_PER_MONTH`, `PRICE_PER_DATASET_ITEM`, `PAY_PER_EVENT`),
`includeUnrunnableActors`, `allowsAgenticUsers`, `responseFormat` (`full`, `agent`).

```java
Iterator<ActorStoreListItem> it = client.store().iterate(new StoreListOptions().search("crawler").limit(20L));
int shown = 0;
while (shown < 5 && it.hasNext()) {
ActorStoreListItem item = it.next();
System.out.println(item.getUsername() + "/" + item.getName());
shown++;
}
client.store().iterate(new StoreListOptions().search("crawler").limit(20L))
.limit(5)
.forEach(item -> System.out.println(item.getUsername() + "/" + item.getName()));
```

`ActorStoreListItem` fields: `getId()`, `getName()`, `getUsername()`, `getTitle()`.
Expand Down
14 changes: 9 additions & 5 deletions docs/runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ Access the run collection with `client.runs()` (or `client.actor(id).runs()` /
|---|---|
| `list(ListOptions, RunListOptions)` | List runs. Returns `PaginationList<ActorRun>`. |

`RunListOptions` adds `status(List<String>)` (e.g. `SUCCEEDED`, `RUNNING`; sent comma-separated) and,
for Actor/task-scoped collections, `startedAfter(String)` / `startedBefore(String)` (ISO-8601).
`RunListOptions` adds `status(List<RunStatus>)` (e.g. `RunStatus.SUCCEEDED`, `RunStatus.RUNNING`; sent
comma-separated) and, for Actor/task-scoped collections, `startedAfter(String)` /
`startedBefore(String)` (ISO-8601).

```java
PaginationList<ActorRun> runs = client.runs().list(
new ListOptions().limit(10L),
new RunListOptions().status(List.of("SUCCEEDED")));
new RunListOptions().status(List.of(RunStatus.SUCCEEDED)));
```

## `RunClient`
Expand All @@ -26,7 +27,7 @@ PaginationList<ActorRun> runs = client.runs().list(
| `getWithWait(Long waitForFinishSecs)` | Fetch, optionally waiting server-side for the run to finish (clamped to the request timeout; the API caps server-side waiting at 60s). Returns `Optional<ActorRun>`. |
| `update(Object)` | Update the run. Returns `ActorRun`. |
| `delete()` | Delete the run. |
| `abort(Boolean gracefully)` | Abort the run (`null` = server default). Returns `ActorRun`. |
| `abort()` / `abort(boolean gracefully)` | Abort the run (no-arg = server default). Returns `ActorRun`. |
| `metamorph(String targetActorId, Object input, MetamorphOptions)` | Metamorph into another Actor. Returns `ActorRun`. |
| `reboot()` | Reboot the run. Returns `ActorRun`. |
| `resurrect(RunResurrectOptions)` | Resurrect a finished run. Returns `ActorRun`. |
Expand All @@ -42,9 +43,12 @@ use `log()` for the full log text or for non-raw/download options.
To set the current run's status message from inside an Actor, use the top-level
`client.setStatusMessage(...)` (see [the docs index](README.md#setting-single-resource-status)).

`ActorRun` fields include `getId()`, `getActId()`, `getUserId()`, `getStatus()`,
`ActorRun` fields include `getId()`, `getActId()`, `getUserId()`, `getStatus()` (a `RunStatus` enum),
`getStatusMessage()`, `getStartedAt()`, `getFinishedAt()`, `getBuildId()`, `getDefaultDatasetId()`,
`getDefaultKeyValueStoreId()`, `getDefaultRequestQueueId()`, `getContainerUrl()`, plus `isTerminal()`.
`RunStatus` is the run/build lifecycle enum (`READY`, `RUNNING`, `SUCCEEDED`, `FAILED`, `TIMING_OUT`,
`TIMED_OUT`, `ABORTING`, `ABORTED`, and `UNKNOWN` for a value this client version does not recognise);
`RunStatus.isTerminal()` reports whether a status is final.

`RunChargeOptions` (constructed with the required event name) uses plain values: `count(Long)` and
`idempotencyKey(String)` — the latter is auto-generated when unset so a transport-retried charge is
Expand Down
9 changes: 3 additions & 6 deletions docs/storages.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ unsigned.
> **Public-URL limitation (matches the JavaScript reference client).** The public-URL builders
> (`createItemsPublicUrl`, and the key-value-store `getRecordPublicUrl` / `createKeysPublicUrl`)
> build the URL from the client's own resource path plus the signature. When called on a *last-run
> nested* client (e.g. `client.actor(id).lastRun("SUCCEEDED").dataset().createItemsPublicUrl(...)`)
> nested* client (e.g. `client.actor(id).lastRun(RunStatus.SUCCEEDED).dataset().createItemsPublicUrl(...)`)
> the returned URL keeps the unpinned `.../runs/last/dataset` path and does not carry the `status`
> filter, so it may resolve a different run when opened. Build public URLs from a concrete
> `client.dataset(id)` / `client.keyValueStore(id)` client when you need a stable shareable URL.
Expand Down Expand Up @@ -138,16 +138,13 @@ expiry-aware storage-content signature — hence only `createKeysPublicUrl` take
| `prolongRequestLock(String id, long lockSecs, boolean forefront)` | Extend a lock. Returns `JsonNode`. |
| `deleteRequestLock(String id, boolean forefront)` | Release a lock. No return value. |
| `unlockRequests()` | Release all the client's locks. Returns `JsonNode`. |
| `paginateRequests(Long pageLimit)` | A lazy `Iterator<RequestQueueRequest>` over all requests. |
| `paginateRequests(Long pageLimit)` | A lazy `Stream<RequestQueueRequest>` over all requests. |

```java
RequestQueue rq = client.requestQueues().getOrCreate("my-queue");
RequestQueueClient queue = client.requestQueue(rq.getId());
queue.addRequest(new RequestQueueRequest("https://example.com", "example"), false);
Iterator<RequestQueueRequest> it = queue.paginateRequests(100L);
while (it.hasNext()) {
System.out.println(it.next().getUrl());
}
queue.paginateRequests(100L).forEach(request -> System.out.println(request.getUrl()));
```

`ListRequestsOptions` fields: `limit`, `exclusiveStartId`, `cursor` (mutually exclusive with
Expand Down
2 changes: 1 addition & 1 deletion docs/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Task task = client.tasks().create(Map.of(
| `call(Object input, TaskStartOptions, Long waitSecs)` | Start and poll until finished. |
| `getInput()` | The stored input. Returns `Optional<JsonNode>`. |
| `updateInput(Object)` | Replace the stored input. Returns `JsonNode`. |
| `lastRun(String status)` / `lastRun(LastRunOptions)` | A `RunClient` for the last run (see [`LastRunOptions`](actors.md#actorclient)). |
| `lastRun(RunStatus status)` / `lastRun(LastRunOptions)` | A `RunClient` for the last run (see [`LastRunOptions`](actors.md#actorclient)). |
| `runs()` | Nested run collection client. |
| `webhooks()` | Read-only nested webhook collection (`NestedWebhookCollectionClient`, list only). |

Expand Down
17 changes: 12 additions & 5 deletions docs/webhooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ exposes `list(ListOptions)` only — it has no `create(...)`. To create a webhoo
Actor or task, use `client.webhooks().create(...)` and set the Actor/task in the webhook's
`condition`.

A webhook definition supplies `eventTypes` (a list of event-type strings such as
A webhook definition supplies `eventTypes` (the events that trigger it, such as
`ACTOR.RUN.SUCCEEDED`, `ACTOR.RUN.FAILED`, `ACTOR.RUN.ABORTED`, `ACTOR.RUN.TIMED_OUT`,
`ACTOR.RUN.CREATED`, `ACTOR.BUILD.SUCCEEDED`, …), a `condition` and a `requestUrl`. The definition is
a plain JSON-serializable value (e.g. a `Map`); this client does not wrap it in a typed enum:
`ACTOR.RUN.CREATED`, `ACTOR.BUILD.SUCCEEDED`, …), a `condition` and a `requestUrl`. The definition you
pass to `create(...)` is a plain JSON-serializable value (e.g. a `Map`), so on create the event types
must be their wire strings (e.g. `"ACTOR.RUN.SUCCEEDED"`). When reading a webhook back,
`Webhook.getEventTypes()` returns them as a typed `WebhookEventType` enum (with `UNKNOWN` for any value
this client version does not recognise). To stay type-safe on create, derive the wire strings from the
enum with `WebhookEventType.getWireValue()`:

```java
Webhook webhook = client.webhooks().create(Map.of(
"isAdHoc", false,
"eventTypes", List.of("ACTOR.RUN.SUCCEEDED", "ACTOR.RUN.FAILED"),
"eventTypes", List.of(
WebhookEventType.ACTOR_RUN_SUCCEEDED.getWireValue(),
WebhookEventType.ACTOR_RUN_FAILED.getWireValue()),
"condition", Map.of("actorId", "apify/hello-world"),
"requestUrl", "https://example.com/webhook"));
```
Expand All @@ -49,7 +55,8 @@ WebhookDispatch dispatch = client.webhook("WEBHOOK_ID").test();
System.out.println(dispatch.getId());
```

`Webhook` fields: `getId()`, `getUserId()`, `getRequestUrl()`, `getEventTypes()`.
`Webhook` fields: `getId()`, `getUserId()`, `getRequestUrl()`, `getEventTypes()` (a
`List<WebhookEventType>`).

## `WebhookDispatchCollectionClient` and `WebhookDispatchClient`

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.apify</groupId>
<artifactId>apify-client</artifactId>
<version>0.1.1</version>
<version>0.2.0</version>
<packaging>jar</packaging>

<name>Apify Java Client</name>
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/com/apify/client/ActorClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ public BuildClient defaultBuild(Long waitForFinish) {
}

/**
* Returns a client for the last run of this Actor, optionally filtered by status (e.g. {@code
* "SUCCEEDED"}). Pass {@code null} or empty for no filter.
* Returns a client for the last run of this Actor, optionally filtered by status (e.g. {@link
* RunStatus#SUCCEEDED}). Pass {@code null} for no filter.
*/
public RunClient lastRun(String status) {
public RunClient lastRun(RunStatus status) {
return lastRun(new LastRunOptions().status(status));
}

Expand Down
12 changes: 4 additions & 8 deletions src/main/java/com/apify/client/ActorRun.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public final class ActorRun extends ApifyResource {
private String actId;
private String actorTaskId;
private String userId;
private String status;
private RunStatus status;
private String statusMessage;
private Instant startedAt;
private Instant finishedAt;
Expand Down Expand Up @@ -38,12 +38,8 @@ public String getUserId() {
return userId;
}

/**
* The current run status. One of the eight {@code ActorJobStatus} values: {@code READY}, {@code
* RUNNING}, {@code SUCCEEDED}, {@code FAILED}, {@code TIMING-OUT}, {@code TIMED-OUT}, {@code
* ABORTING}, {@code ABORTED}.
*/
public String getStatus() {
/** The current run status, or {@code null} if the API did not report one. */
public RunStatus getStatus() {
return status;
}

Expand Down Expand Up @@ -89,6 +85,6 @@ public String getContainerUrl() {

/** Whether the run has reached a terminal (finished) status. */
public boolean isTerminal() {
return Statuses.isTerminal(status);
return status != null && status.isTerminal();
}
}
Loading
Loading