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
77 changes: 77 additions & 0 deletions .github/workflows/java-integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Java integration tests

# Language-specific workflow: only runs for the Java client. Triggers on PRs to master that touch
# Java client, test, example, or documentation code, and can be dispatched manually from any branch.
on:
pull_request:
branches: [master]
paths:
- '**/*.java'
- 'pom.xml'
# The "Test examples" step validates the in-documentation snippets, so doc changes must
# re-run the workflow even though Markdown is not Java code.
- 'docs/**'
- 'README.md'
- '.github/workflows/java-integration-tests.yml'
workflow_dispatch:

# Avoid concurrent runs of the same ref racing on the shared test account.
concurrency:
group: java-integration-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

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

# The formatter/lint gate mandated by the coding rules (google-java-format + import hygiene).
- name: Check formatting (Spotless)
run: mvn -B spotless:check

- name: Build
run: mvn -B -DskipTests test-compile

# Static-analysis linter (bug finder), the coding-rules-mandated CI lint gate.
- 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.
- name: Unit tests
run: mvn -B test -Dtest='UnitHttpTest,ReviewFixesTest,ClientMetaTest,SignatureTest,ConfigTest' -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),
# so a green run would not prove the API logic actually executed.
- name: Require APIFY_TOKEN secret
env:
APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}
run: |
if [ -z "${APIFY_TOKEN}" ]; then
echo "::error::APIFY_TOKEN secret is empty or missing; integration tests would not run against the API."
exit 1
fi

- name: Integration tests
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

# 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
# snippet is valid, runnable code (DocSnippetsTest compiles each fenced block).
- name: Test examples
env:
APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}
run: mvn -B test -Dtest='ExamplesTest,DocSnippetsTest' -DfailIfNoTests=true
145 changes: 145 additions & 0 deletions .github/workflows/java-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Publish Java client

# Language-specific publish workflow for the Java client. It is triggered manually only
# (workflow_dispatch) so a maintainer deliberately decides when a release is cut. The release
# version is the single source of truth in pom.xml (<version>). The artifact is published to Maven
# Central via the Sonatype Central Publisher Portal.
on:
workflow_dispatch:
inputs:
dry_run:
description: 'Run all checks and build artifacts but do not deploy or create the release.'
type: boolean
default: false

# Never allow two publish runs to race; publishing two releases concurrently is hard to undo.
concurrency:
group: java-publish
cancel-in-progress: false

permissions:
contents: write # create the tagged GitHub release

jobs:
publish:
runs-on: ubuntu-latest
# The Maven Central repository credentials live in this protected GitHub environment, so the
# publish job can only read them here (and any environment protection rules gate the release).
environment: Publishing
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

# A release must only ever be cut from master.
- name: Require master branch
run: |
if [ "${GITHUB_REF}" != "refs/heads/master" ]; then
echo "::error::Publishing is only allowed from master, but this run is on '${GITHUB_REF}'."
exit 1
fi

- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
cache: maven
# Write a Maven settings.xml whose "central" server reads its username/password from the
# named environment variables at deploy time (populated from the "Publishing"
# environment's secrets in the publish step). Nothing is stored in the repo.
server-id: central
server-username: MAVEN_CENTRAL_REPOSITORY_USERNAME
server-password: MAVEN_CENTRAL_REPOSITORY_PASSWORD
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE

# Gate the release on the same quality bar as CI so a broken build can never be published.
- name: Check formatting (Spotless)
run: mvn -B spotless:check

- name: Static analysis (SpotBugs)
run: mvn -B -DskipTests compile spotbugs:check

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

- name: Resolve version from pom.xml
id: version
run: |
version=$(mvn -B -q -DforceStdout help:evaluate -Dexpression=project.version)
if ! echo "${version}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::project.version '${version}' is not a bare semver (X.Y.Z)."
exit 1
fi
echo "tag=v${version}" >> "$GITHUB_OUTPUT"
echo "Resolved release tag: v${version}"

- name: Ensure tag does not already exist
env:
TAG: ${{ steps.version.outputs.tag }}
run: |
if git ls-remote --exit-code --tags origin "${TAG}" >/dev/null 2>&1; then
echo "::error::Tag ${TAG} already exists on origin; bump the version in pom.xml first."
exit 1
fi

# Verify the "Publishing" environment holds the three Maven Central repository secrets that the
# project's publishing policy mandates, so a release cannot silently proceed with a
# mis-provisioned environment. The mvn deploy below authenticates the "central" server with the
# username/password pair; the base64 form is the same credential pre-encoded as the Central
# Portal bearer token (username:password), kept in the environment per policy for tooling that
# talks to the Central Portal API directly.
- name: Require Maven Central credentials
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
MAVEN_CENTRAL_REPOSITORY_USERNAME: ${{ secrets.MAVEN_CENTRAL_REPOSITORY_USERNAME }}
MAVEN_CENTRAL_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_CENTRAL_REPOSITORY_PASSWORD }}
MAVEN_CENTRAL_REPOSITORY_USERNAME_PASSWORD_BASE64: ${{ secrets.MAVEN_CENTRAL_REPOSITORY_USERNAME_PASSWORD_BASE64 }}
run: |
missing=""
[ -z "${MAVEN_CENTRAL_REPOSITORY_USERNAME}" ] && missing="${missing} MAVEN_CENTRAL_REPOSITORY_USERNAME"
[ -z "${MAVEN_CENTRAL_REPOSITORY_PASSWORD}" ] && missing="${missing} MAVEN_CENTRAL_REPOSITORY_PASSWORD"
[ -z "${MAVEN_CENTRAL_REPOSITORY_USERNAME_PASSWORD_BASE64}" ] && missing="${missing} MAVEN_CENTRAL_REPOSITORY_USERNAME_PASSWORD_BASE64"
if [ -n "${missing}" ]; then
echo "::error::Missing Publishing environment secret(s):${missing}"
exit 1
fi

# Build, sign and publish to Maven Central via the Sonatype Central Publisher Portal. The
# central-publishing-maven-plugin authenticates the "central" server with the username/password
# provisioned by setup-java above; both come from the "Publishing" environment's secrets.
- name: Publish to Maven Central
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
MAVEN_CENTRAL_REPOSITORY_USERNAME: ${{ secrets.MAVEN_CENTRAL_REPOSITORY_USERNAME }}
MAVEN_CENTRAL_REPOSITORY_PASSWORD: ${{ secrets.MAVEN_CENTRAL_REPOSITORY_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: mvn -B -Prelease -DskipTests deploy

- name: Build artifacts only (dry run)
if: ${{ github.event.inputs.dry_run == 'true' }}
env:
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
run: mvn -B -Prelease -DskipTests verify

- name: Create and push release tag
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
TAG: ${{ steps.version.outputs.tag }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "${TAG}" -m "Release ${TAG}"
git push origin "${TAG}"

- name: Create GitHub release
if: ${{ github.event.inputs.dry_run != 'true' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
run: |
gh release create "${TAG}" \
--title "${TAG}" \
--notes "Apify Java client ${TAG}. See CHANGELOG.md for details."
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*

# Maven build output
target/

# IDE
.idea/
*.iml
.vscode/
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Changelog

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.1.0] - 2026-07-03

Initial release of the official (experimental, AI-generated and AI-maintained) Java client for the
Apify API, verified against OpenAPI specification version `v2-2026-07-02T131926Z`.

### Added

- Resource-oriented `ApifyClient` mirroring the JavaScript reference client, with accessors for
Actors, Actor versions and environment variables, builds, runs,
datasets, key-value stores, request queues, tasks, schedules, webhooks, webhook dispatches, the
Apify Store, users, and logs.
- Replaceable HTTP transport via the `HttpBackend` interface (default `DefaultHttpBackend`, backed
by the JDK `java.net.http.HttpClient`), configurable through `ApifyClient.builder().httpBackend(...)`.
- Cross-cutting request behaviour applied to every call: bearer-token authentication, the mandated
`User-Agent` header, exponential-backoff-with-jitter retries (429, 5xx and network errors), and a
growing-but-capped per-attempt timeout.
- Convenience helpers matching the reference client: `actor(...).call` / `task(...).call` (start and
wait), `run(...).waitForFinish` / `build(...).waitForFinish`, `actor(...).defaultBuild`,
`run(...).metamorph`/`reboot`/`resurrect`/`charge`, run-nested default storages, lazy
`store().iterate()` and `requestQueue(...).paginateRequests()` iterators, dataset
`downloadItems`/`getStatistics`/`createItemsPublicUrl`, key-value-store record and key-list public
URLs with HMAC-SHA256 signing, the request-queue lock lifecycle, and `setStatusMessage`.
- `setRecord` write options (`SetRecordOptions`: `timeoutSecs`, `doNotRetryTimeouts`),
`createKeysPublicUrl(ListKeysOptions, ...)` key-listing filters, and `batchAddRequests`
tuning (`BatchAddRequestsOptions`: `maxUnprocessedRequestsRetries`, `maxParallel`,
`minDelayBetweenUnprocessedRequestsRetriesMillis`) with automatic retry of unprocessed requests,
all matching the reference client. `downloadItems` forwards the full set of item-selection
parameters via `DatasetDownloadOptions.items(...)`.
- `actor(...).validateInput(input[, ValidateInputOptions])` to validate an input against an Actor's
input schema, matching the reference client.
- `datasets()`/`keyValueStores()` `getOrCreate(name, schema)` overloads that send a creation schema,
matching the reference client.
- `batchAddRequests` surfaces a non-retryable client error (a 4xx other than 429) as an
`ApifyApiException`; persistent rate-limit/server failures are returned as `unprocessedRequests`.
- Read-only nested webhook collections (`actor(id).webhooks()`, `task(id).webhooks()`); `create(...)`
is exposed only on the account-wide `client.webhooks()`, matching the GET-only nested API.
- Single-resource getters return an empty `Optional` when the API responds `200` with `{"data": null}`.
- Public version constants `Version.CLIENT_VERSION` and `Version.API_SPEC_VERSION`.
- Forward-compatible models that capture unmodelled API fields in an `extra` map.
- Offline unit tests (mock HTTP backend) and an integration test suite (one simple GET plus one
CRUD/complex flow per resource), with runnable, CI-tested documentation examples.
- Language-specific GitHub Actions workflows: `Java integration tests` (Spotless, SpotBugs, build,
unit, integration, and a standalone `Test examples` step) and a manually triggered `Publish Java
client` workflow that releases to Maven Central via the Sonatype Central Publisher Portal,
authenticating with the Maven Central repository credentials from the protected `Publishing`
GitHub environment, and creates a tagged GitHub release.
Loading
Loading