Skip to content

feat(sdk/java): Spring Boot starter, reactive hardening, examples, live integration#25

Merged
ancongui merged 1 commit into
mainfrom
feat/java-sdk-spring-boot-starter-and-hardening
May 19, 2026
Merged

feat(sdk/java): Spring Boot starter, reactive hardening, examples, live integration#25
ancongui merged 1 commit into
mainfrom
feat/java-sdk-spring-boot-starter-and-hardening

Conversation

@ancongui
Copy link
Copy Markdown
Contributor

Summary

A four-axis pass on the Java SDK requested by the user:

  1. Worked examples in parity with the Python SDK -- 6 new classes under flydocs-examples/.
  2. flydocs-spring-boot-starter autoconfig module -- one-line drop-in for any Boot 3.5.x app.
  3. Deep reactive correctness audit + hardening -- AutoCloseable, opt-in retry, cleaner waitForCompletion, bounded Netty pool.
  4. Live integration test against the running flydocs API -- 5 tag-gated tests proving end-to-end wire compatibility.

The SDK is now structured as a Maven multi-module under sdks/java/:

sdks/java/
├── pom.xml                          (parent, packaging=pom)
├── flydocs-sdk/                     (core reactive client)
├── flydocs-spring-boot-starter/     (autoconfig + properties)
└── flydocs-examples/                (runnable examples; not deployed)

What changed in the core (flydocs-sdk)

Change Why
FlydocsClient + FlydocsClientAsync implement AutoCloseable The Netty ConnectionProvider is owned by the client and released on close(). The starter wires this via destroyMethod="close".
Opt-in retry filter (Builder.maxAttempts(N).retryMinBackoff(d)) Transient 5xx + timeouts retry with exponential backoff + jitter; 4xx is never retried.
waitForCompletion rewritten on repeatWhenEmpty One timeout subscriber, no nested-Mono chain, easier to reason about.
New builder knobs: maxConnections, pendingAcquireTimeout, maxInMemorySize Bounded pool + caller-tunable buffer limits.
Removed redundant codec config .codecs() + .exchangeStrategies() were both setting maxInMemorySize; consolidated.

What flydocs-spring-boot-starter does

flydocs:
  base-url: http://localhost:8400
  timeout: 60s
  max-attempts: 3
  webhook:
    hmac-secret: ${FLYDOCS_WEBHOOK_HMAC_SECRET}
@RestController
class Ctl {
  Ctl(FlydocsClientAsync flydocs) { /* autowired */ }
}
  • @AutoConfiguration + @ConditionalOnClass(FlydocsClientAsync.class) + @ConditionalOnProperty("flydocs.base-url") -- only activates when the SDK is on the classpath AND the base URL is configured.
  • @ConditionalOnMissingBean on every produced bean -- user @Bean overrides win.
  • Bean destroyMethod="close" -- the Netty pool is released cleanly on context shutdown.
  • WebhookVerifier only published when flydocs.webhook.hmac-secret is set.
  • spring-boot-configuration-processor for IDE completion in application.yaml.
  • Registered via META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Boot 3.x discovery).

Examples (1:1 with Python)

Class Mirrors
FirstExtractionExample 01_first_extraction.py
TypedSchemaAndRulesExample 02_typed_schema_and_rules.py
AsyncJobWithWaitExample 03_async_job_with_wait.py
WebhookReceiverApplication 04_webhook_receiver_fastapi.py
ErrorHandlingExample 05_error_handling.py
SyncFacadeExample 06_sync_facade.py

Plus ExampleHelpers mirroring examples_helpers.py. maven.deploy.skip=true so the module is compile-checked but never published.

Live integration

flydocs-sdk/src/test/.../integration/LiveApiIntegrationTest -- @Tag("integration"), @EnabledIfEnvironmentVariable("FLYDOCS_BASE_URL"). 5 tests covering version, health, validate, listJobs, typed-404. Activate with:

FLYDOCS_BASE_URL=http://localhost:8400 \
  mvn -pl flydocs-sdk test -Dgroups=integration

Verified against a real running service before this PR (5/5 passing).

CI

.github/workflows/publish-sdks.yaml: versions:set now uses -DprocessAllModules=true so child versions track the parent on tag push. The reactor publishes flydocs-sdk and flydocs-spring-boot-starter; flydocs-examples is built (compile-check) but never deployed.

Test plan

  • mvn verify from sdks/java: 48 unit + 5 starter = 53 passing, 5 integration cleanly skipped.
  • FLYDOCS_BASE_URL=http://localhost:8400 mvn -pl flydocs-sdk test -Dgroups=integration: 5/5 passing against a live API.
  • All 6 example classes compile.
  • CLI smoke: mvn -pl flydocs-examples compile exec:java -Dexec.mainClass=... works for each example.

…ve integration

Restructures the Java SDK into a 3-module Maven build and ships the
"best" Boot 3.5.9 reactive surface in one pass:

flydocs-sdk (core)
==================
  * AutoCloseable on both FlydocsClient and FlydocsClientAsync -- the
    Netty ConnectionProvider is owned and disposed on close().
  * Opt-in retry filter via Builder.maxAttempts(N) + retryMinBackoff.
    Retries transient 5xx + timeouts with exponential backoff; never
    retries 4xx (intentional state like 409 job_not_cancellable).
  * Builder knobs: maxConnections, pendingAcquireTimeout, maxInMemorySize
    -- bounded pool + caller-tunable buffer limits.
  * waitForCompletion: replaced the recursive flatMap with
    Mono.defer + repeatWhenEmpty + outer timeout -- one timeout
    subscriber, no nested chain.
  * Cleaned up redundant codec config (was setting maxInMemorySize twice).

flydocs-spring-boot-starter (NEW)
=================================
  * FlydocsAutoConfiguration with @autoConfiguration + @ConditionalOnClass
    + @ConditionalOnProperty(prefix="flydocs", name="base-url").
  * FlydocsProperties: timeout, max-attempts, retry-min-backoff,
    max-connections, pending-acquire-timeout, max-in-memory-size,
    tenant-id, webhook.hmac-secret.
  * @ConditionalOnMissingBean on every produced bean so users can
    override with a custom @bean.
  * Bean destroyMethod="close" so the Netty pool is released cleanly
    on Spring context shutdown.
  * WebhookVerifier bean conditional on flydocs.webhook.hmac-secret.
  * spring-boot-configuration-processor for IDE completion in
    application.yaml.
  * META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    so Spring Boot 3.x discovery picks it up without spring.factories.
  * 5 tests using ApplicationContextRunner cover the conditional
    wiring, user-bean override, and property binding.

flydocs-examples (NEW; not deployed)
====================================
  Six runnable example classes in 1:1 parity with the Python SDK:
    FirstExtractionExample          (01_first_extraction.py)
    TypedSchemaAndRulesExample      (02_typed_schema_and_rules.py)
    AsyncJobWithWaitExample         (03_async_job_with_wait.py)
    WebhookReceiverApplication      (04_webhook_receiver_fastapi.py)
    ErrorHandlingExample            (05_error_handling.py)
    SyncFacadeExample               (06_sync_facade.py)
  + ExampleHelpers fixture mirror of examples_helpers.py.
  maven.deploy.skip=true so the module compile-checks but never ships.

Live integration tests
======================
  LiveApiIntegrationTest in flydocs-sdk/src/test/.../integration/,
  tagged @tag("integration") and gated on FLYDOCS_BASE_URL. Verified
  end-to-end against a running service: version, health, validate,
  listJobs, typed-404 on unknown job id. Excluded from default mvn
  test; activate with -Dgroups=integration.

CI + docs
=========
  * .github/workflows/publish-sdks.yaml: versions:set now uses
    -DprocessAllModules=true so child modules' versions track the
    parent on tag push.
  * sdks/java/README.md: starter quickstart, examples table, retry +
    AutoCloseable docs, integration-test invocation.

mvn verify is green: 48 unit + 5 starter + 5 integration when
FLYDOCS_BASE_URL is set, 5 cleanly skipped otherwise.
@ancongui ancongui merged commit 50ba95e into main May 19, 2026
5 of 6 checks passed
@ancongui ancongui deleted the feat/java-sdk-spring-boot-starter-and-hardening branch May 19, 2026 10:25
ancongui added a commit that referenced this pull request May 19, 2026
* chore: clean up ruff findings introduced by #23 / #24 / #25

The Lint check failed on all three merged PRs because the new files
tripped ``F401`` (unused ``typing.Any``), ``SIM105`` (replace
``try``/``except``/``pass`` with ``contextlib.suppress``), ``UP041``
(replace ``asyncio.TimeoutError`` with builtin ``TimeoutError``),
``I001`` (import ordering), and ``F841`` (unused local).

The other CI jobs (Unit tests, SDK Python, SDK Java, Typecheck, Docling)
were all green on each PR; the merges weren't gated on Lint. This is
the follow-up sweep so ``ruff check`` is clean on ``main``.

11 errors fixed (8 auto-fixed by ``ruff --fix``, 3 manual).

* chore: apply ``ruff format`` to the same files

The Lint job runs both ``ruff check`` and ``ruff format --check``.
The previous commit cleared the ``check`` half; this one runs
``ruff format`` over the 8 files in the same change set so the
formatter half passes too.

No behaviour change.

---------

Co-authored-by: ancongui <andres.contreras@soon.es>
ancongui added a commit that referenced this pull request May 31, 2026
…ve integration (#25)

Restructures the Java SDK into a 3-module Maven build and ships the
"best" Boot 3.5.9 reactive surface in one pass:

flydocs-sdk (core)
==================
  * AutoCloseable on both FlydocsClient and FlydocsClientAsync -- the
    Netty ConnectionProvider is owned and disposed on close().
  * Opt-in retry filter via Builder.maxAttempts(N) + retryMinBackoff.
    Retries transient 5xx + timeouts with exponential backoff; never
    retries 4xx (intentional state like 409 job_not_cancellable).
  * Builder knobs: maxConnections, pendingAcquireTimeout, maxInMemorySize
    -- bounded pool + caller-tunable buffer limits.
  * waitForCompletion: replaced the recursive flatMap with
    Mono.defer + repeatWhenEmpty + outer timeout -- one timeout
    subscriber, no nested chain.
  * Cleaned up redundant codec config (was setting maxInMemorySize twice).

flydocs-spring-boot-starter (NEW)
=================================
  * FlydocsAutoConfiguration with @autoConfiguration + @ConditionalOnClass
    + @ConditionalOnProperty(prefix="flydocs", name="base-url").
  * FlydocsProperties: timeout, max-attempts, retry-min-backoff,
    max-connections, pending-acquire-timeout, max-in-memory-size,
    tenant-id, webhook.hmac-secret.
  * @ConditionalOnMissingBean on every produced bean so users can
    override with a custom @bean.
  * Bean destroyMethod="close" so the Netty pool is released cleanly
    on Spring context shutdown.
  * WebhookVerifier bean conditional on flydocs.webhook.hmac-secret.
  * spring-boot-configuration-processor for IDE completion in
    application.yaml.
  * META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    so Spring Boot 3.x discovery picks it up without spring.factories.
  * 5 tests using ApplicationContextRunner cover the conditional
    wiring, user-bean override, and property binding.

flydocs-examples (NEW; not deployed)
====================================
  Six runnable example classes in 1:1 parity with the Python SDK:
    FirstExtractionExample          (01_first_extraction.py)
    TypedSchemaAndRulesExample      (02_typed_schema_and_rules.py)
    AsyncJobWithWaitExample         (03_async_job_with_wait.py)
    WebhookReceiverApplication      (04_webhook_receiver_fastapi.py)
    ErrorHandlingExample            (05_error_handling.py)
    SyncFacadeExample               (06_sync_facade.py)
  + ExampleHelpers fixture mirror of examples_helpers.py.
  maven.deploy.skip=true so the module compile-checks but never ships.

Live integration tests
======================
  LiveApiIntegrationTest in flydocs-sdk/src/test/.../integration/,
  tagged @tag("integration") and gated on FLYDOCS_BASE_URL. Verified
  end-to-end against a running service: version, health, validate,
  listJobs, typed-404 on unknown job id. Excluded from default mvn
  test; activate with -Dgroups=integration.

CI + docs
=========
  * .github/workflows/publish-sdks.yaml: versions:set now uses
    -DprocessAllModules=true so child modules' versions track the
    parent on tag push.
  * sdks/java/README.md: starter quickstart, examples table, retry +
    AutoCloseable docs, integration-test invocation.

mvn verify is green: 48 unit + 5 starter + 5 integration when
FLYDOCS_BASE_URL is set, 5 cleanly skipped otherwise.

Co-authored-by: ancongui <andres.contreras@soon.es>
ancongui added a commit that referenced this pull request May 31, 2026
* chore: clean up ruff findings introduced by #23 / #24 / #25

The Lint check failed on all three merged PRs because the new files
tripped ``F401`` (unused ``typing.Any``), ``SIM105`` (replace
``try``/``except``/``pass`` with ``contextlib.suppress``), ``UP041``
(replace ``asyncio.TimeoutError`` with builtin ``TimeoutError``),
``I001`` (import ordering), and ``F841`` (unused local).

The other CI jobs (Unit tests, SDK Python, SDK Java, Typecheck, Docling)
were all green on each PR; the merges weren't gated on Lint. This is
the follow-up sweep so ``ruff check`` is clean on ``main``.

11 errors fixed (8 auto-fixed by ``ruff --fix``, 3 manual).

* chore: apply ``ruff format`` to the same files

The Lint job runs both ``ruff check`` and ``ruff format --check``.
The previous commit cleared the ``check`` half; this one runs
``ruff format`` over the 8 files in the same change set so the
formatter half passes too.

No behaviour change.

---------

Co-authored-by: ancongui <andres.contreras@soon.es>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant