Official Java/Spring Boot client for flydocs — the pure-multimodal Intelligent Document Processing service from Firefly OperationOS.
- Java 25 toolchain (compiled to release 25).
- Spring Boot 3.x managed dependencies — drops cleanly into any Boot 3.x app.
- Reactive WebClient with a blocking
FlydocsClientfacade. flydocs-spring-boot-starter— drop-in autoconfig driven byflydocs.*properties, including a@FlydocsWebhookargument resolver that injects signature-verifiedEventEnvelopes into controller methods.- Records for every DTO, snake_case on the wire via Jackson
@JsonProperty, idiomatic camelCase in Java. - Typed errors mapping the service's RFC 7807 problem-details.
- HMAC webhook verifier with constant-time comparison.
- Opt-in retries for transient 5xx + timeouts with exponential backoff.
AutoCloseable— own the Netty pool lifecycle explicitly, or let the starter manage it.
| Artifact | Use it when … |
|---|---|
flydocs-sdk |
You're not on Spring Boot, or you want to build the client manually. |
flydocs-spring-boot-starter |
You're on Boot 3.x and want the client autowired from flydocs.* properties. Pulls in flydocs-sdk transitively. |
flydocs-examples |
Runnable, compile-checked examples; not deployed. |
The artifact is published to GitHub Packages.
Add the server credentials to your ~/.m2/settings.xml:
<servers>
<server>
<id>github</id>
<username>YOUR_GITHUB_USER</username>
<!-- Personal access token with `read:packages` scope. -->
<password>YOUR_GITHUB_PAT</password>
</server>
</servers>Then in your project's pom.xml:
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/firefly-operationOS/flydocs</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<!-- Plain SDK -->
<dependency>
<groupId>com.firefly.flydocs</groupId>
<artifactId>flydocs-sdk</artifactId>
<version>26.6.0</version>
</dependency>
<!-- ...OR Spring Boot starter (recommended on Boot 3.x) -->
<dependency>
<groupId>com.firefly.flydocs</groupId>
<artifactId>flydocs-spring-boot-starter</artifactId>
<version>26.6.0</version>
</dependency># application.yaml
flydocs:
base-url: http://localhost:8080
api-key: ${FLYDOCS_API_KEY} # optional; sent as Authorization: Bearer …
timeout: 60s
max-attempts: 3 # retry transient 5xx with exponential backoff
webhook:
secret: ${FLYDOCS_WEBHOOK_SECRET} # optional; only set if you receive webhooks@RestController
class MyController {
private final FlydocsClientAsync flydocs; // autowired by the starter
MyController(FlydocsClientAsync flydocs) { this.flydocs = flydocs; }
@PostMapping("/extract")
public Mono<ExtractionResult> extract(@RequestBody ExtractionRequest req) {
return flydocs.extract(req);
}
}The starter publishes a FlydocsClientAsync, a FlydocsClient, and (when flydocs.webhook.secret is set) a WebhookVerifier plus a FlydocsWebhookArgumentResolver. The Netty pool is released cleanly on context shutdown.
import com.firefly.flydocs.sdk.FlydocsClient;
import com.firefly.flydocs.sdk.model.*;
import java.nio.file.Path;
FlydocsClient flydocs = FlydocsClient.builder()
.baseUrl("http://localhost:8080")
.apiKey(System.getenv("FLYDOCS_API_KEY"))
.build();
DocumentTypeSpec invoice = DocumentTypeSpec.builder("invoice")
.addFieldGroup("totals",
Field.required("total_amount", FieldType.NUMBER),
Field.required("currency", FieldType.STRING))
.build();
ExtractionRequest req = ExtractionRequest.builder()
.addFile(FileInput.ofPath(Path.of("invoice.pdf")))
.addDocumentType(invoice)
.options(ExtractionOptions.builder()
.stages(StageToggles.builder().judge(true).bboxRefine(true).build())
.build())
.build();
ExtractionResult result = flydocs.extract(req);
System.out.printf("id=%s status=%s model=%s latency=%dms%n",
result.id(), result.status(),
result.pipeline().model(), result.pipeline().latencyMs());See TUTORIAL.md for the full walkthrough — schemas, rules, async extractions, webhooks, errors, reactive usage.
import com.firefly.flydocs.sdk.FlydocsClientAsync;
import com.firefly.flydocs.sdk.model.*;
import java.time.Duration;
FlydocsClientAsync flydocs = FlydocsClientAsync.builder()
.baseUrl("http://localhost:8080")
.build();
flydocs.extractions().create(submitRequest, "my-app:invoice:42")
.doOnNext(r -> log.info("queued {}", r.id()))
.flatMap(submit -> flydocs.extractions().waitForCompletion(
submit.id(),
Duration.ofSeconds(2),
Duration.ofMinutes(10)))
.filter(s -> s.status() == ExtractionStatus.SUCCEEDED)
.flatMap(s -> flydocs.extractions().getResult(s.id()))
.subscribe(envelope ->
log.info("got {} documents", envelope.result().documents().size()));import com.firefly.flydocs.sdk.webhook.WebhookVerifier;
import com.firefly.flydocs.sdk.webhook.WebhookVerificationException;
WebhookVerifier verifier = new WebhookVerifier(System.getenv("FLYDOCS_WEBHOOK_SECRET"));
@PostMapping(value = "/flydocs/webhook", consumes = APPLICATION_JSON_VALUE)
public ResponseEntity<Void> onWebhook(
@RequestHeader("X-Flydocs-Signature") String signature,
HttpEntity<byte[]> body) {
try {
verifier.verify(body.getBody(), signature);
} catch (WebhookVerificationException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
// ... parse the JSON onto EventEnvelope, handle event_type, etc.
return ResponseEntity.accepted().build();
}The starter ships a @FlydocsWebhook annotation + argument resolver. The starter verifies the X-Flydocs-Signature HMAC and deserialises the body onto an EventEnvelope record before your controller method ever runs:
@PostMapping("/flydocs/webhook")
public ResponseEntity<Void> onWebhook(@FlydocsWebhook EventEnvelope event) {
if (EventEnvelope.TYPE_EXTRACTION_COMPLETED.equals(event.eventType())) {
// ... event.extraction(), event.result(), event.metadata()
}
return ResponseEntity.accepted().build();
}| SDK method | HTTP | Returns |
|---|---|---|
extract(req) |
POST /api/v1/extract |
ExtractionResult |
validate(req) |
POST /api/v1/extract:validate |
Map<String, Object> |
extractions().create(req, idemKey) |
POST /api/v1/extractions |
Extraction |
extractions().get(id) |
GET /api/v1/extractions/{id} |
Extraction |
extractions().cancel(id) |
DELETE /api/v1/extractions/{id} |
Extraction |
extractions().getResult(id, waitForBboxes, t) |
GET /api/v1/extractions/{id}/result |
ExtractionResultEnvelope |
extractions().list(query) |
GET /api/v1/extractions |
ExtractionListResponse |
extractions().waitForCompletion(id, poll, t) |
polls GET /api/v1/extractions/{id} |
terminal Extraction |
version() |
GET /api/v1/version |
VersionInfo |
health(probe) |
GET /actuator/health/{probe} |
Map<String, Object> |
| Type | Purpose |
|---|---|
StageToggles |
Opt-in switches for every optional pipeline stage. Has a fluent builder(). |
ExtractionOptions |
Per-request knobs, including escalation sub-object. Has a fluent builder(). |
DocumentTypeSpec |
One expected document type. Has a fluent builder(). |
FieldGroup, Field |
Field schema. Single recursive Field covers primitives, arrays, objects. |
ValidatorSpec |
Built-in field validator (iban, bic, vat_id, …). |
RuleSpec + RuleParent (sealed: Field/Validator/Rule) |
Business-rule DAG. |
Transformation (sealed: EntityResolutionTransformation/LlmTransformation) |
Post-extraction transformations. |
ExtractionRequest.builder() / SubmitExtractionRequest.builder() |
Top-level request builders. |
Every error subclasses FlydocsException:
| Class | When |
|---|---|
FlydocsTimeoutException |
SDK's own HTTP timeout fired (no service response). |
FlydocsClientException |
Other transport failure (DNS, connect, TLS). |
FlydocsHttpException |
Service returned 4xx/5xx. Carries statusCode(), code(), title(), detail(), and the raw payload() map. |
try {
flydocs.extract(req);
} catch (FlydocsHttpException e) {
if ("timeout".equals(e.code())) {
flydocs.extractions().create(submitReq); // fall back to async
}
}Six runnable examples live in flydocs-examples/, in 1:1 parity with the Python SDK's examples:
| 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 |
Run an example with:
mvn -pl flydocs-examples compile exec:java \
-Dexec.mainClass=com.firefly.flydocs.examples.FirstExtractionExample \
-Dexec.args="path/to/invoice.pdf"cd sdks/java
mvn verify # core + starter unit tests
# Live integration tests against a running service (tag-gated):
FLYDOCS_BASE_URL=http://localhost:8080 \
mvn -pl flydocs-sdk test -Dgroups=integrationApache-2.0. Copyright (c) 2026 Firefly Software Foundation.