Skip to content
Merged
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
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,30 @@ 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.8.0] - 2026-10-12

### Added

- `UserProfile.getReadme()`, mirroring the OpenAPI spec's new `Profile.readme` field.
- `ActorStartOptions.waitForResources()`/`TaskStartOptions.waitForResources()` (and the matching
`ActorCallOptions`/`TaskCallOptions` overloads): retry starting a run while the account lacks the
memory or a concurrent-run slot for it, matching the reference client.

### Fixed

- `RunClient.waitForFinish()`/`BuildClient.waitForFinish()` polled a run/build id that kept
returning 404 for the entire wait budget (up to ~11.5 days when unbounded) instead of giving up
promptly; they now throw the `NotFoundError` once the id has been missing for 3 seconds straight,
matching the reference client.
- `StreamedLog` silently stopped redirecting (without ever reopening) when the live log stream ended
immediately with no bytes - the case for a run that has not logged anything yet (e.g. still queued
for resources). It now reopens the stream, with capped exponential backoff, until it delivers
content; if stopped first, it fetches the log in one plain request instead of redirecting nothing.

### Changed

- Updated to Apify OpenAPI specification `v2-2026-10-08T073338Z`.

## [0.7.0] - 2026-10-02

### Added
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Maven (Maven Central is a default repository, so no extra configuration is neede
<dependency>
<groupId>com.apify</groupId>
<artifactId>apify-client</artifactId>
<version>0.7.0</version>
<version>0.8.0</version>
</dependency>
```

Expand All @@ -35,7 +35,7 @@ repositories {
}

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

Expand Down Expand Up @@ -317,10 +317,10 @@ try {
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.7.0`).
- `Version.CLIENT_VERSION` — the semantic version of this client (`0.8.0`).
- `Version.API_SPEC_VERSION` — the version of the [Apify OpenAPI specification](https://docs.apify.com/api/openapi.json)
(its `info.version` field) that this client's endpoints, parameters and models were last generated
and checked against (`v2-2026-10-01T153946Z`). It is a snapshot, not a live compatibility
and checked against (`v2-2026-10-08T073338Z`). It is a snapshot, not a live compatibility
guarantee: the client keeps working against newer, backward-compatible spec revisions, but a
feature added to the API after this snapshot has no corresponding method here yet.

Expand Down
7 changes: 5 additions & 2 deletions docs/actors.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,12 @@ to charge, pay-per-result Actors), `maxTotalChargeUsd(Double)` (maximum total ch
pay-per-event Actors), `contentType(String)` (content type of the input body, defaults to
`application/json`), `restartOnError(Boolean)` (restart the run if it fails),
`forcePermissionLevel(String)` (override the Actor's permission level for this run:
`LIMITED_PERMISSIONS`/`FULL_PERMISSIONS`), and `webhooks(List<Object>)` — ad-hoc webhook definitions
`LIMITED_PERMISSIONS`/`FULL_PERMISSIONS`), `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.
wire — and `waitForResources(boolean)`/`waitForResources(Long maxSecs)`: retry the start, every 10
seconds, while the account lacks the memory or a concurrent-run slot for the run (`true` retries
until it starts — `ActorStartOptions.WAIT_FOR_RESOURCES_INDEFINITELY`; a bound throws the last such
error once exceeded; omitted, the default, does not retry at all).

`ActorCallOptions` (for the log-streaming `call` overload) mirrors `ActorStartOptions` field for
field, but omits `waitForFinish`: that field asks the API to hold the HTTP response open
Expand Down
2 changes: 1 addition & 1 deletion docs/builds.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ builds) and a single build with `client.build(id)`.
| `getWithWait(Long waitForFinishSecs)` | Fetch, optionally waiting server-side for the build to finish (clamped to the request timeout; the API caps server-side waiting at 60s). Completes with `Optional<Build>`. |
| `abort()` | Abort the build. Completes with `Build`. |
| `delete()` | Delete the build. |
| `waitForFinish(Long waitSecs)` | Poll until the build finishes (`null` waits indefinitely). Completes with `Build`. |
| `waitForFinish(Long waitSecs)` | Poll until the build finishes (`null` waits indefinitely); see [the run equivalent](runs.md#runclient) for its 404 handling. Completes with `Build`. |
| `getOpenApiDefinition()` | The build's OpenAPI definition. Completes with `Optional<JsonNode>`. |
| `log()` | A `LogClient` for the build's log. |

Expand Down
5 changes: 3 additions & 2 deletions docs/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ me.ifPresent(
JsonNode usage = client.me().monthlyUsage().join();
```

`User` fields: `getId()`, `getUsername()`, `getProfile()` (`UserProfile` — `getBio()`, `getName()`,
`getPictureUrl()`, `getGithubUsername()`, `getWebsiteUrl()`, `getTwitterUsername()`, all nullable).
`User` fields: `getId()`, `getUsername()`, `getProfile()` (`UserProfile` — `getBio()`, `getReadme()`,
`getName()`, `getPictureUrl()`, `getGithubUsername()`, `getWebsiteUrl()`, `getTwitterUsername()`, all
nullable).
Only present for `me()`: `getEmail()`, `getProxy()` (`UserProxy` — `getPassword()`, `getGroups()`
as `List<ProxyGroup>`, each with `getName()`/`getDescription()`/`getAvailableCount()` as `Long`),
`getPlan()` (`UserPlan` — `getId()`, `getDescription()`, `getIsEnabled()`,
Expand Down
10 changes: 10 additions & 0 deletions docs/runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ this same possibly-non-terminal `ActorRun` for the same reason: `call` is `start
`waitForFinish`, so a `call` with a short `waitSecs` on a long-running Actor can return before the
run finishes.

If the id instead keeps returning 404 (e.g. a typo, or it was never created) `waitForFinish` does
not poll it for the whole wait budget: a 404 is tolerated for up to 3 seconds (database-replica lag
right after a run/build is created), past which it throws that `NotFoundError`.

### Streamed log redirection

`getStreamedLog()` completes with a `StreamedLog`: a helper that follows the run's live raw log in a
Expand All @@ -68,6 +72,12 @@ Actor name and run id (looked up automatically). `StreamedLogOptions` customizes
- `fromStart(boolean)` — when `false`, skip log lines produced before redirection started
(default `true`).

A run that has not logged anything yet (e.g. still queued for resources, see
[`waitForResources`](actors.md#actorclient)) serves its log as a stream that ends at once;
`StreamedLog` reopens it, with capped exponential backoff, until it actually delivers content,
rather than silently giving up. If `stop()`/`close()` is called before that happens, it fetches the
log in one plain request instead, so whatever was logged by then is still delivered.

```java
RunClient runClient = client.run("RUN_ID");
List<String> collected = new ArrayList<>();
Expand Down
3 changes: 2 additions & 1 deletion docs/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ Task task = client.tasks().create(Map.of(

`TaskStartOptions` mirrors `ActorStartOptions` but omits the Actor-only `contentType` and
`forcePermissionLevel`: `build`, `memoryMbytes`, `timeoutSecs`, `waitForFinish`, `maxItems`,
`maxTotalChargeUsd`, `restartOnError`, `webhooks`. `TaskCallOptions` mirrors `TaskStartOptions` in
`maxTotalChargeUsd`, `restartOnError`, `webhooks`, `waitForResources` (see
[`ActorStartOptions`](actors.md#actorclient)). `TaskCallOptions` mirrors `TaskStartOptions` in
turn, but additionally omits `waitForFinish`: that field asks the API to hold the HTTP response
open server-side while the run finishes, which is redundant with (and wastes a request slot next
to) `call`'s own client-side `waitSecs` polling.
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.7.0</version>
<version>0.8.0</version>
<packaging>jar</packaging>

<name>Apify Java Client</name>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/apify/client/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public final class Version {
* The semantic version of this client library (see <a href="https://semver.org/">SemVer</a>).
* Changes to the public interface other than additive ones are considered breaking changes.
*/
public static final String CLIENT_VERSION = "0.7.0";
public static final String CLIENT_VERSION = "0.8.0";

/**
* The version of the Apify OpenAPI specification this client was generated and verified against.
* Corresponds to the {@code info.version} field of the Apify OpenAPI document.
*/
public static final String API_SPEC_VERSION = "v2-2026-10-01T153946Z";
public static final String API_SPEC_VERSION = "v2-2026-10-08T073338Z";

private Version() {}
}
12 changes: 12 additions & 0 deletions src/main/java/com/apify/client/actor/ActorCallOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ public ActorCallOptions webhooks(List<Object> webhooks) {
return this;
}

/** See {@link ActorStartOptions#waitForResources(boolean)}. */
public ActorCallOptions waitForResources(boolean waitForResources) {
startOptions.waitForResources(waitForResources);
return this;
}

/** See {@link ActorStartOptions#waitForResources(Long)}. */
public ActorCallOptions waitForResources(Long maxSecs) {
startOptions.waitForResources(maxSecs);
return this;
}

/**
* Disables the default log streaming, matching the reference client's {@code log: null}. {@code
* call} then only starts the run and polls for completion, as if built from {@link
Expand Down
18 changes: 15 additions & 3 deletions src/main/java/com/apify/client/actor/ActorClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ public CompletableFuture<Void> delete() {
* input} is any JSON-serializable value (or {@code null} for no input).
*/
public CompletableFuture<ActorRun> start(Object input, ActorStartOptions options) {
return RunStartSupport.start(ctx, input, options::apply, options.contentTypeOrDefault());
return RunStartSupport.start(
ctx,
input,
options::apply,
options.contentTypeOrDefault(),
options.waitForResourcesSecsValue());
}

/**
Expand All @@ -78,7 +83,13 @@ public CompletableFuture<ActorRun> start(Object input, ActorStartOptions options
*/
public CompletableFuture<ActorRun> call(Object input, ActorStartOptions options, Long waitSecs) {
return RunStartSupport.call(
root, ctx, input, options::apply, options.contentTypeOrDefault(), waitSecs);
root,
ctx,
input,
options::apply,
options.contentTypeOrDefault(),
waitSecs,
options.waitForResourcesSecsValue());
}

/**
Expand All @@ -104,7 +115,8 @@ public CompletableFuture<ActorRun> call(Object input, ActorCallOptions options,
startOptions.contentTypeOrDefault(),
opts.logStreamingEnabledValue(),
opts.logOptionsValue(),
waitSecs);
waitSecs,
startOptions.waitForResourcesSecsValue());
}

/** Validates the given input against the Actor's default-build input schema. */
Expand Down
40 changes: 38 additions & 2 deletions src/main/java/com/apify/client/actor/ActorStartOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@
* Configures starting an Actor run ({@link ActorClient#start}/{@link ActorClient#call}). All fields
* are optional.
*
* <p>{@link #apply}/{@link #contentTypeOrDefault} are package-private on purpose — see {@link
* com.apify.client.internal.RunStartSupport}'s class javadoc for why.
* <p>{@link #apply}/{@link #contentTypeOrDefault}/{@link #waitForResourcesSecsValue} are
* package-private on purpose — see {@link com.apify.client.internal.RunStartSupport}'s class
* javadoc for why.
*/
public final class ActorStartOptions {

/**
* Sentinel for {@link #waitForResources(Long)} (and what {@link #waitForResources(boolean)}
* passes for {@code true}): retry starting the run until it succeeds, with no time bound.
*/
public static final long WAIT_FOR_RESOURCES_INDEFINITELY = -1L;

private String build;
private Long memoryMbytes;
private Long timeoutSecs;
Expand All @@ -24,6 +32,7 @@ public final class ActorStartOptions {
private Boolean restartOnError;
private String forcePermissionLevel;
private List<Object> webhooks;
private Long waitForResourcesSecs;

/** The tag or number of the build to run (e.g. {@code "latest"}, {@code "0.1.2"}). */
public ActorStartOptions build(String build) {
Expand Down Expand Up @@ -91,12 +100,39 @@ public ActorStartOptions webhooks(List<Object> webhooks) {
return this;
}

/**
* Retries the start while the account lacks the memory or a concurrent-run slot for the run (the
* API rejects it with {@code actor-memory-limit-exceeded} or {@code
* concurrent-runs-limit-exceeded}, both of which clear as other runs or builds finish), waiting
* 10 seconds between attempts. {@code true} retries until the run starts ({@link
* #WAIT_FOR_RESOURCES_INDEFINITELY}); {@code false} (the default) does not retry — a
* resource-limit error is thrown immediately, like any other error.
*/
public ActorStartOptions waitForResources(boolean waitForResources) {
this.waitForResourcesSecs = waitForResources ? WAIT_FOR_RESOURCES_INDEFINITELY : null;
return this;
}

/**
* As {@link #waitForResources(boolean)}, bounding the retrying to at most {@code maxSecs} seconds
* - once exceeded, the last resource-limit error is thrown. {@code null} disables retrying (the
* default).
*/
public ActorStartOptions waitForResources(Long maxSecs) {
this.waitForResourcesSecs = maxSecs;
return this;
}

String contentTypeOrDefault() {
return (contentType != null && !contentType.isEmpty())
? contentType
: ResourceContext.CONTENT_TYPE_JSON;
}

Long waitForResourcesSecsValue() {
return waitForResourcesSecs;
}

void apply(QueryParams q) {
q.addString("build", build)
.addLong("memory", memoryMbytes)
Expand Down
Loading
Loading